Compare commits

..

574 Commits

Author SHA1 Message Date
Dion Moult c79eb60959 #1153 Drawings that are pure annotation now do not generate references 2022-04-17 12:29:34 +10:00
Dion Moult 538419a23f #1153 New markers and patterns for insulation batting, steel, and wood grain 2022-04-17 12:19:20 +10:00
Dion Moult eb19165ebc #1153 Predefined type is now also included in CSS classes for drawings. 2022-04-16 18:32:17 +10:00
Dion Moult 1138ffee7e #2139 Fix bug where some objects were invalidated during undo or redo 2022-04-15 19:45:59 +10:00
Dion Moult 0f3416774c #1153 Drawing layers (underlay, linework, annotation) are now per drawing instead of global 2022-04-15 11:45:23 +10:00
Dion Moult 1ba7977587 #1153 Fix newlines in text literals and smart tags in SVGs 2022-04-14 21:08:06 +10:00
Dion Moult 5b1b8e8f50 #1153 Annotation reference and sheet IDs now implemented in SVG. 2022-04-14 15:20:09 +10:00
Dion Moult 78811d331b #2136 Minor fix 2022-04-14 15:20:09 +10:00
Jesusbill 4aa91cfa7f add feature to toggle wireframe visibility for linked models - no testcases 2022-04-13 18:28:48 +02:00
Dion Moult c099a6cb75 #1153 Drawing titles now display IFC scale and identification data 2022-04-13 12:21:18 +10:00
Dion Moult 029b402d11 Minor fix 2022-04-12 18:30:03 +10:00
Dion Moult a243cdd6c6 Minor fix 2022-04-12 18:09:45 +10:00
Dion Moult 1fae835de9 Update installation docs to distinguish between package from source and developer setups 2022-04-12 18:07:39 +10:00
Dion Moult f0c7ff1e57 #1153. Titleblock revision / data is are derived from IfcDocumentInformation 2022-04-12 08:29:29 +10:00
Thomas Krijnen 460fd5ca87 #2133 faceset helper: compare index vectors by address instead of value 2022-04-11 14:12:17 +02:00
Thomas Krijnen 054c0a9a27 Improve faceset logging 2022-04-11 14:08:44 +02:00
Thomas Krijnen 5af1baa48b Update release.yml 2022-04-10 20:47:01 +02:00
Thomas Krijnen 0e5d2cc0c9 Update release.yml 2022-04-10 10:15:07 +02:00
Dion Moult 48148f00a3 #2129 Support some FreeCAD annotation types. 2022-04-09 10:22:12 +10:00
Thomas Krijnen 564c4b7644 #2126 reverse 1st operand inner wires in 2d opening subtraction 2022-04-08 10:45:28 +02:00
Bruno Postle 33c53a2c9a regenerate site library 2022-04-08 16:49:06 +10:00
Bruno Postle c049007ee8 add missing import 2022-04-08 16:49:06 +10:00
Bruno Postle 05345e8ddf Add missing demo library 2022-04-08 16:49:06 +10:00
Thomas Krijnen 461b78665a OCCT 7.6 compatibility 2022-04-04 08:29:29 +00:00
Boris Brangeon 6160d65d18 Update selector.py
filter_rule.children[2].children[0].lower() == 'true'
2022-03-31 20:50:26 +02:00
Thomas Krijnen d526314190 #2115 Let --strict-tolerance govern degeneracy of periodic curve trim 2022-03-31 11:28:30 +02:00
Massimo Fabbro 85bac37bed Fix two broken links
Fixed "./installation.rst" and "./running_tests.rst" links
2022-03-30 20:31:00 +11:00
Dion Moult e38184b152 Adding a document now auto adds a reference for convenience. 2022-03-30 18:15:26 +11:00
Dion Moult a0c8d87ad0 #1153. You can now assign and remove drawings from sheets. 2022-03-30 18:15:11 +11:00
Dion Moult 8686462eed #1153. Section markers are now supported in SVG. 2022-03-29 20:44:20 +11:00
Dion Moult 6cd4a2d78b #1153. Add support for elevation symbols in generated SVGs. 2022-03-29 20:30:10 +11:00
Dion Moult 2c6cb2fbb2 #1153. Section references are now auto generated when activating a drawing. 2022-03-29 17:04:50 +11:00
Dion Moult a60dfd8dd5 #1153. Elevation references are now auto generated when activating drawings. 2022-03-29 10:01:41 +11:00
Dion Moult 1b1fd1e6c5 #2048. Minor fix. 2022-03-28 12:52:29 +11:00
Dion Moult 1e60f23880 #2081. Bump IfcOpenShell build. 2022-03-28 11:58:38 +11:00
Dion Moult eb7babb1b0 #2053. Minor fix. Enable strict tolerance everywhere. 2022-03-27 19:05:05 +11:00
Dion Moult 5797937825 #1951. Fix bug where opening standard cases couldn't be added as voids. 2022-03-27 18:21:22 +11:00
Dion Moult 28336140ca #2048. Blender materials, IFC materials and IFC presentation style names are now bi-directional. 2022-03-27 14:43:05 +11:00
Dion Moult ca03a88601 #2104. Support for adding conversion based units to the project. 2022-03-27 10:39:01 +11:00
Thomas Krijnen 0303d217bf #2105 Fix fillets 2022-03-26 13:50:36 +01:00
Dion Moult 4184f2bf46 #2094. Removing styles now also purges Blender material object if possible. 2022-03-26 21:17:52 +11:00
Dion Moult 3b0429ace9 #2106. Fix bug where geometry cache was not cleared when openings were changed 2022-03-26 20:38:30 +11:00
Dion Moult baf75f552e New barebones presentation style manager to see the styles in your project. 2022-03-26 18:15:30 +11:00
Dion Moult 4ea0c73081 Fix bug where we now accommodate invalid durations in the scheduling tree 2022-03-25 19:37:52 +11:00
Dion Moult 33852be3f1 Fix bug where lag times couldn't be edited 2022-03-25 19:37:09 +11:00
Dion Moult 48d476c2b3 Gantt charts now show ISO durations and task captions 2022-03-25 16:09:22 +11:00
Dion Moult 9db07883b7 Scheduling calculations no longer calculate from midnight and now handle a single 9-5 day as a duration of 1 day, as per industry standard practice. 2022-03-25 16:09:22 +11:00
Dion Moult 7af53c543d Fix bug with schedule calculation on zero duration tasks 2022-03-25 16:09:22 +11:00
Dion Moult e7f6ed73ac Fix bug where recalculating a schedule with no time data would fail 2022-03-25 16:09:22 +11:00
Dion Moult 8afa1a4f7d Fix bug where counting working days on a task without a calendar would fail 2022-03-25 16:09:22 +11:00
Dion Moult 1dd85f6c48 Fix bug where removing a rooted or calendar constrained task would purge all calendar assignments 2022-03-25 16:09:22 +11:00
dependabot[bot] 8bd826fb20 Bump numpy in /src/ifcopenshell-python/ifcopenshell
Bumps [numpy](https://github.com/numpy/numpy) from 1.20.3 to 1.21.0.
- [Release notes](https://github.com/numpy/numpy/releases)
- [Changelog](https://github.com/numpy/numpy/blob/main/doc/HOWTO_RELEASE.rst.txt)
- [Commits](https://github.com/numpy/numpy/compare/v1.20.3...v1.21.0)

---
updated-dependencies:
- dependency-name: numpy
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-24 10:02:23 +01:00
dependabot[bot] cbc6eefca7 Bump lxml from 4.6.3 to 4.6.5 in /src/ifcopenshell-python/ifcopenshell
Bumps [lxml](https://github.com/lxml/lxml) from 4.6.3 to 4.6.5.
- [Release notes](https://github.com/lxml/lxml/releases)
- [Changelog](https://github.com/lxml/lxml/blob/master/CHANGES.txt)
- [Commits](https://github.com/lxml/lxml/compare/lxml-4.6.3...lxml-4.6.5)

---
updated-dependencies:
- dependency-name: lxml
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-24 10:01:13 +01:00
Boris Brangeon 4f86dd143a Update selector.py (#2102)
Add BOOLEAN: "TRUE" | "FALSE" | "true" | "false"| "True" | "False"
2022-03-24 11:02:58 +11:00
Dion Moult c45c73c563 #2078. Fix bug where flat materials didn't import correctly. 2022-03-23 20:19:19 +11:00
Dion Moult 38f1c622ac Allow case insensitive IFC file extensions when exporting 2022-03-23 11:54:54 +11:00
Thomas Krijnen 38170db32a #2080 Don't allow parameterless Kernel constructor 2022-03-22 22:30:27 +01:00
Dion Moult 43d0c098a0 #2089. Fix and simplify OffsetObjectPlacement recipe for multiple absolute placements. 2022-03-22 17:27:38 +11:00
Dion Moult 533111a1fb #2099. Materials are now sorted alphabetically. 2022-03-22 14:30:45 +11:00
Dion Moult 0bba31e5f9 You can now select elements by material in the material manager. 2022-03-22 13:25:26 +11:00
Dion Moult 9b9f0058f9 The material manager now shows total elements used by a material 2022-03-22 12:50:10 +11:00
Dion Moult 0c8273eb70 Minor fix 2022-03-22 11:47:37 +11:00
Dion Moult c072554f21 You can now use the selector with a prefiltered list of elements 2022-03-22 11:32:40 +11:00
Dion Moult 18c9a57bea Using the selector now supports recursive decomposition in the spatial hierarchy 2022-03-22 11:23:13 +11:00
Dion Moult eaa3301b8f New support for filtering elements by checking values against an enumeration 2022-03-22 10:49:48 +11:00
Dion Moult efb89acfa3 You can now use a "not" operator when selecting elements 2022-03-22 10:40:05 +11:00
Dion Moult 96256d365a Fix bug where you couldn't compare less than or equal to with the selector. 2022-03-22 10:39:10 +11:00
Dion Moult fa6ec25a39 #2082. You can now use the selector to filter by strict data types. 2022-03-21 18:03:25 +11:00
Dion Moult 44afa0df5d Selector now uses classmethods to make it easier for scriptwriters to use 2022-03-21 17:28:16 +11:00
Dion Moult c478e5bf1b #2094. Deleting materials now also deletes Blender material if not used for a style 2022-03-21 12:04:51 +11:00
Dion Moult ea530858bd You can now remove material sets independent of elements 2022-03-21 12:03:15 +11:00
Dion Moult 0b4cfea3a9 Removing a material now also removes material properties 2022-03-21 10:50:19 +11:00
Dion Moult e909f25432 Removing a property set now also removes properties 2022-03-21 10:22:31 +11:00
Thomas Krijnen 2985bba157 #650 Unsure about 1b5c20e14f 2022-03-19 20:18:12 +00:00
Thomas Krijnen a1ba61636d HDF5 debug postfix 2022-03-19 19:32:42 +00:00
Thomas Krijnen 1b5c20e14f #650 fclose() 2022-03-19 14:45:19 +01:00
Dion Moult a43fae68b6 #2094. You can now add material sets independently of IFC objects in the material manager 2022-03-19 22:13:07 +11:00
Dion Moult fdd03d7294 #2094. Removing materials now removes material definitions too for styles 2022-03-19 22:13:07 +11:00
Thomas Krijnen 2049bdd976 todo note 2022-03-19 11:36:38 +01:00
Thomas Krijnen d45a439f6c todo note 2022-03-19 11:36:30 +01:00
Thomas Krijnen 8fa5873e7e #2095 take into account face-face distance on operand a 2022-03-19 10:49:51 +01:00
Dion Moult 3fa8fce994 Fix bug where gizmo still shown after deleting objects 2022-03-19 17:27:45 +11:00
Dion Moult b8aa90de00 #2094. New rudimentary project materials browser to manage materials. 2022-03-18 17:24:59 +11:00
Dion Moult dcc1ed5607 #1153. References to grids in drawings are now auto synchronised when activating a drawing view. 2022-03-18 16:00:49 +11:00
Dion Moult 2bfff12988 #1153. Drawing names are now generated based on their location and show an icon based on the drawing type. 2022-03-18 15:49:56 +11:00
Dion Moult 074a55b816 Fix bug where delete confirmation was not consistent with vanilla Blender 2022-03-17 14:10:51 +11:00
Dion Moult a53fd97e5a Fix #2092. Fix bug where door and window styles were excluded from authoring options. 2022-03-17 09:49:55 +11:00
Dion Moult b39a0d1a7c #2053. Revert strict tolerance due to side-effects with caching 2022-03-16 19:11:37 +11:00
Dion Moult 44b786a21b Ensure that exported 4D schedules use a .xml extension 2022-03-16 19:11:13 +11:00
Dion Moult bddc64cad3 Fix #2049. Allow relative paths for IFC links. 2022-03-16 10:55:36 +11:00
Dion Moult 3713ec00f9 #2049. You can now edit linked project paths when unloaded. Thanks brunoperdigao! 2022-03-16 10:46:42 +11:00
Dion Moult b5a58998d5 Fix #2083. hpp-fcl now has support for Python 3.10. 2022-03-16 09:58:52 +11:00
Dion Moult ca3b4d17ba Fix #2055. Disable IfcContext in IFC2X3 because it didn't exist. 2022-03-16 09:37:20 +11:00
Dion Moult 983a1530b8 #2053. Potentially fix geometry issues with importing small objects. 2022-03-16 09:09:43 +11:00
Dion Moult 74e18a56ce Fall back to information data if reference data is null for associated documents. 2022-03-15 19:54:04 +11:00
Cyril Waechter 05834d071f Handle empty ConnectionGeometry + refactor (#2085) 2022-03-15 19:34:37 +11:00
Dion Moult 8f70f462cc You can now easily open associated documents with support for inherited locations 2022-03-15 19:32:29 +11:00
Dion Moult 456f45b346 You can now use relative paths when browsing for file URIs. 2022-03-15 18:58:51 +11:00
Dion Moult 53a9175802 You can now browse to select files for any URI reference attributes 2022-03-15 18:05:44 +11:00
Dion Moult 379d6d9392 Minor fix 2022-03-15 16:53:31 +11:00
Dion Moult 38b708ff1f Redesigned document UI to handle hierarchical documents and make it easier to reference 2022-03-15 14:49:20 +11:00
Dion Moult 233e73f9f6 Removing documents now removes all subdocuments recursively and references 2022-03-15 14:45:18 +11:00
Dion Moult db85aa90d3 You can now add document references that are part of a document information 2022-03-15 14:43:24 +11:00
Dion Moult 6fb1e34ab4 You can now add IFC subdocuments or supersede documents 2022-03-15 14:41:47 +11:00
Dion Moult 9cc683ea6e Fix bug where ownership history was not tracked on document association. 2022-03-14 20:39:39 +11:00
Dion Moult e343494ab4 Document information is now always assigned to the project by default. 2022-03-14 20:39:11 +11:00
Dion Moult bc9e403a58 #2088. Minor fix. Sorry more stuff I forgot to commit. 2022-03-14 20:07:51 +11:00
Thomas Krijnen 8ffd9dc1f9 #2086 surface_area and volume 2022-03-14 09:54:28 +01:00
Dion Moult 57f6c07240 Minor fix 2022-03-14 18:40:54 +11:00
Dion Moult f518bbfb00 Minor fix 2022-03-14 16:48:44 +11:00
Dion Moult 0f34420834 Fix missing pkg_resources brickschema dependency 2022-03-14 16:43:27 +11:00
Dion Moult 3ef6077a0d Create build for Blender 3.1 Python 3.10 2022-03-14 16:43:10 +11:00
Gorgious56 3c26cb672b Compute wall net quantities (#2086)
* Compute wall net quantities

* Remove opening check clause
2022-03-14 12:48:50 +11:00
Dion Moult 673f757a51 Fix bug where adding document references didn't work. 2022-03-09 22:14:59 +11:00
Dion Moult 578ab140cc Create section and elevation contexts on new projects. See #1153. 2022-03-09 21:44:26 +11:00
Thomas Krijnen 96902bf38a Safeguard against runtime error when invoking HLR without storey containment 2022-02-27 20:57:05 +01:00
Thomas Krijnen b5133c6874 Add caching option in iterate() helper 2022-02-27 12:56:16 +01:00
Thomas Krijnen 5dfe85691b #1153 Exclude walls and slabs in profile edges hack 2022-02-27 12:55:39 +01:00
Thomas Krijnen 3b7bf8844a Update default SVG CSS for projections 2022-02-27 12:55:11 +01:00
ArturTomczak 8038b77504 typo 2022-02-26 21:56:02 +01:00
ArturTomczak b64601ee0a Update subrepo with sample files 2022-02-26 21:56:02 +01:00
ArturTomczak 320bb7a086 Update to IDS 0.5.8 2022-02-26 21:56:02 +01:00
ArturTomczak ad135f70f1 encoding clean up
the use of binary access to file was not intentional, switched to @aothms  proposal.
2022-02-26 21:56:02 +01:00
ArturTomczak 15b24d8ba4 validation tests 2022-02-26 21:56:02 +01:00
ArturTomczak 3c2b25d923 fix recurrsion 2022-02-26 21:56:02 +01:00
ArturTomczak 3282d75aa5 fix encoding bcf bug, add tests 2022-02-26 21:56:02 +01:00
ArturTomczak 72fc617196 internalise tests #1711
moved test files to submodule to avoid URL requests. Had to turn off (comment) two tests as they were not working but will fix them with new schema update.
2022-02-26 21:56:02 +01:00
ArturTomczak 47cad88c38 add IDS requirements #1727 2022-02-26 21:56:02 +01:00
Thomas Krijnen e81dd9c7df Option to yield progress from iterate() and consume_iterator() 2022-02-26 14:57:21 +01:00
Thomas Krijnen d16f92a445 Option to set log format from python 2022-02-26 14:53:09 +01:00
Thomas Krijnen 8be79f9aee Add XML and glTF serializers to python 2022-02-26 14:52:58 +01:00
Dion Moult ccc6f2c4c6 Fix bug in date calculations when editing task times without sufficient data 2022-02-23 13:08:52 +11:00
Dion Moult deb0597000 Fix infinite loops: accommodate invalid calendars with no working times in sequence utility 2022-02-23 13:04:29 +11:00
Dion Moult 348373d47e Fix bug with editing task start times without finishes 2022-02-23 12:28:34 +11:00
Dion Moult 9e22e9486a You can now export Microsoft Project XMLs from Blender 2022-02-21 19:57:00 +11:00
Dion Moult 3cf9017f27 New IFC2MSP conversion tool in IFC4D to convert IFC to Microsoft Project 2022-02-21 19:56:06 +11:00
Dion Moult 21479482b4 Fix bug where MS project durations expressed in hours were not parsed correctly 2022-02-21 19:55:32 +11:00
Dion Moult 97c660fec8 Fix bug with incorrect MS Project XML working day imports 2022-02-21 19:55:08 +11:00
Dion Moult 32af2025ec Fix bug with incorrect MS Project XML sequence type import 2022-02-21 19:54:45 +11:00
Dion Moult ffc82dfb1e Fix bug where timeline date didn't update during animations 2022-02-21 19:52:17 +11:00
Dion Moult 2a69af43f6 Calendars are now assigned when importing Microsoft Project XML to IFC 2022-02-21 15:24:03 +11:00
Dion Moult 5c9984a12a Fix bug with broken MS Project to IFC conversion 2022-02-21 13:51:26 +11:00
Dion Moult c938d6ae9b Very minimal implementation of generating parametric duct segments from duct size properties 2022-02-18 19:21:35 +11:00
Dion Moult 63d65f0c39 Fix #2046. Fix crash if style was updated from an already loaded IFC dataset 2022-02-18 18:44:25 +11:00
Dion Moult 472a66b7ed Fix bug where element types couldn't get psets applied to them due to lack of specificity in IFC templates 2022-02-18 17:11:24 +11:00
Dion Moult ddaed9e93d Fix bug where psets were unable to be applied for specific predefined types 2022-02-18 17:10:38 +11:00
Dion Moult 4d6eba0a06 Fix #2045. Fix bug where IFC2X3 occurrences not correctly autodetected from types. 2022-02-18 10:06:13 +11:00
ssg ed09b7ba31 Fixed failure for VS2017
Script was trying to pick up vs19 instead of vs15 even for builds on vs15, fixed that by adding a check for vs15.
2022-02-12 08:13:16 +01:00
Thomas Krijnen 2c2954b11a #2036 check for null curve in IfcIndexedPolyCurve 2022-02-10 14:04:08 +01:00
Thomas Krijnen 5317746a82 #2037 Rerun codegen 2022-02-10 13:06:32 +01:00
Thomas Krijnen 5af7e95bee #2037 Option to optain declaration from early bound select definitions 2022-02-10 13:06:04 +01:00
Thomas Krijnen 618d902a57 #2037 throw on invalid cast in schema code 2022-02-10 13:05:33 +01:00
Thomas Krijnen 68baa416fa #2037 option to throw on invalid cast 2022-02-10 13:05:05 +01:00
Dion Moult 7f212963d5 The storey is now the default container when starting a project for convenience. 2022-02-10 11:40:38 +11:00
Dion Moult d3a43c6e81 Be more forgiving to invalid Revit IFCs when importing. 2022-02-10 11:06:23 +11:00
Dion Moult a44a13f04c Implement loading glTF style textures from IFC. 2022-02-09 16:40:23 +11:00
Dion Moult 126ba8d88f Fix #2032. Fix bug where certain elements could not be extracted or appended. 2022-02-09 15:16:22 +11:00
Dion Moult caf04200ce Experimental optimisation attempt for native faceted brep handling by aothms. See #841. 2022-02-09 14:36:14 +11:00
Dion Moult 05f3cd41e2 glTF rendering styles for unlit and PBR materials are now supported when loading IFCs 2022-02-09 14:26:39 +11:00
Dion Moult 80bc4cdb1b Fix #2028. OffsetObjectPlacements can now rotate then move, or vice versa. Warning: new default! Thanks hvn74! 2022-02-09 12:17:07 +11:00
Dion Moult dd7a47ab99 Texture support is now compatible with glTF texture maps 2022-02-09 11:34:59 +11:00
Dion Moult 4eaf0e7a9c Fix bug where you couldn't downgrade or upgrade between shading and rendering styles 2022-02-09 11:31:09 +11:00
Dion Moult 3db252bcc0 Minor fix 2022-02-08 15:50:55 +11:00
Dion Moult d03685abfa UV coordinates for triangulated face sets are now saved in IFC. 2022-02-08 15:32:03 +11:00
Dion Moult 3e4b1d95cb Standalone section shader add-on for those who don't need the full BlenderBIM add-on features 2022-02-06 20:27:44 +11:00
Dion Moult b151ec2b0e Fix bug where false geolocation offsets did not take into account rotation. 2022-02-06 20:04:53 +11:00
Thomas Krijnen f909adc2dc Update README.md 2022-02-05 09:57:56 +01:00
Dion Moult 913a428794 Fix #2017. Fix P6 import bug where older P6 XER versions <8.1 did not store hours per day. 2022-02-05 17:44:59 +11:00
Dion Moult fe6d9ae926 Triangulated face set geometry exports are now supported. 2022-02-05 11:33:14 +11:00
Thomas Krijnen 4a679812ca #2018 prevent select types to be serialized as booleans 2022-02-04 13:54:07 +01:00
Dion Moult 6be87c035f Annotations are now auto collected correctly and simpler drawings UI. See #1153. 2022-02-04 19:21:24 +11:00
Dion Moult 6ce8a3621a You can now manage IFC actors in Blender. 2022-02-04 18:21:53 +11:00
Dion Moult 60ea856e51 New functions for IFC actor in API 2022-02-04 17:56:45 +11:00
Dion Moult 419502ddb7 Disable adding a grid before a project exists. 2022-02-04 16:23:04 +11:00
Dion Moult 9f530d0578 Updating drawing names now updates it in IFC too. See #1153. 2022-02-04 15:04:30 +11:00
Dion Moult 524483e9cc Target view is now read from IFC. See #1153. 2022-02-04 12:04:20 +11:00
Dion Moult 7116a9f71a Removing drawings now roundtrips in IFC. See #1153. 2022-02-04 10:54:18 +11:00
Dion Moult 868ced2990 See #2017. Fix bug in P6 importer where nonlabor types were not considered. 2022-02-04 09:10:55 +11:00
Dion Moult 6a8292d24c Fix #890. New N/S/E/W location presets for creating sections, elevations, and RCPs. See #1153. 2022-02-04 08:14:36 +11:00
Dion Moult fca4d9dea0 New save and save as buttons in project panel with last saved date. 2022-02-03 21:29:44 +11:00
Dion Moult 89d5c44855 You can now create new projects starting from a template. 2022-02-03 18:25:03 +11:00
Dion Moult 3e99393895 You can now add plan views based on a building storey. See #890. 2022-02-03 17:28:19 +11:00
Gorgious56 098ab2c237 Spatial Elements Utilities and QOL (#2008)
* Tool to add storey to specific building

* Operator to add new storey to building

* Add new storey to building button in aggregates panel

* Minor fix

* add method to data class to retrieve ifc class name

* Extract aggregate creation logic

* Prevent aggregating non-ifc elements + display popup info

* Add tooltips + rename operators

* Rename function to get relating object

* Rename relating object data variable to be able to have access to related objects

* Add utilities to get related objects

* Add utility function to get related objects from element

* display related object in Aggregates panel in object properties

* Revert "Rename function to get relating object"

This reverts commit 5248627a41.

* Update method names

* Add Operator to select parts of an element

* Ditch box layout and only display number of parts

* Add operator to select aggregate of an element

* Display operator to select aggregate of element

* Move logic to aggregate module

* Add utility methods to Blender tool

* Add buttons to create Ifcsite and IfcBuilding from object panel
2022-02-03 07:16:43 +01:00
Dion Moult 94ff8349d3 You can now connect, disconnect, and set flow directions of distribution ports in Blender. 2022-02-03 12:05:05 +11:00
Henri J. Norden d333cb1b78 Cleanup changes 2022-02-02 14:20:46 +01:00
Henri J. Norden 3f4c4a8e52 Hoist compiling mpir & mpfr before HDF5, since they are more likely to fail and HDF5 takes way too long 2022-02-02 14:20:46 +01:00
Henri J. Norden b103e0dde5 Fix build-deps.cmd for VS2019 2022-02-02 14:20:46 +01:00
Dion Moult 883b8a523c Material node trees compliant with basic X3D multitextures are now stored in IFC 2022-02-02 17:51:40 +11:00
Dion Moult 8c0b06b36e Different BSDFs are now recognised for different reflectance methods. 2022-02-02 17:50:38 +11:00
Dion Moult 72397f3aff Saving surface specular roughness values to IFC is now supported. 2022-02-02 15:09:37 +11:00
Dion Moult 77821caae4 Adding surface styles now respect mutually exclusive style types 2022-02-02 15:04:19 +11:00
Dion Moult 10138e28c9 You can now add ports to distribution elements in Blender. 2022-02-01 19:16:31 +11:00
Gorgious f9ce0903f7 Fix #2010 : Authoring data class throws error in BIM tool if no ifc file is selected 2022-02-01 09:12:42 +01:00
Dion Moult a2c0045a40 Support relative object placements for IFC2X3 ports. 2022-02-01 18:23:55 +11:00
Dion Moult 3e10f52965 Relative placements for nested elements are now supported. 2022-02-01 17:39:55 +11:00
Dion Moult fd8aa6d142 IfcOpenShell API now supports port connections and disconnections. 2022-02-01 16:51:42 +11:00
Dion Moult ddb38b2ea0 The IfcOpenShell API now supports adding a distribution port. 2022-01-31 18:28:31 +11:00
Dion Moult 74174b1e09 Fix bug where drawing objects could be added to the wrong context. 2022-01-30 15:09:54 +11:00
Dion Moult 96179db7ad IFC element names no longer treat the Blender name suffix as significant 2022-01-30 15:08:32 +11:00
Dion Moult 116252f857 Object names now guard against duplicate IFC prefixes when assigning. 2022-01-30 15:06:01 +11:00
Dion Moult 538024e035 All element's spatial containment collections are now synced when assigning a class. 2022-01-30 15:04:50 +11:00
Dion Moult e9670a6906 Fix bug where editing then switching to the current representation crashes. 2022-01-30 14:47:22 +11:00
Dion Moult c7428dd160 Fix bug where syncing an object removed from the collection tree breaks. 2022-01-30 14:44:21 +11:00
Dion Moult bf9d46b506 Structural items in the collection hierarchy are now auto synced. 2022-01-30 10:11:19 +11:00
Dion Moult 7ee589cfc2 Remove legacy unassign class feature in favour of just deleting objects. Thanks theoryshaw! 2022-01-30 08:56:43 +11:00
Dion Moult 3083b66f61 Fix #2012. Minor fix. 2022-01-30 08:49:46 +11:00
Dion Moult 074fe0d3a6 You can now specify a target view when creating a new drawing. See #1153. 2022-01-29 21:11:35 +11:00
Dion Moult 46e1a46dd2 Drawing collection hierarchy is now auto fixed if shuffled around manually. See #1153. 2022-01-29 21:11:35 +11:00
Thomas Krijnen 677a9d715c #2009 2022-01-29 09:43:33 +01:00
Dion Moult a210cf654e Drawings list now reads data from IFC, not Blender. See #1153. 2022-01-29 17:26:09 +11:00
Dion Moult 44e4ee70ae Removing sheets now works in IFC. See #1153. 2022-01-29 15:45:45 +11:00
Dion Moult 27e6d702e9 You can now create drawing sheets in IFC. See #1153. 2022-01-29 13:42:46 +11:00
Dion Moult fb76f985c7 Minor fix 2022-01-29 09:01:35 +11:00
Dion Moult 9279aeccf6 You can now show and hide distribution ports of an element. 2022-01-29 08:52:37 +11:00
Dion Moult 92a1739d93 Minor fix - sorry, really need a local Sphinx setup 2022-01-28 20:41:20 +11:00
Dion Moult dfba9827a6 Minor fix 2022-01-28 20:37:18 +11:00
Dion Moult 19e007a67c New diagrams on the developer guide. 2022-01-28 20:32:41 +11:00
Dion Moult d3ef3f1501 Finish building minimum viable product of the IFC2P6 converter. Thanks myoualid! 2022-01-28 18:14:16 +11:00
Dion Moult f2e9f76631 Minor tweaks. See #2006. 2022-01-28 10:48:22 +11:00
Dion Moult 52739418d8 Fix #1514. You can now customise the default names of occurrences create from a type. 2022-01-27 17:21:11 +11:00
Dion Moult 7f1fddf426 New software architecture section in the developer guide. 2022-01-27 09:32:24 +11:00
Dion Moult ee4f48b566 Minor fix 2022-01-26 21:35:27 +11:00
Dion Moult 1d47ad3cd4 New demo module to teach developers how to hack on the BlenderBIM Add-on. 2022-01-26 21:27:59 +11:00
Dion Moult 36a7985f5a Fix #2002. Fix bug where sometimes shape IDs wouldn't be pure integers. 2022-01-25 08:59:01 +01:00
Dion Moult e129f4afa9 Fix #2001. Fix bug where you could not undo deletions. 2022-01-25 17:27:42 +11:00
Dion Moult 8fd4351195 Minor fix. Don't know all the answers. 2022-01-25 16:50:36 +11:00
Dion Moult 01f25528e8 Build untested IFC to P6 XML export feature. 2022-01-25 16:44:37 +11:00
Dion Moult 3222c9ce99 Fix #1998. Fix bug where colours don't show if styling is applied to the mapped item instead of the actual representation item. 2022-01-25 13:40:52 +11:00
Gorgious 28e6bb9e3d Fix error when updating representation on object data with no BIMMeshProperties 2022-01-24 13:22:17 +01:00
Dion Moult 5884af0678 Updates to space boundary geometry now auto updates on export. 2022-01-24 21:52:51 +11:00
Dion Moult f88a1038a8 IfcCSV now loads the selector by default so its much more fun to use a lib. 2022-01-24 17:52:32 +11:00
Dion Moult cafe8ce5f2 You can now remove bricks from a Brickschema model. 2022-01-24 17:52:04 +11:00
Dion Moult 8569bcf350 New utility function for getting ports of an element 2022-01-24 16:16:08 +11:00
Dion Moult e38759c39d Implement assign and unassign ports in IfcOpenShell API 2022-01-24 16:13:55 +11:00
Dion Moult 50ed8eccfd Fix bug where you couldn't select properties with a dot in the name for IfcCSV 2022-01-24 11:07:01 +11:00
Gorgious56 d63d62ddd6 Improve section cutaway shader (#2000)
* Dynamically update section shader backface color

* Refactor add_section_plane operator

* Prevent error if last section has been removed

* Refactor section plane remover
No longer removes object if it is not a section plane
Offsets nodes so that they are always nice and ordered

* Fix shader glitch when user deletes last cutaway object

* Fix nodes offset when deleting cutaway object
2022-01-24 09:47:22 +11:00
Dion Moult 0bdb4b9fcf Smart annotation variables for drawings now reimplemented. See #1153. 2022-01-22 21:00:26 +11:00
aothms 7e7c54a535 #1993 vs2022 hdf5 2022-01-21 12:13:58 +00:00
Dion Moult 817fdead9b Fix bug where zero durations were not allowed in task times. 2022-01-21 18:52:40 +11:00
Dion Moult 68a43b92ee Editing task times no longer tries to be clever with actual dates because you know what you're doing. 2022-01-21 18:42:59 +11:00
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
957 changed files with 50922 additions and 12509 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 \
+2 -1
View File
@@ -23,12 +23,13 @@ jobs:
run: echo ok go
build:
needs: activate
name: ${{ matrix.config.name }}-${{ matrix.pyver }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
pyver: [py37, py39]
pyver: [py37, py39, py310]
config:
- {
name: "Windows Build",
+4 -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 \
@@ -83,6 +82,8 @@ jobs:
sudo /usr/bin/python -m pip install src/bcf
sudo /usr/bin/python -m pip install pytest
sudo /usr/bin/python -m pip install isodate
sudo /usr/bin/python -m pip install lark
sudo /usr/bin/python -m pip install networkx
- name: Test
run: |
+4 -4
View File
@@ -18,14 +18,14 @@ jobs:
- name: Checkout
uses: actions/checkout@v2
with:
lfs: true
submodules: recursive
-
name: Update ubuntu
run: |
sudo apt update
sudo apt-get install git cmake gcc g++ libboost1.67-all-dev python3-all-dev swig libpcre3-dev libxml2-dev
sudo apt-get install liboce-foundation-dev liboce-modeling-dev liboce-ocaf-dev liboce-visualization-dev liboce-ocaf-lite-dev \
libhdf5-dev
sudo apt-get install libocct-foundation-dev libocct-modeling-algorithms-dev libocct-modeling-data-dev libocct-ocaf-dev libocct-visualization-dev libocct-data-exchange-dev \
libhdf5-dev libcgal-dev
- name: Build
env:
@@ -39,7 +39,7 @@ jobs:
-DCMAKE_PREFIX_PATH=/usr \
-DCMAKE_SYSTEM_PREFIX_PATH=/usr \
-DBUILD_PACKAGE=On \
-DOCC_INCLUDE_DIR=/usr/include/oce \
-DOCC_INCLUDE_DIR=/usr/include/opencascade \
-DOCC_LIBRARY_DIR=/usr/lib/x86_64-linux-gnu \
-DPYTHON_EXECUTABLE:FILEPATH=/usr/bin/python3 \
-DPYTHON_INCLUDE_DIR:PATH=/usr/include/python3.8 \
+6
View File
@@ -8,3 +8,9 @@
[submodule "src/svgfill"]
path = src/svgfill
url = https://github.com/IfcOpenShell/svgfill
[submodule "src/ifcopenshell-python/ifcopenshell/Sample-BIM-Files"]
path = src/ifcopenshell-python/ifcopenshell/Sample-BIM-Files
url = https://github.com/atomczak/Sample-BIM-Files
[submodule "src/ifcopenshell-python/test/Sample-BIM-Files"]
path = src/ifcopenshell-python/test/Sample-BIM-Files
url = https://github.com/atomczak/Sample-BIM-Files
+29 -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,11 @@ Dependencies
Building IfcOpenShell
---------------------
**Note:** The path where the source code is cloned to can contain spaces but non-ASCII characters are very likely to cause problems with the build.
Note 1: The path where the source code is cloned to can contain spaces but non-ASCII characters are very likely to cause problems with the build.
Note 2: If you had not used `git clone --recursive https://github.com/IfcOpenShell/IfcOpenshell.git`, update the submodules by running `git submodule init & git submodule update`.
Note 3: Be careful with special characters is the path when using the nix or win build scripts, because the OpenCASCADE build will fail on paths containing ++ and likely other situations.
### Compiling on Windows
The preferred way to fetch and build this project's dependencies is to use the build scripts
@@ -81,17 +87,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 +116,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 +141,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.
+29 -19
View File
@@ -60,6 +60,11 @@ 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)
IF(NOT CMAKE_BUILD_TYPE)
SET(CMAKE_BUILD_TYPE "Release")
ENDIF()
# 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 +149,7 @@ ELSE()
MESSAGE(FATAL_ERROR "Unable to find JSON for Modern C++ header file, aborting")
ENDIF()
add_definitions(-DWITH_GLTF)
set(SWIG_DEFINES ${SWIG_DEFINES} -DWITH_GLTF)
endif()
# Set INSTALL_RPATH for target
@@ -200,6 +206,7 @@ find_package(LibXml2 REQUIRED)
if (IFCXML_SUPPORT)
add_definitions(-DWITH_IFCXML)
set(SWIG_DEFINES ${SWIG_DEFINES} -DWITH_IFCXML)
endif()
FIND_PACKAGE(Boost REQUIRED COMPONENTS ${BOOST_COMPONENTS})
@@ -371,6 +378,8 @@ IF(COLLADA_SUPPORT)
IF(COLLADASWStreamWriter_h)
MESSAGE(STATUS "OpenCOLLADA header files found")
ADD_DEFINITIONS(-DWITH_OPENCOLLADA)
set(SWIG_DEFINES ${SWIG_DEFINES} -DWITH_OPENCOLLADA)
SET(OPENCOLLADA_LIBRARY_NAMES
GeneratedSaxParser MathMLSolver OpenCOLLADABaseUtils OpenCOLLADAFramework OpenCOLLADASaxFrameworkLoader
OpenCOLLADAStreamWriter UTF buffer ftoa
@@ -439,13 +448,17 @@ if(HDF5_SUPPORT)
else()
set(lib_ext a)
endif()
if ("${CMAKE_BUILD_TYPE}" STREQUAL "Debug")
set(debug_postfix "_debug")
endif()
SET(HDF5_LIBRARIES
"${HDF5_LIBRARY_DIR}/libhdf5_cpp.${lib_ext}"
"${HDF5_LIBRARY_DIR}/libhdf5.${lib_ext}"
"${HDF5_LIBRARY_DIR}/libz${zlib_post}.${lib_ext}"
"${HDF5_LIBRARY_DIR}/libsz.${lib_ext}"
"${HDF5_LIBRARY_DIR}/libaec.${lib_ext}"
"${HDF5_LIBRARY_DIR}/libhdf5_cpp${debug_postfix}.${lib_ext}"
"${HDF5_LIBRARY_DIR}/libhdf5${debug_postfix}.${lib_ext}"
"${HDF5_LIBRARY_DIR}/libz${zlib_post}${debug_postfix}.${lib_ext}"
"${HDF5_LIBRARY_DIR}/libsz${debug_postfix}.${lib_ext}"
"${HDF5_LIBRARY_DIR}/libaec${debug_postfix}.${lib_ext}"
)
endif()
@@ -462,12 +475,9 @@ if(HDF5_SUPPORT)
endif()
add_definitions(-DWITH_HDF5)
set(SWIG_DEFINES ${SWIG_DEFINES} -DWITH_HDF5)
endif()
IF(NOT CMAKE_BUILD_TYPE)
SET(CMAKE_BUILD_TYPE "Release")
ENDIF()
if(ENABLE_BUILD_OPTIMIZATIONS)
if(MSVC)
# NOTE: RelWithDebInfo and Release use O2 (= /Ox /Gl /Gy/ = Og /Oi /Ot /Oy /Ob2 /Gs /GF /Gy) by default,
@@ -692,7 +702,7 @@ TARGET_LINK_LIBRARIES(IfcParse ${Boost_LIBRARIES} ${BCRYPT_LIBRARIES} ${LIBXML2_
if (BUILD_IFCGEOM)
# IfcGeom
file(GLOB IFCGEOM_H_FILES ../src/ifcgeom/*.h)
file(GLOB IFCGEOM_H_FILES ../src/ifcgeom/*.h ../src/ifcgeom/*.i)
file(GLOB IFCGEOM_CPP_FILES ../src/ifcgeom/*.cpp)
set(IFCGEOM_FILES ${IFCGEOM_CPP_FILES} ${IFCGEOM_H_FILES})
@@ -784,21 +794,20 @@ INSTALL(TARGETS IfcGeomServer
endif()
if (ADD_COMMIT_SHA)
find_package (Git)
if (GIT_FOUND)
message("git found: ${GIT_EXECUTABLE} with version ${GIT_VERSION_STRING}")
execute_process(
COMMAND ${GIT_EXECUTABLE} show -s --pretty=%D HEAD
COMMAND ${GIT_EXECUTABLE} branch --contains HEAD
OUTPUT_VARIABLE git_branches
OUTPUT_STRIP_TRAILING_WHITESPACE
)
string(REPLACE ", " ";" git_branch_list "${git_branches}")
string(REPLACE " -> " ";" git_branch_list "${git_branch_list}")
string(REPLACE "\n" ";" git_branch_list "${git_branches}")
foreach(git_branch_candidate IN ITEMS ${git_branch_list})
if (NOT git_branch_candidate MATCHES "/")
if (NOT git_branch_candidate MATCHES "^HEAD$")
set(git_branch ${git_branch_candidate})
endif()
string(STRIP "${git_branch_candidate}" git_branch_candidate_2)
if (NOT git_branch_candidate_2 MATCHES "^HEAD$")
set(git_branch ${git_branch_candidate_2})
endif()
endforeach()
execute_process(
@@ -806,11 +815,12 @@ if (GIT_FOUND)
OUTPUT_VARIABLE git_sha
OUTPUT_STRIP_TRAILING_WHITESPACE
)
message(STATUS "IfcOpenShell branch ${git_branch}")
message(STATUS "IfcOpenShell commit ${git_sha}")
message(STATUS "IfcOpenShell branch: \"${git_branch}\"")
message(STATUS "IfcOpenShell commit: \"${git_sha}\"")
add_definitions(-DIFCOPENSHELL_BRANCH=${git_branch})
add_definitions(-DIFCOPENSHELL_COMMIT=${git_sha})
endif()
endif()
# Documentation
IF(BUILD_DOCUMENTATION)
+7 -1
View File
@@ -24,13 +24,19 @@ requirements:
- swig >=3.0.9
- libboost
- icu
- xmlschema
- lxml
- numpy
run:
- libgcc # [osx]
- python
- oce ==0.18.3
- libboost
- icu
- xmlschema
- lxml
- numpy
about:
home: http://ifcopenshell.org
license: LGPL
+14 -7
View File
@@ -70,6 +70,7 @@ logger.addHandler(ch)
PROJECT_NAME = "IfcOpenShell"
USE_CURRENT_PYTHON_VERSION = os.getenv("USE_CURRENT_PYTHON_VERSION")
ADD_COMMIT_SHA = os.getenv("ADD_COMMIT_SHA")
PYTHON_VERSIONS = ["3.6.14", "3.7.12", "3.8.12", "3.9.7", "3.10.0"]
JSON_VERSION = "v3.6.1"
@@ -664,13 +665,12 @@ if "cgal" in targets:
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)
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...")
@@ -692,6 +692,7 @@ cmake_args = [
"-DGLTF_SUPPORT=" "ON",
"-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:
@@ -782,6 +783,7 @@ if "IfcOpenShell-Python" in targets:
"-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(f"\rBuilding python {python_version} wrapper... ")
@@ -799,7 +801,12 @@ if "IfcOpenShell-Python" in targets:
if USE_CURRENT_PYTHON_VERSION:
python_info = sysconfig.get_paths()
compile_python_wrapper(platform.python_version(), python_info["purelib"], python_info["include"], sys.executable)
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*.*"])
+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
+79 -5
View File
@@ -27,6 +27,10 @@ ifeq ($(PYVERSION), py39)
PYLIBDIR:=python3.9
PYNUMBER:=39
endif
ifeq ($(PYVERSION), py310)
PYLIBDIR:=python3.10
PYNUMBER:=31
endif
ifeq ($(PLATFORM), linux)
ifeq ($(PYVERSION), py37)
@@ -41,6 +45,12 @@ EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.5/download/linux-64/ei
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/linux-64/boost-1.74.0-py39h5472131_3.tar.bz2
LXML_URL:=https://files.pythonhosted.org/packages/19/d9/a69c6aff5673554df48120565a14a50eaa41d29ae03b02faa0b023666318/lxml-4.6.3-cp39-cp39-manylinux2014_x86_64.whl
endif
ifeq ($(PYVERSION), py310)
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.8.0/download/linux-64/hpp-fcl-1.8.0-py310hdaf7e41_1.tar.bz2
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.11/download/linux-64/eigenpy-2.6.11-py310hf3e5c9c_0.tar.bz2
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/linux-64/boost-1.74.0-py310h7c3ba0c_5.tar.bz2
LXML_URL:=https://files.pythonhosted.org/packages/25/1e/19b46d8e8881fe0df2e20945d51919eeb1817836d62a90efa8506530e45c/lxml-4.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl
endif
ASSIMP_URL:=https://anaconda.org/conda-forge/assimp/5.0.1/download/linux-64/assimp-5.0.1-hedfc422_6.tar.bz2
OCTOMAP_URL:=https://anaconda.org/conda-forge/octomap/1.9.7/download/linux-64/octomap-1.9.7-h4bd325d_0.tar.bz2
ZLIB_URL:=https://anaconda.org/conda-forge/zlib/1.2.11/download/linux-64/zlib-1.2.11-h516909a_1010.tar.bz2
@@ -59,6 +69,12 @@ EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.5/download/osx-64/eige
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/osx-64/boost-1.74.0-py39ha641261_3.tar.bz2
LXML_URL:=https://files.pythonhosted.org/packages/b8/74/a71f7ad72e8db54ce899efab84507b801660750cbbfa6a39e6717557d36a/lxml-4.6.3-cp39-cp39-macosx_10_9_x86_64.whl
endif
ifeq ($(PYVERSION), py310)
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.8.0/download/osx-64/hpp-fcl-1.8.0-py310h651ac30_1.tar.bz2
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.11/download/osx-64/eigenpy-2.6.11-py310hc03097c_0.tar.bz2
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/osx-64/boost-1.74.0-py310h509978a_5.tar.bz2
LXML_URL:=https://files.pythonhosted.org/packages/a1/44/17b7dac7a18807d30e2fe10c3328c152808f5464565e230bfd0e77f178c6/lxml-4.8.0-cp310-cp310-macosx_10_15_x86_64.whl
endif
ASSIMP_URL:=https://anaconda.org/conda-forge/assimp/5.0.1/download/osx-64/assimp-5.0.1-h1224e73_6.tar.bz2
OCTOMAP_URL:=https://anaconda.org/conda-forge/octomap/1.9.7/download/osx-64/octomap-1.9.7-h940c156_0.tar.bz2
ZLIB_URL:=https://anaconda.org/conda-forge/zlib/1.2.11/download/osx-64/zlib-1.2.11-h7795811_1010.tar.bz2
@@ -77,6 +93,12 @@ EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.5/download/win-64/eige
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/win-64/boost-1.74.0-py39hefe7e4c_3.tar.bz2
LXML_URL:=https://files.pythonhosted.org/packages/72/d4/426ecb8849c47c3e370c87aa0ac05d85768df917ffea27fcd6686a5e6495/lxml-4.6.3-cp39-cp39-win_amd64.whl
endif
ifeq ($(PYVERSION), py310)
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.8.0/download/win-64/hpp-fcl-1.8.0-py310hc5a3c62_1.tar.bz2
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.11/download/win-64/eigenpy-2.6.11-py310hbd43d28_0.tar.bz2
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/win-64/boost-1.74.0-py310hc781a3c_5.tar.bz2
LXML_URL:=https://files.pythonhosted.org/packages/f6/71/65c80a4caa1617a4c6e8fe1500cebb179db96232e2f623bfe6a1f4294e39/lxml-4.8.0-cp310-cp310-win_amd64.whl
endif
ASSIMP_URL:=https://anaconda.org/conda-forge/assimp/5.0.1/download/win-64/assimp-5.0.1-hc2aa0de_6.tar.bz2
OCTOMAP_URL:=https://anaconda.org/conda-forge/octomap/1.9.7/download/win-64/octomap-1.9.7-h5362a0b_0.tar.bz2
ZLIB_URL:=https://anaconda.org/conda-forge/zlib/1.2.11/download/win-64/zlib-1.2.11-h62dcd97_1010.tar.bz2
@@ -93,9 +115,9 @@ endif
cp -r blenderbim/* dist/blenderbim/
# Provides IfcOpenShell Python functionality
cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcblender-python-$(PYNUMBER)-v0.7.0-58ee049-$(PLATFORM)64.zip
cd dist/working && unzip ifcblender*
cp -r dist/working/io_import_scene_ifc/ifcopenshell dist/blenderbim/libs/site/packages/
cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-$(PYNUMBER)-v0.7.0-2985bba-$(PLATFORM)64.zip
cd dist/working && unzip ifcopenshell-python*
cp -r dist/working/ifcopenshell dist/blenderbim/libs/site/packages/
# See bug #812
cd dist/working && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.7.0/src/blenderbim/occ_utils.py
@@ -133,6 +155,8 @@ endif
cp -r dist/working/IfcOpenShell-0.7.0/src/ifc4d/ifc4d dist/blenderbim/libs/site/packages/
# Provides IFC5D functionality
cp -r dist/working/IfcOpenShell-0.7.0/src/ifc5d/ifc5d dist/blenderbim/libs/site/packages/
# Provides IFCCityJSON functionality
cp -r dist/working/IfcOpenShell-0.7.0/src/ifccityjson dist/blenderbim/libs/site/packages/
rm -rf dist/working
# Provides Mustache templating in construction documentation
@@ -255,6 +279,15 @@ endif
cp -r dist/working/tqdm-4.50.0/tqdm dist/blenderbim/libs/site/packages/
rm -rf dist/working
# Required by IFCCityJSON
mkdir dist/working
cd dist/working && python -m venv env
cd dist/working && mkdir site-packages
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)
@@ -360,7 +393,7 @@ endif
cd dist/working/ && patch ../blenderbim/libs/site/packages/behave/runner_util.py < runner_util.patch
rm -rf dist/working
# Required by ids
# Required by ids - uh, not any more, but I'm trying it out in the drawing module
mkdir dist/working
cd dist/working && wget $(LXML_URL)
cd dist/working && cp *.whl lxml.zip && unzip lxml.zip
@@ -444,6 +477,47 @@ endif
cd dist/working/olca-ipc-0.0.10/ && cp -r olca ../../blenderbim/libs/site/packages/
rm -rf dist/working
# Provides Brickschema fucntionality
mkdir dist/working
cd dist/working && wget https://files.pythonhosted.org/packages/ca/37/8309da9a72407d2f5eb5489c197ac3cfe96ee3ccba1902d2a0ecc7843f92/brickschema-0.5.1.tar.gz
cd dist/working && tar -xzvf brickschema*
# This is an evil hack because we don't want to bundle flask
echo "" > dist/working/brickschema-0.5.1/brickschema/web.py
cd dist/working/brickschema-0.5.1/ && cp -r brickschema ../../blenderbim/libs/site/packages/
# For now lets bundle the latest nightly schema
cd dist/working && wget https://github.com/BrickSchema/Brick/releases/download/nightly/Brick.ttl
cd dist/working && cp Brick.ttl ../../blenderbim/bim/schema/Brick.ttl
rm -rf dist/working
# Required by brickschema
mkdir dist/working
cd dist/working && wget https://files.pythonhosted.org/packages/c7/22/37bb938be8e5c20d443b0c9ba0a243573b671b01739efb0a19f81bc5b470/pyshacl-0.17.2.tar.gz
cd dist/working && tar -xzvf pyshacl*
cd dist/working/pyshacl-0.17.2/ && cp -r pyshacl ../../blenderbim/libs/site/packages/
rm -rf dist/working
# Required by brickschema
mkdir dist/working
cd dist/working && wget https://files.pythonhosted.org/packages/d2/a7/be8244688bfcee37c23733ab4fe8e6afa6d4403bd2674a3ae7bd2cecc77b/rdflib-6.0.2.tar.gz
cd dist/working && tar -xzvf rdflib*
cd dist/working/rdflib-6.0.2/ && cp -r rdflib ../../blenderbim/libs/site/packages/
rm -rf dist/working
# Required by brickschema
mkdir dist/working
cd dist/working && wget https://files.pythonhosted.org/packages/7d/c7/208aece36279e4f1236e437119358786e39530ecc1719d4e1afeddba5288/owlrl-6.0.2.tar.gz
cd dist/working && tar -xzvf owlrl*
cd dist/working/owlrl-6.0.2/ && cp -r owlrl ../../blenderbim/libs/site/packages/
rm -rf dist/working
# Required by brickschema
# This is a bit of a dodgy one, it should be provided by setuptools which Blender doesn't ship.
mkdir dist/working
cd dist/working && wget https://blenderbim.org/builds/pkg_resources.tar.gz
cd dist/working && tar -xzvf pkg_resources*
cd dist/working/ && cp -r pkg_resources ../blenderbim/libs/site/packages/
rm -rf dist/working
cd dist/blenderbim && sed -i "s/999999/$(VERSION)/" __init__.py
cd dist && zip -r blenderbim-$(VERSION)-$(PYVERSION)-$(PLATFORM).zip ./*
rm -rf dist/blenderbim
@@ -479,7 +553,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

+32 -9
View File
@@ -43,6 +43,7 @@ modules = {
"sequence": None,
"group": None,
"system": None,
"brick": None,
"structural": None,
"boundary": None,
"profile": None,
@@ -53,6 +54,7 @@ modules = {
"pset": None,
"qto": None,
"classification": None,
"library": None,
"constraint": None,
"document": None,
"pset_template": None,
@@ -62,9 +64,13 @@ modules = {
"bimtester": None,
"diff": None,
"patch": None,
"gis": None,
"covetool": None,
"augin": None,
"debug": None,
# Uncomment this line to enable loading of the demo module. Happy hacking!
# The name "demo" must correlate to a folder name in `bim/module/`.
# "demo": None,
}
@@ -73,17 +79,18 @@ for name in modules.keys():
classes = [
operator.OpenUri,
operator.SelectDataDir,
operator.SelectSchemaDir,
operator.SelectIfcFile,
operator.OpenUpstream,
operator.AddSectionPlane,
operator.RemoveSectionPlane,
operator.ReloadIfcFile,
operator.AddIfcFile,
operator.RemoveIfcFile,
operator.BIM_OT_add_section_plane,
operator.BIM_OT_remove_section_plane,
operator.ConfigureVisibility,
operator.OpenUpstream,
operator.OpenUri,
operator.ReloadIfcFile,
operator.RemoveIfcFile,
operator.SelectDataDir,
operator.SelectIfcFile,
operator.SelectSchemaDir,
operator.SelectURIAttribute,
prop.StrProperty,
prop.ObjProperty,
prop.Attribute,
@@ -99,6 +106,22 @@ classes = [
ui.BIM_UL_generic,
ui.BIM_UL_topics,
ui.BIM_ADDON_preferences,
# Scene panel groups
ui.BIM_PT_project_info,
ui.BIM_PT_project_setup,
ui.BIM_PT_collaboration,
ui.BIM_PT_geometry,
ui.BIM_PT_services,
ui.BIM_PT_structural,
ui.BIM_PT_4D5D,
ui.BIM_PT_quality_control,
ui.BIM_PT_integrations,
# Object panel groups
ui.BIM_PT_object_metadata,
ui.BIM_PT_geometry_object,
ui.BIM_PT_services_object,
ui.BIM_PT_utilities_object,
ui.BIM_PT_misc_object,
]
for mod in modules.values():
@@ -1,10 +1,44 @@
<link href="jsgantt.css" rel="stylesheet" type="text/css"/>
<script src="jsgantt.js" type="text/javascript"></script>
<style>
.gtaskcellwkend,
.gtaskcellcurrent,
.gminorheadingwkend {
background-color: #e1e1e1;
}
.gitemhighlight td {
background-color: #ffdaaa;
}
.gtaskblue {
background: #4281A4;
}
.gtaskred {
background: #C1666B;
}
.gtaskgreen {
background: #48A9A6;
}
.gtaskyellow {
background: #D4B483;
}
.gmainleft {
overflow: visible;
flex: 0 1 auto;
}
</style>
<a href="#print" id="print">Print Mode</a>
<div style="position:relative" class="gantt" id="GanttChartDIV"></div>
<script type="text/javascript">
var g = new JSGantt.GanttChart(document.getElementById('GanttChartDIV'), 'day');
g.setOptions({
vCaptionType: 'Complete', // Set to Show Caption : None,Caption,Resource,Duration,Complete,
vCaptionType: 'Caption', // Set to Show Caption : None,Caption,Resource,Duration,Complete,
vQuarterColWidth: 36,
vDateTaskDisplayFormat: 'day dd month yyyy', // Shown in tool tip box
vDayMajorDateDisplayFormat: 'mon yyyy - Week ww',// Set format to dates in the "Major" header of the "Day" view
@@ -14,7 +48,16 @@ g.setOptions({
vShowEndWeekDate: 0, // Show/Hide the date for the last day of the week in header for daily
vUseSingleCell: 10000, // Set the threshold cell per table row (Helps performance for large data.
vFormatArr: ['Day', 'Week', 'Month', 'Quarter'], // Even with setUseSingleCell using Hour format on such a large chart can cause issues in some browsers,
vTotalHeight: 1000,
vShowRes: false, // Disable the resource column.
vShowComp: false, // Disable the completion column.
vShowDur: false, // Disable the duration column, because jsgantt doesn't calculate durations the way we want.
vAdditionalHeaders: {ifcduration: {title: 'Duration'}},
vUseToolTip: false, // Disable tooltips.
vTotalHeight: 900,
});
document.getElementById('print').addEventListener('click', function() {
g.setTotalHeight("");
g.Draw();
});
var json_data = `
{{{json_data}}}
File diff suppressed because one or more lines are too long
@@ -0,0 +1,16 @@
ISO-10303-21;
HEADER;
FILE_DESCRIPTION((),'2;1');
FILE_NAME('EPset_Drawing.ifc','2020-01-01T00:00:00',(),(),'EPset_Drawing','EPset_Drawing',$);
FILE_SCHEMA(('IFC4'));
ENDSEC;
DATA;
#1=IFCPROPERTYSETTEMPLATE('2JhNIvqZrFnAgxfhK0XVQX',$,'EPset_Drawing','',.PSET_OCCURRENCEDRIVEN.,'IfcAnnotation',(#2,#3,#4,#5,#6,#7));
#2=IFCSIMPLEPROPERTYTEMPLATE('23JavTMk98ZxXhrUEnjAcf',$,'TargetView','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
#3=IFCSIMPLEPROPERTYTEMPLATE('1yVWUt5H9DAOuu0OaMMLpe',$,'Scale','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
#4=IFCSIMPLEPROPERTYTEMPLATE('3gsuPBtU93b8f0gg1pjkq6',$,'HumanScale','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
#5=IFCSIMPLEPROPERTYTEMPLATE('0AK5C2UpL4$eaac2LszAx$',$,'HasUnderlay','',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
#6=IFCSIMPLEPROPERTYTEMPLATE('2j2ZEZR8X5tONm7kli5hM6',$,'HasLinework','',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
#7=IFCSIMPLEPROPERTYTEMPLATE('1ttChRysH9UuEX2FeMj5Hu',$,'HasAnnotation','',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
ENDSEC;
END-ISO-10303-21;
@@ -19,14 +19,26 @@
*/
* { stroke-linecap: round; stroke-linejoin: round; }
*[id] { fill: black; stroke: black; stroke-linecap: 'round'; stroke-width: 0.35; }
text { fill: black; stroke: none; }
.cut { fill: black; stroke: black; stroke-linecap: 'round'; stroke-width: 0.35; fill-rule: evenodd; }
.projection { fill: white; stroke: black; stroke-linecap: 'round'; stroke-width: 0.25; }
.annotation { fill: none; stroke: black; stroke-linecap: 'round'; stroke-width: 0.25; }
.hidden { stroke-dasharray: 3, 2; }
.solid {}
.leader { marker-end: url(#leader-marker); }
.stair { marker-start: url(#stair-marker-start); marker-end: url(#stair-marker-end); }
.IfcAnnotation { fill: none; stroke: black; stroke-linecap: 'round'; stroke-width: 0.25; }
.PredefinedType-MISC { stroke: black; stroke-width: 0.25; }
.PredefinedType-BACKGROUND { stroke: black; stroke-width: 0.18; }
.PredefinedType-GRID { marker-start: url(#grid-marker); marker-end: url(#grid-marker); }
.PredefinedType-SECTIONLEVEL { marker-start: url(#section-level-marker); stroke-dasharray: 12.5, 3, 3, 3; }
.PredefinedType-PLANLEVEL { marker-end: url(#plan-level-marker); }
.PredefinedType-DIMENSION { marker-start: url(#dimension-marker-start); marker-end: url(#dimension-marker-end); }
.PredefinedType-HIDDENLINE { stroke-dasharray: 3, 2; }
.PredefinedType-STAIR { marker-start: url(#stair-marker-start); marker-end: url(#stair-marker-end); }
.PredefinedType-BOUNDARY { fill: none; stroke: red; stroke-width: 1; stroke-dasharray: 12, 4, 3, 4, 3, 4; }
.PredefinedType-BATTING { marker-start: url(#batting); stroke: none; }
.PredefinedType-SEALANT { fill: url(#crosshatch1); stroke-width: 0.25; }
.PredefinedType-TEXT { fill: black; stroke: none; }
path.PredefinedType-TEXTLEADER { marker-end: url(#leader-marker); }
text.PredefinedType-TEXTLEADER { fill: black; stroke: none; }
.break { fill: white; }
.breakline { fill: none; stroke: black; stroke-linecap: 'round'; stroke-width: 0.25; marker-mid: url(#breakline-marker); }
.material-blank { fill: white; }
@@ -44,5 +56,9 @@ text { fill: black; stroke: none; }
.material-honeycomb { fill: url(#honeycomb); }
.material-sand { fill: url(#sand); }
.material-concrete { fill: url(#concrete); stroke-width: 0.5; }
.material-boundary { fill: none; stroke: red; stroke-width: 1; stroke-dasharray: 12,4,3,4,3,4; }
.IfcSpace { fill: none; stroke: none; }
.PredefinedType-STUD { stroke: black; stroke-width: 1; }
.PredefinedType-WOOD { fill: url(#wood); stroke: black; stroke-width: 0.5; }
.PredefinedType-STEEL { fill: url(#steel); stroke: black; stroke-width: 0.5; }
.PredefinedType-CONCRETE { fill: url(#concrete); stroke: black; stroke-width: 0.5; }
.PredefinedType-PLASTERBOARD { fill: url(#sand); stroke: black; stroke-width: 0.25; }
@@ -1,5 +1,9 @@
<?xml version="1.0" encoding="utf-8" ?>
<svg baseProfile="full" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:ev="http://www.w3.org/2001/xml-events" xmlns:xlink="http://www.w3.org/1999/xlink">
<marker id="batting" markerHeight="10" markerWidth="4" orient="auto" refX="0" refY="5" markerUnits="userSpaceOnUse">
<!-- Tip: https://svg-path-visualizer.netlify.app/ -->
<path style="fill: none; stroke:black; stroke-width:0.18" d="M 0 2 A 2 2 0 0 1 4 2 L 2 8 M 0 2 L 2 8 A 2 2 0 0 0 4 10 M 2 8 A 2 2 0 0 1 0 10" />
</marker>
<marker id="eng-dimension-marker-start" markerHeight="14" markerWidth="11" orient="auto" refX="1" refY="7">
<g>
<path d="M 11 3.5 L 11 10.5 L 1 7" class="annotation" style="fill:black;" />

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

@@ -3,6 +3,10 @@
<pattern id="demolish" width="1" height="1" patternTransform="rotate(45 0 0)" patternUnits="userSpaceOnUse">
<line x1="0" y1="0" x2="0" y2="1" style="stroke:red; stroke-width:0.25" />
</pattern>
<pattern id="steel" width="3" height="1" patternTransform="rotate(45 0 0)" patternUnits="userSpaceOnUse">
<line x1="0" y1="0" x2="0" y2="1" style="stroke:black; stroke-width:0.25" />
<line x1="1" y1="0" x2="1" y2="1" style="stroke:black; stroke-width:0.25" />
</pattern>
<pattern id="diagonal1" width="1" height="1" patternTransform="rotate(45 0 0)" patternUnits="userSpaceOnUse">
<line x1="0" y1="0" x2="0" y2="1" style="stroke:black; stroke-width:0.25" />
</pattern>
@@ -80,6 +84,9 @@
<path d="M 1.25,2.165 L 2.5,4.33 L 5,4.33 L 6.25,2.165 L 5,0 L 2.5,0 L 1.25,2.165" style="stroke:black;stroke-width:0.25;stroke-linecap:round;fill:none" />
<path d="M 6.25,2.165 L 7.5,2.165" style="stroke:black;stroke-width:0.25;stroke-linecap:round" />
</pattern>
<pattern id="wood" width="50" height="50" patternUnits="userSpaceOnUse">
<path style="fill:none; stroke: black; stroke-width:0.25;" d="M 49.649999,1.3460737 A 69.019995,69.019995 0 0 0 48.653938,0.34999949 M 49.649999,4.9389583 A 66.554994,66.554994 0 0 0 45.06105,0.34999949 M 49.649999,8.6983527 A 64.089999,64.089999 0 0 0 41.301656,0.34999949 M 49.649999,12.675003 A 61.624998,61.624998 0 0 0 37.325,0.34999949 M 49.649999,16.948067 A 59.159997,59.159997 0 0 0 33.05192,0.34999949 M 49.649999,21.652972 A 56.694997,56.694997 0 0 0 28.347017,0.34999949 M 49.649999,27.057906 A 54.229999,54.229999 0 0 0 22.942096,0.34999949 M 49.649999,33.866298 A 51.764998,51.764998 0 0 0 16.1337,0.34999949 M 49.649999,49.649989 A 49.299997,49.299997 0 0 0 0.34999949,0.34999949 M 47.185,49.649989 A 46.834999,46.834999 0 0 0 0.34999949,2.8149951 M 44.72,49.649989 A 44.369998,44.369998 0 0 0 0.34999949,5.2799906 M 42.254999,49.649989 A 41.904997,41.904997 0 0 0 0.34999949,7.7450123 M 39.790001,49.649989 A 39.439999,39.439999 0 0 0 0.34999949,10.210008 M 37.325,49.649989 A 36.974998,36.974998 0 0 0 0.34999949,12.675003 M 34.859999,49.649989 A 34.509998,34.509998 0 0 0 0.34999949,15.139998 M 32.395001,49.649989 A 32.044999,32.044999 0 0 0 0.34999949,17.604994 M 29.93,49.649989 A 29.579999,29.579999 0 0 0 0.34999949,20.069989 M 27.465,49.649989 A 27.114998,27.114998 0 0 0 0.34999949,22.535011 M 24.999997,49.649989 A 24.649999,24.649999 0 0 0 0.34999949,25.000007 M 22.534998,49.649989 A 22.184999,22.184999 0 0 0 0.34999949,27.465003 M 20.069998,49.649989 A 19.719999,19.719999 0 0 0 0.34999949,29.929999 M 17.604998,49.649989 A 17.254999,17.254999 0 0 0 0.34999949,32.394994 M 15.139998,49.649989 A 14.789999,14.789999 0 0 0 0.34999949,34.85999 M 12.674999,49.649989 A 12.325,12.325 0 0 0 0.34999949,37.325011 M 10.209999,49.649989 A 9.8599994,9.8599994 0 0 0 0.34999949,39.790007 M 7.7449994,49.649989 A 7.3949999,7.3949999 0 0 0 0.34999949,42.255002 M 5.2799992,49.649989 A 4.9299997,4.9299997 0 0 0 0.34999949,44.719998 M 2.8149995,49.649989 A 2.4649999,2.4649999 0 0 0 0.34999949,47.184993 M 0.34999949,49.649989 H 3.8010003 M 0.34999949,49.649989 H 3.8010003 M 0.34999949,49.649989 H 3.8010003" />
</pattern>
<pattern id="sand" width="50" height="50" patternUnits="userSpaceOnUse">
<path style="fill: white;" d="M 0 0 50 0 50 50 0 50" />
<path

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 67 KiB

@@ -5,6 +5,20 @@
</g>
<g id="door-tag">
<circle cy="1.75" r="5" fill="white" stroke="black" style="stroke-width: 0.25;" />
<line x1="-5" y1="1.75" x2="5" y2="1.75" style="stroke:black; stroke-width: 0.25;" />
<line x1="-5" y1="1.75" x2="5" y2="1.75" style="stroke: black; stroke-width: 0.25;" />
</g>
<g id="elevation-arrow">
<path style="fill: black;" d="M -7.07106 0 0 -7.07106 7.07106 0" />
</g>
<g id="elevation-tag">
<circle r="5" fill="white" stroke="black" style="stroke-width: 0.25;" />
<line x1="-5" y1="0" x2="5" y2="0" style="stroke: black; stroke-width: 0.25;" />
</g>
<g id="section-arrow">
<path style="fill: black;" d="M -7.07106 0 0 -7.07106 7.07106 0" />
</g>
<g id="section-tag">
<circle r="5" fill="white" stroke="black" style="stroke-width: 0.25;" />
<line x1="-5" y1="0" x2="5" y2="0" style="stroke: black; stroke-width: 0.25;" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 572 B

After

Width:  |  Height:  |  Size: 1.2 KiB

@@ -173,7 +173,7 @@
style="stroke-width:0.999999px"
y="1036.1166"
x="3030.9653"
id="tspan2527">{{revision}}</tspan></text>
id="tspan2527">{{Revision}}</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:13.3918px;line-height:125%;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.999999px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
@@ -183,7 +183,7 @@
id="tspan2531"
x="2804.7021"
y="1036.1166"
style="stroke-width:0.999999px">{{number}}</tspan></text>
style="stroke-width:0.999999px">{{Identification}} - {{Name}}</tspan></text>
</g>
<g
transform="matrix(0.26458333,0,0,0.26458334,-3.180917e-5,297.00003)"

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

@@ -169,7 +169,7 @@
style="stroke-width:0.264583px"
y="397.13919"
x="554.94287"
id="tspan2527">{{revision}}</tspan></text>
id="tspan2527">{{Revision}}</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:3.54325px;line-height:125%;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
@@ -179,7 +179,7 @@
id="tspan2531"
x="495.07742"
y="397.13919"
style="stroke-width:0.264583px">{{number}}</tspan></text>
style="stroke-width:0.264583px">{{Identification}} - {{Name}}</tspan></text>
<g
style="stroke-width:3.77953"
id="g90669"

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

@@ -172,7 +172,7 @@
id="tspan2527"
x="544.94287"
y="284.13919"
style="stroke-width:0.264583px">{{revision}}</tspan></text>
style="stroke-width:0.264583px">{{Revision}}</tspan></text>
<text
id="text2533"
y="284.13919"
@@ -182,7 +182,7 @@
style="stroke-width:0.264583px"
y="284.13919"
x="485.07742"
id="tspan2531">{{number}}</tspan></text>
id="tspan2531">{{Identification}} - {{Name}}</tspan></text>
</g>
<g
style="stroke-width:3.77953"

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

@@ -70,7 +70,7 @@
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:4.96055365px;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';text-align:center;text-anchor:middle;stroke-width:0.26458332px"
y="103.10933"
x="32.822662"
id="tspan857">{{no}}</tspan></text>
id="tspan857">{{Identification}}</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:17.71626091px;line-height:125%;font-family:Arial;-inkscape-font-specification:Arial;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458329px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
@@ -80,7 +80,7 @@
id="tspan861"
x="39.12896"
y="100.19173"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:3.54325247px;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';text-align:start;text-anchor:start;stroke-width:0.26458329px">{{name}}</tspan></text>
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:3.54325247px;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';text-align:start;text-anchor:start;stroke-width:0.26458329px">{{Name}}</tspan></text>
<text
id="text867"
y="105.0553"
@@ -90,6 +90,6 @@
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:3.54325247px;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';text-align:start;text-anchor:start;stroke-width:0.26458332px"
y="105.0553"
x="39.285267"
id="tspan865">{{scale}}</tspan></text>
id="tspan865">{{Scale}}</tspan></text>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

+66 -42
View File
@@ -28,10 +28,12 @@ import ifcopenshell
import ifcopenshell.api
import ifcopenshell.util.placement
import blenderbim.tool as tool
import blenderbim.core.geometry
import blenderbim.core.aggregate
import blenderbim.core.spatial
import blenderbim.core.style
from blenderbim.bim.ifc import IfcStore
from mathutils import Vector
class IfcExporter:
@@ -41,20 +43,22 @@ class IfcExporter:
def export(self):
self.file = IfcStore.get_file()
self.set_header()
IfcStore.update_cache()
if bpy.context.scene.BIMProjectProperties.is_authoring:
self.sync_object_placements_and_deletions()
self.sync_deletions()
self.sync_all_objects()
self.sync_edited_objects()
extension = self.ifc_export_settings.output_file.split(".")[-1]
extension = self.ifc_export_settings.output_file.split(".")[-1].lower()
if extension == "ifczip":
with tempfile.TemporaryDirectory() as unzipped_path:
filename, ext = os.path.splitext(os.path.basename(self.ifc_export_settings.output_file))
tmp_name = "{}.ifc".format(filename)
tmp_file = os.path.join(unzipped_path, tmp_name)
tmp_filename = f"{filename}.ifc"
tmp_file = os.path.join(unzipped_path, tmp_filename)
self.file.write(tmp_file)
with zipfile.ZipFile(
self.ifc_export_settings.output_file, mode="w", compression=zipfile.ZIP_DEFLATED, compresslevel=9
) as zf:
zf.write(tmp_file)
zf.write(tmp_file, arcname=tmp_filename)
elif extension == "ifc":
self.file.write(self.ifc_export_settings.output_file)
elif extension == "ifcjson":
@@ -83,27 +87,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
tool.Collector.sync(obj)
self.sync_object_placement(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,35 +127,51 @@ 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 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
return True
def sync_grid_axis_object_placement(self, obj, element):
grid = (element.PartOfU or element.PartOfV or element.PartOfW)[0]
@@ -148,15 +180,7 @@ class IfcExporter:
self.sync_object_placement(grid_obj)
if grid_obj.matrix_world != obj.matrix_world:
bpy.ops.bim.update_representation(obj=obj.name)
def should_delete(self, obj):
try:
# This will throw an exception if the Blender object no longer exists
foo = obj.name
foo
return False
except:
return True
tool.Geometry.record_object_position(obj)
def get_application_name(self):
return "BlenderBIM"
+11 -13
View File
@@ -26,9 +26,7 @@ from blenderbim.bim.module.drawing.prop import RasterStyleProperty
from bpy.app.handlers import persistent
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.module.owner.prop import get_user_person, get_user_organisation
from ifcopenshell.api.attribute.data import Data as AttributeData
from ifcopenshell.api.material.data import Data as MaterialData
from ifcopenshell.api.style.data import Data as StyleData
from ifcopenshell.api.type.data import Data as TypeData
@@ -68,11 +66,10 @@ def name_callback(obj, data):
if isinstance(obj, bpy.types.Material):
if obj.BIMObjectProperties.ifc_definition_id:
IfcStore.get_file().by_id(obj.BIMObjectProperties.ifc_definition_id).Name = obj.name
AttributeData.load(IfcStore.get_file(), obj.BIMObjectProperties.ifc_definition_id)
MaterialData.load_materials()
if obj.BIMMaterialProperties.ifc_style_id:
IfcStore.get_file().by_id(obj.BIMMaterialProperties.ifc_style_id).Name = obj.name
StyleData.load(IfcStore.get_file(), obj.BIMMaterialProperties.ifc_style_id)
refresh_ui_data()
return
if not obj.BIMObjectProperties.ifc_definition_id or "/" not in obj.name:
@@ -96,7 +93,7 @@ def name_callback(obj, data):
if element.is_a("IfcTypeProduct"):
TypeData.purge()
element.Name = "/".join(obj.name.split("/")[1:])
AttributeData.load(IfcStore.get_file(), obj.BIMObjectProperties.ifc_definition_id)
refresh_ui_data()
def color_callback(obj, data):
@@ -160,12 +157,12 @@ def loadIfcStore(scene):
if not IfcStore.get_file():
return
IfcStore.get_schema()
IfcStore.reload_linked_elements()
IfcStore.relink_all_objects()
@persistent
def undo_pre(scene):
IfcStore.update_undo_redo_stack_objects()
IfcStore.track_undo_redo_stack_object_map()
@persistent
@@ -174,13 +171,13 @@ def undo_post(scene):
IfcStore.last_transaction = bpy.context.scene.BIMProperties.last_transaction
IfcStore.undo()
purge_module_data()
IfcStore.update_undo_redo_stack_objects()
IfcStore.reload_linked_elements(objects=[bpy.data.objects.get(o) for o in IfcStore.undo_redo_stack_objects])
IfcStore.track_undo_redo_stack_selected_objects()
IfcStore.reload_undo_redo_stack_objects()
@persistent
def redo_pre(scene):
IfcStore.update_undo_redo_stack_objects()
IfcStore.track_undo_redo_stack_object_map()
@persistent
@@ -189,8 +186,8 @@ def redo_post(scene):
IfcStore.last_transaction = bpy.context.scene.BIMProperties.last_transaction
IfcStore.redo()
purge_module_data()
IfcStore.update_undo_redo_stack_objects()
IfcStore.reload_linked_elements(objects=[bpy.data.objects.get(o) for o in IfcStore.undo_redo_stack_objects])
IfcStore.track_undo_redo_stack_selected_objects()
IfcStore.reload_undo_redo_stack_objects()
@persistent
@@ -236,6 +233,7 @@ def setDefaultProperties(scene):
)
ifcopenshell.api.owner.settings.get_user = lambda ifc: core_owner.get_user(tool.Owner)
ifcopenshell.api.owner.settings.get_application = get_application
# TODO: Move to drawing module
if len(bpy.context.scene.DocProperties.drawing_styles) == 0:
drawing_style = bpy.context.scene.DocProperties.drawing_styles.add()
drawing_style.name = "Technical"
@@ -289,7 +287,7 @@ def setDefaultProperties(scene):
RasterStyleProperty.SHADING_SHADOW_INTENSITY.value: 0.5,
RasterStyleProperty.DISPLAY_LIGHT_DIRECTION.value: (0.5, 0.5, 0.5),
RasterStyleProperty.VIEW_USE_CURVE_MAPPING.value: False,
RasterStyleProperty.OVERLAY_SHOW_WIREFRAMES.value: True,
RasterStyleProperty.OVERLAY_SHOW_WIREFRAMES.value: False,
RasterStyleProperty.OVERLAY_WIREFRAME_THRESHOLD.value: 0,
RasterStyleProperty.OVERLAY_SHOW_FLOOR.value: False,
RasterStyleProperty.OVERLAY_SHOW_AXIS_X.value: False,
+48
View File
@@ -19,6 +19,7 @@
import bpy
import json
import math
import zipfile
import ifcopenshell
import ifcopenshell.util.attribute
from mathutils import geometry
@@ -47,6 +48,9 @@ def draw_attribute(attribute, layout, copy_operator=None):
if copy_operator:
op = layout.operator(f"{copy_operator}", text="", icon="COPYDOWN")
op.name = attribute.name
if attribute.is_uri:
op = layout.operator("bim.select_uri_attribute", text="", icon="FILE_FOLDER")
op.data_path = attribute.path_from_id("string_value")
def import_attributes(ifc_class, props, data, callback=None):
@@ -77,6 +81,8 @@ def import_attribute(attribute, props, data, callback=None):
props.remove(len(props) - 1)
elif data_type == "string":
new.string_value = "" if new.is_null else data[attribute.name()]
if attribute.type_of_attribute().declared_type().name() == "IfcURIReference":
new.is_uri = True
elif data_type == "boolean":
new.bool_value = False if new.is_null else data[attribute.name()]
elif data_type == "integer":
@@ -97,3 +103,45 @@ def export_attributes(props, callback=None):
continue # Our job is done
attributes[prop.name] = prop.get_value()
return attributes
class IfcHeaderExtractor:
def __init__(self, filepath: str):
self.filepath = filepath
def extract(self):
extension = self.filepath.split(".")[-1]
if extension.lower() == "ifc":
with open(self.filepath) as ifc_file:
return self.extract_ifc_spf(ifc_file)
elif extension.lower() == "ifczip":
return self.extract_ifc_zip()
def extract_ifc_spf(self, ifc_file):
# https://www.steptools.com/stds/step/IS_final_p21e3.html#clause-8
data = {}
max_lines_to_parse = 50
for _ in range(max_lines_to_parse):
line = next(ifc_file)
if isinstance(line, bytes):
line = line.decode("utf-8")
if line.startswith("FILE_DESCRIPTION"):
for i, part in enumerate(line.split("'")):
if i == 1:
data["description"] = part
elif i == 3:
data["implementation_level"] = part
elif line.startswith("FILE_NAME"):
for i, part in enumerate(line.split("'")):
if i == 1:
data["name"] = part
elif i == 3:
data["time_stamp"] = part
elif line.startswith("FILE_SCHEMA"):
data["schema_name"] = line.split("'")[1]
break
return data
def extract_ifc_zip(self):
archive = zipfile.ZipFile(self.filepath, "r")
return self.extract_ifc_spf(archive.open(archive.filelist[0]))
+130 -16
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
@@ -38,28 +43,36 @@ class IfcStore:
library_file = None
element_listeners = set()
undo_redo_stack_objects = set()
undo_redo_stack_object_names = {}
current_transaction = ""
last_transaction = ""
history = []
future = []
schema_identifiers = ["IFC4", "IFC2X3"]
@staticmethod
def purge():
IfcStore.path = ""
IfcStore.file = None
IfcStore.schema = None
IfcStore.cache = None
IfcStore.cache_path = None
IfcStore.id_map = {}
IfcStore.guid_map = {}
IfcStore.deleted_ids = set()
IfcStore.edited_objs = set()
IfcStore.pset_template_path = ""
IfcStore.pset_template_file = None
IfcStore.library_path = ""
IfcStore.library_file = None
IfcStore.schema_identifiers = ["IFC4", "IFC2X3"]
@staticmethod
def get_file():
if IfcStore.file is None:
IfcStore.path = bpy.context.scene.BIMProperties.ifc_file
if not os.path.isabs(IfcStore.path):
IfcStore.path = os.path.abspath(os.path.join(bpy.path.abspath("//"), IfcStore.path))
if IfcStore.path:
try:
IfcStore.load_file(IfcStore.path)
@@ -67,6 +80,31 @@ class IfcStore:
pass
return IfcStore.file
@staticmethod
def get_cache():
if IfcStore.cache is None and IfcStore.path:
ifc_key = IfcStore.path + IfcStore.file.wrapped_data.header.file_name.time_stamp
ifc_hash = hashlib.md5(ifc_key.encode("utf-8")).hexdigest()
IfcStore.cache_path = os.path.join(bpy.context.scene.BIMProperties.data_dir, "cache", f"{ifc_hash}.h5")
cache_settings = ifcopenshell.geom.settings()
cache_settings.set(cache_settings.STRICT_TOLERANCE, True)
try:
IfcStore.cache = ifcopenshell.geom.serializers.hdf5(IfcStore.cache_path, cache_settings)
except:
return
return IfcStore.cache
@staticmethod
def update_cache():
if not IfcStore.cache:
return
ifc_key = IfcStore.path + IfcStore.file.wrapped_data.header.file_name.time_stamp
ifc_hash = hashlib.md5(ifc_key.encode("utf-8")).hexdigest()
new_cache_path = os.path.join(bpy.context.scene.BIMProperties.data_dir, "cache", f"{ifc_hash}.h5")
IfcStore.cache = None
os.replace(IfcStore.cache_path, new_cache_path)
IfcStore.get_cache()
@staticmethod
def load_file(path):
extension = path.split(".")[-1]
@@ -85,7 +123,9 @@ class IfcStore:
@staticmethod
def get_schema():
if IfcStore.file is None:
return
IfcStore.schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(
bpy.context.scene.BIMProjectProperties.export_schema
)
elif IfcStore.schema is None:
IfcStore.schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(IfcStore.file.schema)
return IfcStore.schema
@@ -108,14 +148,32 @@ class IfcStore:
IfcStore.element_listeners.add(callback)
@staticmethod
def update_undo_redo_stack_objects():
def track_undo_redo_stack_object_map():
"""Keeps track of currently mapped object names, typically during undo and redo
When any Blender object is stored outside a Blender PointerProperty, such as
in a regular Python list, there is the likely probability that the object
will be invalidated when undo or redo occurs. Object invalidation seems to
occur whenever an object is affected during an operation.
For example, if an operator deletes a modifier on o1, then o1 will be invalidated.
"""
for key, value in IfcStore.id_map.items():
try:
IfcStore.undo_redo_stack_object_names[key] = value.name
except:
continue
@staticmethod
def track_undo_redo_stack_selected_objects():
"""Keeps track of selected object names, typically during undo and redo
When any Blender object is stored outside a Blender PointerProperty, such as
in a regular Python list, there is the likely probability that the object
will be invalidated when undo or redo occurs. Object invalidation seems to
only occur for selected objects either pre/post undo/redo event, including
selected objects for consecutive undo/redos, and all children.
occur for selected objects either pre/post undo/redo event, including
selected objects for consecutive undo/redos, and all children. This is
important because selected objects are often deleted from the scene.
So if I first select o1, then o2, then o3, then press undo, o3 will be
invalidated. If instead I press undo twice, o3 and o2 will be invalidated.
@@ -130,14 +188,23 @@ class IfcStore:
IfcStore.undo_redo_stack_objects |= objects
@staticmethod
def reload_linked_elements(objects=None):
def reload_undo_redo_stack_objects():
"""Reloads any invalidated objects after undo or redo
After an undo or redo operation, objects may have been invalidated in
our id_map and guid_map. Invalidated objects are typically those that
have been manipulated or deleted. This checks the cache of mapped and
selected objects prior to the operation and ensures that if the object
is invalidated, they are reloaded based on the object name that was
tracked prior to the undo / redo.
"""
file = IfcStore.get_file()
if not file:
return
if objects is None:
objects = bpy.data.objects
for obj in objects:
# First, reload objects that were selected or active
for name in IfcStore.undo_redo_stack_objects:
obj = bpy.data.objects.get(name)
if not obj:
continue
if not obj.BIMObjectProperties.ifc_definition_id:
@@ -148,6 +215,55 @@ class IfcStore:
data["guid"] = element.GlobalId
IfcStore.commit_link_element(data)
# Scan for any straggling invalidated objects which were indirectly affected and reload them too.
for key, value in IfcStore.id_map.items():
try:
value.name
except:
obj = bpy.data.objects.get(IfcStore.undo_redo_stack_object_names[key])
if not obj or not obj.BIMObjectProperties.ifc_definition_id:
continue
element = file.by_id(obj.BIMObjectProperties.ifc_definition_id)
data = {"id": element.id(), "obj": obj.name}
if hasattr(element, "GlobalId"):
data["guid"] = element.GlobalId
IfcStore.commit_link_element(data)
@staticmethod
def relink_all_objects():
file = IfcStore.get_file()
if not file:
return
for obj in bpy.data.objects:
if not obj:
continue
if not obj.BIMObjectProperties.ifc_definition_id:
continue
element = file.by_id(obj.BIMObjectProperties.ifc_definition_id)
data = {"id": element.id(), "obj": obj.name}
if hasattr(element, "GlobalId"):
data["guid"] = element.GlobalId
IfcStore.commit_link_element(data)
@staticmethod
def delete_element(element):
IfcStore.deleted_ids.add(element.id())
if IfcStore.history:
data = {"id": element.id()}
IfcStore.history[-1]["operations"].append(
{"rollback": IfcStore.rollback_delete_element, "commit": IfcStore.commit_delete_element, "data": data}
)
@staticmethod
def rollback_delete_element(data):
IfcStore.deleted_ids.remove(data["id"])
@staticmethod
def commit_delete_element(data):
IfcStore.deleted_ids.add(data["id"])
@staticmethod
def link_element(element, obj):
IfcStore.id_map[element.id()] = obj
@@ -224,7 +340,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 +350,6 @@ class IfcStore:
is_top_level_operator = not bool(IfcStore.current_transaction)
if is_top_level_operator:
active_object = context.active_object
IfcStore.begin_transaction(operator)
IfcStore.get_file().begin_transaction()
# This empty transaction ensures that each operator has at least one transaction
@@ -248,17 +365,14 @@ class IfcStore:
operator, rollback=lambda d: IfcStore.get_file().undo(), commit=lambda d: IfcStore.get_file().redo()
)
IfcStore.end_transaction(operator)
try:
active_object.name
context.view_layer.objects.active = active_object
except:
pass
blenderbim.bim.handler.refresh_ui_data()
return result
@staticmethod
def begin_transaction(operator):
IfcStore.undo_redo_stack_objects = set()
IfcStore.undo_redo_stack_object_names = {}
IfcStore.current_transaction = str(uuid.uuid4())
operator.transaction_key = IfcStore.current_transaction
+475 -137
View File
@@ -16,11 +16,13 @@
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import os
import re
import bpy
import time
import bmesh
import shutil
import logging
import threading
import mathutils
import numpy as np
@@ -31,6 +33,8 @@ import ifcopenshell.util.unit
import ifcopenshell.util.element
import ifcopenshell.util.selector
import ifcopenshell.util.geolocation
import blenderbim.tool as tool
from itertools import chain, accumulate
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.module.drawing.prop import get_diagram_scales
@@ -67,6 +71,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)
@@ -107,7 +112,7 @@ class MaterialCreator:
def parse_representation(self, representation):
has_parsed = False
representation_items = self.resolve_mapped_representation_items(representation)
representation_items = self.resolve_all_representation_items(representation)
for item in representation_items:
if self.parse_representation_item(item):
has_parsed = True
@@ -141,13 +146,12 @@ class MaterialCreator:
]
self.mesh.polygons.foreach_set("material_index", material_index)
def resolve_mapped_representation_items(self, representation):
def resolve_all_representation_items(self, representation):
items = []
for item in representation.Items:
if item.is_a("IfcMappedItem"):
items.extend(item.MappingSource.MappedRepresentation.Items)
else:
items.append(item)
items.append(item)
return items
@@ -159,6 +163,7 @@ class IfcImporter:
self.settings = ifcopenshell.geom.settings()
self.settings.set_deflection_tolerance(self.ifc_import_settings.deflection_tolerance)
self.settings.set_angular_tolerance(self.ifc_import_settings.angular_tolerance)
self.settings.set(self.settings.STRICT_TOLERANCE, True)
self.settings_native = ifcopenshell.geom.settings()
self.settings_native.set(self.settings_native.INCLUDE_CURVES, True)
self.settings_2d = ifcopenshell.geom.settings()
@@ -217,7 +222,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")
@@ -278,10 +283,22 @@ class IfcImporter:
for c in self.file.by_type("IfcGeometricRepresentationSubContext")
if c.ContextIdentifier in ["Body", "Facetation"]
]
# Ideally, all representations should be in a subcontext, but some BIM programs don't do this correctly
self.body_contexts.extend(
[
c.id()
for c in self.file.by_type("IfcGeometricRepresentationContext", include_subtypes=False)
if c.ContextType == "Model"
]
)
if self.body_contexts:
self.settings.set_context_ids(self.body_contexts)
# Annotation is to accommodate broken Revit files
# See https://github.com/Autodesk/revit-ifc/issues/187
self.plan_contexts = [
c.id() for c in self.file.by_type("IfcGeometricRepresentationContext") if c.ContextType == "Plan"
c.id()
for c in self.file.by_type("IfcGeometricRepresentationContext")
if c.ContextType in ["Plan", "Annotation"]
]
if self.plan_contexts:
self.settings_2d.set_context_ids(self.plan_contexts)
@@ -289,8 +306,11 @@ class IfcImporter:
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)
@@ -337,16 +357,17 @@ class IfcImporter:
# FacetedBreps (without voids) are meshes. See #841.
# Commented out as seems currently too slow.
# if self.is_native_faceted_brep(representations):
# self.native_data[element.GlobalId] = {
# "representations": representations,
# "representation": self.get_body_representation(element.Representation.Representations),
# "type": "IfcFacetedBrep",
# }
# return True
# self.native_data[element.GlobalId] = {
# "representations": representations,
# "representation": self.get_body_representation(element.Representation.Representations),
# "type": "IfcFacetedBrep",
# }
# return True
def is_native_swept_disk_solid(self, representations):
for representation in representations:
if len(representation["raw"].Items) == 1 and representation["raw"].Items[0].is_a("IfcSweptDiskSolid"):
items = representation["raw"].Items or [] # Be forgiving of invalid IFCs because Revit :(
if len(items) == 1 and items[0].is_a("IfcSweptDiskSolid"):
return True
return False
@@ -443,7 +464,9 @@ class IfcImporter:
def apply_blender_offset_to_matrix_world(self, obj, matrix):
props = bpy.context.scene.BIMGeoreferenceProperties
if props.has_blender_offset:
if self.is_point_far_away((matrix[0, 3], matrix[1, 3], matrix[2, 3])):
if obj.data and obj.data.get("has_cartesian_point_offset", None):
obj.BIMObjectProperties.blender_offset_type = "CARTESIAN_POINT"
elif self.is_point_far_away((matrix[0, 3], matrix[1, 3], matrix[2, 3])):
obj.BIMObjectProperties.blender_offset_type = "OBJECT_PLACEMENT"
matrix = ifcopenshell.util.geolocation.global2local(
matrix,
@@ -453,8 +476,6 @@ class IfcImporter:
float(props.blender_x_axis_abscissa),
float(props.blender_x_axis_ordinate),
)
else:
obj.BIMObjectProperties.blender_offset_type = "CARTESIAN_POINT"
if self.ifc_import_settings.should_offset_model:
matrix[0, 3] += self.ifc_import_settings.model_offset_coordinates[0]
@@ -502,7 +523,7 @@ class IfcImporter:
mesh = self.create_mesh(axis, shape)
obj = bpy.data.objects.new(f"IfcGridAxis/{axis.AxisTag}", mesh)
self.link_element(axis, obj)
obj.matrix_world = grid_obj.matrix_world
self.set_matrix_world(obj, grid_obj.matrix_world)
grid_collection.objects.link(obj)
def create_type_products(self):
@@ -553,7 +574,6 @@ class IfcImporter:
def create_native_elements(self):
total = 0
checkpoint = time.time()
bm = bmesh.new()
for element in self.native_elements:
total += 1
if total % 250 == 0:
@@ -575,19 +595,14 @@ class IfcImporter:
mesh.name = mesh_name
self.meshes[mesh_name] = mesh
self.create_product(element, mesh=mesh)
if native_data["type"] == "IfcFacetedBrep":
# The current implementation doesn't reuse vertices, so we weld it after assigning materials.
# This welding isn't true to the representation, but is easy and seems inexpensive.
bm.from_mesh(mesh)
bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.001)
bm.to_mesh(mesh)
bm.clear()
bm.free()
print("Done creating geometry")
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)
@@ -615,6 +630,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
@@ -631,7 +649,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)
@@ -650,9 +668,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")
@@ -690,7 +705,7 @@ 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):
@@ -749,7 +764,7 @@ class IfcImporter:
mesh.from_pydata(vertex_list, [], [])
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)
return product
@@ -763,6 +778,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
@@ -793,6 +811,10 @@ 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)
@@ -812,13 +834,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)
@@ -834,79 +856,167 @@ class IfcImporter:
def create_native_faceted_brep(self, element, mesh_name):
# TODO: georeferencing?
# Note: to make this algorithm simpler (it's already confusing) we don't reuse / weld verts
# co [x y z x y z x y z ...]
# vertex_index [i i i i i ...]
# loop_start [0 3 6 9 ...] (for tris)
# loop_total [3 3 3 3 ...] (for tris)
co = []
vertex_index = []
loop_start = []
loop_total = []
total_verts = 0
total_polygons = 0
materials = []
material_ids = []
item_index = 0
self.mesh_data = {
"co": [],
"vertex_index": [],
"loop_start": [],
"loop_total": [],
"total_verts": 0,
"total_polygons": 0,
"materials": [],
"material_ids": [],
}
for representation in element.Representation.Representations:
if representation.ContextOfItems.id() not in self.body_contexts:
continue
self.convert_representation(representation)
mesh = bpy.data.meshes.new("Native")
mesh.vertices.add(self.mesh_data["total_verts"])
mesh.vertices.foreach_set("co", [c * self.unit_scale for c in self.mesh_data["co"]])
# mesh.vertices.foreach_set("co", self.mesh_data["co"])
mesh.loops.add(len(self.mesh_data["vertex_index"]))
mesh.loops.foreach_set("vertex_index", self.mesh_data["vertex_index"])
mesh.polygons.add(self.mesh_data["total_polygons"])
mesh.polygons.foreach_set("loop_start", self.mesh_data["loop_start"])
mesh.polygons.foreach_set("loop_total", self.mesh_data["loop_total"])
mesh.update()
mesh["ios_materials"] = self.mesh_data["materials"]
mesh["ios_material_ids"] = self.mesh_data["material_ids"]
return mesh
def convert_representation(self, representation):
for item in representation.Items:
self.convert_representation_item(item)
def convert_representation_item(self, item):
if item.is_a("IfcMappedItem"):
# mapping_target = matrix
self.convert_representation(item.MappingSource.MappedRepresentation)
elif item.is_a() == "IfcFacetedBrep":
self.convert_representation_item_faceted_brep(item)
def convert_representation_item_faceted_brep(self, item):
# On a few occasions, we flatten a list. This seems to be the most efficient way to do it.
# https://stackoverflow.com/questions/20112776/how-do-i-flatten-a-list-of-lists-nested-lists
mesh = item.get_info_2(recursive=True)
# For huge face sets it might be better to do a "flatmap" instead of sum()
# bounds = sum((f["Bounds"] for f in mesh["Outer"]["CfsFaces"] if len(f["Bounds"]) == 1), ())
bounds = tuple(chain.from_iterable(f["Bounds"] for f in mesh["Outer"]["CfsFaces"] if len(f["Bounds"]) == 1))
# Here are some untested alternatives, are they faster?
# bounds = tuple((f["Bounds"] for f in mesh["Outer"]["CfsFaces"] if len(f["Bounds"]) == 1))[0]
# bounds = chain.from_iterable(f["Bounds"] for f in mesh["Outer"]["CfsFaces"] if len(f["Bounds"]) == 1)
polygons = [[(p["id"], p["Coordinates"]) for p in b["Bound"]["Polygon"]] for b in bounds]
for face in mesh["Outer"]["CfsFaces"]:
# Blender cannot handle faces with holes.
if len(face["Bounds"]) > 1:
inner_bounds = []
inner_bound_point_ids = []
for bound in face["Bounds"]:
if bound["type"] == "IfcFaceOuterBound":
outer_bound = [[p["Coordinates"] for p in bound["Bound"]["Polygon"]]]
outer_bound_point_ids = [[p["id"] for p in bound["Bound"]["Polygon"]]]
else:
inner_bounds.append([p["Coordinates"] for p in bound["Bound"]["Polygon"]])
inner_bound_point_ids.append([p["id"] for p in bound["Bound"]["Polygon"]])
points = outer_bound[0].copy()
[points.extend(p) for p in inner_bounds]
point_ids = outer_bound_point_ids[0].copy()
[point_ids.extend(p) for p in inner_bound_point_ids]
tessellated_polygons = mathutils.geometry.tessellate_polygon(outer_bound + inner_bounds)
polygons.extend([[(point_ids[pi], points[pi]) for pi in t] for t in tessellated_polygons])
# Clever vertex welding algorithm by Thomas Krijnen. See #841.
# by id
di0 = {}
# by coords
di1 = {}
vertex_index_offset = self.mesh_data["total_verts"]
def lookup(id_coords):
idx = di0.get(id_coords[0])
if idx is None:
idx = di1.get(id_coords[1])
if idx is None:
l = len(di0)
di0[id_coords[0]] = l
di1[id_coords[1]] = l
return l + vertex_index_offset
else:
return idx + vertex_index_offset
else:
return idx + vertex_index_offset
mapped_polygons = [list(map(lookup, p)) for p in polygons]
self.mesh_data["vertex_index"].extend(chain.from_iterable(mapped_polygons))
# Flattened vertex coords
self.mesh_data["co"].extend(chain.from_iterable(di1.keys()))
self.mesh_data["total_verts"] += len(di1.keys())
loop_total = [len(p) for p in mapped_polygons]
total_polygons = len(mapped_polygons)
self.mesh_data["total_polygons"] += total_polygons
self.mesh_data["materials"].append(self.get_representation_item_material_name(item) or "NULLMAT")
material_index = len(self.mesh_data["materials"]) - 1
if self.mesh_data["materials"][material_index] == "NULLMAT":
# Magic number -1 represents no material, until this has a better approach
self.mesh_data["material_ids"] += [-1] * total_polygons
else:
self.mesh_data["material_ids"] += [material_index] * total_polygons
if self.mesh_data["loop_start"]:
loop_start_offset = self.mesh_data["loop_start"][-1] + self.mesh_data["loop_total"][-1]
else:
loop_start_offset = 0
loop_start = [loop_start_offset] + [loop_start_offset + i for i in list(accumulate(loop_total[0:-1]))]
self.mesh_data["loop_total"].extend(loop_total)
self.mesh_data["loop_start"].extend(loop_start)
# list(di1.keys())
def create_native_swept_disk_solid(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: if I reimplement native faceted breps, recheck this material implementation
materials.append(self.get_representation_item_material_name(item) or "NULLMAT")
mesh = item.get_info_2(recursive=True) # See bug #841
total_item_polygons = 0
for face in mesh["Outer"]["CfsFaces"]:
# Blender cannot handle faces with holes.
if len(face["Bounds"]) > 1:
inner_bounds = []
for bound in face["Bounds"]:
if bound["type"] == "IfcFaceOuterBound":
outer_bound = [[p["Coordinates"] for p in bound["Bound"]["Polygon"]]]
else:
inner_bounds.append([p["Coordinates"] for p in bound["Bound"]["Polygon"]])
points = outer_bound[0].copy()
[points.extend(p) for p in inner_bounds]
tessellated_polygons = mathutils.geometry.tessellate_polygon(outer_bound + inner_bounds)
tessellated_faces = [({"Coordinates": points[pi]} for pi in t) for t in tessellated_polygons]
else:
tessellated_faces = [face["Bounds"][0]["Bound"]["Polygon"]]
# TODO: support inner radius, start param, and end param
geometry = ifcopenshell.geom.create_shape(self.settings_native, item.Directrix)
e = geometry.edges
v = geometry.verts
vertices = [[v[i], v[i + 1], v[i + 2], 1] for i in range(0, len(v), 3)]
edges = [[e[i], e[i + 1]] for i in range(0, len(e), 2)]
v2 = None
for edge in edges:
v1 = vertices[edge[0]]
if v1 != v2:
polyline = curve.splines.new("POLY")
polyline.points[-1].co = representation["matrix"] @ mathutils.Vector(v1)
v2 = vertices[edge[1]]
polyline.points.add(1)
polyline.points[-1].co = representation["matrix"] @ mathutils.Vector(v2)
for tessellated_face in tessellated_faces:
loop_start.append(total_verts)
loop_count = 0
total_polygons += 1
total_item_polygons += 1
for point in tessellated_face:
co.extend(
representation["matrix"]
@ mathutils.Vector((c * self.unit_scale for c in point["Coordinates"]))
)
total_verts += 1
loop_count += 1
loop_total.append(loop_count)
vertex_index = range(0, total_verts)
if materials[item_index] == "NULLMAT":
# Magic number -1 represents no material, until this has a better approach
material_ids += [-1] * total_item_polygons
else:
material_ids += [item_index] * total_item_polygons
item_index += 1
mesh = bpy.data.meshes.new("Tester")
curve.bevel_depth = self.unit_scale * item.Radius
return curve
mesh.vertices.add(total_verts)
mesh.vertices.foreach_set("co", co)
mesh.loops.add(total_verts)
mesh.loops.foreach_set("vertex_index", vertex_index)
mesh.polygons.add(total_polygons)
mesh.polygons.foreach_set("loop_start", loop_start)
mesh.polygons.foreach_set("loop_total", loop_total)
mesh.update()
mesh["ios_materials"] = materials
mesh["ios_material_ids"] = material_ids
return mesh
def create_native_swept_disk_solid(self, element, mesh_name):
def create_native_annotation(self, element, mesh_name):
# TODO: georeferencing?
curve = bpy.data.curves.new(mesh_name, type="CURVE")
curve.dimensions = "3D"
@@ -1082,6 +1192,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):
@@ -1093,6 +1204,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:
@@ -1171,25 +1298,172 @@ class IfcImporter:
name = style.Name or str(style.id())
blender_material = bpy.data.materials.new(name)
old_definition_id = blender_material.BIMObjectProperties.ifc_definition_id
if not old_definition_id:
self.link_element(style, blender_material)
blender_material.BIMObjectProperties.ifc_definition_id = old_definition_id
self.link_element(style, blender_material)
blender_material.BIMMaterialProperties.ifc_style_id = style.id()
self.material_creator.styles[style.id()] = blender_material
rendering_style = None
texture_style = None
for surface_style in style.Styles:
if surface_style.is_a("IfcSurfaceStyleShading"):
alpha = 1.0
# Transparency was added in IFC4
if hasattr(surface_style, "Transparency") and surface_style.Transparency:
alpha = 1 - surface_style.Transparency
blender_material.diffuse_color = (
surface_style.SurfaceColour.Red,
surface_style.SurfaceColour.Green,
surface_style.SurfaceColour.Blue,
alpha,
if surface_style.is_a() == "IfcSurfaceStyleShading":
self.create_surface_style_shading(blender_material, surface_style)
elif surface_style.is_a("IfcSurfaceStyleRendering"):
rendering_style = surface_style
self.create_surface_style_rendering(blender_material, surface_style)
elif surface_style.is_a("IfcSurfaceStyleWithTextures"):
texture_style = surface_style
if rendering_style and texture_style:
self.create_surface_style_with_textures(blender_material, rendering_style, texture_style)
def create_surface_style_shading(self, blender_material, surface_style):
alpha = 1.0
# Transparency was added in IFC4
if hasattr(surface_style, "Transparency") and surface_style.Transparency:
alpha = 1 - surface_style.Transparency
blender_material.diffuse_color = (
surface_style.SurfaceColour.Red,
surface_style.SurfaceColour.Green,
surface_style.SurfaceColour.Blue,
alpha,
)
def create_surface_style_rendering(self, blender_material, surface_style):
self.create_surface_style_shading(blender_material, surface_style)
if surface_style.ReflectanceMethod in ["PHYSICAL", "NOTDEFINED"]:
blender_material.use_nodes = True
bsdf = blender_material.node_tree.nodes["Principled BSDF"]
if surface_style.DiffuseColour and surface_style.DiffuseColour.is_a("IfcColourRgb"):
bsdf.inputs["Base Color"].default_value = (
surface_style.DiffuseColour.Red,
surface_style.DiffuseColour.Green,
surface_style.DiffuseColour.Blue,
1,
)
if surface_style.SpecularColour and surface_style.SpecularColour.is_a("IfcNormalisedRatioMeasure"):
bsdf.inputs["Metallic"].default_value = surface_style.SpecularColour.wrappedValue
if surface_style.SpecularHighlight and surface_style.SpecularHighlight.is_a("IfcSpecularRoughness"):
bsdf.inputs["Roughness"].default_value = surface_style.SpecularHighlight.wrappedValue
if hasattr(surface_style, "Transparency") and surface_style.Transparency:
bsdf.inputs["Alpha"].default_value = 1 - surface_style.Transparency
blender_material.blend_method = "BLEND"
elif surface_style.ReflectanceMethod == "FLAT":
blender_material.use_nodes = True
output = {n.type: n for n in blender_material.node_tree.nodes}.get("OUTPUT_MATERIAL", None)
bsdf = blender_material.node_tree.nodes["Principled BSDF"]
mix = blender_material.node_tree.nodes.new(type="ShaderNodeMixShader")
mix.location = bsdf.location
blender_material.node_tree.links.new(mix.outputs[0], output.inputs["Surface"])
blender_material.node_tree.nodes.remove(bsdf)
lightpath = blender_material.node_tree.nodes.new(type="ShaderNodeLightPath")
lightpath.location = mix.location - mathutils.Vector((200, -200))
blender_material.node_tree.links.new(lightpath.outputs[0], mix.inputs[0])
bsdf = blender_material.node_tree.nodes.new(type="ShaderNodeBsdfTransparent")
bsdf.location = mix.location - mathutils.Vector((200, 0))
blender_material.node_tree.links.new(bsdf.outputs[0], mix.inputs[1])
rgb = blender_material.node_tree.nodes.new(type="ShaderNodeRGB")
rgb.location = mix.location - mathutils.Vector((200, 200))
blender_material.node_tree.links.new(rgb.outputs[0], mix.inputs[2])
if surface_style.DiffuseColour and surface_style.DiffuseColour.is_a("IfcColourRgb"):
rgb.outputs[0].default_value = (
surface_style.DiffuseColour.Red,
surface_style.DiffuseColour.Green,
surface_style.DiffuseColour.Blue,
1,
)
def create_surface_style_with_textures(self, blender_material, rendering_style, texture_style):
for texture in texture_style.Textures:
mode = getattr(texture, "Mode", None)
node = None
if texture.is_a("IfcImageTexture"):
image_url = texture.URLReference
if not os.path.abspath(texture.URLReference) and tool.Ifc.get_path():
image_url = os.path.join(os.path.dirname(tool.Ifc.get_path()), texture.URLReference)
if rendering_style.ReflectanceMethod in ["PHYSICAL", "NOTDEFINED"]:
bsdf = blender_material.node_tree.nodes["Principled BSDF"]
if mode == "NORMAL":
normalmap = blender_material.node_tree.nodes.new(type="ShaderNodeNormalMap")
normalmap.location = bsdf.location - mathutils.Vector((200, 0))
blender_material.node_tree.links.new(normalmap.outputs[0], bsdf.inputs["Normal"])
node = blender_material.node_tree.nodes.new(type="ShaderNodeTexImage")
node.location = normalmap.location - mathutils.Vector((200, 0))
image = bpy.data.images.load(image_url)
image.colorspace_settings.name = "Non-Color"
node.image = image
blender_material.node_tree.links.new(node.outputs[0], normalmap.inputs["Color"])
elif mode == "EMISSIVE":
output = {n.type: n for n in blender_material.node_tree.nodes}.get("OUTPUT_MATERIAL", None)
add = blender_material.node_tree.nodes.new(type="ShaderNodeAddShader")
add.location = bsdf.location + mathutils.Vector((200, 0))
blender_material.node_tree.links.new(bsdf.outputs[0], add.inputs[1])
blender_material.node_tree.links.new(add.outputs[0], output.inputs[0])
emission = blender_material.node_tree.nodes.new(type="ShaderNodeEmission")
emission.location = add.location - mathutils.Vector((200, 0))
blender_material.node_tree.links.new(emission.outputs[0], add.inputs[0])
node = blender_material.node_tree.nodes.new(type="ShaderNodeTexImage")
node.location = emission.location - mathutils.Vector((200, 0))
image = bpy.data.images.load(image_url)
node.image = image
blender_material.node_tree.links.new(node.outputs[0], emission.inputs[0])
elif mode == "METALLICROUGHNESS":
separate = blender_material.node_tree.nodes.new(type="ShaderNodeSeparateRGB")
separate.location = bsdf.location - mathutils.Vector((200, 0))
blender_material.node_tree.links.new(separate.outputs[1], bsdf.inputs["Roughness"])
blender_material.node_tree.links.new(separate.outputs[2], bsdf.inputs["Metallic"])
node = blender_material.node_tree.nodes.new(type="ShaderNodeTexImage")
node.location = separate.location - mathutils.Vector((200, 0))
image = bpy.data.images.load(image_url)
image.colorspace_settings.name = "Non-Color"
node.image = image
blender_material.node_tree.links.new(node.outputs[0], separate.inputs[0])
elif mode == "OCCLUSION":
# TODO work out how to implement glTF settings here
# https://docs.blender.org/manual/en/dev/addons/import_export/scene_gltf2.html
pass
elif mode == "DIFFUSE":
node = blender_material.node_tree.nodes.new(type="ShaderNodeTexImage")
node.location = bsdf.location - mathutils.Vector((400, 0))
image = bpy.data.images.load(image_url)
node.image = image
blender_material.node_tree.links.new(node.outputs[0], bsdf.inputs["Base Color"])
blender_material.node_tree.links.new(node.outputs[1], bsdf.inputs["Alpha"])
blender_material.blend_method = "BLEND"
elif rendering_style.ReflectanceMethod == "FLAT":
bsdf = blender_material.node_tree.nodes["Mix Shader"]
if mode == "EMISSIVE":
node = blender_material.node_tree.nodes.new(type="ShaderNodeTexImage")
node.location = bsdf.location - mathutils.Vector((200, 0))
image = bpy.data.images.load(image_url)
node.image = image
blender_material.node_tree.links.new(node.outputs[0], bsdf.inputs[2])
if node and getattr(texture, "IsMappedBy", None):
coordinates = texture.IsMappedBy[0]
coord = blender_material.node_tree.nodes.new(type="ShaderNodeTexCoord")
coord.location = node.location - mathutils.Vector((200, 0))
if coordinates.is_a("IfcTextureCoordinateGenerator") and coordinates.Mode == "COORD":
blender_material.node_tree.links.new(coord.outputs["Generated"], node.inputs["Vector"])
elif coordinates.is_a("IfcTextureCoordinateGenerator") and coordinates.Mode == "COORD-EYE":
blender_material.node_tree.links.new(coord.outputs["Camera"], node.inputs["Vector"])
else:
blender_material.node_tree.links.new(coord.outputs["UV"], node.inputs["Vector"])
def get_name(self, element):
return "{}/{}".format(element.is_a(), element.Name)
@@ -1235,16 +1509,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:
@@ -1260,6 +1527,37 @@ 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",
"LEADER",
"HATCH",
"LINEWORK",
"AREA",
]
def get_element_matrix(self, element):
result = ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement)
result[0][3] *= self.unit_scale
@@ -1384,8 +1682,39 @@ class IfcImporter:
else:
camera.BIMCameraProperties.diagram_scale = "CUSTOM"
camera.BIMCameraProperties.custom_diagram_scale = pset["Scale"]
if "HasUnderlay" in pset:
camera.BIMCameraProperties.has_underlay = pset["HasUnderlay"]
if "HasLinework" in pset:
camera.BIMCameraProperties.has_linework = pset["HasLinework"]
if "HasAnnotation" in pset:
camera.BIMCameraProperties.has_annotation = pset["HasAnnotation"]
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"):
@@ -1401,20 +1730,22 @@ class IfcImporter:
and geometry.verts
and self.is_point_far_away((geometry.verts[0], geometry.verts[1], geometry.verts[2]))
):
ordinate_index = 0
verts = [None] * len(geometry.verts)
offset_point = (
float(props.blender_eastings) * self.unit_scale,
float(props.blender_northings) * self.unit_scale,
float(props.blender_orthogonal_height) * self.unit_scale,
)
for i, vert in enumerate(geometry.verts):
if ordinate_index > 2:
ordinate_index = 0
verts[i] = vert - offset_point[ordinate_index]
ordinate_index += 1
for i in range(0, len(geometry.verts), 3):
verts[i], verts[i + 1], verts[i + 2] = ifcopenshell.util.geolocation.enh2xyz(
geometry.verts[i],
geometry.verts[i + 1],
geometry.verts[i + 2],
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),
)
mesh["has_cartesian_point_offset"] = True
else:
verts = geometry.verts
mesh["has_cartesian_point_offset"] = False
if geometry.faces:
num_vertices = len(verts) // 3
@@ -1502,7 +1833,12 @@ class IfcImporter:
if "-" in geometry.id:
mesh.BIMMeshProperties.ifc_definition_id = int(geometry.id.split("-")[0])
else:
mesh.BIMMeshProperties.ifc_definition_id = int(geometry.id)
# TODO: See #2002
mesh.BIMMeshProperties.ifc_definition_id = int(geometry.id.replace(",", ""))
def set_matrix_world(self, obj, matrix_world):
obj.matrix_world = matrix_world
tool.Geometry.record_object_position(obj)
class IfcImportSettings:
@@ -1525,11 +1861,13 @@ class IfcImportSettings:
self.collection_mode = "DECOMPOSITION"
@staticmethod
def factory(context, input_file, logger):
scene_diff = context.scene.DiffProperties
props = context.scene.BIMProjectProperties
def factory(context=None, input_file=None, logger=None):
scene_diff = bpy.context.scene.DiffProperties
props = bpy.context.scene.BIMProjectProperties
settings = IfcImportSettings()
settings.input_file = input_file
if logger is None:
logger = logging.getLogger("ImportIFC")
settings.logger = logger
settings.diff_file = scene_diff.diff_json_file
settings.collection_mode = props.collection_mode
@@ -20,11 +20,14 @@ import bpy
from . import ui, prop, operator
classes = (
operator.AssignObject,
operator.UnassignObject,
operator.EnableEditingAggregate,
operator.DisableEditingAggregate,
operator.AddAggregate,
operator.BIM_OT_assign_object,
operator.BIM_OT_unassign_object,
operator.BIM_OT_enable_editing_aggregate,
operator.BIM_OT_disable_editing_aggregate,
operator.BIM_OT_add_aggregate,
operator.BIM_OT_select_parts,
operator.BIM_OT_select_aggregate,
operator.BIM_OT_add_part_to_object,
prop.BIMObjectAggregateProperties,
ui.BIM_PT_aggregate,
)
@@ -32,24 +32,50 @@ class AggregateData:
@classmethod
def load(cls):
cls.data = {
"has_aggregate": cls.has_aggregate(),
"label": cls.get_label(),
"has_relating_object": cls.has_relating_object(),
"relating_object_label": cls.get_relating_object_label(),
"relating_object_id": cls.get_relating_object_id(),
"has_related_objects": cls.has_related_objects(),
"related_objects_amount": cls.get_related_objects_amount(),
"ifc_class": cls.ifc_class(),
}
cls.is_loaded = True
@classmethod
def has_aggregate(cls):
def get_relating_object(cls):
return ifcopenshell.util.element.get_aggregate(tool.Ifc.get_entity(bpy.context.active_object))
@classmethod
def get_label(cls):
aggregate = ifcopenshell.util.element.get_aggregate(tool.Ifc.get_entity(bpy.context.active_object))
def has_relating_object(cls) -> bool:
return cls.get_relating_object() is not None
@classmethod
def get_relating_object_label(cls) -> str:
aggregate = cls.get_relating_object()
if aggregate:
return f"{aggregate.is_a()}/{aggregate.Name or ''}"
@classmethod
def get_relating_object_id(cls):
aggregate = ifcopenshell.util.element.get_aggregate(tool.Ifc.get_entity(bpy.context.active_object))
def get_relating_object_id(cls) -> int:
aggregate = cls.get_relating_object()
if aggregate:
return aggregate.id()
@classmethod
def get_related_objects(cls):
return ifcopenshell.util.element.get_parts(tool.Ifc.get_entity(bpy.context.active_object))
@classmethod
def get_related_objects_amount(cls):
parts = cls.get_related_objects()
return len(parts) if parts else 0
@classmethod
def has_related_objects(cls) -> bool:
return bool(cls.get_related_objects())
@classmethod
def ifc_class(cls) -> str:
element = tool.Ifc.get_entity(bpy.context.active_object)
if element:
return element.is_a()
@@ -17,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
@@ -30,7 +33,9 @@ class Operator:
return {"FINISHED"}
class AssignObject(bpy.types.Operator, Operator):
class BIM_OT_assign_object(bpy.types.Operator, Operator):
"""Create aggregation relationship between two ifc elements"""
bl_idname = "bim.assign_object"
bl_label = "Assign Object"
bl_options = {"REGISTER", "UNDO"}
@@ -47,7 +52,9 @@ class AssignObject(bpy.types.Operator, Operator):
)
class UnassignObject(bpy.types.Operator, Operator):
class BIM_OT_unassign_object(bpy.types.Operator, Operator):
"""Remove aggregation relationship between two ifc elements"""
bl_idname = "bim.unassign_object"
bl_label = "Unassign Object"
bl_options = {"REGISTER", "UNDO"}
@@ -63,7 +70,9 @@ class UnassignObject(bpy.types.Operator, Operator):
)
class EnableEditingAggregate(bpy.types.Operator, Operator):
class BIM_OT_enable_editing_aggregate(bpy.types.Operator, Operator):
"""Enable editing aggregation relationship"""
bl_idname = "bim.enable_editing_aggregate"
bl_label = "Enable Editing Aggregate"
bl_options = {"REGISTER", "UNDO"}
@@ -72,7 +81,9 @@ class EnableEditingAggregate(bpy.types.Operator, Operator):
core.enable_editing_aggregate(tool.Aggregate, obj=context.active_object)
class DisableEditingAggregate(bpy.types.Operator, Operator):
class BIM_OT_disable_editing_aggregate(bpy.types.Operator, Operator):
"""Disable editing aggregation relationship"""
bl_idname = "bim.disable_editing_aggregate"
bl_label = "Disable Editing Aggregate"
bl_options = {"REGISTER", "UNDO"}
@@ -81,7 +92,9 @@ class DisableEditingAggregate(bpy.types.Operator, Operator):
core.disable_editing_aggregate(tool.Aggregate, obj=context.active_object)
class AddAggregate(bpy.types.Operator):
class BIM_OT_add_aggregate(bpy.types.Operator):
"""Add aggregate to IFC element"""
bl_idname = "bim.add_aggregate"
bl_label = "Add Aggregate"
bl_options = {"REGISTER", "UNDO"}
@@ -92,10 +105,99 @@ class AddAggregate(bpy.types.Operator):
def _execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
element = tool.Ifc.get_entity(obj)
if not element:
return {"FINISHED"}
aggregate = self.create_aggregate(context)
tool.Collector.sync(obj)
current_aggregate = ifcopenshell.util.element.get_aggregate(element)
current_container = ifcopenshell.util.element.get_container(element)
if current_aggregate:
core.assign_object(
tool.Ifc,
tool.Aggregate,
tool.Collector,
relating_obj=tool.Ifc.get_object(current_aggregate),
related_obj=aggregate,
)
elif current_container:
blenderbim.core.spatial.assign_container(
tool.Ifc,
tool.Collector,
tool.Spatial,
structure_obj=tool.Ifc.get_object(current_container),
element_obj=aggregate,
)
core.assign_object(tool.Ifc, tool.Aggregate, tool.Collector, relating_obj=aggregate, related_obj=obj)
return {"FINISHED"}
def create_aggregate(self, context):
aggregate_collection = bpy.data.collections.new("IfcElementAssembly/Assembly")
context.scene.collection.children.link(aggregate_collection)
aggregate = bpy.data.objects.new("Assembly", None)
aggregate_collection.objects.link(aggregate)
bpy.ops.bim.assign_class(obj=aggregate.name, ifc_class="IfcElementAssembly")
core.assign_object(tool.Ifc, tool.Aggregate, tool.Collector, relating_obj=aggregate, related_obj=obj)
return aggregate
class BIM_OT_select_parts(bpy.types.Operator):
"""Select Parts"""
bl_idname = "bim.select_parts"
bl_label = "Select Parts"
bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
def execute(self, context):
self.file = IfcStore.get_file()
obj = bpy.data.objects.get(self.obj) or context.active_object
parts = ifcopenshell.util.element.get_parts(tool.Ifc.get_entity(obj))
parts_objs = set(tool.Ifc.get_object(part) for part in parts)
selectable_parts_objs = set(context.selectable_objects).intersection(parts_objs)
for selectable_part_obj in selectable_parts_objs:
selectable_part_obj.select_set(True)
return {"FINISHED"}
class BIM_OT_select_aggregate(bpy.types.Operator):
"""Select Aggregate"""
bl_idname = "bim.select_aggregate"
bl_label = "Select Aggregate"
bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
def execute(self, context):
self.file = IfcStore.get_file()
obj = bpy.data.objects.get(self.obj) or context.active_object
aggregate = ifcopenshell.util.element.get_aggregate(tool.Ifc.get_entity(obj))
aggregate_obj = tool.Ifc.get_object(aggregate)
if aggregate_obj in context.selectable_objects:
aggregate_obj.select_set(True)
return {"FINISHED"}
class BIM_OT_add_part_to_object(bpy.types.Operator, Operator):
bl_idname = "bim.add_part_to_object"
bl_label = "Add Part to Object"
bl_options = {"REGISTER", "UNDO"}
part_class: bpy.props.StringProperty(name="Class", options={"HIDDEN"})
part_name: bpy.props.StringProperty(name="Name")
obj: bpy.props.StringProperty(options={"HIDDEN"})
def invoke(self, context, event):
self.part_name = "My " + self.part_class.lstrip("Ifc")
return context.window_manager.invoke_props_dialog(self)
def _execute(self, context):
obj = bpy.data.objects.get(self.obj) or context.active_object
core.add_part_to_object(
tool.Ifc,
tool.Aggregate,
tool.Collector,
tool.Blender,
obj=obj,
part_class=self.part_class,
part_name=self.part_name,
)
@@ -32,6 +32,17 @@ from bpy.props import (
)
def update_relating_object(self, context):
def message(self, context):
self.layout.label(text="Please select a valid Ifc Element")
if self.relating_object is None:
return
if not self.relating_object.BIMObjectProperties.ifc_definition_id:
context.window_manager.popup_menu(message, title="Invalid Element Selected", icon="INFO")
self.relating_object = None
class BIMObjectAggregateProperties(PropertyGroup):
is_editing: BoolProperty(name="Is Editing")
relating_object: PointerProperty(name="Aggregate", type=bpy.types.Object)
relating_object: PointerProperty(name="Aggregate", type=bpy.types.Object, update=update_relating_object)
@@ -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):
@@ -43,13 +44,14 @@ class BIM_PT_aggregate(Panel):
return True
def draw(self, context):
layout = self.layout
if not AggregateData.is_loaded:
AggregateData.load()
props = context.active_object.BIMObjectAggregateProperties
if props.is_editing:
row = self.layout.row(align=True)
row = layout.row(align=True)
row.prop(props, "relating_object", text="")
if props.relating_object:
op = row.operator("bim.assign_object", icon="CHECKMARK", text="")
@@ -57,15 +59,37 @@ class BIM_PT_aggregate(Panel):
op.related_object = context.active_object.BIMObjectProperties.ifc_definition_id
row.operator("bim.disable_editing_aggregate", icon="CANCEL", text="")
else:
row = self.layout.row(align=True)
if AggregateData.data["has_aggregate"]:
row.label(text=AggregateData.data["label"])
row = layout.row(align=True)
if AggregateData.data["has_relating_object"]:
row.label(text=AggregateData.data["relating_object_label"], icon="TRIA_UP")
op = row.operator("bim.select_aggregate", icon="RESTRICT_SELECT_OFF", text="")
op.obj = context.active_object.name
row.operator("bim.enable_editing_aggregate", icon="GREASEPENCIL", text="")
row.operator("bim.add_aggregate", icon="ADD", text="")
op = row.operator("bim.unassign_object", icon="X", text="")
op.relating_object = AggregateData.data["relating_object_id"]
op.related_object = context.active_object.BIMObjectProperties.ifc_definition_id
else:
row.label(text="No Aggregate Found")
row.label(text="No Aggregate", icon="TRIA_UP")
row.operator("bim.enable_editing_aggregate", icon="GREASEPENCIL", text="")
row.operator("bim.add_aggregate", icon="ADD", text="")
row = layout.row(align=True)
parts = AggregateData.data["related_objects_amount"]
row.label(text=f"{parts or 'No'} Part{'s' if parts > 1 else ''}", icon="TRIA_DOWN")
if AggregateData.data["has_related_objects"]:
op = row.operator("bim.select_parts", icon="RESTRICT_SELECT_OFF", text="")
op.obj = context.active_object.name
ifc_class = AggregateData.data["ifc_class"]
part_class = ""
if ifc_class == "IfcBuilding":
part_class = "IfcBuildingStorey"
elif ifc_class == "IfcSite":
part_class = "IfcBuilding"
elif ifc_class == "IfcProject":
part_class = "IfcSite"
if part_class != "":
op = layout.operator("bim.add_part_to_object", text="Add " + part_class.lstrip("Ifc"))
op.part_class = part_class
op.obj = context.active_object.name
@@ -59,6 +59,7 @@ class BIM_PT_object_attributes(Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
bl_parent_id = "BIM_PT_object_metadata"
@classmethod
def poll(cls, context):
@@ -26,6 +26,7 @@ class BIM_PT_augin(bpy.types.Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_integrations"
def draw(self, context):
layout = self.layout
@@ -842,6 +842,8 @@ class ActivateBcfViewpoint(bpy.types.Operator):
obj.data.angle = 2 * atan(
(0.5 * cam_height) / (0.5 * cam_width / tan(radians(camera.field_of_view) / 2))
)
else:
return
z_axis = Vector(
(-camera.camera_direction.x, -camera.camera_direction.y, -camera.camera_direction.z)
@@ -29,6 +29,7 @@ class BIM_PT_bcf(Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_collaboration"
def draw(self, context):
layout = self.layout
@@ -19,7 +19,7 @@
import os
from pathlib import Path
from blenderbim.bim.prop import StrProperty
from blenderbim.bim.module.root.prop import getIfcClasses
from blenderbim.bim.module.root.prop import get_ifc_classes
from bpy.types import PropertyGroup
from bpy.props import (
PointerProperty,
@@ -33,14 +33,11 @@ from bpy.props import (
)
scenarios_enum = []
classes_enum = []
def purge():
global scenarios_enum
global classes_enum
scenarios_enum = []
classes_enum = []
def getScenarios(self, context):
@@ -68,7 +65,7 @@ class BimTesterProperties(PropertyGroup):
feature: StringProperty(default="", name="Feature / IDS", update=refreshScenarios)
steps: StringProperty(default="", name="Custom Steps")
ifc_file: StringProperty(default="", name="IFC File")
audit_ifc_class: EnumProperty(items=getIfcClasses, name="Audit Class")
audit_ifc_class: EnumProperty(items=get_ifc_classes, name="Audit Class")
qa_reject_element_reason: StringProperty(name="Element Rejection Reason")
scenario: EnumProperty(items=getScenarios, name="Scenario")
should_load_from_memory: BoolProperty(default=False, name="Load from Memory")
@@ -27,6 +27,7 @@ class BIM_PT_qa(Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_quality_control"
def draw(self, context):
self.layout.use_property_split = True
@@ -17,14 +17,28 @@
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
from . import ui
from . import ui, operator, prop
classes = (ui.BIM_PT_boundary,)
classes = (
operator.LoadProjectSpaceBoundaries,
operator.LoadSpaceBoundaries,
operator.LoadBoundary,
operator.SelectProjectBoundaries,
operator.ColourByRelatedBuildingElement,
operator.EnableEditingBoundary,
operator.DisableEditingBoundary,
operator.EditBoundaryAttributes,
operator.UpdateBoundaryGeometry,
ui.BIM_PT_Boundary,
ui.BIM_PT_SpaceBoundaries,
ui.BIM_PT_SceneBoundaries,
prop.BIMBoundaryProperties,
)
def register():
pass
bpy.types.Object.bim_boundary_properties = bpy.props.PointerProperty(type=prop.BIMBoundaryProperties)
def unregister():
pass
del bpy.types.Object.bim_boundary_properties
@@ -0,0 +1,290 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2020, 2021, 2022 Cyril Waechter <cyril@biminsight.ch>
#
# This file is part of BlenderBIM Add-on.
#
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BlenderBIM Add-on is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
import logging
import ifcopenshell.api
import blenderbim.tool as tool
from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.boundary.data import Data
import blenderbim.bim.import_ifc as import_ifc
def get_boundaries_collection(blender_space):
space_collection = bpy.data.collections.get(blender_space.name)
collection_name = f"Boundaries/{blender_space.BIMObjectProperties.ifc_definition_id}"
boundaries_collection = space_collection.children.get(collection_name)
if not boundaries_collection:
boundaries_collection = bpy.data.collections.new(collection_name)
space_collection.children.link(boundaries_collection)
return boundaries_collection
class Loader:
def __init__(self):
self.ifc_file = None
self.logger = None
self.ifc_importer = None
self.settings = None
self.load_settings()
self.load_importer()
def create_mesh(self, boundary):
# ConnectionGeometry is optional in IFC schema for some reasons.
if not boundary.ConnectionGeometry:
return None
surface = boundary.ConnectionGeometry.SurfaceOnRelatingElement
# workaround for unvalid geometry provided by Revit. See https://github.com/IfcOpenShell/IfcOpenShell/issues/635#issuecomment-770366838
if surface.is_a("IfcCurveBoundedPlane") and not getattr(surface, "InnerBoundaries", None):
surface.InnerBoundaries = ()
shape = ifcopenshell.geom.create_shape(self.settings, surface)
mesh = self.ifc_importer.create_mesh(None, shape)
self.ifc_importer.link_mesh(shape, mesh)
return mesh
def load_settings(self):
self.settings = ifcopenshell.geom.settings()
self.settings.set(self.settings.EXCLUDE_SOLIDS_AND_SURFACES, False)
self.settings.set(self.settings.USE_BREP_DATA, False)
def load_importer(self):
self.ifc_file = tool.Ifc.get()
self.logger = logging.getLogger("ImportIFC")
ifc_import_settings = import_ifc.IfcImportSettings.factory(bpy.context, IfcStore.path, self.logger)
self.ifc_importer = import_ifc.IfcImporter(ifc_import_settings)
self.ifc_importer.file = self.ifc_file
def load_boundary(self, boundary_id, blender_space):
boundary = self.ifc_file.by_id(boundary_id)
obj = tool.Ifc.get_object(boundary)
if obj:
return obj
mesh = self.create_mesh(boundary)
obj = bpy.data.objects.new(f"{boundary.is_a()}/{boundary.Name}", mesh)
obj.matrix_world = blender_space.matrix_world
boundaries_collection = get_boundaries_collection(blender_space)
boundaries_collection.objects.link(obj)
tool.Ifc.link(boundary, obj)
return obj
class LoadProjectSpaceBoundaries(bpy.types.Operator):
bl_idname = "bim.load_project_space_boundaries"
bl_label = "Load all project space boundaries"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
loader = Loader()
if not Data.is_loaded:
Data.load(tool.Ifc.get())
for space_id, boundaries_id in Data.spaces.items():
blender_space = tool.Ifc.get_object(tool.Ifc.get().by_id(space_id))
for boundary_id in boundaries_id:
loader.load_boundary(boundary_id, blender_space)
return {"FINISHED"}
class LoadBoundary(bpy.types.Operator):
bl_idname = "bim.load_boundary"
bl_label = "Load boundary"
bl_options = {"REGISTER", "UNDO"}
boundary_id: bpy.props.IntProperty()
def execute(self, context):
loader = Loader()
blender_space = context.active_object
for obj in context.visible_objects:
obj.select_set(False)
obj = loader.load_boundary(self.boundary_id, blender_space)
obj.select_set(True)
bpy.context.view_layer.objects.active = obj
return {"FINISHED"}
class LoadSpaceBoundaries(bpy.types.Operator):
bl_idname = "bim.load_space_boundaries"
bl_label = "Load selected space boundaries"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
blender_space = context.active_object
oprops = context.active_object.BIMObjectProperties
loader = Loader()
if not Data.is_loaded:
Data.load(tool.Ifc.get())
for boundary_id in Data.spaces.get(oprops.ifc_definition_id, []):
loader.load_boundary(boundary_id, blender_space)
return {"FINISHED"}
class SelectProjectBoundaries(bpy.types.Operator):
bl_idname = "bim.select_project_space_boundaries"
bl_label = "Select all project space boundaries"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
for obj in context.visible_objects:
obj.select_set(False)
ifc_file = tool.Ifc.get()
for boundary_id in Data.boundaries:
boundary = ifc_file.by_id(boundary_id)
obj = tool.Ifc.get_object(boundary)
if obj:
obj.select_set(True)
return {"FINISHED"}
def get_colour(ifc_boundary):
"""Return a color depending on IfcClass given"""
product_colors = {
"IfcWall": (0.7, 0.3, 0, 1),
"IfcWindow": (0, 0.7, 1, 1),
"IfcSlab": (0.7, 0.7, 0.5, 1),
"IfcRoof": (0, 0.3, 0, 1),
"IfcDoor": (1, 1, 1, 1),
}
if ifc_boundary.PhysicalOrVirtualBoundary == "VIRTUAL":
return (1, 0, 1, 1)
element = ifc_boundary.RelatedBuildingElement
if not element:
return (1, 0, 0, 1)
for product, colour in product_colors.items():
if element.is_a(product):
return colour
return (0, 0, 0, 1)
class ColourByRelatedBuildingElement(bpy.types.Operator):
bl_idname = "bim.colour_by_related_building_element"
bl_label = "Apply colour based on related building elements"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
IfcStore.begin_transaction(self)
self.store_state(context)
result = self._execute(context)
IfcStore.add_transaction_operation(self)
IfcStore.end_transaction(self)
return result
def _execute(self, context):
self.file = tool.Ifc.get()
for obj in context.visible_objects:
if not obj.BIMObjectProperties.ifc_definition_id:
continue
element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
if not element.is_a("IfcRelSpaceBoundary"):
continue
obj.color = get_colour(element)
areas = [a for a in context.screen.areas if a.type == "VIEW_3D"]
if areas:
areas[0].spaces[0].shading.color_type = "OBJECT"
return {"FINISHED"}
def store_state(self, context):
areas = [a for a in context.screen.areas if a.type == "VIEW_3D"]
if areas:
self.transaction_data = {"area": areas[0], "color_type": areas[0].spaces[0].shading.color_type}
def rollback(self, data):
if data:
data["area"].spaces[0].shading.color_type = data["color_type"]
def commit(self, data):
if data:
data["area"].spaces[0].shading.color_type = "OBJECT"
EDITABLE_ATTRIBUTES = {
"RelatingSpace": "relating_space",
"RelatedBuildingElement": "related_building_element",
"ParentBoundary": "parent_boundary",
"CorrespondingBoundary": "corresponding_boundary",
}
class EnableEditingBoundary(bpy.types.Operator):
bl_idname = "bim.enable_editing_boundary"
bl_label = "Edit boundary relations"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
bprops = context.active_object.bim_boundary_properties
bprops.is_editing = True
boundary = tool.Ifc.get_entity(context.active_object)
for ifc_attribute, blender_property in EDITABLE_ATTRIBUTES.items():
entity = getattr(boundary, ifc_attribute, None)
if not entity:
continue
obj = tool.Ifc.get_object(entity)
if entity and obj:
setattr(bprops, blender_property, obj)
return {"FINISHED"}
class DisableEditingBoundary(bpy.types.Operator):
bl_idname = "bim.disable_editing_boundary"
bl_label = "Disable editing boundary relations"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
bprops = context.active_object.bim_boundary_properties
bprops.is_editing = False
for ifc_attribute, blender_property in EDITABLE_ATTRIBUTES.items():
setattr(bprops, blender_property, None)
return {"FINISHED"}
class EditBoundaryAttributes(bpy.types.Operator):
bl_idname = "bim.edit_boundary_attributes"
bl_label = "Disable editing boundary relations"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
bprops = context.active_object.bim_boundary_properties
boundary = tool.Ifc.get_entity(context.active_object)
attributes = dict()
for ifc_attribute, blender_property in EDITABLE_ATTRIBUTES.items():
obj = getattr(bprops, blender_property, None)
entity = tool.Ifc.get_entity(obj)
attributes[blender_property] = entity
ifcopenshell.api.run("boundary.edit_attributes", tool.Ifc.get(), entity=boundary, **attributes)
bpy.ops.bim.disable_editing_boundary()
return {"FINISHED"}
class UpdateBoundaryGeometry(bpy.types.Operator):
bl_idname = "bim.update_boundary_geometry"
bl_label = "Update boundary geometry"
bl_description = """
Update boundary connection geometry from mesh.
Mesh must lie on a single plane. It should look like a face or a face with holes.
"""
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
settings = tool.Boundary.get_assign_connection_geometry_settings(context.active_object)
ifcopenshell.api.run("boundary.assign_connection_geometry", tool.Ifc.get(), **settings)
return {"FINISHED"}
@@ -0,0 +1,60 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BlenderBIM Add-on.
#
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BlenderBIM Add-on is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
from bpy.types import PropertyGroup
from bpy.props import (
PointerProperty,
StringProperty,
EnumProperty,
BoolProperty,
IntProperty,
FloatProperty,
FloatVectorProperty,
CollectionProperty,
)
import blenderbim.tool as tool
def space_filter(self, object):
entity = tool.Ifc.get_entity(object)
if entity:
return entity.is_a("IfcSpace") or entity.is_a("IfcExternalSpatialElement")
return False
def boundary_filter(self, object):
entity = tool.Ifc.get_entity(object)
if entity:
return entity.is_a("IfcRelSpaceBoundary")
return False
def element_filter(self, object):
entity = tool.Ifc.get_entity(object)
if entity:
return entity.is_a("IfcElement")
return False
class BIMBoundaryProperties(PropertyGroup):
is_editing: BoolProperty(name="Is Editing")
relating_space: PointerProperty(name="RelatingSpace", type=bpy.types.Object, poll=space_filter)
related_building_element: PointerProperty(name="RelatedBuildingElement", type=bpy.types.Object, poll=element_filter)
parent_boundary: PointerProperty(name="ParentBoundary", type=bpy.types.Object, poll=boundary_filter)
corresponding_boundary: PointerProperty(name="CorrespondingBoundary", type=bpy.types.Object, poll=boundary_filter)
@@ -20,16 +20,37 @@ import bpy
import blenderbim.bim.helper
from bpy.types import Panel, UIList
from blenderbim.bim.ifc import IfcStore
import blenderbim.tool as tool
from ifcopenshell.api.boundary.data import Data
class BIM_PT_boundary(Panel):
class BIM_PT_SceneBoundaries(Panel):
bl_label = "IFC Space Boundaries"
bl_idname = "BIM_PT_boundary"
bl_id_name = "BIM_PT_scene_boundaries"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_geometry"
@classmethod
def poll(cls, context):
return IfcStore.get_file()
def draw(self, context):
row = self.layout.row(align=True)
row.operator("bim.load_project_space_boundaries")
row.operator("bim.select_project_space_boundaries", text="", icon="RESTRICT_SELECT_OFF")
row.operator("bim.colour_by_related_building_element", text="", icon="BRUSH_DATA")
class BIM_PT_Boundary(Panel):
bl_label = "IFC Space Boundary"
bl_idname = "BIM_PT_Boundary"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
bl_parent_id = "BIM_PT_geometry_object"
@classmethod
def poll(cls, context):
@@ -40,15 +61,97 @@ class BIM_PT_boundary(Panel):
return False
if not IfcStore.get_element(props.ifc_definition_id):
return False
if IfcStore.get_file().by_id(props.ifc_definition_id).is_a() not in ["IfcSpace", "IfcExternalSpatialElement"]:
return False
return True
entity = IfcStore.get_file().by_id(props.ifc_definition_id)
return entity.is_a("IfcRelSpaceBoundary")
def draw(self, context):
self.oprops = context.active_object.BIMObjectProperties
if not Data.is_loaded:
Data.load(IfcStore.get_file())
for boundary_id in Data.spaces.get(self.oprops.ifc_definition_id, []):
boundary = Data.boundaries[boundary_id]
props = context.active_object.BIMObjectProperties
ifc_file = tool.Ifc.get()
element = ifc_file.by_id(props.ifc_definition_id)
boundary = ifc_file.by_id(props.ifc_definition_id)
self.bprops = context.active_object.bim_boundary_properties
if self.bprops.is_editing:
row = self.layout.row(align=True)
row.operator("bim.edit_boundary_attributes", icon="CHECKMARK", text="Save Attributes")
row.operator("bim.disable_editing_boundary", icon="CANCEL", text="")
self.draw_relation_editor(boundary, "RelatingSpace", "relating_space")
self.draw_relation_editor(boundary, "RelatedBuildingElement", "related_building_element")
self.draw_relation_editor(boundary, "ParentBoundary", "parent_boundary")
self.draw_relation_editor(boundary, "CorrespondingBoundary", "corresponding_boundary")
else:
row = self.layout.row()
row.label(text=f"{boundary_id}", icon="GHOST_ENABLED")
row.operator("bim.enable_editing_boundary", icon="GREASEPENCIL", text="Edit")
self.draw_relation_data(boundary, "RelatingSpace")
self.draw_relation_data(boundary, "RelatedBuildingElement")
self.draw_relation_data(boundary, "ParentBoundary")
self.draw_relation_data(boundary, "CorrespondingBoundary")
if hasattr(boundary, "InnerBoundaries"):
for i, inner_boundary in enumerate(getattr(boundary, "InnerBoundaries", ())):
row = self.layout.row(align=True)
row.label(text="InnerBoundaries")
row.label(text=f"[{i}]")
row.label(text=f"{inner_boundary.is_a()}/{inner_boundary.Name}")
row = self.layout.row()
row.operator("bim.update_boundary_geometry")
def draw_relation_data(self, boundary, ifc_attribute: str):
row = self.layout.row(align=True)
if hasattr(boundary, ifc_attribute):
row.label(text=ifc_attribute)
entity = getattr(boundary, ifc_attribute)
if entity:
row.label(text=f"{entity.is_a()}/{entity.Name}")
op = row.operator("bim.select_global_id", text="", icon="TRACKER")
op.global_id = entity.GlobalId
else:
row.label(text="")
def draw_relation_editor(self, boundary, ifc_attribute: str, blender_property: str):
if hasattr(boundary, ifc_attribute):
row = self.layout.row(align=True)
row.prop(self.bprops, blender_property)
class BIM_PT_SpaceBoundaries(Panel):
bl_label = "IFC Space Boundaries"
bl_idname = "BIM_PT_SpaceBoundaries"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
bl_parent_id = "BIM_PT_geometry_object"
@classmethod
def poll(cls, context):
if not context.active_object:
return False
props = context.active_object.BIMObjectProperties
if not props.ifc_definition_id:
return False
if not IfcStore.get_element(props.ifc_definition_id):
return False
valid_classes = ("IfcSpace", "IfcExternalSpatialElement")
entity = IfcStore.get_file().by_id(props.ifc_definition_id)
for ifc_class in valid_classes:
if entity.is_a(ifc_class):
return True
return False
def draw(self, context):
self.props = context.active_object.BIMObjectProperties
self.ifc_file = tool.Ifc.get()
row = self.layout.row()
row.operator("bim.load_space_boundaries")
if not Data.is_loaded:
Data.load(self.ifc_file)
for boundary_id in Data.spaces.get(self.props.ifc_definition_id, []):
boundary_data = Data.boundaries[boundary_id]
building_element = self.ifc_file.by_id(boundary_data["RelatedBuildingElement"])
row = self.layout.row()
if building_element:
bld_el_description = f"{building_element.is_a()}/{building_element.Name}"
else:
bld_el_description = None
row.label(text=f"{boundary_id} > {bld_el_description}", icon="GHOST_ENABLED")
op = row.operator("bim.load_boundary", text="", icon="RESTRICT_SELECT_OFF")
op.boundary_id = boundary_id
@@ -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
from . import ui, prop, operator
classes = (
operator.AddBrick,
operator.AddBrickFeed,
operator.AssignBrickReference,
operator.CloseBrickProject,
operator.ConvertBrickProject,
operator.ConvertIfcToBrick,
operator.LoadBrickProject,
operator.NewBrickFile,
operator.RefreshBrickViewer,
operator.RemoveBrick,
operator.RewindBrickClass,
operator.ViewBrickClass,
operator.ViewBrickItem,
prop.Brick,
prop.BIMBrickProperties,
ui.BIM_PT_brickschema,
ui.BIM_PT_ifc_brickschema_references,
ui.BIM_UL_bricks,
)
def register():
bpy.types.Scene.BIMBrickProperties = bpy.props.PointerProperty(type=prop.BIMBrickProperties)
def unregister():
del bpy.types.Scene.BIMBrickProperties
@@ -0,0 +1,180 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BlenderBIM Add-on.
#
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BlenderBIM Add-on is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
import blenderbim.tool as tool
from blenderbim.tool.brick import BrickStore
try:
from rdflib import URIRef, BNode
except:
# See #1860
print("Warning: brickschema not available.")
def refresh():
BrickschemaData.is_loaded = False
BrickschemaReferencesData.is_loaded = False
class BrickschemaData:
data = {}
is_loaded = False
@classmethod
def load(cls):
cls.is_loaded = True
cls.data = {
"is_loaded": cls.get_is_loaded(),
"attributes": cls.attributes(),
"namespaces": cls.namespaces(),
"brick_equipment_classes": cls.brick_equipment_classes(),
}
@classmethod
def get_is_loaded(cls):
return BrickStore.graph is not None
@classmethod
def attributes(cls):
if BrickStore.graph is None:
return []
props = bpy.context.scene.BIMBrickProperties
try:
brick = props.bricks[props.active_brick_index]
except:
return []
results = []
uri = brick.uri
query = BrickStore.graph.query(
"""
PREFIX brick: <https://brickschema.org/schema/Brick#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
SELECT DISTINCT ?name ?value ?sp ?sv WHERE {
<{uri}> ?name ?value .
OPTIONAL {
{ ?name rdfs:range brick:TimeseriesReference . }
UNION
{ ?name a brick:EntityProperty . }
?value ?sp ?sv }
}
""".replace(
"{uri}", uri
)
)
for row in query:
name = row.get("name").toPython().split("#")[-1]
value = row.get("value")
results.append(
{
"name": name,
"value": value.toPython().split("#")[-1],
"is_uri": isinstance(value, URIRef),
"value_uri": value.toPython(),
"is_globalid": name == "globalID",
}
)
if isinstance(row.get("value"), BNode):
for s, p, o in BrickStore.graph.triples((value, None, None)):
results.append(
{
"name": name + ":" + p.toPython().split("#")[-1],
"value": o.toPython().split("#")[-1],
"is_uri": isinstance(o, URIRef),
"value_uri": o.toPython(),
"is_globalid": p.toPython().split("#")[-1] == "globalID",
}
)
return results
@classmethod
def namespaces(cls):
if BrickStore.graph is None:
return []
results = []
for alias, uri in BrickStore.graph.namespaces():
results.append((uri, f"{alias}: {uri}", ""))
return results
@classmethod
def brick_equipment_classes(cls):
if BrickStore.graph is None:
return []
results = []
query = BrickStore.graph.query(
"""
PREFIX brick: <https://brickschema.org/schema/Brick#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
SELECT ?class WHERE {
?class rdfs:subClassOf* brick:Equipment .
}
"""
)
for uri in sorted([x[0].toPython() for x in query]):
results.append((uri, uri.split("#")[-1], ""))
return results
class BrickschemaReferencesData:
data = {}
is_loaded = False
@classmethod
def load(cls):
cls.is_loaded = True
cls.data = {"is_loaded": cls.get_is_loaded(), "libraries": cls.libraries(), "references": cls.references()}
@classmethod
def get_is_loaded(cls):
return BrickStore.graph is not None
@classmethod
def libraries(cls):
results = []
ifc = tool.Ifc.get()
if not ifc:
return results
for library in ifc.by_type("IfcLibraryInformation"):
if tool.Ifc.get_schema() == "IFC2X3":
results.append((str(library.id()), library.Name or "Unnamed", ""))
elif ".ttl" in library.Location:
results.append((str(library.id()), library.Name or "Unnamed", ""))
return results
@classmethod
def references(cls):
results = []
for rel in getattr(tool.Ifc.get_entity(bpy.context.active_object), "HasAssociations", []):
if rel.is_a("IfcRelAssociatesLibrary"):
reference = rel.RelatingLibrary
if tool.Ifc.get_schema() == "IFC2X3" and "#" not in reference.ItemReference:
continue
if tool.Ifc.get_schema() != "IFC2X3" and "#" not in reference.Identification:
continue
results.append(
{
"id": reference.id(),
"identification": reference.ItemReference
if tool.Ifc.get_schema() == "IFC2X3"
else reference.Identification,
"name": reference.Name or "Unnamed",
}
)
return results
@@ -0,0 +1,191 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BlenderBIM Add-on.
#
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BlenderBIM Add-on is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
import ifcopenshell.api
import blenderbim.tool as tool
import blenderbim.core.brick as core
import blenderbim.bim.handler
from blenderbim.bim.ifc import IfcStore
class Operator:
def execute(self, context):
self._execute(context)
blenderbim.bim.handler.refresh_ui_data()
return {"FINISHED"}
class LoadBrickProject(bpy.types.Operator, Operator):
bl_idname = "bim.load_brick_project"
bl_label = "Load Brickschema Project"
bl_options = {"REGISTER", "UNDO"}
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
filter_glob: bpy.props.StringProperty(default="*.ttl", options={"HIDDEN"})
def _execute(self, context):
core.load_brick_project(tool.Brick, filepath=self.filepath)
def invoke(self, context, event):
context.window_manager.fileselect_add(self)
return {"RUNNING_MODAL"}
class ViewBrickClass(bpy.types.Operator, Operator):
bl_idname = "bim.view_brick_class"
bl_label = "View Brick Class"
bl_options = {"REGISTER", "UNDO"}
brick_class: bpy.props.StringProperty(name="Brick Class")
def _execute(self, context):
core.view_brick_class(tool.Brick, brick_class=self.brick_class)
class ViewBrickItem(bpy.types.Operator, Operator):
bl_idname = "bim.view_brick_item"
bl_label = "View Brick Item"
bl_options = {"REGISTER", "UNDO"}
item: bpy.props.StringProperty(name="Brick Item")
def _execute(self, context):
core.view_brick_item(tool.Brick, item=self.item)
class RewindBrickClass(bpy.types.Operator, Operator):
bl_idname = "bim.rewind_brick_class"
bl_label = "Rewind Brick Class"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
core.rewind_brick_class(tool.Brick)
class CloseBrickProject(bpy.types.Operator, Operator):
bl_idname = "bim.close_brick_project"
bl_label = "Close Brick Project"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
core.close_brick_project(tool.Brick)
class ConvertBrickProject(bpy.types.Operator, Operator):
bl_idname = "bim.convert_brick_project"
bl_label = "Convert Brick Project"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
core.convert_brick_project(tool.Ifc, tool.Brick)
class AssignBrickReference(bpy.types.Operator, Operator):
bl_idname = "bim.assign_brick_reference"
bl_label = "Assign Brick Reference"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
props = context.scene.BIMBrickProperties
core.assign_brick_reference(
tool.Ifc,
tool.Brick,
element=tool.Ifc.get_entity(context.active_object),
library=tool.Ifc.get().by_id(int(props.libraries)),
brick_uri=props.bricks[props.active_brick_index].uri,
)
class AddBrick(bpy.types.Operator, Operator):
bl_idname = "bim.add_brick"
bl_label = "Add Brick"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
props = context.scene.BIMBrickProperties
library = None
if props.libraries:
library = tool.Ifc.get().by_id(int(props.libraries))
core.add_brick(
tool.Ifc,
tool.Brick,
element=tool.Ifc.get_entity(context.active_object) if context.selected_objects else None,
namespace=props.namespace,
brick_class=props.brick_equipment_class,
library=library,
)
class AddBrickFeed(bpy.types.Operator, Operator):
bl_idname = "bim.add_brick_feed"
bl_label = "Add Brick Feed"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
source = tool.Ifc.get_entity([o for o in context.selected_objects if o != context.active_object][0])
destination = tool.Ifc.get_entity(context.active_object)
core.add_brick_feed(
tool.Ifc,
tool.Brick,
source=source,
destination=destination,
)
class ConvertIfcToBrick(bpy.types.Operator, Operator):
bl_idname = "bim.convert_ifc_to_brick"
bl_label = "Convert IFC To Brick"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
props = context.scene.BIMBrickProperties
library = None
if props.libraries:
library = tool.Ifc.get().by_id(int(props.libraries))
core.convert_ifc_to_brick(tool.Brick, namespace=props.namespace, library=library)
class NewBrickFile(bpy.types.Operator, Operator):
bl_idname = "bim.new_brick_file"
bl_label = "New Brick File"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
core.new_brick_file(tool.Brick)
class RefreshBrickViewer(bpy.types.Operator, Operator):
bl_idname = "bim.refresh_brick_viewer"
bl_label = "Refresh Brick Viewer"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
core.refresh_brick_viewer(tool.Brick)
class RemoveBrick(bpy.types.Operator, Operator):
bl_idname = "bim.remove_brick"
bl_label = "Remove Brick"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
props = context.scene.BIMBrickProperties
core.remove_brick(
tool.Ifc,
tool.Brick,
library=tool.Ifc.get().by_id(int(props.libraries)) if props.libraries else None,
brick_uri=props.bricks[props.active_brick_index].uri,
)
@@ -0,0 +1,71 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BlenderBIM Add-on.
#
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BlenderBIM Add-on is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
from blenderbim.bim.module.brick.data import BrickschemaData, BrickschemaReferencesData
from blenderbim.bim.prop import StrProperty, Attribute
from bpy.types import PropertyGroup
from bpy.props import (
PointerProperty,
StringProperty,
EnumProperty,
BoolProperty,
IntProperty,
FloatProperty,
FloatVectorProperty,
CollectionProperty,
)
def update_active_brick_index(self, context):
BrickschemaData.is_loaded = False
def get_libraries(self, context):
if not BrickschemaReferencesData.is_loaded:
BrickschemaReferencesData.load()
return BrickschemaReferencesData.data["libraries"]
def get_namespaces(self, context):
if not BrickschemaData.is_loaded:
BrickschemaData.load()
return BrickschemaData.data["namespaces"]
def get_brick_equipment_classes(self, context):
if not BrickschemaData.is_loaded:
BrickschemaData.load()
return BrickschemaData.data["brick_equipment_classes"]
class Brick(PropertyGroup):
name: StringProperty(name="Name")
label: StringProperty(name="Label")
uri: StringProperty(name="URI")
total_items: IntProperty(name="Total Items")
class BIMBrickProperties(PropertyGroup):
active_brick_class: StringProperty(name="Active Brick Class")
brick_breadcrumbs: CollectionProperty(name="Brick Breadcrumbs", type=StrProperty)
bricks: CollectionProperty(name="Bricks", type=Brick)
active_brick_index: IntProperty(name="Active Brick Index", update=update_active_brick_index)
libraries: EnumProperty(name="Libraries", items=get_libraries)
namespace: EnumProperty(name="Namespace", items=get_namespaces)
brick_equipment_class: EnumProperty(name="Brick Equipment Class", items=get_brick_equipment_classes)
@@ -0,0 +1,132 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BlenderBIM Add-on.
#
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BlenderBIM Add-on is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import blenderbim.tool as tool
from bpy.types import Panel, UIList
from blenderbim.bim.module.brick.data import BrickschemaData, BrickschemaReferencesData
class BIM_PT_brickschema(Panel):
bl_label = "Brickschema Project"
bl_idname = "BIM_PT_brickschema"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
def draw(self, context):
if not BrickschemaData.is_loaded:
BrickschemaData.load()
self.props = context.scene.BIMBrickProperties
if not BrickschemaData.data["is_loaded"]:
row = self.layout.row(align=True)
row.operator("bim.new_brick_file", text="Create Project")
row.operator("bim.load_brick_project", text="Load Project")
return
row = self.layout.row(align=True)
if len(self.props.brick_breadcrumbs):
row.operator("bim.rewind_brick_class", text="", icon="FRAME_PREV")
row.label(text=self.props.active_brick_class)
row.operator("bim.refresh_brick_viewer", text="", icon="FILE_REFRESH")
row.operator("bim.close_brick_project", text="", icon="CANCEL")
row = self.layout.row(align=True)
row.prop(self.props, "namespace", text="")
row.prop(self.props, "brick_equipment_class", text="")
row.operator("bim.add_brick", text="", icon="ADD")
row = self.layout.row(align=True)
row.alignment = "RIGHT"
row.operator("bim.add_brick_feed", text="", icon="PLUGIN")
row.operator("bim.remove_brick", text="", icon="X")
self.layout.template_list("BIM_UL_bricks", "", self.props, "bricks", self.props, "active_brick_index")
for attribute in BrickschemaData.data["attributes"]:
row = self.layout.row(align=True)
row.label(text=attribute["name"])
row.label(text=attribute["value"])
if attribute["is_uri"]:
op = row.operator("bim.view_brick_item", text="", icon="DISCLOSURE_TRI_RIGHT")
op.item = attribute["value_uri"]
if attribute["is_globalid"]:
op = row.operator("bim.select_global_id", icon="RESTRICT_SELECT_OFF", text="")
op.global_id = attribute["value"]
class BIM_PT_ifc_brickschema_references(Panel):
bl_label = "IFC Brickschema References"
bl_idname = "BIM_PT_ifc_brickschema_references"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_brickschema"
@classmethod
def poll(cls, context):
return tool.Ifc.get()
def draw(self, context):
if not BrickschemaReferencesData.is_loaded:
BrickschemaReferencesData.load()
self.props = context.scene.BIMBrickProperties
if not BrickschemaReferencesData.data["is_loaded"]:
row = self.layout.row()
row.label(text="No Brickschema Project Loaded")
return
if not BrickschemaReferencesData.data["libraries"]:
row = self.layout.row(align=True)
row.label(text="No IFC Libraries")
row.operator("bim.convert_brick_project", text="", icon="ADD")
return
row = self.layout.row(align=True)
row.prop(self.props, "libraries")
row.operator("bim.convert_brick_project", text="", icon="ADD")
row = self.layout.row(align=True)
row.operator("bim.assign_brick_reference", icon="ADD")
row.operator("bim.convert_ifc_to_brick", icon="IMPORT")
if not BrickschemaReferencesData.data["references"]:
row = self.layout.row()
row.label(text="No References")
for reference in BrickschemaReferencesData.data["references"]:
row = self.layout.row(align=True)
row.label(text=reference["identification"], icon="ASSET_MANAGER")
row.label(text=reference["name"])
row.operator("bim.unassign_library_reference", text="", icon="X").reference = reference["id"]
row.operator("bim.view_brick_item", text="", icon="DISCLOSURE_TRI_RIGHT").item = reference["identification"]
class BIM_UL_bricks(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
if item:
row = layout.row(align=True)
if item.total_items:
op = row.operator("bim.view_brick_class", text="", icon="DISCLOSURE_TRI_RIGHT", emboss=False)
op.brick_class = item.name
row.label(text=item.label if item.label else item.name)
if item.total_items:
row.label(text=str(item.total_items))
@@ -34,9 +34,6 @@ classes = (
operator.SmartClashGroup,
operator.SelectSmartGroup,
operator.LoadSmartGroupsForActiveClashSet,
operator.SetBlenderClashSetA,
operator.SetBlenderClashSetB,
operator.ExecuteBlenderClash,
prop.ClashSource,
prop.ClashSet,
prop.SmartClashGroup,
@@ -411,82 +411,3 @@ class SelectSmartGroup(bpy.types.Operator):
obj.select_set(True)
return {"FINISHED"}
class BlenderClasher:
def process_clash_set(self, context):
import collision
a_cm = collision.CollisionManager()
b_cm = collision.CollisionManager()
self.add_to_cm(a_cm, context.scene.BIMClashProperties.blender_clash_set_a, context)
self.add_to_cm(b_cm, context.scene.BIMClashProperties.blender_clash_set_b, context)
results = a_cm.in_collision_other(b_cm, return_data=True)
if not results[0]:
print("No clashes")
return
for contact in results[1]:
if contact.raw.penetration_depth < 0.01:
continue
print("-----")
print(contact.names)
print(contact.raw.normal)
print(contact.raw.pos)
def add_to_cm(self, cm, object_names, context):
import ifcclash
for object_name in object_names:
name = object_name.name
obj = bpy.data.objects[name]
triangulated_mesh = self.triangulate_mesh(obj, context)
mesh = ifcclash.Mesh()
mesh.vertices = np.array([tuple(obj.matrix_world @ v.co) for v in triangulated_mesh.vertices])
mesh.faces = np.array([tuple(p.vertices) for p in triangulated_mesh.polygons])
cm.add_object(name, mesh)
def triangulate_mesh(self, obj, context):
mesh = obj.evaluated_get(context.evaluated_depsgraph_get()).to_mesh()
bm = bmesh.new()
bm.from_mesh(mesh)
bmesh.ops.triangulate(bm, faces=bm.faces)
bm.to_mesh(mesh)
bm.free()
del bm
return mesh
class SetBlenderClashSetA(bpy.types.Operator):
bl_idname = "bim.set_blender_clash_set_a"
bl_label = "Set Blender Clash Set A"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
context.scene.BIMClashProperties.blender_clash_set_a.clear()
for obj in context.selected_objects:
new = context.scene.BIMClashProperties.blender_clash_set_a.add()
new.name = obj.name
return {"FINISHED"}
class SetBlenderClashSetB(bpy.types.Operator):
bl_idname = "bim.set_blender_clash_set_b"
bl_label = "Set Blender Clash Set B"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
context.scene.BIMClashProperties.blender_clash_set_b.clear()
for obj in context.selected_objects:
new = context.scene.BIMClashProperties.blender_clash_set_b.add()
new.name = obj.name
return {"FINISHED"}
class ExecuteBlenderClash(bpy.types.Operator):
bl_idname = "bim.execute_blender_clash"
bl_label = "Execute Blender Clash"
def execute(self, context):
blender_clasher = BlenderClasher()
blender_clasher.process_clash_set(context)
return {"FINISHED"}
@@ -27,6 +27,7 @@ class BIM_PT_ifcclash(Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_quality_control"
def draw(self, context):
layout = self.layout
@@ -34,17 +35,6 @@ class BIM_PT_ifcclash(Panel):
scene = context.scene
props = scene.BIMClashProperties
layout.label(text="Blender Clash:")
row = layout.row(align=True)
row.operator("bim.set_blender_clash_set_a")
row.operator("bim.set_blender_clash_set_b")
row = layout.row(align=True)
row.operator("bim.execute_blender_clash")
layout.label(text="IFC Clash:")
row = layout.row(align=True)
row.operator("bim.add_clash_set")
row.operator("bim.import_clash_sets", text="", icon="IMPORT")
@@ -29,6 +29,7 @@ class BIM_PT_classifications(Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_project_setup"
@classmethod
def poll(cls, context):
@@ -86,6 +87,7 @@ class BIM_PT_classification_references(Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
bl_parent_id = "BIM_PT_object_metadata"
@classmethod
def poll(cls, context):
@@ -27,6 +27,7 @@ class BIM_PT_cobie(Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_collaboration"
def draw(self, context):
layout = self.layout
@@ -29,6 +29,7 @@ class BIM_PT_constraints(Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_project_setup"
@classmethod
def poll(cls, context):
@@ -73,6 +74,7 @@ class BIM_PT_object_constraints(Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
bl_parent_id = "BIM_PT_misc_object"
@classmethod
def poll(cls, context):
@@ -29,6 +29,7 @@ class BIM_PT_context(bpy.types.Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_geometry"
@classmethod
def poll(cls, context):
@@ -31,6 +31,7 @@ class BIM_PT_cost_schedules(Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_4D5D"
@classmethod
def poll(cls, context):
@@ -26,6 +26,7 @@ class BIM_PT_covetool(bpy.types.Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_integrations"
def draw(self, context):
layout = self.layout
@@ -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"}
@@ -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:")
@@ -0,0 +1,58 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2022 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BlenderBIM Add-on.
#
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BlenderBIM Add-on is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
# ############################################################################ #
# Hey there! Welcome to the BlenderBIM Add-on code. Please feel free to reach
# out if you have any questions or need further guidance. Happy hacking!
# ############################################################################ #
# Every module has a __init__.py file to load all of its classes. Every
# operation, property, and interface needs to be registered with the Blender
# system when the add-on loads. This is where it happens.
import bpy
from . import ui, prop, operator
# You'll need to provide a list of every one of your classes here. If you forget
# to specify your class, it won't load and you won't be able to use that
# operator, property, or interface.
classes = (
operator.DemonstrateHelloWorld,
operator.DemonstrateRenameProject,
prop.BIMDemoProperties,
ui.BIM_PT_demo,
)
# When the add-on loads, this register function is called. This allows you to
# perform additional tasks during startup. If you need to store custom
# properties, this is where you tell Blender where they are going to be stored.
# You might see more advanced registrations happening in other modules.
def register():
# Properties are usually stored on bpy.types.Scene when they are something
# that affects everything in the project, or bpy.types.Object when they
# affect a single BIM element.
bpy.types.Scene.BIMDemoProperties = bpy.props.PointerProperty(type=prop.BIMDemoProperties)
# When someone disables the add-on, we need to unload everything we loaded. This
# does the reverse of the register function.
def unregister():
del bpy.types.Scene.BIMDemoProperties
@@ -0,0 +1,78 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2022 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BlenderBIM Add-on.
#
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BlenderBIM Add-on is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
# ############################################################################ #
# Hey there! Welcome to the BlenderBIM Add-on code. Please feel free to reach
# out if you have any questions or need further guidance. Happy hacking!
# ############################################################################ #
# Every module has a data.py file to load data for its interface panels to
# display. When a panel needs to show information that doesn't come from a
# Blender property, it gets that data from one of these classes. This separation
# of "fetching data" and "displaying data" is a common concept that makes UI
# code much simpler, more efficient, and less error-prone to refresh data.
import blenderbim.tool as tool
# All data must have a refresh function. The refresh function simply sets all
# data class to be not yet loaded. So the next time the interface is drawn to
# the user, it will force the data to be reloaded.
def refresh():
# When you define your own data classes, just add to this list!
DemoData.is_loaded = False
# This is a sample data class. It correlates to a single interface panel. Panels
# should not share data classes. This makes it easy to write your interface
# without having your code mixed in with other parts of the interface. As a
# convention, the class is named the same name as the panel.
class DemoData:
# All data classes must have two variables. One to store all the data it has
# loaded and another to store the load state.
data = {}
is_loaded = False
# Every data class must have a load function. This lets us load data in a
# predictable manner.
@classmethod
def load(cls):
# The load function always has two responsibilities: populate the data,
# and set is_loaded to true.
cls.data = {
"has_project": cls.has_project(),
"project_name": cls.project_name(),
}
cls.is_loaded = True
@classmethod
def has_project(cls):
# Here, we check whether or not there is an active IFC project in our
# Blender session.
return bool(tool.Ifc.get())
@classmethod
def project_name(cls):
# Imagine how messy our UI code would be if all of this was mixed in
# with our layout code. Here, it's isolated and testable, and the UX
# becomes easier to maintain and change.
ifc = tool.Ifc.get()
if ifc:
return ifc.by_type("IfcProject")[0].Name or "Unnamed"
@@ -0,0 +1,79 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2022 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BlenderBIM Add-on.
#
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BlenderBIM Add-on is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
# ############################################################################ #
# Hey there! Welcome to the BlenderBIM Add-on code. Please feel free to reach
# out if you have any questions or need further guidance. Happy hacking!
# ############################################################################ #
# Every module has an operator.py file to define all of the buttons that a user
# can click on from the Blender interface. Blender calls these buttons
# "Operators", since they correlate to a single user operation.
import bpy
import blenderbim.tool as tool
import blenderbim.core.demo as core
import blenderbim.bim.handler
# Each button correlates to a class like the one below. In this case, we're
# creating a new button that will execute a hello world feature.
class DemonstrateHelloWorld(bpy.types.Operator, tool.Ifc.Operator):
# Every operator has a unique ID. If you enable Python tooltips and hover
# over any button in the interface, you will see each button will run a
# function that uses this ID. For example, hovering over this button will
# show that the code it executes is "bpy.ops.bim.demonstrate_hello_world()"
bl_idname = "bim.demonstrate_hello_world"
# In the interface, this button will have the text "Print Hello World".
bl_label = "Print Hello World"
# This code means that the user can undo or redo after pressing the button.
bl_options = {"REGISTER", "UNDO"}
# When hovering over the button, this helpful description will be shown.
bl_description = "Prints the text 'Hello World'"
# When the button is pressed, this _execute() function will run.
def _execute(self, context):
# Every operator should do one thing only: execute a core function. In
# order to execute a core function, the operator's responsibility is to
# pass in all of the inputs the core needs to do its job.
# A core function simply tells tools what to do, so a core function will
# always need at least one tool as an input.
core.demonstrate_hello_world(tool.Demo)
# This class, just like the one above, will correlate to another button in the
# interface. It has a different ID and a different label.
class DemonstrateRenameProject(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.demonstrate_rename_project"
bl_label = "Rename IFC Project"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Renames your IFC project name and prints the name"
def _execute(self, context):
# This core function requires three inputs: two tools, and a name
# string. In this case, the name is taken from some custom Blender
# properties. Generally, the inputs to the core function will come from
# properties (such as an input field) or data from the scene (like the
# actively selected object).
core.demonstrate_rename_project(tool.Ifc, tool.Demo, name=bpy.context.scene.BIMDemoProperties.name)
@@ -0,0 +1,61 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2022 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BlenderBIM Add-on.
#
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BlenderBIM Add-on is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
# ############################################################################ #
# Hey there! Welcome to the BlenderBIM Add-on code. Please feel free to reach
# out if you have any questions or need further guidance. Happy hacking!
# ############################################################################ #
# Every module has a prop.py file to define Blender properties. Any time you
# want an interface widget like an input field, dropdown, checkbox, or number
# slider, you need a Blender property to store that widget's data. If you want
# to store data that will affect the Blender interface, you also need a
# property. Properties are stored in the .blend file, so when your user closes
# their Blender session, and reopens it, things are how they left it.
import bpy
from bpy.types import PropertyGroup
# Properties have many different data types. We won't use all of them in this
# demo module, but this is a list for your reference.
from bpy.props import (
PointerProperty,
StringProperty,
EnumProperty,
BoolProperty,
IntProperty,
FloatProperty,
FloatVectorProperty,
CollectionProperty,
)
# All properties must belong in a property group. Usually, you'd have a group
# named after your module.
class BIMDemoProperties(PropertyGroup):
# This first property is a string. This means that in the interface, it will
# represent a text input field. We can give it a name and a default value.
# The name will be the label shown next to the input field in the interface.
name: StringProperty(name="Name", default="New Project Name")
# Not all properties need to be shown using their equivalent input widget.
# In this case, we can store a message string, but we will never show it as
# an input text field in the ui.py.
message: StringProperty(name="Message")
show_hints: BoolProperty(name="Show Hints", default=False)
@@ -0,0 +1,131 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2022 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BlenderBIM Add-on.
#
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BlenderBIM Add-on is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
# ############################################################################ #
# Hey there! Welcome to the BlenderBIM Add-on code. Please feel free to reach
# out if you have any questions or need further guidance. Happy hacking!
# ############################################################################ #
# Every module has a ui.py file to define its interface. Interfaces describe how
# panels, buttons, labels, and input fields are laid out.
import bpy
from blenderbim.bim.module.demo.data import DemoData
# Every panel in the interface correlates to one of these classes. If you enable
# the "Developer Extras" option in Blender, then you can actually right click on
# any panel in Blender, and click "Edit Source". This will bring you to a ui.py
# file like this one where you can see this code. Pretty neat!
class BIM_PT_demo(bpy.types.Panel):
# Every panel has a title.
bl_label = "BlenderBIM Demo"
# Every panel must have an ID. It must be unique. For example, you may want
# to later reference the panel (such as if you want to create a nested
# subpanel).
bl_idname = "BIM_PT_demo"
# This tells the panel to appear in the properties section (in the bottom
# right by default) of the Blender interface.
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
# This tells the panel to appear in the "scene" tab of the properties panel.
bl_context = "scene"
# Every panel has a draw function. This draws all the layout of the panel,
# including all of its labels, buttons, and so on. Note that if the panel is
# hidden, this draw function will not be called.
def draw(self, context):
# Before drawing any content, the panel must load any dynamic variables
# that it will show. Most panels have dynamic content, which display
# changing data from your BIM model. You must load that data first. The
# two lines are always the same, "if not loaded, then load".
if not DemoData.is_loaded:
# Each panel should have its own data class that it loads data from.
# Note that we only load data if it hasn't already been loaded.
# This is because interface panels are drawn continuously. This
# draw() function will be called every time you scroll or move your
# mouse over it. Loading data is slow, so we only refresh data when
# we have to.
DemoData.load()
# Interface panels often show properties. For convenience, define where
# the properties are stored for the module.
self.props = context.scene.BIMDemoProperties
# This defines a new "row" in our layout. When a new row is defined, the
# things on that row, like buttons, labels, and input fields, show on a
# new line.
row = self.layout.row()
# This is the simplest interface element - a label placed in our row.
# If you enable the "Icon Viewer" add-on, you can view a list of
# Blender's built-in icons to choose from in the Blender text editor's
# side panel.
row.label(text="This is a demo panel", icon="INFO")
# Our interface can contain simple logic. For example, if we don't have
# an IFC project, show an error message and don't draw anything else.
# The ui.py should always have very simple logic. Details like how to
# determine whether we have a project is delegated to the data loader.
if not DemoData.data["has_project"]:
row = self.layout.row()
row.label(text="Load or create an IFC project first", icon="ERROR")
return
row = self.layout.row()
# When you want to show a button in the interface, you have to specify
# an operator. Operators are defined in operator.py. You reference an
# operator using its unique ID.
row.operator("bim.demonstrate_hello_world")
# Blender properties can be used to affect what your panel shows. Notice
# how the logic is very simple. Only use simple boolean predicates and
# loops in your layout. No complex logic should ever been seen in the
# interface code.
if self.props.message:
row = self.layout.row()
row.label(text=self.props.message)
row = self.layout.row(align=True)
row.label(text="Project Name")
# Sometimes you need data that doesn't come from Blender properties,
# like from your IFC model. In this case, just get some data from your
# data loader. For those familiar with how templating languages work,
# this is exactly the same.
row.label(text=DemoData.data["project_name"])
row = self.layout.row()
# We've seen how to show text labels and buttons using operators. What
# about text input fields, number sliders, dropdowns, checkboxes, and
# scrollable lists? The way Blender works is that you simply show a
# Blender property in your interface. The data type of that property
# determines which UI widget is shown. A text property will show a text
# field. A number property will show a number slider. An enum property
# will show a drop down. And so on. In this case, our name property is a
# text data type, so expect to see a text input field show up here.
row.prop(self.props, "name")
row = self.layout.row()
# Here's another button, referencing another operator.
row.operator("bim.demonstrate_rename_project")
if self.props.show_hints:
row = self.layout.row()
row.label(text="Name cannot be blank!")
@@ -27,6 +27,7 @@ class BIM_PT_diff(Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_quality_control"
def draw(self, context):
layout = self.layout
@@ -20,35 +20,29 @@ import bpy
from . import ui, prop, operator
classes = (
operator.LoadInformation,
operator.LoadDocumentReferences,
operator.DisableDocumentEditingUI,
operator.EnableEditingDocument,
operator.DisableEditingDocument,
operator.AddInformation,
operator.AddDocumentReference,
operator.EditInformation,
operator.EditDocumentReference,
operator.RemoveDocument,
operator.EnableAssigningDocument,
operator.DisableAssigningDocument,
operator.AddInformation,
operator.AssignDocument,
operator.DisableDocumentEditingUI,
operator.DisableEditingDocument,
operator.EditDocument,
operator.EnableEditingDocument,
operator.LoadDocument,
operator.LoadParentDocument,
operator.LoadProjectDocuments,
operator.RemoveDocument,
operator.UnassignDocument,
prop.Document,
prop.BIMDocumentProperties,
prop.BIMObjectDocumentProperties,
ui.BIM_PT_documents,
ui.BIM_PT_object_documents,
ui.BIM_UL_documents,
ui.BIM_UL_object_documents,
)
def register():
bpy.types.Scene.BIMDocumentProperties = bpy.props.PointerProperty(type=prop.BIMDocumentProperties)
bpy.types.Object.BIMObjectDocumentProperties = bpy.props.PointerProperty(type=prop.BIMObjectDocumentProperties)
def unregister():
del bpy.types.Scene.BIMDocumentProperties
del bpy.types.Object.BIMObjectDocumentProperties
@@ -0,0 +1,116 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2022 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BlenderBIM Add-on.
#
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BlenderBIM Add-on is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import os
import bpy
import ifcopenshell
import ifcopenshell.util.schema
import blenderbim.tool as tool
def refresh():
DocumentData.is_loaded = False
ObjectDocumentData.is_loaded = False
class DocumentData:
data = {}
is_loaded = False
@classmethod
def load(cls):
cls.data = {
"total_information": cls.total_information(),
"parent_document": cls.parent_document(),
}
cls.is_loaded = True
@classmethod
def total_information(cls):
return len(
[
rel
for rel in tool.Ifc.get().by_type("IfcProject")[0].HasAssociations or []
if rel.is_a("IfcRelAssociatesDocument") and rel.RelatingDocument.is_a("IfcDocumentInformation")
]
)
@classmethod
def parent_document(cls):
props = bpy.context.scene.BIMDocumentProperties
if len(props.breadcrumbs):
parent = tool.Ifc.get().by_id(int(props.breadcrumbs[-1].name))
if tool.Ifc.get_schema() == "IFC2X3":
return str(parent.DocumentId)
return str(parent.Identification)
return ""
class ObjectDocumentData:
data = {}
is_loaded = False
@classmethod
def load(cls):
cls.data = {
"documents": cls.documents(),
}
cls.is_loaded = True
@classmethod
def documents(cls):
results = []
element = tool.Ifc.get_entity(bpy.context.active_object)
if not element:
return results
for rel in getattr(element, "HasAssociations", []):
if rel.is_a("IfcRelAssociatesDocument"):
if not rel.RelatingDocument.is_a("IfcDocumentReference"):
continue
name = rel.RelatingDocument.Name
if not name and rel.RelatingDocument.ReferencedDocument:
name = rel.RelatingDocument.ReferencedDocument.Name
if tool.Ifc.get_schema() == "IFC2X3":
identification = rel.RelatingDocument.ItemReference
if not identification and rel.RelatingDocument.ReferencedDocument:
identification = rel.RelatingDocument.ReferencedDocument.DocumentId
else:
identification = rel.RelatingDocument.Identification
if not identification and rel.RelatingDocument.ReferencedDocument:
identification = rel.RelatingDocument.ReferencedDocument.Identification
location = rel.RelatingDocument.Location
if location is None and rel.RelatingDocument.ReferencedDocument:
location = rel.RelatingDocument.ReferencedDocument.Location
if location:
if not "://" in location:
if not os.path.isabs(location):
location = os.path.abspath(os.path.join(os.path.dirname(tool.Ifc.get_path()), location))
location = "file://" + location
results.append(
{
"id": rel.RelatingDocument.id(),
"identification": identification,
"name": name,
"location": location,
}
)
return results
@@ -1,5 +1,5 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
# Copyright (C) 2020, 2021, 2022 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BlenderBIM Add-on.
#
@@ -20,303 +20,133 @@ import bpy
import json
import ifcopenshell.api
import ifcopenshell.util.attribute
import blenderbim.tool as tool
import blenderbim.core.document as core
from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.document.data import Data
class LoadInformation(bpy.types.Operator):
bl_idname = "bim.load_information"
bl_label = "Load Information"
class LoadProjectDocuments(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.load_project_documents"
bl_label = "Load Project Documents"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
self.file = IfcStore.get_file()
props = context.scene.BIMDocumentProperties
props.documents.clear()
for information_id, information in Data.information.items():
new = props.documents.add()
new.name = information["Name"] or "Unnamed"
if self.file.schema == "IFC2X3":
new.identification = information["DocumentId"] or "*"
else:
new.identification = information["Identification"] or "*"
new.ifc_definition_id = information_id
props.is_editing = "information"
bpy.ops.bim.disable_editing_document()
return {"FINISHED"}
def _execute(self, context):
core.load_project_documents(tool.Document)
class LoadDocumentReferences(bpy.types.Operator):
bl_idname = "bim.load_document_references"
bl_label = "Load Document References"
class LoadDocument(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.load_document"
bl_label = "Load Document"
bl_options = {"REGISTER", "UNDO"}
document: bpy.props.IntProperty()
def _execute(self, context):
core.load_document(tool.Document, document=tool.Ifc.get().by_id(self.document))
class LoadParentDocument(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.load_parent_document"
bl_label = "Load Parent Document"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
self.file = IfcStore.get_file()
props = context.scene.BIMDocumentProperties
props.documents.clear()
for reference_id, reference in Data.references.items():
new = props.documents.add()
new.name = reference["Name"] or "Unnamed"
if self.file.schema == "IFC2X3":
new.identification = reference["ItemReference"] or "*"
else:
new.identification = reference["Identification"] or "*"
new.ifc_definition_id = reference_id
props.is_editing = "reference"
bpy.ops.bim.disable_editing_document()
return {"FINISHED"}
def _execute(self, context):
core.load_parent_document(tool.Document)
class DisableDocumentEditingUI(bpy.types.Operator):
class DisableDocumentEditingUI(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.disable_document_editing_ui"
bl_label = "Disable Document Editing UI"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
context.scene.BIMDocumentProperties.is_editing = ""
bpy.ops.bim.disable_editing_document()
return {"FINISHED"}
def _execute(self, context):
core.disable_document_editing_ui(tool.Document)
class EnableEditingDocument(bpy.types.Operator):
class EnableEditingDocument(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.enable_editing_document"
bl_label = "Enable Editing Document"
bl_options = {"REGISTER", "UNDO"}
document: bpy.props.IntProperty()
def execute(self, context):
props = context.scene.BIMDocumentProperties
props.document_attributes.clear()
if props.is_editing == "information":
data = Data.information[self.document]
ifc_class = "IfcDocumentInformation"
elif props.is_editing == "reference":
data = Data.references[self.document]
ifc_class = "IfcDocumentReference"
for attribute in IfcStore.get_schema().declaration_by_name(ifc_class).all_attributes():
data_type = ifcopenshell.util.attribute.get_primitive_type(attribute)
if data_type == "entity":
continue
new = props.document_attributes.add()
new.name = attribute.name()
new.is_null = data[attribute.name()] is None
new.is_optional = attribute.optional()
new.data_type = data_type
if data_type == "string":
new.string_value = "" if new.is_null else data[attribute.name()]
elif data_type == "enum":
new.enum_items = json.dumps(ifcopenshell.util.attribute.get_enum_items(attribute))
if data[attribute.name()]:
new.enum_value = data[attribute.name()]
props.active_document_id = self.document
return {"FINISHED"}
def _execute(self, context):
core.enable_editing_document(tool.Document, document=tool.Ifc.get().by_id(self.document))
class DisableEditingDocument(bpy.types.Operator):
class DisableEditingDocument(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.disable_editing_document"
bl_label = "Disable Editing Document"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
context.scene.BIMDocumentProperties.active_document_id = 0
return {"FINISHED"}
def _execute(self, context):
core.disable_editing_document(tool.Document)
class AddInformation(bpy.types.Operator):
class AddInformation(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.add_information"
bl_label = "Add Information"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
result = ifcopenshell.api.run("document.add_information", IfcStore.get_file())
Data.load(IfcStore.get_file())
bpy.ops.bim.load_information()
bpy.ops.bim.enable_editing_document(document=result.id())
return {"FINISHED"}
core.add_information(tool.Ifc, tool.Document)
class AddDocumentReference(bpy.types.Operator):
class AddDocumentReference(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.add_document_reference"
bl_label = "Add Document Reference"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
result = ifcopenshell.api.run("document.add_reference", IfcStore.get_file())
Data.load(IfcStore.get_file())
bpy.ops.bim.load_document_references()
bpy.ops.bim.enable_editing_document(document=result.id())
return {"FINISHED"}
core.add_reference(tool.Ifc, tool.Document)
class EditInformation(bpy.types.Operator):
bl_idname = "bim.edit_information"
class EditDocument(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.edit_document"
bl_label = "Edit Information"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
props = context.scene.BIMDocumentProperties
attributes = {}
for attribute in props.document_attributes:
if attribute.is_null:
attributes[attribute.name] = None
elif attribute.enum_items:
attributes[attribute.name] = attribute.enum_value
else:
attributes[attribute.name] = attribute.string_value
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"document.edit_information",
self.file,
**{"information": self.file.by_id(props.active_document_id), "attributes": attributes}
)
Data.load(IfcStore.get_file())
bpy.ops.bim.load_information()
return {"FINISHED"}
core.edit_document(tool.Ifc, tool.Document, document=tool.Ifc.get().by_id(props.active_document_id))
class EditDocumentReference(bpy.types.Operator):
bl_idname = "bim.edit_document_reference"
bl_label = "Edit Document Reference"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
props = context.scene.BIMDocumentProperties
attributes = {}
for attribute in props.document_attributes:
if attribute.is_null:
attributes[attribute.name] = None
else:
attributes[attribute.name] = attribute.string_value
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"document.edit_reference",
self.file,
**{"reference": self.file.by_id(props.active_document_id), "attributes": attributes}
)
Data.load(IfcStore.get_file())
bpy.ops.bim.load_document_references()
return {"FINISHED"}
class RemoveDocument(bpy.types.Operator):
class RemoveDocument(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.remove_document"
bl_label = "Remove Document"
bl_options = {"REGISTER", "UNDO"}
document: bpy.props.IntProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
props = context.scene.BIMDocumentProperties
self.file = IfcStore.get_file()
ifcopenshell.api.run("document.remove_document", self.file, **{"document": self.file.by_id(self.document)})
Data.load(IfcStore.get_file())
if props.is_editing == "information":
bpy.ops.bim.load_information()
elif props.is_editing == "reference":
bpy.ops.bim.load_document_references()
return {"FINISHED"}
core.remove_document(tool.Ifc, tool.Document, document=tool.Ifc.get().by_id(self.document))
class EnableAssigningDocument(bpy.types.Operator):
bl_idname = "bim.enable_assigning_document"
bl_label = "Enable Assigning Document"
bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
def execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
props = obj.BIMObjectDocumentProperties
if props.available_document_types == "IfcDocumentInformation":
bpy.ops.bim.load_information()
elif props.available_document_types == "IfcDocumentReference":
bpy.ops.bim.load_document_references()
props.is_adding = props.available_document_types
return {"FINISHED"}
class DisableAssigningDocument(bpy.types.Operator):
bl_idname = "bim.disable_assigning_document"
bl_label = "Disable Assigning Document"
bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
def execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
props = obj.BIMObjectDocumentProperties
props.is_adding = ""
return {"FINISHED"}
class AssignDocument(bpy.types.Operator):
class AssignDocument(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.assign_document"
bl_label = "Assign Document"
bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
document: bpy.props.IntProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
self.file = IfcStore.get_file()
document = tool.Ifc.get().by_id(self.document)
objs = [bpy.data.objects.get(self.obj)] if self.obj else context.selected_objects
for obj in objs:
obj_id = obj.BIMObjectProperties.ifc_definition_id
if not obj_id:
continue
ifcopenshell.api.run(
"document.assign_document",
self.file,
**{
"product": self.file.by_id(obj_id),
"document": self.file.by_id(self.document),
}
)
Data.load(self.file, obj_id)
return {"FINISHED"}
element = tool.Ifc.get_entity(obj)
if element:
core.assign_document(tool.Ifc, product=element, document=document)
class UnassignDocument(bpy.types.Operator):
class UnassignDocument(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.unassign_document"
bl_label = "Unassign Document"
bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
document: bpy.props.IntProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
self.file = IfcStore.get_file()
document = tool.Ifc.get().by_id(self.document)
objs = [bpy.data.objects.get(self.obj)] if self.obj else context.selected_objects
for obj in objs:
obj_id = obj.BIMObjectProperties.ifc_definition_id
if not obj_id:
continue
ifcopenshell.api.run(
"document.unassign_document",
self.file,
**{
"product": self.file.by_id(obj_id),
"document": self.file.by_id(self.document),
}
)
Data.load(self.file, obj_id)
return {"FINISHED"}
element = tool.Ifc.get_entity(obj)
if element:
core.unassign_document(tool.Ifc, product=element, document=document)
@@ -34,6 +34,7 @@ from bpy.props import (
class Document(PropertyGroup):
name: StringProperty(name="Name")
identification: StringProperty(name="Identification")
is_information: BoolProperty(name="Is Information")
ifc_definition_id: IntProperty(name="IFC Definition ID")
@@ -41,12 +42,6 @@ class BIMDocumentProperties(PropertyGroup):
document_attributes: CollectionProperty(name="Document Attributes", type=Attribute)
active_document_id: IntProperty(name="Active Document Id")
documents: CollectionProperty(name="Documents", type=Document)
breadcrumbs: CollectionProperty(name="Breadcrumbs", type=StrProperty)
active_document_index: IntProperty(name="Active Document Index")
is_editing: StringProperty(name="Is Editing")
class BIMObjectDocumentProperties(PropertyGroup):
is_adding: StringProperty(name="Is Adding")
available_document_types: EnumProperty(
items=[(d, d, "") for d in ["IfcDocumentInformation", "IfcDocumentReference"]], name="Available Document Types"
)
is_editing: BoolProperty(name="Is Editing", default=False)
@@ -19,7 +19,7 @@
from bpy.types import Panel, UIList
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.helper import draw_attributes
from ifcopenshell.api.document.data import Data
from blenderbim.bim.module.document.data import DocumentData, ObjectDocumentData
class BIM_PT_documents(Panel):
@@ -29,45 +29,50 @@ class BIM_PT_documents(Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_project_setup"
@classmethod
def poll(cls, context):
return IfcStore.get_file()
def draw(self, context):
if not Data.is_loaded:
Data.load(IfcStore.get_file())
if not DocumentData.is_loaded:
DocumentData.load()
self.props = context.scene.BIMDocumentProperties
if not self.props.is_editing or self.props.is_editing == "information":
row = self.layout.row(align=True)
row.label(text="{} Documents Found".format(len(Data.information)), icon="FILE")
if self.props.is_editing == "information":
row.operator("bim.add_information", text="", icon="ADD")
row.operator("bim.disable_document_editing_ui", text="", icon="CANCEL")
else:
row.operator("bim.load_information", text="", icon="IMPORT")
if not self.props.is_editing or self.props.is_editing == "reference":
row = self.layout.row(align=True)
row.label(text="{} References Found".format(len(Data.references)), icon="FILE_HIDDEN")
if self.props.is_editing == "reference":
row.operator("bim.add_document_reference", text="", icon="ADD")
row.operator("bim.disable_document_editing_ui", text="", icon="CANCEL")
else:
row.operator("bim.load_document_references", text="", icon="IMPORT")
row = self.layout.row(align=True)
row.label(text="{} Documents Found".format(DocumentData.data["total_information"]), icon="FILE")
if self.props.is_editing:
self.layout.template_list(
"BIM_UL_documents", "", self.props, "documents", self.props, "active_document_index"
)
row.operator("bim.disable_document_editing_ui", text="", icon="CANCEL")
else:
row.operator("bim.load_project_documents", text="", icon="IMPORT")
if not self.props.is_editing:
return
row = self.layout.row(align=True)
if self.props.breadcrumbs:
row.operator("bim.load_parent_document", text="", icon="FRAME_PREV")
row.label(text=DocumentData.data["parent_document"])
else:
row.alignment = "RIGHT"
row.operator("bim.add_information", text="", icon="ADD")
if self.props.breadcrumbs:
row.operator("bim.add_document_reference", text="", icon="FILE_HIDDEN")
if self.props.active_document_id:
self.draw_editable_ui(context)
row.operator("bim.edit_document", text="", icon="CHECKMARK")
row.operator("bim.disable_editing_document", text="", icon="CANCEL")
elif self.props.documents and self.props.active_document_index < len(self.props.documents):
ifc_definition_id = self.props.documents[self.props.active_document_index].ifc_definition_id
row.operator("bim.enable_editing_document", text="", icon="GREASEPENCIL").document = ifc_definition_id
row.operator("bim.remove_document", text="", icon="X").document = ifc_definition_id
def draw_editable_ui(self, context):
draw_attributes(self.props.document_attributes, self.layout)
self.layout.template_list("BIM_UL_documents", "", self.props, "documents", self.props, "active_document_index")
if self.props.active_document_id:
draw_attributes(self.props.document_attributes, self.layout)
class BIM_PT_object_documents(Panel):
@@ -77,6 +82,7 @@ class BIM_PT_object_documents(Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
bl_parent_id = "BIM_PT_misc_object"
@classmethod
def poll(cls, context):
@@ -87,88 +93,64 @@ class BIM_PT_object_documents(Panel):
return bool(context.active_object.BIMObjectProperties.ifc_definition_id)
def draw(self, context):
if not ObjectDocumentData.is_loaded:
ObjectDocumentData.load()
obj = context.active_object
self.oprops = obj.BIMObjectProperties
self.sprops = context.scene.BIMDocumentProperties
self.props = obj.BIMObjectDocumentProperties
self.props = context.scene.BIMDocumentProperties
self.file = IfcStore.get_file()
if not Data.is_loaded:
Data.load(IfcStore.get_file())
if self.oprops.ifc_definition_id not in Data.products:
Data.load(IfcStore.get_file(), self.oprops.ifc_definition_id)
self.draw_add_ui()
document_ids = Data.products[self.oprops.ifc_definition_id]
if not document_ids:
if not ObjectDocumentData.data["documents"]:
row = self.layout.row(align=True)
row.label(text="No Documents", icon="FILE")
for document_id in document_ids:
try:
document = Data.information[document_id]
document_type = "IfcDocumentInformation"
icon = "FILE"
except:
document = Data.references[document_id]
document_type = "IfcDocumentReference"
icon = "FILE_HIDDEN"
for document in ObjectDocumentData.data["documents"]:
row = self.layout.row(align=True)
if self.file.schema == "IFC2X3":
if document_type == "IfcDocumentInformation":
row.label(text=document.get("DocumentId") or "*", icon=icon)
elif document_type == "IfcDocumentReference":
row.label(text=document.get("ItemReference") or "*", icon=icon)
else:
row.label(text=document.get("Identification") or "*", icon=icon)
row.label(text=document.get("Name") or "Unnamed")
row.operator("bim.unassign_document", text="", icon="X").document = document_id
row.label(text=document["identification"] or "*", icon="FILE")
row.label(text=document["name"] or "Unnamed")
if document["location"]:
row.operator("bim.open_uri", icon="URL", text="").uri = document["location"]
row.operator("bim.unassign_document", text="", icon="X").document = document["id"]
def draw_add_ui(self):
if self.props.is_adding:
if not self.props.is_editing:
row = self.layout.row(align=True)
icon = "FILE" if self.props.is_adding == "IfcDocumentInformation" else "FILE_HIDDEN"
row.label(text="Adding {}".format(self.props.is_adding), icon=icon)
row.operator("bim.disable_assigning_document", text="", icon="CANCEL")
self.layout.template_list(
"BIM_UL_object_documents",
"",
self.sprops,
"documents",
self.sprops,
"active_document_index",
)
row.operator("bim.load_project_documents", text="Assign Document References", icon="ADD")
return
row = self.layout.row(align=True)
if self.props.breadcrumbs:
row.operator("bim.load_parent_document", text="", icon="FRAME_PREV")
row.label(text=DocumentData.data["parent_document"])
else:
row = self.layout.row(align=True)
row.prop(self.props, "available_document_types", text="")
row.operator("bim.enable_assigning_document", text="", icon="ADD")
row.alignment = "RIGHT"
if self.props.documents and self.props.active_document_index < len(self.props.documents):
document = self.props.documents[self.props.active_document_index]
if not document.is_information:
row.operator("bim.assign_document", text="", icon="ADD").document = document.ifc_definition_id
row.operator("bim.disable_document_editing_ui", text="", icon="CANCEL")
self.layout.template_list("BIM_UL_documents", "", self.props, "documents", self.props, "active_document_index")
class BIM_UL_documents(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
if item:
row = layout.row(align=True)
row.label(text=item.identification)
row.label(text=item.name)
if context.scene.BIMDocumentProperties.active_document_id == item.ifc_definition_id:
if context.scene.BIMDocumentProperties.is_editing == "information":
row.operator("bim.edit_information", text="", icon="CHECKMARK")
elif context.scene.BIMDocumentProperties.is_editing == "reference":
row.operator("bim.edit_document_reference", text="", icon="CHECKMARK")
row.operator("bim.disable_editing_document", text="", icon="CANCEL")
elif context.scene.BIMDocumentProperties.active_document_id:
row.operator("bim.remove_document", text="", icon="X").document = item.ifc_definition_id
else:
op = row.operator("bim.enable_editing_document", text="", icon="GREASEPENCIL")
if item.is_information:
op = row.operator("bim.load_document", text="", emboss=False, icon="DISCLOSURE_TRI_RIGHT")
op.document = item.ifc_definition_id
row.operator("bim.remove_document", text="", icon="X").document = item.ifc_definition_id
row.label(text="", icon="FILE")
else:
row.label(text="", icon="BLANK1")
row.label(text="", icon="FILE_HIDDEN")
class BIM_UL_object_documents(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
if item:
row = layout.row(align=True)
row.label(text=item.identification)
row.label(text=item.name)
row.operator("bim.assign_document", text="", icon="ADD").document = item.ifc_definition_id
split1 = row.split(factor=0.1)
split1.label(text=item.identification)
split2 = split1.split(factor=0.9)
split2.label(text=item.name)
@@ -20,40 +20,48 @@ import bpy
from . import ui, prop, operator, handler, gizmos
classes = (
operator.AddDrawing,
operator.CreateDrawing,
operator.AddAnnotation,
operator.AddSheet,
operator.OpenSheet,
operator.AddDrawingToSheet,
operator.CreateSheets,
operator.OpenView,
operator.OpenViewCamera,
operator.ActivateView,
operator.SelectDocIfcFile,
operator.GenerateReferences,
operator.ResizeText,
operator.AddVariable,
operator.RemoveVariable,
operator.PropagateTextData,
operator.RemoveDrawing,
operator.AddDrawingStyle,
operator.RemoveDrawingStyle,
operator.SaveDrawingStyle,
operator.ActivateDrawingStyle,
operator.EditVectorStyle,
operator.RemoveSheet,
operator.AddSchedule,
operator.RemoveSchedule,
operator.SelectScheduleFile,
operator.BuildSchedule,
operator.AddScheduleToSheet,
operator.ActivateView,
operator.AddAnnotation,
operator.AddDrawing,
operator.AddDrawingStyle,
operator.AddDrawingStyleAttribute,
operator.RemoveDrawingStyleAttribute,
operator.RefreshDrawingList,
operator.CleanWireframes,
operator.CopyGrid,
operator.AddDrawingToSheet,
operator.AddSchedule,
operator.AddScheduleToSheet,
operator.AddSectionsAnnotations,
operator.AddSheet,
operator.BuildSchedule,
operator.CleanWireframes,
operator.ContractSheet,
operator.CopyGrid,
operator.CreateDrawing,
operator.CreateSheets,
operator.DisableEditingDrawings,
operator.DisableEditingSheets,
operator.DisableEditingText,
operator.DisableEditingTextProduct,
operator.EditText,
operator.EditTextProduct,
operator.EditVectorStyle,
operator.EnableEditingText,
operator.EnableEditingTextProduct,
operator.ExpandSheet,
operator.GenerateReferences,
operator.LoadDrawings,
operator.LoadSheets,
operator.OpenSheet,
operator.OpenView,
operator.RemoveDrawing,
operator.RemoveDrawingFromSheet,
operator.RemoveDrawingStyle,
operator.RemoveDrawingStyleAttribute,
operator.RemoveSchedule,
operator.RemoveSheet,
operator.ResizeText,
operator.SaveDrawingStyle,
operator.SelectDocIfcFile,
operator.SelectScheduleFile,
prop.Variable,
prop.Drawing,
prop.Schedule,
@@ -64,12 +72,13 @@ classes = (
prop.BIMTextProperties,
ui.BIM_PT_camera,
ui.BIM_PT_drawing_underlay,
ui.BIM_PT_annotation_utilities,
ui.BIM_PT_sheets,
ui.BIM_PT_drawings,
ui.BIM_PT_schedules,
ui.BIM_PT_sheets,
ui.BIM_PT_text,
ui.BIM_PT_annotation_utilities,
ui.BIM_UL_drawinglist,
ui.BIM_UL_sheets,
gizmos.UglyDotGizmo,
gizmos.DotGizmo,
gizmos.DimensionLabelGizmo,
@@ -81,6 +90,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
@@ -34,13 +35,13 @@ class Annotator:
return float(sizes[str(size)])
@staticmethod
def add_text(context, related_element=None):
def add_text(related_element=None):
curve = bpy.data.curves.new(type="FONT", name="Text")
curve.body = "TEXT"
obj = bpy.data.objects.new("Text", curve)
obj.matrix_world = context.scene.camera.matrix_world
obj.matrix_world = bpy.context.scene.camera.matrix_world
if related_element is None:
location, _, _, _ = Annotator.get_placeholder_coords(context)
location, _, _, _ = Annotator.get_placeholder_coords(bpy.context)
else:
obj.data.BIMTextProperties.related_element = related_element
location = related_element.location
@@ -49,12 +50,12 @@ class Annotator:
font = bpy.data.fonts.get("OpenGost TypeB TT")
if not font:
font = bpy.data.fonts.load(
os.path.join(context.scene.BIMProperties.data_dir, "fonts", "OpenGost Type B TT.ttf")
os.path.join(bpy.context.scene.BIMProperties.data_dir, "fonts", "OpenGost Type B TT.ttf")
)
font.name = "OpenGost Type B TT"
obj.data.font = font
obj.data.BIMTextProperties.font_size = "2.5"
collection = context.scene.camera.users_collection[0]
collection = bpy.context.scene.camera.users_collection[0]
collection.objects.link(obj)
Annotator.resize_text(obj)
return obj
@@ -82,9 +83,9 @@ class Annotator:
text_obj.data.size = font_size
@staticmethod
def add_line_to_annotation(obj, context, co1=None, co2=None):
def add_line_to_annotation(obj, co1=None, co2=None):
if co1 is None:
co1, co2, _, _ = Annotator.get_placeholder_coords(context)
co1, co2, _, _ = Annotator.get_placeholder_coords()
co1 = obj.matrix_world.inverted() @ co1
co2 = obj.matrix_world.inverted() @ co2
if isinstance(obj.data, bpy.types.Mesh):
@@ -101,8 +102,8 @@ class Annotator:
return obj
@staticmethod
def add_plane_to_annotation(obj, context):
co1, co2, co3, co4 = Annotator.get_placeholder_coords(context)
def add_plane_to_annotation(obj):
co1, co2, co3, co4 = Annotator.get_placeholder_coords()
co1 = obj.matrix_world.inverted() @ co1 # bot left
co2 = obj.matrix_world.inverted() @ co2 # top left
co3 = obj.matrix_world.inverted() @ co3 # bot right
@@ -150,27 +151,43 @@ class Annotator:
return obj
@staticmethod
def get_annotation_obj(name, data_type, context):
collection = context.scene.camera.users_collection[0]
for obj in collection.objects:
if name in obj.name:
return obj
if data_type == "mesh":
data = bpy.data.meshes.new(name)
elif data_type == "curve":
data = bpy.data.curves.new(name, type="CURVE")
def get_annotation_obj(object_type, data_type):
collection = bpy.context.scene.camera.users_collection[0]
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
@staticmethod
def get_placeholder_coords(context):
camera = context.scene.camera
def get_placeholder_coords():
camera = bpy.context.scene.camera
z_offset = camera.matrix_world.to_quaternion() @ Vector((0, 0, -1))
if context.scene.render.resolution_x > context.scene.render.resolution_y:
y = camera.data.ortho_scale * (context.scene.render.resolution_y / context.scene.render.resolution_x) / 4
if bpy.context.scene.render.resolution_x > bpy.context.scene.render.resolution_y:
y = (
camera.data.ortho_scale
* (bpy.context.scene.render.resolution_y / bpy.context.scene.render.resolution_x)
/ 4
)
else:
y = camera.data.ortho_scale / 4
y_offset = camera.matrix_world.to_quaternion() @ Vector((0, y, 0))
@@ -0,0 +1,96 @@
# 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
SheetsData.is_loaded = False
DrawingsData.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"
class SheetsData:
data = {}
is_loaded = False
@classmethod
def load(cls):
cls.data = {"total_sheets": cls.total_sheets()}
cls.is_loaded = True
@classmethod
def total_sheets(cls):
return len([d for d in tool.Ifc.get().by_type("IfcDocumentInformation") if d.Scope == "DOCUMENTATION"])
class DrawingsData:
data = {}
is_loaded = False
@classmethod
def load(cls):
cls.data = {"total_drawings": cls.total_drawings(), "location_hint": cls.location_hint()}
cls.is_loaded = True
@classmethod
def total_drawings(cls):
return len([e for e in tool.Ifc.get().by_type("IfcAnnotation") if e.ObjectType == "DRAWING"])
@classmethod
def location_hint(cls):
if bpy.context.scene.DocProperties.target_view in ["PLAN_VIEW", "REFLECTED_PLAN_VIEW"]:
results = [("0", "Origin", "")]
results.extend(
[(str(s.id()), s.Name or "Unnamed", "") for s in tool.Ifc.get().by_type("IfcBuildingStorey")]
)
return results
return [(h.upper(), h, "") for h in ["North", "South", "East", "West"]]
@@ -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 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;
@@ -1044,30 +1125,33 @@ class GridDecorator(BaseDecorator):
self.draw_label(context, text, p1, dir, vcenter=True, gap=0)
class SectionViewDecorator(LevelDecorator):
basename = "IfcAnnotation/Section"
class ElevationDecorator(LevelDecorator):
objecttype = "ELEVATION"
DEF_GLSL = (
BaseDecorator.DEF_GLSL
+ """
#define CIRCLE_SIZE 8.0
#define TRIANGLE_L 32.0
#define TRIANGLE_W 16.0
#define TRIANGLE_L 22.63
#define TRIANGLE_W 11.31
"""
)
GEOM_GLSL = """
uniform vec2 winsize;
uniform float viewportDrawingScale;
layout(lines) in;
layout(line_strip, max_vertices=MAX_POINTS) out;
void triangle_head(in vec4 dir, in vec4 side, in float length, in float width, out vec4 head[3]) {
vec4 nose = dir * length;
vec4 ear = side * width;
head[0] = vec4(0);
head[1] = nose * .5 + ear;
head[2] = nose;
void triangle_head(in vec4 side, in vec4 dir, in float length, in float width, in float radius, out vec4 head[5]) {
vec4 nose = side * length;
vec4 ear = dir * width;
head[0] = side * -radius;
head[1] = nose * -.5;
head[2] = vec4(0) + ear;
head[3] = nose * .5;
head[4] = side * radius;
}
void main() {
@@ -1078,75 +1162,290 @@ class SectionViewDecorator(LevelDecorator):
vec4 p0w = CLIP2WIN(p0), p1w = CLIP2WIN(p1);
vec4 edge = p1w - p0w, dir = normalize(edge);
vec4 gap = dir * 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];
vec4 head5[5];
// start edge circle
for(int i=0; i<CIRCLE_SEGS; i++) {
p = p0w + gap + head[i];
p = p0w + head[i];
gl_Position = WIN2CLIP(p);
EmitVertex();
}
p = p0w + gap + head[0];
p = p0w + head[0];
gl_Position = WIN2CLIP(p);
EmitVertex();
EndPrimitive();
// start edge triangle
triangle_head(dir, -side, TRIANGLE_L, TRIANGLE_W, head3);
p = p0w + head3[0];
// edge triangle
triangle_head(side, dir, viewportDrawingScale * TRIANGLE_L, viewportDrawingScale * TRIANGLE_W, viewportDrawingScale * CIRCLE_SIZE, head5);
p = p0w + head5[0];
gl_Position = WIN2CLIP(p);
EmitVertex();
p = p0w + head3[1];
p = p0w + head5[1];
gl_Position = WIN2CLIP(p);
EmitVertex();
p = p0w + head3[2];
p = p0w + head5[2];
gl_Position = WIN2CLIP(p);
EmitVertex();
p = p0w + head5[3];
gl_Position = WIN2CLIP(p);
EmitVertex();
p = p0w + head5[4];
gl_Position = WIN2CLIP(p);
EmitVertex();
EndPrimitive();
// end edge circle
for(int i=0; i<CIRCLE_SEGS; i++) {
p = p1w - gap + head[i];
gl_Position = WIN2CLIP(p);
EmitVertex();
}
p = p1w - gap + head[0];
// reference divider
p = p0w + normalize(vec4(-1, 0, 0, 0)) * viewportDrawingScale * CIRCLE_SIZE;
gl_Position = WIN2CLIP(p);
EmitVertex();
EndPrimitive();
// end edge triangle
triangle_head(-dir, -side, TRIANGLE_L, TRIANGLE_W, head3);
p = p1w + head3[0];
p = p0w + normalize(vec4(1, 0, 0, 0)) * viewportDrawingScale * CIRCLE_SIZE;
gl_Position = WIN2CLIP(p);
EmitVertex();
p = p1w + head3[1];
gl_Position = WIN2CLIP(p);
EmitVertex();
p = p1w + head3[2];
gl_Position = WIN2CLIP(p);
EmitVertex();
EndPrimitive();
// stem
gl_Position = p0;
EmitVertex();
gl_Position = p1;
EmitVertex();
EndPrimitive();
}
"""
def decorate(self, context, obj):
verts, _, _ = self.get_path_geom(obj, topo=False)
self.draw_lines(context, obj, verts, [(0, 1)])
center = obj.matrix_world.translation
v1 = obj.matrix_world @ Vector((0, 0, 0))
v2 = obj.matrix_world @ Vector((0, 0, -1))
self.draw_lines(context, obj, [v1, v2], [(0, 1)])
class SectionDecorator(LevelDecorator):
objecttype = "SECTION"
DEF_GLSL = (
BaseDecorator.DEF_GLSL
+ """
#define CIRCLE_SIZE 8.0
#define TRIANGLE_L 22.63
#define TRIANGLE_W 11.31
"""
)
GEOM_GLSL = """
uniform vec2 winsize;
uniform float viewportDrawingScale;
layout(lines) in;
layout(line_strip, max_vertices=MAX_POINTS) out;
void triangle_head(in vec4 side, in vec4 dir, in float length, in float width, in float radius, out vec4 head[5]) {
vec4 nose = side * length;
vec4 ear = dir * width;
head[0] = side * -radius;
head[1] = nose * -.5;
head[2] = vec4(0) + ear;
head[3] = nose * .5;
head[4] = side * radius;
}
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, side = normalize(edge);
vec4 gap = side * viewportDrawingScale * CIRCLE_SIZE;
vec4 dir = vec4(cross(vec3(side.xy, 0), vec3(0, 0, 1)).xy, 0, 0);
vec4 p;
vec4 head[CIRCLE_SEGS];
circle_head(viewportDrawingScale * CIRCLE_SIZE, head);
vec4 head5[5];
// start edge circle
for(int i=0; i<CIRCLE_SEGS; i++) {
p = p0w + head[i];
gl_Position = WIN2CLIP(p);
EmitVertex();
}
p = p0w + head[0];
gl_Position = WIN2CLIP(p);
EmitVertex();
EndPrimitive();
// start edge triangle
triangle_head(side, dir, viewportDrawingScale * TRIANGLE_L, viewportDrawingScale * TRIANGLE_W, viewportDrawingScale * CIRCLE_SIZE, head5);
p = p0w + head5[0];
gl_Position = WIN2CLIP(p);
EmitVertex();
p = p0w + head5[1];
gl_Position = WIN2CLIP(p);
EmitVertex();
p = p0w + head5[2];
gl_Position = WIN2CLIP(p);
EmitVertex();
p = p0w + head5[3];
gl_Position = WIN2CLIP(p);
EmitVertex();
p = p0w + head5[4];
gl_Position = WIN2CLIP(p);
EmitVertex();
EndPrimitive();
// start reference divider
p = p0w + normalize(vec4(-1, 0, 0, 0)) * viewportDrawingScale * CIRCLE_SIZE;
gl_Position = WIN2CLIP(p);
EmitVertex();
p = p0w + normalize(vec4(1, 0, 0, 0)) * viewportDrawingScale * CIRCLE_SIZE;
gl_Position = WIN2CLIP(p);
EmitVertex();
EndPrimitive();
// end edge circle
for(int i=0; i<CIRCLE_SEGS; i++) {
p = p1w - head[i];
gl_Position = WIN2CLIP(p);
EmitVertex();
}
p = p1w - head[0];
gl_Position = WIN2CLIP(p);
EmitVertex();
EndPrimitive();
// end edge triangle
triangle_head(side, dir, viewportDrawingScale * TRIANGLE_L, viewportDrawingScale * TRIANGLE_W, viewportDrawingScale * CIRCLE_SIZE, head5);
p = p1w + head5[0];
gl_Position = WIN2CLIP(p);
EmitVertex();
p = p1w + head5[1];
gl_Position = WIN2CLIP(p);
EmitVertex();
p = p1w + head5[2];
gl_Position = WIN2CLIP(p);
EmitVertex();
p = p1w + head5[3];
gl_Position = WIN2CLIP(p);
EmitVertex();
p = p1w + head5[4];
gl_Position = WIN2CLIP(p);
EmitVertex();
EndPrimitive();
// end reference divider
p = p1w + normalize(vec4(-1, 0, 0, 0)) * viewportDrawingScale * CIRCLE_SIZE;
gl_Position = WIN2CLIP(p);
EmitVertex();
p = p1w + normalize(vec4(1, 0, 0, 0)) * viewportDrawingScale * CIRCLE_SIZE;
gl_Position = WIN2CLIP(p);
EmitVertex();
EndPrimitive();
// stem
p = p0w + gap;
gl_Position = WIN2CLIP(p);
EmitVertex();
p = p1w - gap;
gl_Position = WIN2CLIP(p);
EmitVertex();
EndPrimitive();
}
"""
def decorate(self, context, obj):
if obj.data.is_editmode:
verts, idxs = self.get_editmesh_geom(obj)
else:
verts, idxs = self.get_mesh_geom(obj)
vert_map = {v.freeze(): (obj.matrix_world.inverted() @ v).x for v in verts}
verts = sorted(verts, key=lambda v: vert_map[v], reverse=True)
self.draw_lines(context, obj, verts, idxs)
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:
@@ -1161,7 +1460,9 @@ class DecorationsHandler:
SectionLevelDecorator,
StairDecorator,
BreakDecorator,
SectionViewDecorator,
SectionDecorator,
ElevationDecorator,
TextDecorator,
]
installed = None
@@ -45,6 +45,3 @@ def set_active_camera_resolution(scene):
scene.render.resolution_x = scene.camera.data.BIMCameraProperties.raster_x
scene.render.resolution_y = scene.camera.data.BIMCameraProperties.raster_y
current_drawing = scene.DocProperties.drawings[scene.DocProperties.current_drawing_index]
if scene.camera != current_drawing.camera:
scene.DocProperties.current_drawing_index = scene.DocProperties.drawings.find(scene.camera.name.split("/")[1])
bpy.ops.bim.activate_view(drawing_index=scene.DocProperties.current_drawing_index)
@@ -19,6 +19,7 @@
import bpy
import math
import mathutils.geometry
import blenderbim.tool as tool
from mathutils import Vector
# Code taken and updated from https://blenderartists.org/t/detecting-intersection-of-bounding-boxes/457520/2
@@ -259,13 +260,11 @@ def format_distance(value, isArea=False, hide_units=True):
def get_active_drawing(scene):
"""Get active drawing collection and camera"""
props = scene.DocProperties
if props.active_drawing_index is None or len(props.drawings) == 0:
return None, None
try:
drawing = props.active_drawing
return scene.collection.children["Views"].children[f"IfcGroup/{drawing.name}"], drawing.camera
except (KeyError, IndexError):
raise RuntimeError("missing drawing collection")
camera = tool.Ifc.get_object(tool.Ifc.get().by_id(props.active_drawing_id))
return camera.users_collection[0], camera
except:
return None, None
def get_project_collection(scene):
File diff suppressed because it is too large Load Diff
@@ -18,11 +18,14 @@
import os
import bpy
import json
import enum
import ifcopenshell
import blenderbim.tool as tool
import blenderbim.core.drawing as core
import blenderbim.bim.module.drawing.annotation as annotation
import blenderbim.bim.module.drawing.decoration as decoration
import enum
from blenderbim.bim.module.drawing.data import DrawingsData
from pathlib import Path
from blenderbim.bim.prop import Attribute, StrProperty
from bpy.types import PropertyGroup
@@ -55,12 +58,24 @@ def purge():
vector_styles_enum = []
def update_target_view(self, context):
DrawingsData.data["location_hint"] = DrawingsData.location_hint()
def get_location_hint(self, context):
if not DrawingsData.is_loaded:
DrawingsData.load()
return DrawingsData.data["location_hint"]
def update_diagram_scale(self, context):
scale = self.diagram_scale
if scale == "CUSTOM":
scale = self.custom_diagram_scale
scale = scale.split("|")[1]
element = tool.Ifc.get_entity(context.scene.camera)
if "|" not in scale:
return
human_scale, scale = scale.split("|")
element = tool.Ifc.get_entity(context.active_object)
if not element:
return
pset = ifcopenshell.util.element.get_psets(element).get("EPset_Drawing")
@@ -68,7 +83,9 @@ def update_diagram_scale(self, context):
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})
ifcopenshell.api.run(
"pset.edit_pset", tool.Ifc.get(), pset=pset, properties={"Scale": scale, "HumanScale": human_scale}
)
def get_diagram_scales(self, context):
@@ -131,31 +148,42 @@ def get_diagram_scales(self, context):
return diagram_scales_enum
def updateDrawingName(self, context):
if not self.camera:
return
if self.camera.name == self.name:
return
self.camera.name = "IfcAnnotation/{}".format(self.name)
unique_name = "/".join(self.camera.name.split("/")[1:])
self.camera.users_collection[0].name = "IfcGroup/{}".format(unique_name)
if self.name != unique_name:
self.name = unique_name
def update_drawing_name(self, context):
drawing = tool.Ifc.get().by_id(self.ifc_definition_id)
core.update_drawing_name(tool.Ifc, tool.Drawing, drawing=drawing, name=self.name)
def refreshActiveDrawingIndex(self, context):
bpy.ops.bim.activate_view(drawing_index=context.scene.DocProperties.active_drawing_index)
def update_has_underlay(self, context):
update_layer(self, context, "HasUnderlay", self.has_underlay)
def update_has_linework(self, context):
update_layer(self, context, "HasLinework", self.has_linework)
def update_has_annotation(self, context):
update_layer(self, context, "HasAnnotation", self.has_annotation)
def update_layer(self, context, name, value):
element = tool.Ifc.get_entity(context.active_object)
if not element:
return
pset = ifcopenshell.util.element.get_psets(element).get("EPset_Drawing")
if pset:
pset = tool.Ifc.get().by_id(pset["id"])
else:
pset = ifcopenshell.api.run("pset.add_pset", tool.Ifc.get(), product=element, name="EPset_Drawing")
ifcopenshell.api.run("pset.edit_pset", tool.Ifc.get(), pset=pset, properties={name: value})
def getTitleblocks(self, context):
global titleblocks_enum
if len(titleblocks_enum) < 1:
titleblocks_enum.clear()
for filename in Path(os.path.join(context.scene.BIMProperties.data_dir, "templates", "titleblocks")).glob(
"*.svg"
):
f = str(filename.stem)
titleblocks_enum.append((f, f, ""))
files = Path(os.path.join(context.scene.BIMProperties.data_dir, "templates", "titleblocks")).glob("*.svg")
files = sorted([str(f.stem) for f in files])
titleblocks_enum.extend([(f, f, "") for f in files])
return titleblocks_enum
@@ -168,6 +196,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()
@@ -193,8 +225,9 @@ class Variable(PropertyGroup):
class Drawing(PropertyGroup):
name: StringProperty(name="Name", update=updateDrawingName)
camera: PointerProperty(name="Camera", type=bpy.types.Object)
ifc_definition_id: IntProperty(name="IFC Definition ID")
name: StringProperty(name="Name", update=update_drawing_name)
target_view: StringProperty(name="Target View")
class Schedule(PropertyGroup):
@@ -213,9 +246,12 @@ class Sheet(PropertyGroup):
def get_name(self):
return self.get("name")
ifc_definition_id: IntProperty(name="IFC Definition ID")
identification: StringProperty(name="Identification")
name: StringProperty(name="Name", get=get_name, set=set_name)
drawings: CollectionProperty(name="Drawings", type=Drawing)
active_drawing_index: IntProperty(name="Active Drawing Index")
is_sheet: BoolProperty(name="Is Sheet", default=False)
reference_type: StringProperty(name="Reference Type")
is_expanded: BoolProperty(name="Is Expanded", default=False)
class DrawingStyle(PropertyGroup):
@@ -264,54 +300,52 @@ class RasterStyleProperty(enum.Enum):
class DocProperties(PropertyGroup):
has_underlay: BoolProperty(name="Underlay", default=False)
has_linework: BoolProperty(name="Linework", default=True)
has_annotation: BoolProperty(name="Annotation", default=True)
should_use_underlay_cache: BoolProperty(name="Use Underlay Cache", default=False)
should_use_linework_cache: BoolProperty(name="Use Linework Cache", default=False)
should_use_annotation_cache: BoolProperty(name="Use Annotation Cache", default=False)
should_extract: BoolProperty(name="Should Extract", default=True)
is_editing_drawings: BoolProperty(name="Is Editing Drawings", default=False)
target_view: EnumProperty(
items=[
("PLAN_VIEW", "Plan", ""),
("ELEVATION_VIEW", "Elevation", ""),
("SECTION_VIEW", "Section", ""),
("REFLECTED_PLAN_VIEW", "RCP", ""),
("MODEL_VIEW", "Model", ""),
],
name="Target View",
default="PLAN_VIEW",
update=update_target_view,
)
location_hint: EnumProperty(items=get_location_hint, name="Location Hint")
drawings: CollectionProperty(name="Drawings", type=Drawing)
active_drawing_index: IntProperty(name="Active Drawing Index", update=refreshActiveDrawingIndex)
active_drawing_id: IntProperty(name="Active Drawing Id")
active_drawing_index: IntProperty(name="Active Drawing Index")
current_drawing_index: IntProperty(name="Current Drawing Index")
schedules: CollectionProperty(name="Schedules", type=Schedule)
active_schedule_index: IntProperty(name="Active Schedule Index")
titleblock: EnumProperty(items=getTitleblocks, name="Titleblock", update=refreshTitleblocks)
is_editing_sheets: BoolProperty(name="Is Editing Sheets", default=False)
sheets: CollectionProperty(name="Sheets", type=Sheet)
active_sheet_index: IntProperty(name="Active Sheet Index")
ifc_files: CollectionProperty(name="IFCs", type=StrProperty)
drawing_styles: CollectionProperty(name="Drawing Styles", type=DrawingStyle)
should_draw_decorations: BoolProperty(name="Should Draw Decorations", update=toggleDecorations)
decorations_colour: FloatVectorProperty(
name="Decorations Colour", subtype="COLOR", default=(1, 0, 0, 1), min=0.0, max=1.0, size=4
name="Decorations Colour", subtype="COLOR", default=(1, 1, 1, 1), min=0.0, max=1.0, size=4
)
@property
def active_schedule(self):
return self.schedules[self.active_schedule_index]
@property
def active_drawing(self):
return self.drawings[self.active_drawing_index]
@property
def active_sheet(self):
return self.sheets[self.active_sheet_index]
class BIMCameraProperties(PropertyGroup):
has_underlay: BoolProperty(name="Underlay", default=False, update=update_has_underlay)
has_linework: BoolProperty(name="Linework", default=True, update=update_has_linework)
has_annotation: BoolProperty(name="Annotation", default=True, update=update_has_annotation)
representation: StringProperty(name="Representation")
view_name: StringProperty(name="View Name")
target_view: EnumProperty(
items=[
("PLAN_VIEW", "PLAN_VIEW", ""),
("ELEVATION_VIEW", "ELEVATION_VIEW", ""),
("SECTION_VIEW", "SECTION_VIEW", ""),
("REFLECTED_PLAN_VIEW", "REFLECTED_PLAN_VIEW", ""),
("MODEL_VIEW", "MODEL_VIEW", ""),
],
name="Target View",
default="PLAN_VIEW",
)
diagram_scale: EnumProperty(items=get_diagram_scales, name="Drawing Scale", update=update_diagram_scale)
custom_diagram_scale: StringProperty(name="Custom Scale", update=update_diagram_scale)
raster_x: IntProperty(name="Raster X", default=1000)
@@ -332,8 +366,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", ""),
@@ -342,6 +397,7 @@ class BIMTextProperties(PropertyGroup):
("5.0", "5.0 - Header", ""),
("7.0", "7.0 - Title", ""),
],
default="2.5",
update=refreshFontSize,
name="Font Size",
)
@@ -354,5 +410,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)
@@ -16,11 +16,12 @@
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import xml.etree.ElementTree as ET
import urllib.parse
import pystache
import ntpath
import os
import ntpath
import pystache
import urllib.parse
import xml.etree.ElementTree as ET
import blenderbim.tool as tool
from shutil import copy
from xml.dom import minidom
@@ -31,7 +32,7 @@ class SheetBuilder:
self.scale = "NTS"
def create(self, name, titleblock_name):
sheet_path = os.path.join(self.data_dir, f"{name}.svg")
sheet_path = os.path.join(self.data_dir, "sheets", f"{name}.svg")
root = ET.Element("svg")
root.attrib["xmlns"] = "http://www.w3.org/2000/svg"
root.attrib["xmlns:xlink"] = "http://www.w3.org/1999/xlink"
@@ -59,11 +60,16 @@ class SheetBuilder:
with open(sheet_path, "w") as f:
f.write(minidom.parseString(ET.tostring(root)).toprettyxml(indent=" "))
def add_drawing(self, view_name, sheet_name):
sheet_path = os.path.join(self.data_dir, "sheets", sheet_name + ".svg")
view_path = os.path.join(self.data_dir, "diagrams", view_name + ".svg")
def add_drawing(self, drawing, sheet):
filename = drawing.Name
sheet_name = tool.Drawing.get_sheet_filename(sheet)
sheet_dir = os.path.join(self.data_dir, "sheets")
drawing_dir = os.path.join(self.data_dir, "diagrams")
sheet_path = os.path.join(sheet_dir, sheet_name + ".svg")
drawing_path = os.path.join(drawing_dir, filename + ".svg")
underlay_path = os.path.join(drawing_dir, filename + "-underlay.png")
if not os.path.isfile(view_path):
if not os.path.isfile(sheet_path):
raise FileNotFoundError
ET.register_namespace("", "http://www.w3.org/2000/svg")
@@ -72,7 +78,7 @@ class SheetBuilder:
sheet_tree = ET.parse(sheet_path)
sheet_root = sheet_tree.getroot()
view_tree = ET.parse(view_path)
view_tree = ET.parse(drawing_path)
view_root = view_tree.getroot()
# The view is placed into a group with a background image element.
@@ -80,26 +86,48 @@ class SheetBuilder:
# here to accommodate browsers which do not nest images.
view = ET.SubElement(sheet_root, "g")
view.attrib["data-type"] = "drawing"
view.attrib["data-guid"] = drawing.GlobalId
view_width = self.convert_to_mm(view_root.attrib.get("width"))
view_height = self.convert_to_mm(view_root.attrib.get("height"))
background = ET.SubElement(view, "image")
background.attrib["xlink:href"] = "../diagrams/{}.png".format(view_name)
background.attrib["x"] = "30"
background.attrib["y"] = "30"
background.attrib["width"] = str(view_width)
background.attrib["height"] = str(view_height)
if os.path.isfile(underlay_path):
background = ET.SubElement(view, "image")
background.attrib["data-type"] = "background"
background.attrib["xlink:href"] = os.path.relpath(underlay_path, sheet_dir)
background.attrib["x"] = "30"
background.attrib["y"] = "30"
background.attrib["width"] = str(view_width)
background.attrib["height"] = str(view_height)
foreground = ET.SubElement(view, "image")
foreground.attrib["xlink:href"] = "../diagrams/{}.svg".format(view_name)
foreground.attrib["x"] = "30"
foreground.attrib["y"] = "30"
foreground.attrib["width"] = str(view_width)
foreground.attrib["height"] = str(view_height)
if os.path.isfile(drawing_path):
foreground = ET.SubElement(view, "image")
foreground.attrib["data-type"] = "foreground"
foreground.attrib["xlink:href"] = os.path.relpath(drawing_path, sheet_dir)
foreground.attrib["x"] = "30"
foreground.attrib["y"] = "30"
foreground.attrib["width"] = str(view_width)
foreground.attrib["height"] = str(view_height)
self.add_view_title(30, view_height + 35, view)
sheet_tree.write(sheet_path)
def remove_drawing(self, drawing, sheet):
sheet_name = tool.Drawing.get_sheet_filename(sheet)
sheet_dir = os.path.join(self.data_dir, "sheets")
sheet_path = os.path.join(sheet_dir, sheet_name + ".svg")
ET.register_namespace("", "http://www.w3.org/2000/svg")
sheet_tree = ET.parse(sheet_path)
sheet_root = sheet_tree.getroot()
for g in sheet_root.findall("{http://www.w3.org/2000/svg}g"):
if g.attrib["data-type"] == "drawing" and g.attrib["data-guid"] == drawing.GlobalId:
sheet_root.remove(g)
break
sheet_tree.write(sheet_path)
def add_schedule(self, schedule_name, sheet_name):
sheet_path = os.path.join(self.data_dir, "sheets", sheet_name + ".svg")
view_path = os.path.join(self.data_dir, "schedules", schedule_name + ".svg")
@@ -132,13 +160,15 @@ class SheetBuilder:
title_tree = ET.parse(os.path.join(self.data_dir, "templates", "view-title.svg"))
title_root = title_tree.getroot()
title = ET.SubElement(parent, "image")
title.attrib["data-type"] = "view-title"
title.attrib["xlink:href"] = "../templates/view-title.svg"
title.attrib["x"] = str(x)
title.attrib["y"] = str(y)
title.attrib["width"] = str(self.convert_to_mm(title_root.attrib.get("width")))
title.attrib["height"] = str(self.convert_to_mm(title_root.attrib.get("height")))
def build(self, sheet_name):
def build(self, sheet):
sheet_name = tool.Drawing.get_sheet_filename(sheet)
os.makedirs(os.path.join(self.data_dir, "build", sheet_name), exist_ok=True)
sheet_path = os.path.join(self.data_dir, "sheets", f"{sheet_name}.svg")
@@ -149,48 +179,60 @@ class SheetBuilder:
tree = ET.parse(sheet_path)
root = tree.getroot()
titleblock = root.findall('{http://www.w3.org/2000/svg}g[@data-type="titleblock"]')[0]
image = titleblock.findall("{http://www.w3.org/2000/svg}image")[0]
titleblock.append(self.parse_embedded_svg(image, {"number": sheet_name, "revision": "A"}))
titleblock.remove(image)
self.group_number = 1
self.build_drawings(root.findall('{http://www.w3.org/2000/svg}g[@data-type="drawing"]'), sheet_name)
self.build_titleblock(root, sheet)
self.build_drawings(root, sheet)
self.build_schedules(root.findall('{http://www.w3.org/2000/svg}g[@data-type="schedule"]'))
with open(os.path.join(self.data_dir, "build", sheet_name, f"{sheet_name}.svg"), "wb") as output:
tree.write(output)
def build_drawings(self, drawings, sheet_name):
for view in drawings:
def build_titleblock(self, root, sheet):
titleblock = root.findall('{http://www.w3.org/2000/svg}g[@data-type="titleblock"]')[0]
image = titleblock.findall("{http://www.w3.org/2000/svg}image")[0]
titleblock.append(self.parse_embedded_svg(image, sheet.get_info()))
titleblock.remove(image)
def build_drawings(self, root, sheet):
sheet_name = tool.Drawing.get_sheet_filename(sheet)
references = tool.Drawing.get_document_references(sheet)
drawing_references = {tool.Drawing.get_reference_element(r): r for r in references}
for view in root.findall('{http://www.w3.org/2000/svg}g[@data-type="drawing"]'):
drawing = [d for d in drawing_references.keys() if d and d.GlobalId == view.attrib["data-guid"]][0]
reference = drawing_references[drawing]
images = view.findall("{http://www.w3.org/2000/svg}image")
background = images[0]
foreground = images[1]
view_title = images[2]
self.scale = "NTS"
# Add foreground
view.append(self.parse_embedded_svg(foreground, {}))
background = None
foreground = None
view_title = None
# Add background
background_path = os.path.join(self.data_dir, "sheets", self.get_href(background))
for image in images:
if image.attrib["data-type"] == "background":
background = image
elif image.attrib["data-type"] == "foreground":
foreground = image
elif image.attrib["data-type"] == "view-title":
view_title = image
copy(background_path, os.path.join(self.data_dir, "build", sheet_name))
if foreground is not None:
view.append(self.parse_embedded_svg(foreground, {}))
# Add view title
foreground_path = self.get_href(foreground)
view.append(
self.parse_embedded_svg(
view_title,
{"no": self.group_number, "name": ntpath.basename(foreground_path)[0:-4], "scale": self.scale},
)
)
if background is not None:
background_path = os.path.join(self.data_dir, "sheets", self.get_href(background))
copy(background_path, os.path.join(self.data_dir, "build", sheet_name))
if view_title is not None:
foreground_path = self.get_href(foreground)
data = reference.get_info()
if not data["Name"]:
data["Name"] = ntpath.basename(foreground_path)[0:-4]
data["Scale"] = tool.Drawing.get_drawing_human_scale(drawing)
view.append(self.parse_embedded_svg(view_title, data))
for image in images:
view.remove(image)
self.group_number += 1
def build_schedules(self, schedules):
for group in schedules:
images = group.findall("{http://www.w3.org/2000/svg}image")
@@ -203,15 +245,13 @@ class SheetBuilder:
path = self.get_href(schedule)
group.append(
self.parse_embedded_svg(
group_title, {"no": self.group_number, "name": ntpath.basename(path)[0:-4], "scale": self.scale}
group_title, {"no": 1, "name": ntpath.basename(path)[0:-4], "scale": self.scale}
)
)
for image in images:
group.remove(image)
self.group_number += 1
def get_href(self, element):
return urllib.parse.unquote(element.attrib.get("{http://www.w3.org/1999/xlink}href"))
@@ -24,6 +24,9 @@ import pystache
import xml.etree.ElementTree as ET
import svgwrite
import ifcopenshell
import ifcopenshell.util.element
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
@@ -146,13 +149,10 @@ class SvgWriter:
self.draw_dimension_annotations(obj)
self.draw_measureit_arch_dimension_annotations()
if self.annotations.get("break_obj"):
self.draw_break_annotations(self.annotations["break_obj"])
for grid_obj in self.annotations.get("grid_objs", []):
matrix_world = grid_obj.matrix_world
classes = self.get_attribute_classes(grid_obj)
for edge in grid_obj.data.edges:
classes = ["annotation", "grid"]
v0_global = matrix_world @ grid_obj.data.vertices[edge.vertices[0]].co.xyz
v1_global = matrix_world @ grid_obj.data.vertices[edge.vertices[1]].co.xyz
v0 = self.project_point_onto_camera(v0_global)
@@ -165,10 +165,8 @@ class SvgWriter:
start=tuple(start * self.scale), end=tuple(end * self.scale), class_=" ".join(classes)
)
)
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,
@@ -199,66 +197,23 @@ class SvgWriter:
self.draw_ifc_annotation()
for obj in self.annotations.get("misc_objs", []):
self.draw_misc_annotation(obj, ["IfcAnnotation"])
self.draw_misc_annotation(obj)
for obj_data in self.annotations.get("hidden_objs", []):
self.draw_line_annotation(obj_data, ["hidden"])
self.draw_line_annotation(obj_data)
for obj_data in self.annotations.get("solid_objs", []):
self.draw_line_annotation(obj_data, ["solid"])
self.draw_line_annotation(obj_data)
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
classes = self.get_attribute_classes(self.annotations["section_level_obj"])
for spline in self.annotations["section_level_obj"].data.splines:
classes = ["annotation", "section-level"]
points = self.get_spline_points(spline)
projected_points = [self.project_point_onto_camera(matrix_world @ p.co.xyz) for p in points]
d = " ".join(
@@ -269,8 +224,6 @@ class SvgWriter:
)
d = "M{}".format(d[1:])
path = self.svg.add(self.svg.path(d=d, class_=" ".join(classes)))
path["marker-start"] = "url(#section-level-marker)"
path["stroke-dasharray"] = "12.5, 3, 3, 3"
text_position = Vector(
(
(x_offset + projected_points[0].x) * self.scale,
@@ -299,8 +252,8 @@ class SvgWriter:
if self.annotations.get("stair_obj"):
matrix_world = self.annotations["stair_obj"].matrix_world
classes = self.get_attribute_classes(self.annotations["stair_obj"])
for spline in self.annotations["stair_obj"].data.splines:
classes = ["annotation", "stair"]
points = self.get_spline_points(spline)
projected_points = [self.project_point_onto_camera(matrix_world @ p.co.xyz) for p in points]
d = " ".join(
@@ -327,7 +280,18 @@ class SvgWriter:
},
)
)
self.draw_text_annotations()
for obj in self.annotations.get("elevation_objs", []):
self.draw_elevation_annotation(obj)
for obj in self.annotations.get("section_objs", []):
self.draw_section_annotation(obj)
for text_obj in self.annotations.get("text_objs", []):
self.draw_text_annotation(text_obj, text_obj.location)
if self.annotations.get("break_obj"):
self.draw_break_annotations(self.annotations["break_obj"])
def draw_ifc_annotation(self):
x_offset = self.raw_width / 2
@@ -349,13 +313,13 @@ class SvgWriter:
)
)
def draw_misc_annotation(self, obj, classes):
def draw_misc_annotation(self, obj):
# We have to decide whether this should come from Blender or from IFC.
# For the moment, for convenience of experimenting with ideas, it comes
# from Blender. In the future, it should probably come from IFC.
if not isinstance(obj.data, bpy.types.Mesh):
return
classes.extend(self.get_attribute_classes(obj))
classes = self.get_attribute_classes(obj)
if len(obj.data.polygons) == 0:
self.draw_edge_annotation(obj, classes)
return
@@ -365,6 +329,7 @@ class SvgWriter:
for polygon in obj.data.polygons:
points = [obj.data.vertices[v] for v in polygon.vertices]
projected_points = [self.project_point_onto_camera(matrix_world @ p.co.xyz) for p in points]
projected_points.append(projected_points[0])
d = " ".join(
[
"L {} {}".format((x_offset + p.x) * self.scale, (y_offset - p.y) * self.scale)
@@ -375,12 +340,12 @@ class SvgWriter:
path = self.svg.add(self.svg.path(d=d, class_=" ".join(classes)))
def get_attribute_classes(self, obj):
classes = [obj.name.split("/")[0]]
for slot in obj.material_slots:
if slot.material:
classes.append("material-{}".format(re.sub("[^0-9a-zA-Z]+", "", slot.material.name)))
global_id = IfcStore.get_file().by_id(obj.BIMObjectProperties.ifc_definition_id).GlobalId
classes.append("globalid-{}".format(global_id))
element = tool.Ifc.get_entity(obj)
global_id = "GlobalId-{}".format(element.GlobalId)
predefined_type = "PredefinedType-" + self.canonicalise_class_name(
str(ifcopenshell.util.element.get_predefined_type(element))
)
classes = [global_id, element.is_a(), predefined_type]
for attribute in self.annotations.get("attributes", []):
result = self.get_obj_value(obj, attribute)
if result:
@@ -389,6 +354,9 @@ class SvgWriter:
)
return classes
def canonicalise_class_name(self, name):
return re.sub("[^0-9a-zA-Z]+", "", name)
def get_obj_value(self, obj, key):
# This is a duplicate implementation of the IFC selector key in Blender
# In the future if all this becomes purely IFC based this can be deleted
@@ -414,14 +382,14 @@ class SvgWriter:
if result:
return result.string_value
def draw_line_annotation(self, obj_data, classes):
def draw_line_annotation(self, obj_data):
# TODO: properly scope these offsets
x_offset = self.raw_width / 2
y_offset = self.raw_height / 2
obj, data = obj_data
classes.extend(["annotation"])
classes = self.get_attribute_classes(obj)
matrix_world = obj.matrix_world
if isinstance(data, bpy.types.Curve):
@@ -455,71 +423,183 @@ class SvgWriter:
self.svg.line(start=tuple(start * self.scale), end=tuple(end * self.scale), class_=" ".join(classes))
)
def draw_text_annotations(self):
def draw_leader_annotations(self):
for obj in self.annotations.get("leader_objs", []):
self.draw_line_annotation((obj, obj.data))
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_section_annotation(self, obj):
x_offset = self.raw_width / 2
y_offset = self.raw_height / 2
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)))
self.draw_line_annotation((obj, obj.data))
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))
)
)
v1 = self.project_point_onto_camera(obj.matrix_world @ Vector((0, 0, 0)))
v2 = self.project_point_onto_camera(obj.matrix_world @ Vector((0, 0, -1)))
angle = -math.degrees((v2 - v1).xy.angle_signed(Vector((0, 1))))
for vert in obj.data.vertices:
point = obj.matrix_world @ vert.co
symbol_position = self.project_point_onto_camera(point)
symbol_position = Vector(((x_offset + symbol_position.x), (y_offset - symbol_position.y)))
transform = "rotate({}, {}, {})".format(
angle,
(text_position * self.scale)[0],
(text_position * self.scale)[1],
(symbol_position * self.scale)[0],
(symbol_position * self.scale)[1],
)
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,
},
)
self.svg.add(
self.svg.use("#section-arrow", insert=tuple(symbol_position * self.scale), transform=transform)
)
self.svg.add(self.svg.use("#section-tag", insert=tuple(symbol_position * self.scale)))
reference_id, sheet_id = self.get_reference_and_sheet_id_from_annotation(tool.Ifc.get_entity(obj))
text_position = list(symbol_position * self.scale)
text_style = {
"font-size": annotation.Annotator.get_svg_text_size(2.5),
"font-family": "OpenGost Type B TT",
"text-anchor": "middle",
"alignment-baseline": "middle",
"dominant-baseline": "middle",
}
self.svg.add(self.svg.text(reference_id, insert=(text_position[0], text_position[1] - 2.5), **text_style))
self.svg.add(self.svg.text(sheet_id, insert=(text_position[0], text_position[1] + 2.5), **text_style))
def draw_elevation_annotation(self, obj):
x_offset = self.raw_width / 2
y_offset = self.raw_height / 2
symbol_position = self.project_point_onto_camera(obj.location)
symbol_position = Vector(((x_offset + symbol_position.x), (y_offset - symbol_position.y)))
v1 = self.project_point_onto_camera(obj.matrix_world @ Vector((0, 0, 0)))
v2 = self.project_point_onto_camera(obj.matrix_world @ Vector((0, 0, -1)))
angle = -math.degrees((v2 - v1).xy.angle_signed(Vector((0, 1))))
transform = "rotate({}, {}, {})".format(
angle,
(symbol_position * self.scale)[0],
(symbol_position * self.scale)[1],
)
self.svg.add(self.svg.use("#elevation-arrow", insert=tuple(symbol_position * self.scale), transform=transform))
self.svg.add(self.svg.use("#elevation-tag", insert=tuple(symbol_position * self.scale)))
reference_id, sheet_id = self.get_reference_and_sheet_id_from_annotation(tool.Ifc.get_entity(obj))
text_position = list(symbol_position * self.scale)
text_style = {
"font-size": annotation.Annotator.get_svg_text_size(2.5),
"font-family": "OpenGost Type B TT",
"text-anchor": "middle",
"alignment-baseline": "middle",
"dominant-baseline": "middle",
}
self.svg.add(self.svg.text(reference_id, insert=(text_position[0], text_position[1] - 2.5), **text_style))
self.svg.add(self.svg.text(sheet_id, insert=(text_position[0], text_position[1] + 2.5), **text_style))
def get_reference_and_sheet_id_from_annotation(self, element):
reference_id = "-"
sheet_id = "-"
drawing = tool.Drawing.get_annotation_element(element)
reference = tool.Drawing.get_drawing_reference(drawing)
if reference:
sheet = tool.Drawing.get_reference_sheet(reference)
if sheet:
if tool.Ifc.get_schema() == "IFC2X3":
reference_id = reference.ItemReference or "-"
sheet_id = sheet.DocumentId or "-"
else:
reference_id = reference.Identification or "-"
sheet_id = sheet.Identification or "-"
return (reference_id, sheet_id)
return ("-", "-")
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]
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(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(f"#{text_obj.BIMTextProperties.symbol}", insert=tuple(text_position * self.scale))
)
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"
literal = text_literal.Literal
product = tool.Drawing.get_text_product(element)
selector = ifcopenshell.util.selector.Selector
variables = {}
for variable in re.findall("{{.*?}}", literal):
literal = literal.replace(variable, selector.get_element_value(product, variable[2:-2]) or "")
for line_number, text_line in enumerate(literal.replace("\\n", "\n").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,
},
)
)
def draw_break_annotations(self, break_obj):
x_offset = self.raw_width / 2
@@ -549,7 +629,54 @@ 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
classes = self.get_attribute_classes(obj)
for spline in obj.data.splines:
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)))
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):
classes = self.get_attribute_classes(dimension_obj)
matrix_world = dimension_obj.matrix_world
for spline in dimension_obj.data.splines:
points = self.get_spline_points(spline)
@@ -558,7 +685,7 @@ class SvgWriter:
continue
v0_global = matrix_world @ points[i].co.xyz
v1_global = matrix_world @ points[i + 1].co.xyz
self.draw_dimension_annotation(v0_global, v1_global, text_override)
self.draw_dimension_annotation(v0_global, v1_global, classes, text_override)
def draw_measureit_arch_dimension_annotations(self):
try:
@@ -568,10 +695,11 @@ class SvgWriter:
except:
return
for coord in coords:
self.draw_dimension_annotation(Vector(coord[0]), Vector(coord[1]))
self.draw_dimension_annotation(
Vector(coord[0]), Vector(coord[1]), ["IfcAnnotation", "PredefinedType-DIMENSION"]
)
def draw_dimension_annotation(self, v0_global, v1_global, text_override=None):
classes = ["annotation", "dimension"]
def draw_dimension_annotation(self, v0_global, v1_global, classes, text_override=None):
x_offset = self.raw_width / 2
y_offset = self.raw_height / 2
v0 = self.project_point_onto_camera(v0_global)
@@ -593,8 +721,6 @@ class SvgWriter:
line = self.svg.add(
self.svg.line(start=tuple(start * self.scale), end=tuple(end * self.scale), class_=" ".join(classes))
)
line["marker-start"] = "url(#dimension-marker-start)"
line["marker-end"] = "url(#dimension-marker-end)"
if text_override is not None:
text = text_override
else:
@@ -613,6 +739,7 @@ class SvgWriter:
)
def project_point_onto_camera(self, point):
# TODO is this needlessly complex?
return self.camera.matrix_world.inverted() @ geometry.intersect_line_plane(
point.xyz,
point.xyz - Vector(self.camera_projection),
+167 -107
View File
@@ -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, SheetsData, DrawingsData
class BIM_PT_camera(Panel):
@@ -44,13 +47,13 @@ class BIM_PT_camera(Panel):
col = layout.column(align=True)
row = col.row(align=True)
row.prop(dprops, "has_underlay", icon="OUTLINER_OB_IMAGE")
row.prop(props, "has_underlay", icon="OUTLINER_OB_IMAGE")
row.prop(dprops, "should_use_underlay_cache", text="", icon="FILE_REFRESH")
row = col.row(align=True)
row.prop(dprops, "has_linework", icon="IMAGE_DATA")
row.prop(props, "has_linework", icon="IMAGE_DATA")
row.prop(dprops, "should_use_linework_cache", text="", icon="FILE_REFRESH")
row = col.row(align=True)
row.prop(dprops, "has_annotation", icon="MOD_EDGESPLIT")
row.prop(props, "has_annotation", icon="MOD_EDGESPLIT")
row.prop(dprops, "should_use_annotation_cache", text="", icon="FILE_REFRESH")
row = layout.row()
@@ -64,9 +67,6 @@ class BIM_PT_camera(Panel):
row = layout.row()
row.operator("bim.resize_text")
row = layout.row()
row.prop(props, "target_view")
row = layout.row()
row.prop(props, "cut_objects")
if props.cut_objects == "CUSTOM":
@@ -146,44 +146,62 @@ class BIM_PT_drawing_underlay(Panel):
class BIM_PT_drawings(Panel):
bl_label = "SVG Drawings"
bl_label = "Drawings"
bl_idname = "BIM_PT_drawings"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "output"
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
bl_category = "BIM Documentation"
def draw(self, context):
layout = self.layout
layout.use_property_split = True
props = context.scene.DocProperties
if not DrawingsData.is_loaded:
DrawingsData.load()
row = layout.row(align=True)
row.operator("bim.add_drawing")
row.operator("bim.refresh_drawing_list", icon="FILE_REFRESH", text="")
self.props = context.scene.DocProperties
if props.drawings:
if props.active_drawing_index < len(props.drawings):
if not self.props.is_editing_drawings:
row = self.layout.row(align=True)
row.label(text=f"{DrawingsData.data['total_drawings']} Drawings Found", icon="IMAGE_DATA")
row.operator("bim.load_drawings", text="", icon="IMPORT")
return
row = self.layout.row(align=True)
row.prop(self.props, "target_view", text="")
row.prop(self.props, "location_hint", text="")
row.operator("bim.add_drawing", text="", icon="ADD")
row.operator("bim.disable_editing_drawings", text="", icon="CANCEL")
if self.props.drawings:
if self.props.active_drawing_index < len(self.props.drawings):
active_drawing = self.props.drawings[self.props.active_drawing_index]
row = self.layout.row(align=True)
row.alignment = "RIGHT"
op = row.operator("bim.open_view", icon="URL", text="")
op.view = props.active_drawing.name
row.operator("bim.remove_drawing", icon="X", text="").index = props.active_drawing_index
layout.template_list("BIM_UL_drawinglist", "", props, "drawings", props, "active_drawing_index")
op.view = active_drawing.name
op = row.operator("bim.activate_view", icon="OUTLINER_OB_CAMERA", text="")
op.drawing = active_drawing.ifc_definition_id
row.operator("bim.create_drawing", text="", icon="OUTPUT")
row.operator("bim.remove_drawing", icon="X", text="").drawing = active_drawing.ifc_definition_id
self.layout.template_list(
"BIM_UL_drawinglist", "", self.props, "drawings", self.props, "active_drawing_index"
)
row = layout.row()
row.operator("bim.add_ifc_file")
# Commented out until federated drawing generation is rebuilt
# row = self.layout.row()
# row.operator("bim.add_ifc_file")
for index, ifc_file in enumerate(props.ifc_files):
row = layout.row(align=True)
row.prop(ifc_file, "name", text="IFC #{}".format(index + 1))
row.operator("bim.select_doc_ifc_file", icon="FILE_FOLDER", text="").index = index
row.operator("bim.remove_ifc_file", icon="X", text="").index = index
# for index, ifc_file in enumerate(self.props.ifc_files):
# row = self.layout.row(align=True)
# row.prop(ifc_file, "name", text="IFC #{}".format(index + 1))
# row.operator("bim.select_doc_ifc_file", icon="FILE_FOLDER", text="").index = index
# row.operator("bim.remove_ifc_file", icon="X", text="").index = index
class BIM_PT_schedules(Panel):
bl_label = "ODS Schedules"
bl_label = "Schedules"
bl_idname = "BIM_PT_schedules"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "output"
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
bl_category = "BIM Documentation"
def draw(self, context):
layout = self.layout
@@ -205,150 +223,192 @@ class BIM_PT_schedules(Panel):
class BIM_PT_sheets(Panel):
bl_label = "SVG Sheets"
bl_label = "Sheets"
bl_idname = "BIM_PT_sheets"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "output"
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
bl_category = "BIM Documentation"
def draw(self, context):
layout = self.layout
props = context.scene.DocProperties
if not SheetsData.is_loaded:
SheetsData.load()
row = layout.row(align=True)
row.prop(props, "titleblock", text="")
row.operator("bim.add_sheet")
self.props = context.scene.DocProperties
if props.sheets:
if not self.props.is_editing_sheets:
row = self.layout.row(align=True)
row.label(text=f"{SheetsData.data['total_sheets']} Sheets Found", icon="FILE")
row.operator("bim.load_sheets", text="", icon="IMPORT")
return
row = self.layout.row(align=True)
row.prop(self.props, "titleblock", text="")
row.operator("bim.add_sheet", text="", icon="ADD")
row.operator("bim.disable_editing_sheets", text="", icon="CANCEL")
if self.props.sheets and self.props.active_sheet_index < len(self.props.sheets):
active_sheet = self.props.sheets[self.props.active_sheet_index]
row = self.layout.row(align=True)
row.alignment = "RIGHT"
row.operator("bim.open_sheet", icon="URL", text="")
row.operator("bim.remove_sheet", icon="X", text="").index = props.active_sheet_index
row.operator("bim.add_drawing_to_sheet", icon="IMAGE_PLANE", text="")
row.operator("bim.add_schedule_to_sheet", icon="PRESET_NEW", text="")
row.operator("bim.create_sheets", icon="FILE_REFRESH", text="")
if active_sheet.is_sheet:
row.operator("bim.remove_sheet", icon="X", text="").sheet = active_sheet.ifc_definition_id
else:
op = row.operator("bim.remove_drawing_from_sheet", icon="X", text="")
op.reference = active_sheet.ifc_definition_id
layout.template_list("BIM_UL_generic", "", props, "sheets", props, "active_sheet_index")
row = layout.row(align=True)
row.operator("bim.add_drawing_to_sheet")
row.operator("bim.add_schedule_to_sheet")
row = layout.row()
row.operator("bim.create_sheets")
self.layout.template_list("BIM_UL_sheets", "", self.props, "sheets", self.props, "active_sheet_index")
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_options = {"DEFAULT_CLOSED"}
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
bl_category = "BlenderBIM"
bl_category = "BIM Documentation"
def draw(self, context):
layout = self.layout
row = layout.row(align=True)
row.operator("bim.clean_wireframes")
row = layout.row(align=True)
row.operator("bim.add_grid")
row = layout.row(align=True)
row.operator("bim.add_sections_annotations")
self.props = context.scene.DocProperties
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
row = layout.row(align=True)
row.operator("bim.add_drawing")
row.operator("bim.refresh_drawing_list", icon="FILE_REFRESH", text="")
if props.drawings:
if props.active_drawing_index < len(props.drawings):
op = row.operator("bim.open_view", icon="URL", text="")
op.view = props.active_drawing.name
row.operator("bim.remove_drawing", icon="X", text="").index = props.active_drawing_index
layout.template_list("BIM_UL_drawinglist", "", props, "drawings", props, "active_drawing_index")
layout.prop(props, "should_draw_decorations")
layout.prop(props, "decorations_colour")
row.prop(self.props, "should_draw_decorations", text="Viewport Annotations")
class BIM_UL_drawinglist(bpy.types.UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
if item:
row = layout.row(align=True)
row.prop(item, "name", text="", emboss=False)
op = row.operator("bim.open_view_camera", icon="OUTLINER_OB_CAMERA", text="")
op.view_name = item.name
icon = "UV_FACESEL"
if item.target_view == "ELEVATION_VIEW":
icon = "UV_VERTEXSEL"
elif item.target_view == "SECTION_VIEW":
icon = "UV_EDGESEL"
elif item.target_view == "REFLECTED_PLAN_VIEW":
icon = "XRAY"
elif item.target_view == "MODEL_VIEW":
icon = "SNAP_VOLUME"
row.prop(item, "name", text="", icon=icon, emboss=False)
else:
layout.label(text="", translate=False)
class BIM_UL_sheets(bpy.types.UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
if item:
row = layout.row(align=True)
if item.is_sheet:
if item.is_expanded:
row.operator(
"bim.contract_sheet", text="", emboss=False, icon="DISCLOSURE_TRI_DOWN"
).sheet = item.ifc_definition_id
else:
row.operator(
"bim.expand_sheet", text="", emboss=False, icon="DISCLOSURE_TRI_RIGHT"
).sheet = item.ifc_definition_id
else:
row.label(text="", icon="BLANK1")
if item.reference_type == "DRAWING":
row.label(text="", icon="IMAGE_DATA")
name = "{} - {}".format(item.identification or "X", item.name or "Unnamed")
row.label(text=name)
else:
layout.label(text="", translate=False)
@@ -25,6 +25,7 @@ classes = (
operator.EditObjectPlacement,
operator.GetRepresentationIfcParameters,
operator.OverrideDelete,
operator.OverrideOutlinerDelete,
operator.OverrideDuplicateMove,
operator.OverrideDuplicateMoveLinked,
operator.OverridePasteBuffer,
@@ -40,6 +40,7 @@ class RepresentationsData:
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"):
@@ -49,15 +50,17 @@ class RepresentationsData:
if representation_type == "MappedRepresentation":
representation_type = representation.Items[0].MappingSource.MappedRepresentation.RepresentationType
representation_type += "*"
results.append(
{
"id": representation.id(),
"ContextType": representation.ContextOfItems.ContextType,
"ContextIdentifier": representation.ContextOfItems.ContextIdentifier,
"TargetView": representation.ContextOfItems.TargetView,
"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
@@ -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):
@@ -30,12 +30,13 @@ 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:
@@ -54,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):
@@ -67,7 +68,9 @@ class AddRepresentation(bpy.types.Operator, Operator):
profile_set_usage: bpy.props.IntProperty()
def _execute(self, context):
ifc_context = self.context_id or int(context.scene.BIMProperties.contexts or "0") or None
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
@@ -81,16 +84,6 @@ class AddRepresentation(bpy.types.Operator, Operator):
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,
)
Data.load(tool.Ifc.get(), obj.BIMObjectProperties.ifc_definition_id)
element = tool.Ifc.get_entity(obj)
if element.is_a("IfcTypeProduct"):
if tool.Ifc.get_schema() == "IFC2X3":
types = element.ObjectTypeOf
else:
types = element.Types
if types:
for element in types[0].RelatedObjects:
Data.load(tool.Ifc.get(), element.id())
class SwitchRepresentation(bpy.types.Operator, Operator):
@@ -111,6 +104,7 @@ class SwitchRepresentation(bpy.types.Operator, Operator):
should_reload=self.should_reload,
enable_dynamic_voids=self.disable_opening_subtractions,
is_global=self.should_switch_all_meshes,
should_sync_changes_first=True,
)
@@ -118,37 +112,15 @@ 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):
@@ -173,6 +145,11 @@ class UpdateRepresentation(bpy.types.Operator):
self.file = IfcStore.get_file()
for obj in objs:
# TODO: write unit tests to see how this bulk operation handles
# contradictory ifc_representation_class values and when
# ifc_representation_class is IfcTextLiteral
if not obj.data:
continue
self.update_obj_mesh_representation(context, obj)
IfcStore.edited_objs.discard(obj)
return {"FINISHED"}
@@ -180,11 +157,19 @@ class UpdateRepresentation(bpy.types.Operator):
def update_obj_mesh_representation(self, context, obj):
product = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
if not product.is_a("IfcGridAxis"):
tool.Geometry.clear_cache(product)
if product.is_a("IfcGridAxis"):
ifcopenshell.api.run("grid.create_axis_curve", self.file, **{"axis_curve": obj, "grid_axis": product})
return
if product.is_a("IfcRelSpaceBoundary"):
# TODO refactor
settings = tool.Boundary.get_assign_connection_geometry_settings(obj)
ifcopenshell.api.run("boundary.assign_connection_geometry", tool.Ifc.get(), **settings)
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
@@ -206,8 +191,9 @@ class UpdateRepresentation(bpy.types.Operator):
"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,
"should_force_faceted_brep": tool.Geometry.should_force_faceted_brep(),
"should_force_triangulation": tool.Geometry.should_force_triangulation(),
"should_generate_uvs": tool.Geometry.should_generate_uvs(obj),
"ifc_representation_class": self.ifc_representation_class,
}
@@ -216,6 +202,7 @@ class UpdateRepresentation(bpy.types.Operator):
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)
@@ -239,6 +226,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):
@@ -246,10 +234,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):
@@ -276,35 +264,20 @@ class UpdateParametricRepresentation(bpy.types.Operator):
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):
@@ -357,9 +330,35 @@ class CopyRepresentation(bpy.types.Operator, Operator):
return r.MappedRepresentation
class OverrideDelete(bpy.types.Operator):
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.delete_element(element)
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"
bl_options = {"REGISTER", "UNDO"}
use_global: bpy.props.BoolProperty(default=False)
confirm: bpy.props.BoolProperty(default=True)
@@ -373,41 +372,99 @@ class OverrideDelete(bpy.types.Operator):
return IfcStore.execute_ifc_operator(self, context)
for obj in context.selected_objects:
bpy.data.objects.remove(obj)
# Required otherwise gizmos are still visible
context.view_layer.objects.active = None
return {"FINISHED"}
def invoke(self, context, event):
return context.window_manager.invoke_confirm(self, event)
if self.confirm:
return context.window_manager.invoke_confirm(self, event)
self.confirm = True
return self.execute(context)
def _execute(self, context):
file = IfcStore.get_file()
for obj in context.selected_objects:
if obj.BIMObjectProperties.ifc_definition_id:
element = file.by_id(obj.BIMObjectProperties.ifc_definition_id)
if element.FillsVoids:
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 element.HasOpenings:
for rel in element.HasOpenings:
self.delete_opening_element(rel.RelatedOpeningElement)
self.delete_ifc_object(obj)
bpy.data.objects.remove(obj)
# Required otherwise gizmos are still visible
context.view_layer.objects.active = None
return {"FINISHED"}
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 OverrideOutlinerDelete(bpy.types.Operator, OverrideDeleteTrait):
bl_idname = "outliner.delete"
bl_label = "Delete"
bl_options = {"REGISTER", "UNDO"}
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)
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"
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
@@ -29,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):
@@ -49,7 +50,7 @@ class BIM_PT_representations(Panel):
layout.label(text="No representations found")
row = layout.row(align=True)
row.prop(context.scene.BIMProperties, "contexts", text="")
row.prop(context.scene.BIMRootProperties, "contexts", text="")
row.operator("bim.add_representation", icon="ADD", text="")
for representation in RepresentationsData.data["representations"]:
@@ -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_project_setup"
@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,
)

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