Compare commits

..

218 Commits

Author SHA1 Message Date
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
740 changed files with 31537 additions and 7193 deletions
+1 -1
View File
@@ -29,7 +29,7 @@ jobs:
strategy:
fail-fast: false
matrix:
pyver: [py37, py39]
pyver: [py37, py39, py310]
config:
- {
name: "Windows Build",
+2
View File
@@ -82,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: |
+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
+2
View File
@@ -39,6 +39,8 @@ Note 1: The path where the source code is cloned to can contain spaces but non-A
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
in win/ folder. **See [win/readme.md] for more information**.
+13 -9
View File
@@ -62,6 +62,10 @@ 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)
include(GNUInstallDirs)
@@ -444,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()
@@ -470,10 +478,6 @@ if(HDF5_SUPPORT)
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,
+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
+33 -3
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-b9ea741-$(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-883b8a5-$(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
@@ -488,6 +510,14 @@ endif
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
+20 -16
View File
@@ -68,6 +68,9 @@ modules = {
"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,
}
@@ -76,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,
@@ -102,22 +106,22 @@ classes = [
ui.BIM_UL_generic,
ui.BIM_UL_topics,
ui.BIM_ADDON_preferences,
#scene panel groups -->
# Scene panel groups
ui.BIM_PT_project_info,
ui.BIM_PT_project_setup,
ui.BIM_PT_utilities,
ui.BIM_PT_collaboration,
ui.BIM_PT_geometry,
ui.BIM_PT_4D5D,
ui.BIM_PT_structural,
ui.BIM_PT_services,
ui.BIM_PT_misc,
ui.BIM_PT_structural,
ui.BIM_PT_4D5D,
ui.BIM_PT_quality_control,
#object panel groups -->
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
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}}}
+6 -4
View File
@@ -28,6 +28,7 @@ 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
@@ -47,17 +48,17 @@ class IfcExporter:
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":
@@ -179,6 +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)
tool.Geometry.record_object_position(obj)
def get_application_name(self):
return "BlenderBIM"
+45 -31
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":
@@ -99,35 +105,43 @@ def export_attributes(props, callback=None):
return attributes
class IFCHeaderSpecs:
class IfcHeaderExtractor:
def __init__(self, filepath: str):
# According to https://www.steptools.com/stds/step/IS_final_p21e3.html#clause-8
self.description = ""
self.implementation_level = ""
self.name = ""
self.time_stamp = ""
self.author = ""
self.organization = ""
self.preprocessor_version = ""
self.originating_system = ""
self.authorization = ""
self.schema_name = ""
with open(filepath) as ifc_file:
max_lines_to_parse = 50
for _ in range(max_lines_to_parse):
line = next(ifc_file)
if line.startswith("FILE_DESCRIPTION"):
for i, part in enumerate(line.split("'")):
if i == 1:
self.description = part
elif i == 3:
self.implementation_level = part
elif line.startswith("FILE_NAME"):
for i, part in enumerate(line.split("'")):
if i == 1:
self.name = part
elif i == 3:
self.time_stamp = part
elif line.startswith("FILE_SCHEMA"):
self.schema_name = line.split("'")[1]
break
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]))
+17
View File
@@ -186,6 +186,23 @@ class IfcStore:
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
+329 -110
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
@@ -32,6 +34,7 @@ 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
@@ -109,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
@@ -143,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
@@ -161,6 +163,8 @@ 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)
# See https://github.com/IfcOpenShell/IfcOpenShell/issues/2053
# 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()
@@ -280,6 +284,14 @@ 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
@@ -346,16 +358,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
@@ -452,7 +465,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,
@@ -462,8 +477,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]
@@ -562,7 +575,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:
@@ -584,14 +596,6 @@ 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):
@@ -853,78 +857,138 @@ 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 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"]]
for representation in element.Representation.Representations:
if representation.ContextOfItems.id() not in self.body_contexts:
continue
self.convert_representation(representation)
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")
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 = 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"] = materials
mesh["ios_material_ids"] = material_ids
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")
@@ -1242,18 +1306,168 @@ class IfcImporter:
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)
@@ -1510,20 +1724,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
@@ -1611,7 +1827,8 @@ 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
@@ -1638,11 +1855,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()
@@ -33,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"}
@@ -50,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"}
@@ -66,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"}
@@ -75,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"}
@@ -84,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"}
@@ -99,12 +109,7 @@ class AddAggregate(bpy.types.Operator):
if not element:
return {"FINISHED"}
aggregate_collection = bpy.data.collections.new("IfcElementAssembly/Assembly")
context.scene.collection.children.link(aggregate_collection)
aggregate = bpy.data.objects.new("Assembly", None)
aggregate_collection.objects.link(aggregate)
bpy.ops.bim.assign_class(obj=aggregate.name, ifc_class="IfcElementAssembly")
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)
@@ -126,3 +131,73 @@ class AddAggregate(bpy.types.Operator):
)
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")
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)
@@ -44,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="")
@@ -58,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
@@ -60,7 +60,6 @@ class BIM_PT_object_attributes(Panel):
bl_region_type = "WINDOW"
bl_context = "object"
bl_parent_id = "BIM_PT_object_metadata"
bl_options = {"DEFAULT_CLOSED"}
@classmethod
def poll(cls, context):
@@ -82,8 +81,6 @@ class BIM_PT_material_attributes(Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "material"
bl_parent_id = "BIM_PT_object_metadata"
bl_options = {"DEFAULT_CLOSED"}
@classmethod
def poll(cls, context):
@@ -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
@@ -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")
@@ -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 Cyril Waechter <cyril@biminsight.ch>
#
# This file is part of BlenderBIM Add-on.
#
@@ -16,16 +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 logging
import bpy
import mathutils
import ifcopenshell.util.attribute
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
from blenderbim.bim.module.geometry.helper import Helper
def get_boundaries_collection(blender_space):
@@ -47,6 +44,19 @@ class Loader:
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)
@@ -64,12 +74,7 @@ class Loader:
obj = tool.Ifc.get_object(boundary)
if obj:
return obj
surface = boundary.ConnectionGeometry.SurfaceOnRelatingElement
# workaround for unvalid geometry provided by Revit. See https://github.com/IfcOpenShell/IfcOpenShell/issues/635#issuecomment-770366838
if surface.is_a("IfcCurveBoundedPlane") and not getattr(surface, "InnerBoundaries", None):
surface.InnerBoundaries = ()
shape = ifcopenshell.geom.create_shape(self.settings, surface)
mesh = self.ifc_importer.create_mesh(None, shape)
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)
@@ -267,20 +272,11 @@ class EditBoundaryAttributes(bpy.types.Operator):
return {"FINISHED"}
def polyline_from_indexes(mesh, indexes):
return tuple(mesh.vertices[i].co for i in indexes)
def polyline_to_2d(polyline, placement_matrix):
matrix_inv = placement_matrix.inverted()
return tuple((matrix_inv @ v).to_2d() for v in polyline)
class UpdateBoundaryGeometry(bpy.types.Operator):
bl_idname = "bim.update_boundary_geometry"
bl_label = "Update boundary geometry"
bl_description = """
Update boundary connection geometry from mesh.
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"}
@@ -289,32 +285,6 @@ class UpdateBoundaryGeometry(bpy.types.Operator):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
ifc_file = tool.Ifc.get()
helper = Helper(ifc_file)
mesh = context.active_object.data
curves = helper.auto_detect_curve_bounded_plane(mesh)
outer_boundary = polyline_from_indexes(mesh, curves["outer_curve"])
inner_boundaries = tuple(polyline_from_indexes(mesh, boundary) for boundary in curves["inner_curves"])
# Create placement matrix
location = outer_boundary[0]
i = (outer_boundary[1] - outer_boundary[0]).normalized()
k = mesh.polygons[0].normal
j = k.cross(i)
matrix = mathutils.Matrix()
matrix[0].xyz = i
matrix[1].xyz = j
matrix[2].xyz = k
matrix.transpose()
matrix.translation = location
settings = {
"rel_space_boundary": tool.Ifc.get_entity(context.active_object),
"outer_boundary": polyline_to_2d(outer_boundary, matrix),
"inner_boundaries": tuple(polyline_to_2d(boundary, matrix) for boundary in inner_boundaries),
"location": location,
"axis": k,
"ref_direction": i,
}
ifcopenshell.api.run("boundary.assign_connection_geometry", ifc_file, **settings)
settings = tool.Boundary.get_assign_connection_geometry_settings(context.active_object)
ifcopenshell.api.run("boundary.assign_connection_geometry", tool.Ifc.get(), **settings)
return {"FINISHED"}
@@ -29,6 +29,7 @@ classes = (
operator.LoadBrickProject,
operator.NewBrickFile,
operator.RefreshBrickViewer,
operator.RemoveBrick,
operator.RewindBrickClass,
operator.ViewBrickClass,
operator.ViewBrickItem,
@@ -148,7 +148,10 @@ class BrickschemaReferencesData:
@classmethod
def libraries(cls):
results = []
for library in tool.Ifc.get().by_type("IfcLibraryInformation"):
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:
@@ -174,3 +174,18 @@ class RefreshBrickViewer(bpy.types.Operator, Operator):
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,
)
@@ -51,7 +51,11 @@ class BIM_PT_brickschema(Panel):
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")
@@ -29,7 +29,7 @@ class BIM_PT_constraints(Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_misc"
bl_parent_id = "BIM_PT_project_setup"
@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
@@ -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!")
@@ -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,46 +29,50 @@ class BIM_PT_documents(Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_collaboration"
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):
@@ -89,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)
@@ -36,6 +36,8 @@ classes = (
operator.CopyGrid,
operator.CreateDrawing,
operator.CreateSheets,
operator.DisableEditingDrawings,
operator.DisableEditingSheets,
operator.DisableEditingText,
operator.DisableEditingTextProduct,
operator.EditText,
@@ -44,10 +46,10 @@ classes = (
operator.EnableEditingText,
operator.EnableEditingTextProduct,
operator.GenerateReferences,
operator.LoadDrawings,
operator.LoadSheets,
operator.OpenSheet,
operator.OpenView,
operator.OpenViewCamera,
operator.RefreshDrawingList,
operator.RemoveDrawing,
operator.RemoveDrawingStyle,
operator.RemoveDrawingStyleAttribute,
@@ -67,12 +69,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,
@@ -35,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
@@ -50,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
@@ -83,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):
@@ -102,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
@@ -151,8 +151,8 @@ class Annotator:
return obj
@staticmethod
def get_annotation_obj(object_type, data_type, context):
collection = context.scene.camera.users_collection[0]
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)
@@ -179,11 +179,15 @@ class Annotator:
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))
@@ -23,6 +23,8 @@ import blenderbim.tool as tool
def refresh():
TextData.is_loaded = False
SheetsData.is_loaded = False
DrawingsData.is_loaded = False
class TextData:
@@ -54,3 +56,41 @@ class TextData:
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"]]
@@ -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
@@ -263,7 +264,8 @@ def get_active_drawing(scene):
return None, None
try:
drawing = props.active_drawing
return drawing.camera.users_collection[0], drawing.camera
camera = tool.Ifc.get_object(tool.Ifc.get().by_id(drawing.ifc_definition_id))
return camera.users_collection[0], camera
except (KeyError, IndexError):
raise RuntimeError("missing drawing collection")
@@ -65,67 +65,23 @@ class Operator:
return {"FINISHED"}
class AddDrawing(bpy.types.Operator):
class AddDrawing(bpy.types.Operator, Operator):
bl_idname = "bim.add_drawing"
bl_label = "Add Drawing"
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
return IfcStore.get_file()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
self.file = IfcStore.get_file()
new = context.scene.DocProperties.drawings.add()
new.name = "DRAWING {}".format(len(context.scene.DocProperties.drawings))
if not bpy.data.collections.get("Views"):
context.scene.collection.children.link(bpy.data.collections.new("Views"))
views_collection = bpy.data.collections.get("Views")
view_collection = bpy.data.collections.new("IfcGroup/" + new.name)
views_collection.children.link(view_collection)
camera = bpy.data.objects.new(new.name, bpy.data.cameras.new(new.name))
camera.location = (0, 0, 1.5) # The view shall be 1.5m above the origin
camera.data.type = "ORTHO"
camera.data.ortho_scale = 50 # The default of 6m is too small
camera.data.clip_end = 10 # A slightly more reasonable default
if context.scene.unit_settings.system == "IMPERIAL":
camera.data.BIMCameraProperties.diagram_scale = '1/8"=1\'-0"|1/96'
else:
camera.data.BIMCameraProperties.diagram_scale = "1:100|1/100"
context.scene.camera = camera
view_collection.objects.link(camera)
area = next(area for area in context.screen.areas if area.type == "VIEW_3D")
area.spaces[0].region_3d.view_perspective = "CAMERA"
new.camera = camera
bpy.ops.bim.assign_class(obj=camera.name, ifc_class="IfcAnnotation", predefined_type="DRAWING")
bpy.ops.bim.activate_drawing_style()
bpy.ops.bim.add_group()
group = self.file.by_id(sorted(GroupData.groups.keys())[-1])
ifcopenshell.api.run("group.edit_group", self.file, **{"group": group, "attributes": {"Name": new.name}})
bpy.ops.bim.assign_group(product=camera.name, group=group.id())
pset = ifcopenshell.api.run(
"pset.add_pset",
self.file,
**{
"product": self.file.by_id(camera.BIMObjectProperties.ifc_definition_id),
"name": "EPset_Drawing",
},
self.props = context.scene.DocProperties
hint = self.props.location_hint
if self.props.target_view in ["PLAN_VIEW", "REFLECTED_PLAN_VIEW"]:
hint = int(hint)
core.add_drawing(
tool.Ifc,
tool.Collector,
tool.Drawing,
target_view=self.props.target_view,
location_hint=hint,
)
ifcopenshell.api.run(
"pset.edit_pset",
self.file,
**{
"pset": pset,
"properties": {"TargetView": "PLAN_VIEW", "Scale": "1/100"},
"pset_template": blenderbim.bim.schema.ifc.psetqto.get_by_name("EPset_Drawing"),
},
)
PsetData.load(IfcStore.get_file(), camera.BIMObjectProperties.ifc_definition_id)
return {"FINISHED"}
class CreateDrawing(bpy.types.Operator):
@@ -144,6 +100,7 @@ class CreateDrawing(bpy.types.Operator):
camera = context.scene.camera
return (
IfcStore.get_file()
and camera
and camera.type == "CAMERA"
and camera.data.type == "ORTHO"
and camera.BIMObjectProperties.ifc_definition_id
@@ -328,9 +285,10 @@ class CreateDrawing(bpy.types.Operator):
# A drawing prioritises a target view context first, followed by a body context as a fallback
body_contexts = []
target_view_contexts = []
target_view = ifcopenshell.util.element.get_psets(self.camera_element)["EPset_Drawing"]["TargetView"]
for rep_context in ifc.by_type("IfcGeometricRepresentationContext"):
if rep_context.is_a("IfcGeometricRepresentationSubContext"):
if rep_context.TargetView == context.scene.camera.data.BIMCameraProperties.target_view:
if rep_context.TargetView == target_view:
target_view_contexts.append(rep_context.id())
continue
if rep_context.ContextIdentifier in ["Body", "Facetation"]:
@@ -441,7 +399,8 @@ class CreateDrawing(bpy.types.Operator):
# https://stackoverflow.com/questions/36018627/sorting-child-elements-with-lxml-based-on-attribute-value
group = root.find("{http://www.w3.org/2000/svg}g")
# group[:] = sorted(group, key=lambda e : "projection" in e.get("class"))
group[:] = reversed(group)
if group:
group[:] = reversed(group)
def canonicalise_class_name(self, name):
return re.sub("[^0-9a-zA-Z]+", "", name)
@@ -562,7 +521,7 @@ class CreateDrawing(bpy.types.Operator):
return "mat-" + str(element.id())
class AddAnnotation(bpy.types.Operator):
class AddAnnotation(bpy.types.Operator, Operator):
bl_idname = "bim.add_annotation"
bl_label = "Add Annotation"
bl_options = {"REGISTER", "UNDO"}
@@ -573,85 +532,31 @@ class AddAnnotation(bpy.types.Operator):
def poll(cls, context):
return IfcStore.get_file() and context.scene.camera
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
context.scene.DocProperties.should_draw_decorations = True
subcontext = ifcopenshell.util.representation.get_context(
IfcStore.get_file(), "Plan", "Annotation", context.scene.camera.data.BIMCameraProperties.target_view
)
if not subcontext:
return {"FINISHED"}
# TODO: reimplement bulk smart tagging
# if self.data_type == "text":
# if context.selected_objects:
# for selected_object in context.selected_objects:
# obj = annotation.Annotator.add_text(context, related_element=selected_object)
# else:
# obj = annotation.Annotator.add_text(context)
# else:
obj = annotation.Annotator.get_annotation_obj(self.object_type, self.data_type, context)
if self.object_type == "BREAKLINE":
obj = annotation.Annotator.add_plane_to_annotation(obj, context)
elif self.object_type != "TEXT":
obj = annotation.Annotator.add_line_to_annotation(obj, context)
if not obj.BIMObjectProperties.ifc_definition_id:
ifc_representation_class = ""
if self.object_type == "TEXT":
ifc_representation_class = "IfcTextLiteral"
elif self.object_type == "TEXT_LEADER":
ifc_representation_class = "IfcGeometricCurveSet/IfcTextLiteral"
bpy.ops.bim.assign_class(
obj=obj.name,
ifc_class="IfcAnnotation",
context_id=subcontext.id(),
ifc_representation_class=ifc_representation_class,
)
element = tool.Ifc.get_entity(obj)
element.ObjectType = self.object_type
camera = tool.Ifc.get_entity(context.scene.camera)
group = [r for r in camera.HasAssignments if r.is_a("IfcRelAssignsToGroup")][0].RelatingGroup
bpy.ops.bim.assign_group(product=obj.name, group=group.id())
else:
bpy.ops.bim.update_representation(obj=obj.name)
bpy.ops.object.select_all(action="DESELECT")
context.view_layer.objects.active = obj
obj.select_set(True)
if obj.data:
bpy.ops.object.mode_set(mode="EDIT")
return {"FINISHED"}
drawing = tool.Ifc.get_entity(context.scene.camera)
if not drawing:
return
core.add_annotation(tool.Ifc, tool.Collector, tool.Drawing, drawing=drawing, object_type=self.object_type)
class AddSheet(bpy.types.Operator):
class AddSheet(bpy.types.Operator, Operator):
bl_idname = "bim.add_sheet"
bl_label = "Add Sheet"
bl_options = {"REGISTER", "UNDO"}
# TODO: check undo redo
def execute(self, context):
scene = context.scene
new = scene.DocProperties.sheets.add()
new.name = "{} - SHEET".format(len(scene.DocProperties.sheets))
sheet_builder = sheeter.SheetBuilder()
sheet_builder.data_dir = scene.BIMProperties.data_dir
sheet_builder.create(new.name, scene.DocProperties.titleblock)
return {"FINISHED"}
def _execute(self, context):
core.add_sheet(tool.Ifc, tool.Drawing, titleblock=context.scene.DocProperties.titleblock)
class OpenSheet(bpy.types.Operator):
class OpenSheet(bpy.types.Operator, Operator):
bl_idname = "bim.open_sheet"
bl_label = "Open Sheet"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
props = context.scene.DocProperties
open_with_user_command(
context.preferences.addons["blenderbim"].preferences.svg_command,
os.path.join(context.scene.BIMProperties.data_dir, "sheets", props.active_sheet.name + ".svg"),
)
return {"FINISHED"}
def _execute(self, context):
self.props = context.scene.DocProperties
sheet = tool.Ifc.get().by_id(self.props.sheets[self.props.active_sheet_index].ifc_definition_id)
core.open_sheet(tool.Drawing, sheet=sheet)
class AddDrawingToSheet(bpy.types.Operator):
@@ -738,40 +643,14 @@ class OpenView(bpy.types.Operator):
return {"FINISHED"}
class OpenViewCamera(bpy.types.Operator):
"""Select this drawing's camera object and expand its drawing properties"""
bl_idname = "bim.open_view_camera"
bl_label = "Open View Camera"
bl_options = {"REGISTER", "UNDO"}
view_name: bpy.props.StringProperty()
@classmethod
def poll(cls, context):
return context.mode == "OBJECT"
def execute(self, context):
doc_props = context.scene.DocProperties
doc_props.active_drawing_index = doc_props.drawings.find(self.view_name)
drawing = doc_props.active_drawing
bpy.ops.object.select_all(action="DESELECT")
drawing.camera.select_set(True)
context.view_layer.objects.active = drawing.camera
for area in context.screen.areas:
if area.ui_type == "PROPERTIES":
for space in area.spaces:
space.context = "DATA"
return {"FINISHED"}
class ActivateView(bpy.types.Operator):
bl_idname = "bim.activate_view"
bl_label = "Activate View"
bl_options = {"REGISTER", "UNDO"}
drawing_index: bpy.props.IntProperty()
drawing: bpy.props.IntProperty()
def execute(self, context):
camera = context.scene.DocProperties.drawings[self.drawing_index].camera
camera = tool.Ifc.get_object(tool.Ifc.get().by_id(self.drawing))
if not camera:
return {"FINISHED"}
area = next(area for area in context.screen.areas if area.type == "VIEW_3D")
@@ -799,6 +678,7 @@ class ActivateView(bpy.types.Operator):
project_collection.children["Views"].children[camera.users_collection[0].name].hide_viewport = False
bpy.data.collections.get(camera.users_collection[0].name).hide_render = False
bpy.ops.bim.activate_drawing_style()
core.sync_references(tool.Ifc, tool.Collector, tool.Drawing, drawing=tool.Ifc.get().by_id(self.drawing))
return {"FINISHED"}
@@ -894,21 +774,16 @@ class ResizeText(bpy.types.Operator):
return {"FINISHED"}
class RemoveDrawing(bpy.types.Operator):
class RemoveDrawing(bpy.types.Operator, Operator):
bl_idname = "bim.remove_drawing"
bl_label = "Remove Drawing"
bl_options = {"REGISTER", "UNDO"}
index: bpy.props.IntProperty()
def execute(self, context):
def _execute(self, context):
props = context.scene.DocProperties
camera = props.drawings[self.index].camera
collection = camera.users_collection[0]
for obj in collection.objects:
bpy.data.objects.remove(obj)
bpy.data.collections.remove(collection, do_unlink=True)
props.drawings.remove(self.index)
return {"FINISHED"}
drawing = tool.Ifc.get().by_id(props.drawings[self.index].ifc_definition_id)
core.remove_drawing(tool.Ifc, tool.Drawing, drawing=drawing)
class AddDrawingStyle(bpy.types.Operator):
@@ -1057,16 +932,16 @@ class EditVectorStyle(bpy.types.Operator):
return {"FINISHED"}
class RemoveSheet(bpy.types.Operator):
class RemoveSheet(bpy.types.Operator, Operator):
bl_idname = "bim.remove_sheet"
bl_label = "Remove Sheet"
bl_options = {"REGISTER", "UNDO"}
index: bpy.props.IntProperty()
def execute(self, context):
props = context.scene.DocProperties
props.sheets.remove(self.index)
return {"FINISHED"}
def _execute(self, context):
self.props = context.scene.DocProperties
sheet = tool.Ifc.get().by_id(self.props.sheets[self.props.active_sheet_index].ifc_definition_id)
core.remove_sheet(tool.Ifc, tool.Drawing, sheet=sheet)
class AddSchedule(bpy.types.Operator):
@@ -1170,23 +1045,6 @@ class RemoveDrawingStyleAttribute(bpy.types.Operator):
return {"FINISHED"}
class RefreshDrawingList(bpy.types.Operator):
bl_idname = "bim.refresh_drawing_list"
bl_label = "Refresh Drawing List"
def execute(self, context):
doc_props = context.scene.DocProperties
doc_props.drawings.clear()
for obj in context.scene.objects:
if not isinstance(obj.data, bpy.types.Camera):
continue
if "IfcAnnotation/" in obj.name:
new = doc_props.drawings.add()
new.name = "/".join(obj.name.split("/")[1:])
new.camera = obj
return {"FINISHED"}
class CleanWireframes(bpy.types.Operator):
bl_idname = "bim.clean_wireframes"
bl_label = "Clean Wireframes"
@@ -1213,8 +1071,10 @@ class CopyGrid(bpy.types.Operator):
return helper.get_active_drawing(context.scene)[0] is not None
def execute(self, context):
drawing = tool.Ifc.get_entity(context.scene.camera)
target_view = tool.Drawing.get_drawing_target_view(drawing)
subcontext = ifcopenshell.util.representation.get_context(
IfcStore.get_file(), "Plan", "Annotation", context.scene.camera.data.BIMCameraProperties.target_view
IfcStore.get_file(), "Plan", "Annotation", target_view
)
if not subcontext:
return {"FINISHED"}
@@ -1223,8 +1083,8 @@ class CopyGrid(bpy.types.Operator):
view_coll, camera = helper.get_active_drawing(context.scene)
is_ortho = camera.data.type == "ORTHO"
bounds = helper.ortho_view_frame(camera.data) if is_ortho else None
clipping = is_ortho and camera.data.BIMCameraProperties.target_view in ("PLAN_VIEW", "REFLECTED_PLAN_VIEW")
elevating = is_ortho and camera.data.BIMCameraProperties.target_view in ("ELEVATION_VIEW", "SECTION_VIEW")
clipping = is_ortho and target_view in ("PLAN_VIEW", "REFLECTED_PLAN_VIEW")
elevating = is_ortho and target_view in ("ELEVATION_VIEW", "SECTION_VIEW")
def grep(coll):
results = []
@@ -1420,3 +1280,39 @@ class DisableEditingTextProduct(bpy.types.Operator, Operator):
def _execute(self, context):
core.disable_editing_text_product(tool.Drawing, obj=context.active_object)
class LoadSheets(bpy.types.Operator, Operator):
bl_idname = "bim.load_sheets"
bl_label = "Load Sheets"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
core.load_sheets(tool.Drawing)
class DisableEditingSheets(bpy.types.Operator, Operator):
bl_idname = "bim.disable_editing_sheets"
bl_label = "Disable Editing Text Product"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
core.disable_editing_sheets(tool.Drawing)
class LoadDrawings(bpy.types.Operator, Operator):
bl_idname = "bim.load_drawings"
bl_label = "Load Drawings"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
core.load_drawings(tool.Drawing)
class DisableEditingDrawings(bpy.types.Operator, Operator):
bl_idname = "bim.disable_editing_drawings"
bl_label = "Disable Editing Text Product"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
core.disable_editing_drawings(tool.Drawing)
@@ -19,11 +19,13 @@
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
@@ -56,6 +58,16 @@ 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":
@@ -132,31 +144,18 @@ 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 refreshActiveDrawingIndex(self, context):
bpy.ops.bim.activate_view(drawing_index=context.scene.DocProperties.active_drawing_index)
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 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
@@ -198,8 +197,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):
@@ -218,6 +218,8 @@ 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")
@@ -276,12 +278,27 @@ class DocProperties(PropertyGroup):
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_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)
@@ -307,17 +324,6 @@ class DocProperties(PropertyGroup):
class BIMCameraProperties(PropertyGroup):
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)
@@ -31,7 +31,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"
@@ -20,7 +20,7 @@ import bpy
import blenderbim.bim.helper
import blenderbim.tool as tool
from bpy.types import Panel
from blenderbim.bim.module.drawing.data import TextData
from blenderbim.bim.module.drawing.data import TextData, SheetsData, DrawingsData
class BIM_PT_camera(Panel):
@@ -67,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":
@@ -149,44 +146,61 @@ 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):
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 = self.props.active_drawing.name
op = row.operator("bim.activate_view", icon="OUTLINER_OB_CAMERA", text="")
op.drawing = self.props.active_drawing.ifc_definition_id
row.operator("bim.create_drawing", text="", icon="OUTPUT")
row.operator("bim.remove_drawing", icon="X", text="").index = self.props.active_drawing_index
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
@@ -208,31 +222,39 @@ 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:
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="")
row.operator("bim.remove_sheet", icon="X", text="").index = self.props.active_sheet_index
layout.template_list("BIM_UL_generic", "", props, "sheets", props, "active_sheet_index")
row = layout.row(align=True)
row.operator("bim.add_drawing_to_sheet")
row.operator("bim.add_schedule_to_sheet")
row = layout.row()
row.operator("bim.create_sheets")
self.layout.template_list("BIM_UL_sheets", "", self.props, "sheets", self.props, "active_sheet_index")
class BIM_PT_text(Panel):
@@ -289,21 +311,15 @@ class BIM_PT_text(Panel):
class BIM_PT_annotation_utilities(Panel):
bl_idname = "BIM_PT_annotation_utilities"
bl_label = "Drawing"
bl_options = {"DEFAULT_CLOSED"}
bl_label = "Annotation"
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")
@@ -345,29 +361,35 @@ class BIM_PT_annotation_utilities(Panel):
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)
split1 = row.split(factor=0.2)
split1.label(text=item.identification or "X")
split2 = split1.split(factor=0.8)
split2.label(text=item.name or "Unnamed")
else:
layout.label(text="", translate=False)
@@ -145,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"}
@@ -155,6 +160,11 @@ class UpdateRepresentation(bpy.types.Operator):
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.Geometry, tool.Surveyor, obj=obj)
@@ -178,8 +188,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,
}
@@ -320,7 +331,7 @@ class OverrideDeleteTrait:
def delete_ifc_object(self, obj):
if obj.BIMObjectProperties.ifc_definition_id:
element = IfcStore.get_file().by_id(obj.BIMObjectProperties.ifc_definition_id)
IfcStore.deleted_ids.add(element.id())
IfcStore.delete_element(element)
if getattr(element, "FillsVoids", None):
self.remove_filling(element)
if element.is_a("IfcOpeningElement"):
@@ -344,6 +355,7 @@ class OverrideDeleteTrait:
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)
@@ -357,21 +369,29 @@ class OverrideDelete(bpy.types.Operator, OverrideDeleteTrait):
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):
for obj in context.selected_objects:
self.delete_ifc_object(obj)
bpy.data.objects.remove(obj)
# Required otherwise gizmos are still visible
context.view_layer.objects.active = None
return {"FINISHED"}
class OverrideOutlinerDelete(bpy.types.Operator, OverrideDeleteTrait):
bl_idname = "outliner.delete"
bl_label = "Delete"
bl_options = {"REGISTER", "UNDO"}
hierarchy: bpy.props.BoolProperty(default=False)
@classmethod
@@ -394,7 +414,6 @@ class OverrideOutlinerDelete(bpy.types.Operator, OverrideDeleteTrait):
if item.bl_rna.identifier == "Collection":
collection = bpy.data.collections.get(item.name)
collection_data = self.get_collection_objects_and_children(collection)
print(collection_data)
objects_to_delete |= collection_data["objects"]
collections_to_delete |= collection_data["children"]
collections_to_delete.add(collection)
@@ -433,7 +452,6 @@ class OverrideOutlinerDelete(bpy.types.Operator, OverrideDeleteTrait):
while queue:
collection = queue.pop()
for obj in collection.objects:
print('obj is', obj)
objects.add(obj)
queue.extend(collection.children)
children = children.union(collection.children)
@@ -443,6 +461,7 @@ class OverrideOutlinerDelete(bpy.types.Operator, OverrideDeleteTrait):
class OverrideDuplicateMove(bpy.types.Operator):
bl_idname = "object.duplicate_move"
bl_label = "Duplicate Objects"
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
@@ -50,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"]:
@@ -28,7 +28,7 @@ class BIM_PT_groups(Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_utilities"
bl_parent_id = "BIM_PT_project_setup"
@classmethod
def poll(cls, context):
@@ -27,6 +27,7 @@ class BIM_PT_lca(Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_integrations"
def draw(self, context):
props = context.scene.BIMLCAProperties
@@ -21,37 +21,47 @@ from . import ui, prop, operator
classes = (
operator.AddConstituent,
operator.AddDefaultMaterial,
operator.AddLayer,
operator.AddListItem,
operator.AddMaterial,
operator.AddMaterialSet,
operator.AddProfile,
operator.AssignMaterial,
operator.AssignParameterizedProfile,
operator.CopyMaterial,
operator.DisableEditingAssignedMaterial,
operator.DisableEditingMaterialSetItem,
operator.DisableEditingMaterials,
operator.EditAssignedMaterial,
operator.EditMaterialSetItem,
operator.EnableEditingAssignedMaterial,
operator.EnableEditingMaterialSetItem,
operator.LoadMaterials,
operator.RemoveConstituent,
operator.RemoveLayer,
operator.RemoveListItem,
operator.RemoveMaterial,
operator.RemoveMaterialSet,
operator.RemoveProfile,
operator.ReorderMaterialSetItem,
operator.SelectByMaterial,
operator.UnassignMaterial,
operator.UnlinkMaterial,
prop.Material,
prop.BIMMaterialProperties,
prop.BIMObjectMaterialProperties,
ui.BIM_PT_materials,
ui.BIM_PT_material,
ui.BIM_PT_object_material,
ui.BIM_UL_materials,
)
def register():
bpy.types.Scene.BIMMaterialProperties = bpy.props.PointerProperty(type=prop.BIMMaterialProperties)
bpy.types.Object.BIMObjectMaterialProperties = bpy.props.PointerProperty(type=prop.BIMObjectMaterialProperties)
def unregister():
del bpy.types.Scene.BIMMaterialProperties
del bpy.types.Object.BIMObjectMaterialProperties
@@ -0,0 +1,86 @@
# 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():
MaterialsData.is_loaded = False
ObjectMaterialData.is_loaded = False
class MaterialsData:
data = {}
is_loaded = False
@classmethod
def load(cls):
cls.data = {
"total_materials": cls.total_materials(),
"material_types": cls.material_types(),
}
cls.is_loaded = True
@classmethod
def total_materials(cls):
if tool.Ifc.get_schema() == "IFC2X3":
return (
len(tool.Ifc.get().by_type("IfcMaterial"))
+ len(tool.Ifc.get().by_type("IfcMaterialLayerSet"))
+ len(tool.Ifc.get().by_type("IfcMaterialList"))
)
return (
len(tool.Ifc.get().by_type("IfcMaterial"))
+ len(tool.Ifc.get().by_type("IfcMaterialConstituentSet"))
+ len(tool.Ifc.get().by_type("IfcMaterialLayerSet"))
+ len(tool.Ifc.get().by_type("IfcMaterialProfileSet"))
+ len(tool.Ifc.get().by_type("IfcMaterialList"))
)
@classmethod
def material_types(cls):
material_types = [
"IfcMaterial",
"IfcMaterialConstituentSet",
"IfcMaterialLayerSet",
"IfcMaterialProfileSet",
"IfcMaterialList",
]
if tool.Ifc.get_schema() == "IFC2X3":
material_types = ["IfcMaterial", "IfcMaterialLayerSet", "IfcMaterialList"]
return [(m, m, "") for m in material_types]
class ObjectMaterialData:
data = {}
is_loaded = False
@classmethod
def load(cls):
cls.data = {
"materials": cls.materials(),
}
cls.is_loaded = True
@classmethod
def materials(cls):
return sorted([(str(m.id()), m.Name or "Unnamed", "") for m in tool.Ifc.get().by_type("IfcMaterial")], key=lambda x: x[1])
@@ -31,11 +31,32 @@ from ifcopenshell.api.material.data import Data
from ifcopenshell.api.profile.data import Data as ProfileData
class Operator:
def execute(self, context):
IfcStore.execute_ifc_operator(self, context)
blenderbim.bim.handler.refresh_ui_data()
return {"FINISHED"}
class LoadMaterials(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.load_materials"
bl_label = "Load Materials"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
core.load_materials(tool.Material, context.scene.BIMMaterialProperties.material_type)
class DisableEditingMaterials(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.disable_editing_materials"
bl_label = "Disable Editing Materials"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
core.disable_editing_materials(tool.Material)
class SelectByMaterial(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.select_by_material"
bl_label = "Select By Material"
bl_options = {"REGISTER", "UNDO"}
material: bpy.props.IntProperty()
def _execute(self, context):
core.select_by_material(tool.Material, material=tool.Ifc.get().by_id(self.material))
class AssignParameterizedProfile(bpy.types.Operator):
@@ -68,70 +89,54 @@ class AssignParameterizedProfile(bpy.types.Operator):
return {"FINISHED"}
class AddDefaultMaterial(bpy.types.Operator, Operator):
bl_idname = "bim.add_default_material"
bl_label = "Add Default Material"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
core.add_default_material(tool.Ifc, tool.Material)
Data.load(IfcStore.get_file())
class AddMaterial(bpy.types.Operator):
class AddMaterial(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.add_material"
bl_label = "Add Material"
bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
obj = bpy.data.materials.get(self.obj) if self.obj else context.active_object.active_material
self.file = IfcStore.get_file()
result = ifcopenshell.api.run("material.add_material", self.file, **{"name": obj.name})
IfcStore.link_element(result, obj)
if obj.BIMMaterialProperties.ifc_style_id:
context = ifcopenshell.util.representation.get_context(self.file, "Model", "Body", "MODEL_VIEW")
if context:
ifcopenshell.api.run(
"style.assign_material_style",
self.file,
**{
"material": result,
"style": self.file.by_id(obj.BIMMaterialProperties.ifc_style_id),
"context": context,
},
)
obj = bpy.data.materials.get(self.obj) if self.obj else None
core.add_material(tool.Ifc, tool.Material, tool.Style, obj=obj)
Data.load(IfcStore.get_file())
material_prop_purge()
return {"FINISHED"}
class RemoveMaterial(bpy.types.Operator):
class AddMaterialSet(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.add_material_set"
bl_label = "Add Material Set"
bl_options = {"REGISTER", "UNDO"}
set_type: bpy.props.StringProperty()
def _execute(self, context):
core.add_material_set(tool.Ifc, tool.Material, set_type=self.set_type)
Data.load(IfcStore.get_file())
material_prop_purge()
class RemoveMaterial(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.remove_material"
bl_label = "Remove Material"
bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
material: bpy.props.IntProperty()
def _execute(self, context):
obj = bpy.data.materials.get(self.obj) if self.obj else context.active_object.active_material
self.file = IfcStore.get_file()
result = ifcopenshell.api.run(
"material.remove_material",
self.file,
**{"material": self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)},
)
obj.BIMObjectProperties.ifc_definition_id = 0
core.remove_material(tool.Ifc, tool.Material, tool.Style, material=tool.Ifc.get().by_id(self.material))
Data.load(IfcStore.get_file())
return {"FINISHED"}
class UnlinkMaterial(bpy.types.Operator, Operator):
class RemoveMaterialSet(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.remove_material_set"
bl_label = "Remove Material Set"
bl_options = {"REGISTER", "UNDO"}
material: bpy.props.IntProperty()
def _execute(self, context):
core.remove_material_set(tool.Ifc, tool.Material, material=tool.Ifc.get().by_id(self.material))
Data.load(IfcStore.get_file())
class UnlinkMaterial(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.unlink_material"
bl_label = "Unlink Material"
bl_options = {"REGISTER", "UNDO"}
@@ -18,6 +18,7 @@
import bpy
from ifcopenshell.api.material.data import Data
from blenderbim.bim.module.material.data import MaterialsData, ObjectMaterialData
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.prop import StrProperty, Attribute
from bpy.types import PropertyGroup
@@ -32,18 +33,15 @@ from bpy.props import (
CollectionProperty,
)
materials_enum = []
materialtypes_enum = []
profileclasses_enum = []
parameterizedprofileclasses_enum = []
def purge():
global materials_enum
global materialtypes_enum
global profileclasses_enum
global parameterizedprofileclasses_enum
materials_enum = []
materialtypes_enum = []
profileclasses_enum = []
parameterizedprofileclasses_enum = []
@@ -77,12 +75,10 @@ def getParameterizedProfileClasses(self, context):
return parameterizedprofileclasses_enum
def getMaterials(self, context):
global materials_enum
if len(materials_enum) == 0 and IfcStore.get_file():
materials_enum.clear()
materials_enum = [(str(m_id), m["Name"], "") for m_id, m in Data.materials.items()]
return materials_enum
def get_materials(self, context):
if not ObjectMaterialData.is_loaded:
ObjectMaterialData.load()
return ObjectMaterialData.data["materials"]
def getMaterialTypes(self, context):
@@ -104,9 +100,28 @@ def getMaterialTypes(self, context):
return materialtypes_enum
def get_material_types(self, context):
if not MaterialsData.is_loaded:
MaterialsData.load()
return MaterialsData.data["material_types"]
class Material(PropertyGroup):
name: StringProperty(name="Name")
ifc_definition_id: IntProperty(name="IFC Definition ID")
total_elements: IntProperty(name="Total Elements")
class BIMMaterialProperties(PropertyGroup):
is_editing: BoolProperty(name="Is Editing", default=False)
material_type: EnumProperty(items=get_material_types, name="Material Type")
materials: CollectionProperty(name="Materials", type=Material)
active_material_index: IntProperty(name="Active Material Index")
class BIMObjectMaterialProperties(PropertyGroup):
material_type: EnumProperty(items=getMaterialTypes, name="Material Type")
material: EnumProperty(items=getMaterials, name="Material")
material: EnumProperty(items=get_materials, name="Material")
is_editing: BoolProperty(name="Is Editing", default=False)
material_set_usage_attributes: CollectionProperty(name="Material Set Usage Attributes", type=Attribute)
material_set_attributes: CollectionProperty(name="Material Set Attributes", type=Attribute)
@@ -115,7 +130,7 @@ class BIMObjectMaterialProperties(PropertyGroup):
material_set_item_profile_attributes: CollectionProperty(
name="Material Set Item Profile Attributes", type=Attribute
)
material_set_item_material: EnumProperty(items=getMaterials, name="Material")
material_set_item_material: EnumProperty(items=get_materials, name="Material")
profile_classes: EnumProperty(items=getProfileClasses, name="Profile Classes")
parameterized_profile_classes: EnumProperty(
items=getParameterizedProfileClasses, name="Parameterized Profile Classes"
@@ -17,11 +17,62 @@
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import blenderbim.bim.helper
from bpy.types import Panel
from bpy.types import Panel, UIList
from ifcopenshell.api.material.data import Data
from ifcopenshell.api.profile.data import Data as ProfileData
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.helper import draw_attributes
from blenderbim.bim.module.material.data import MaterialsData, ObjectMaterialData
class BIM_PT_materials(Panel):
bl_label = "IFC Materials"
bl_idname = "BIM_PT_materials"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_geometry"
@classmethod
def poll(cls, context):
return IfcStore.get_file()
def draw(self, context):
if not MaterialsData.is_loaded:
MaterialsData.load()
self.props = context.scene.BIMMaterialProperties
row = self.layout.row(align=True)
row.label(text="{} Materials Found".format(MaterialsData.data["total_materials"]), icon="MATERIAL")
if self.props.is_editing:
row.operator("bim.disable_editing_materials", text="", icon="CANCEL")
else:
row = self.layout.row(align=True)
row.prop(self.props, "material_type", text="")
row.operator("bim.load_materials", text="", icon="IMPORT")
return
row = self.layout.row(align=True)
row.alignment = "RIGHT"
if self.props.material_type == "IfcMaterial":
row.operator("bim.add_material", text="", icon="ADD")
if self.props.materials and self.props.active_material_index < len(self.props.materials):
material = self.props.materials[self.props.active_material_index]
op = row.operator("bim.select_by_material", text="", icon="RESTRICT_SELECT_OFF")
op.material = material.ifc_definition_id
row.operator("bim.remove_material", text="", icon="X").material = material.ifc_definition_id
else:
row.operator("bim.add_material_set", text="", icon="ADD").set_type = self.props.material_type
if self.props.materials and self.props.active_material_index < len(self.props.materials):
material = self.props.materials[self.props.active_material_index]
op = row.operator("bim.select_by_material", text="", icon="RESTRICT_SELECT_OFF")
op.material = material.ifc_definition_id
row.operator("bim.remove_material_set", text="", icon="X").material = material.ifc_definition_id
self.layout.template_list("BIM_UL_materials", "", self.props, "materials", self.props, "active_material_index")
class BIM_PT_material(Panel):
@@ -37,11 +88,13 @@ class BIM_PT_material(Panel):
def draw(self, context):
row = self.layout.row(align=True)
if bool(context.active_object.active_material.BIMObjectProperties.ifc_definition_id):
row.operator("bim.remove_material", icon="X", text="Remove IFC Material")
material_id = context.active_object.active_material.BIMObjectProperties.ifc_definition_id
if bool(material_id):
row.operator("bim.remove_material", icon="X", text="Remove IFC Material").material = material_id
row.operator("bim.unlink_material", icon="UNLINKED", text="")
else:
row.operator("bim.add_material", icon="ADD", text="Create IFC Material")
op = row.operator("bim.add_material", icon="ADD", text="Create IFC Material")
op.obj = context.active_object.active_material.name
class BIM_PT_object_material(Panel):
@@ -66,6 +119,9 @@ class BIM_PT_object_material(Panel):
return True
def draw(self, context):
if not ObjectMaterialData.is_loaded:
ObjectMaterialData.load()
self.file = IfcStore.get_file()
self.oprops = context.active_object.BIMObjectProperties
self.props = context.active_object.BIMObjectMaterialProperties
@@ -77,10 +133,10 @@ class BIM_PT_object_material(Panel):
ProfileData.load(self.file)
self.product_data = Data.products[self.oprops.ifc_definition_id]
if not Data.materials:
if not ObjectMaterialData.data["materials"]:
row = self.layout.row(align=True)
row.label(text="No Materials Available")
row.operator("bim.add_default_material", icon="ADD", text="")
row.operator("bim.add_material", icon="ADD", text="").obj = ""
return
if self.product_data:
@@ -333,3 +389,13 @@ class BIM_PT_object_material(Panel):
if total_thickness:
row = self.layout.row(align=True)
row.label(text=f"Total Thickness: {total_thickness:.3f}")
class BIM_UL_materials(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
if item:
row = layout.row(align=True)
row.label(text=item.name)
row2 = row.row()
row2.alignment = "RIGHT"
row2.label(text=str(item.total_elements))
@@ -47,3 +47,5 @@ class BIM_PT_misc_utilities(bpy.types.Panel):
row.operator("bim.get_connected_system_elements")
row = layout.row()
row.operator("bim.draw_system_arrows")
row = layout.row()
row.operator("bim.clean_wireframes")
@@ -39,7 +39,12 @@ class AuthoringData:
@classmethod
def ifc_classes(cls):
results = []
classes = {e.is_a() for e in tool.Ifc.get().by_type("IfcElementType")}
classes = {
e.is_a()
for e in tool.Ifc.get().by_type("IfcElementType")
+ tool.Ifc.get().by_type("IfcDoorStyle")
+ tool.Ifc.get().by_type("IfcWindowStyle")
}
results.extend([(c, c, "") for c in sorted(classes)])
return results
@@ -112,7 +112,7 @@ def add_object(self, context):
class BIM_OT_add_object(Operator):
bl_idname = "mesh.add_grid"
bl_label = "Grid"
bl_label = "IFC Grid"
bl_options = {"REGISTER", "UNDO"}
u_spacing: FloatProperty(name="U Spacing", default=10)
@@ -120,6 +120,10 @@ class BIM_OT_add_object(Operator):
v_spacing: FloatProperty(name="V Spacing", default=10)
total_v: IntProperty(name="Number of V Grids", default=3)
@classmethod
def poll(cls, context):
return IfcStore.get_file()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
@@ -0,0 +1,108 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BlenderBIM Add-on.
#
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BlenderBIM Add-on is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
import math
import bmesh
import ifcopenshell
import ifcopenshell.api
import ifcopenshell.util.unit
import ifcopenshell.util.element
import ifcopenshell.util.representation
import mathutils.geometry
import blenderbim.bim.handler
import blenderbim.core.type
import blenderbim.core.root
import blenderbim.core.geometry
import blenderbim.tool as tool
from math import pi, degrees
from mathutils import Vector, Matrix
class MepGenerator:
def __init__(self, relating_type):
self.relating_type = relating_type
def generate(self):
self.file = tool.Ifc.get()
self.collection = bpy.context.view_layer.active_layer_collection.collection
if self.relating_type.is_a("IfcCableCarrierSegmentType"):
pass
elif self.relating_type.is_a("IfcCableSegmentType"):
pass
elif self.relating_type.is_a("IfcDuctSegmentType"):
dimensions = ifcopenshell.util.element.get_psets(self.relating_type).get("Pset_DuctSegmentTypeCommon") or {}
shape = dimensions.get("Shape") or dimensions.get("CrossSectionShape")
if shape == "RECTANGULAR":
self.width = dimensions.get("NominalDiameterOrWidth")
self.height = dimensions.get("NominalHeight")
self.length = 1
self.rotation = 0
return self.derive_from_cursor()
elif self.relating_type.is_a("IfcPipeSegmentType"):
pass
def derive_from_cursor(self):
self.location = bpy.context.scene.cursor.location
return self.create_rectangle_segment()
def create_rectangle_segment(self):
verts = [
Vector((0, self.width, 0)),
Vector((0, 0, 0)),
Vector((0, self.width, self.height)),
Vector((0, 0, self.height)),
Vector((self.length, self.width, 0)),
Vector((self.length, 0, 0)),
Vector((self.length, self.width, self.height)),
Vector((self.length, 0, self.height)),
]
faces = [
[1, 3, 2, 0],
[4, 6, 7, 5],
[1, 0, 4, 5],
[3, 7, 6, 2],
[0, 2, 6, 4],
[1, 5, 7, 3],
]
mesh = bpy.data.meshes.new(name="Segment")
mesh.from_pydata(verts, [], faces)
ifc_classes = ifcopenshell.util.type.get_applicable_entities(self.relating_type.is_a(), self.file.schema)
ifc_class = ifc_classes[0]
obj = bpy.data.objects.new(tool.Model.generate_occurrence_name(self.relating_type, ifc_class), mesh)
obj.location = self.location
obj.rotation_euler[2] = self.rotation
self.collection.objects.link(obj)
bpy.ops.bim.assign_class(
obj=obj.name,
ifc_class=ifc_class,
ifc_representation_class="IfcExtrudedAreaSolid/IfcRectangleProfileDef",
)
blenderbim.core.type.assign_type(tool.Ifc, tool.Type, element=tool.Ifc.get_entity(obj), type=self.relating_type)
element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
pset = ifcopenshell.api.run("pset.add_pset", self.file, product=element, name="EPset_Parametric")
ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"Engine": "BlenderBIM.Mep"})
obj.select_set(True)
return obj
@@ -25,7 +25,7 @@ import ifcopenshell.util.representation
import blenderbim.tool as tool
import blenderbim.core.type
import blenderbim.core.geometry
from . import wall, slab, profile
from . import wall, slab, profile, mep
from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.pset.data import Data as PsetData
from mathutils import Vector, Matrix
@@ -81,6 +81,9 @@ class AddTypeInstance(bpy.types.Operator):
elif material and material.is_a("IfcMaterialLayerSet"):
if self.generate_layered_element(ifc_class, relating_type):
return {"FINISHED"}
if relating_type.is_a("IfcFlowSegmentType"):
if mep.MepGenerator(relating_type).generate():
return {"FINISHED"}
building_obj = None
if len(context.selected_objects) == 1 and context.active_object:
@@ -108,7 +111,7 @@ class AddTypeInstance(bpy.types.Operator):
]
mesh = bpy.data.meshes.new(name="Instance")
mesh.from_pydata(verts, edges, faces)
obj = bpy.data.objects.new("Instance", mesh)
obj = bpy.data.objects.new(tool.Model.generate_occurrence_name(relating_type, instance_class), mesh)
obj.location = context.scene.cursor.location
collection = context.view_layer.active_layer_collection.collection
collection.objects.link(obj)
@@ -138,18 +138,18 @@ class DumbProfileGenerator:
[0, 2, 6, 4],
]
ifc_classes = ifcopenshell.util.type.get_applicable_entities(self.relating_type.is_a(), self.file.schema)
# Standard cases are deprecated, so let's cull them
ifc_class = [c for c in ifc_classes if "StandardCase" not in c][0]
mesh = bpy.data.meshes.new(name="Dumb Profile")
mesh.from_pydata(verts, edges, faces)
obj = bpy.data.objects.new("Profile", mesh)
obj = bpy.data.objects.new(tool.Model.generate_occurrence_name(self.relating_type, ifc_class), mesh)
obj.location = self.location
if self.collection_obj and self.collection_obj.BIMObjectProperties.ifc_definition_id:
obj.location[2] = self.collection_obj.location[2]
self.collection.objects.link(obj)
ifc_classes = ifcopenshell.util.type.get_applicable_entities(self.relating_type.is_a(), self.file.schema)
# Standard cases are deprecated, so let's cull them
ifc_class = [c for c in ifc_classes if "StandardCase" not in c][0]
obj.name = ifc_class[3:]
bpy.ops.bim.assign_class(obj=obj.name, ifc_class=ifc_class, should_add_representation=False)
if self.relating_type.is_a() in ["IfcBeamType", "IfcMemberType"]:
@@ -53,3 +53,8 @@ def update_ifc_class(self, context):
class BIMModelProperties(PropertyGroup):
ifc_class: bpy.props.EnumProperty(items=get_ifc_class, name="IFC Class", update=update_ifc_class)
relating_type: bpy.props.EnumProperty(items=get_relating_type, name="Relating Type")
occurrence_name_style: bpy.props.EnumProperty(
items=[("CLASS", "By Class", ""), ("TYPE", "By Type", ""), ("CUSTOM", "Custom", "")],
name="Occurrence Name Style",
)
occurrence_name_function: bpy.props.StringProperty(name="Occurrence Name Function")
@@ -278,19 +278,18 @@ class DumbSlabGenerator:
edges = []
faces = [[0, 3, 2, 1]]
ifc_classes = ifcopenshell.util.type.get_applicable_entities(self.relating_type.is_a(), self.file.schema)
# Standard cases are deprecated, so let's cull them
ifc_class = [c for c in ifc_classes if "StandardCase" not in c][0]
mesh = bpy.data.meshes.new(name="Dumb Slab")
mesh.from_pydata(verts, edges, faces)
obj = bpy.data.objects.new("Slab", mesh)
obj = bpy.data.objects.new(tool.Model.generate_occurrence_name(self.relating_type, ifc_class), mesh)
modifier = obj.modifiers.new("Slab Depth", "SOLIDIFY")
modifier.use_even_offset = True
modifier.offset = 1
modifier.thickness = self.depth
ifc_classes = ifcopenshell.util.type.get_applicable_entities(self.relating_type.is_a(), self.file.schema)
# Standard cases are deprecated, so let's cull them
ifc_class = [c for c in ifc_classes if "StandardCase" not in c][0]
obj.name = ifc_class[3:]
obj.location = self.location
if self.collection_obj and self.collection_obj.BIMObjectProperties.ifc_definition_id:
obj.location[2] = self.collection_obj.location[2] - self.depth
@@ -795,7 +795,7 @@ class DumbWallGenerator:
# Standard cases are deprecated, so let's cull them
ifc_class = [c for c in ifc_classes if "StandardCase" not in c][0]
obj = bpy.data.objects.new(ifc_class[3:], mesh)
obj = bpy.data.objects.new(tool.Model.generate_occurrence_name(self.relating_type, ifc_class), mesh)
obj.location = self.location
obj.rotation_euler[2] = self.rotation
if self.collection_obj and self.collection_obj.BIMObjectProperties.ifc_definition_id:
@@ -866,25 +866,24 @@ def calculate_quantities(usecase_path, ifc_file, settings):
width = obj.dimensions[1] / unit_scale
height = obj.dimensions[2] / unit_scale
if product.HasOpenings:
# TODO: calculate gross / net
gross_footprint_area = 0
net_footprint_area = 0
gross_side_area = 0
net_side_area = 0
gross_volume = 0
net_volume = 0
else:
bm = bmesh.new()
bm.from_mesh(obj.data)
bm.faces.ensure_lookup_table()
gross_footprint_area = sum([f.calc_area() for f in bm.faces if f.normal.z < -0.9])
net_footprint_area = gross_footprint_area
gross_side_area = sum([f.calc_area() for f in bm.faces if f.normal.y > 0.9])
net_side_area = gross_side_area
gross_volume = bm.calc_volume()
net_volume = gross_volume
bm.free()
bm_gross = bmesh.new()
bm_gross.from_mesh(obj.data)
bm_gross.faces.ensure_lookup_table()
bm_net = bmesh.new()
depsgraph = bpy.context.evaluated_depsgraph_get()
evaluated_mesh = obj.evaluated_get(depsgraph).data
bm_net.from_mesh(evaluated_mesh)
bm_net.faces.ensure_lookup_table()
gross_footprint_area = sum([f.calc_area() for f in bm_gross.faces if f.normal.z < -0.9])
net_footprint_area = sum([f.calc_area() for f in bm_net.faces if f.normal.z < -0.9])
gross_side_area = sum([f.calc_area() for f in bm_gross.faces if f.normal.y > 0.9])
net_side_area = sum([f.calc_area() for f in bm_net.faces if f.normal.y > 0.9])
gross_volume = bm_gross.calc_volume()
net_volume = bm_net.calc_volume()
bm_gross.free()
bm_net.free()
ifcopenshell.api.run(
"pset.edit_qto",
@@ -52,7 +52,7 @@ class BimTool(WorkSpaceTool):
)
def draw_settings(context, layout, tool):
if not AuthoringData.is_loaded:
if not AuthoringData.is_loaded and IfcStore.get_file():
AuthoringData.load()
row = layout.row(align=True)
@@ -20,6 +20,7 @@ import bpy
from . import ui, prop, operator
classes = (
operator.AddActor,
operator.AddAddress,
operator.AddAddressAttribute,
operator.AddOrganisation,
@@ -28,18 +29,22 @@ classes = (
operator.AddPersonAttribute,
operator.AddRole,
operator.ClearUser,
operator.DisableEditingActor,
operator.DisableEditingAddress,
operator.DisableEditingOrganisation,
operator.DisableEditingPerson,
operator.DisableEditingRole,
operator.EditActor,
operator.EditAddress,
operator.EditOrganisation,
operator.EditPerson,
operator.EditRole,
operator.EnableEditingActor,
operator.EnableEditingAddress,
operator.EnableEditingOrganisation,
operator.EnableEditingPerson,
operator.EnableEditingRole,
operator.RemoveActor,
operator.RemoveAddress,
operator.RemoveAddressAttribute,
operator.RemoveOrganisation,
@@ -52,6 +57,7 @@ classes = (
ui.BIM_PT_people,
ui.BIM_PT_organisations,
ui.BIM_PT_owner,
ui.BIM_PT_actor,
)
@@ -24,6 +24,7 @@ def refresh():
PeopleData.is_loaded = False
OrganisationsData.is_loaded = False
OwnerData.is_loaded = False
ActorData.is_loaded = False
class RolesAddressesData:
@@ -202,3 +203,30 @@ class OwnerData:
}
)
return results
class ActorData:
data = {}
is_loaded = False
@classmethod
def load(cls):
cls.data = {
"actor": cls.actor(),
"actors": cls.actors(),
}
cls.is_loaded = True
@classmethod
def actor(cls):
ifc_class = bpy.context.scene.BIMOwnerProperties.actor_type
return [(str(p.id()), p[0] or "Unnamed", "") for p in tool.Ifc.get().by_type(ifc_class)]
@classmethod
def actors(cls):
actors = []
props = bpy.context.scene.BIMOwnerProperties
for actor in tool.Ifc.get().by_type(props.actor_class, include_subtypes=False):
is_editing = props.active_actor_id == actor.id()
actors.append({"id": actor.id(), "name": actor.Name or "Unnamed", "is_editing": is_editing})
return actors
@@ -17,7 +17,6 @@
# 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.owner as core
import blenderbim.bim.handler
@@ -307,3 +306,52 @@ class ClearUser(bpy.types.Operator, Operator):
def _execute(self, context):
core.clear_user(tool.Owner)
class AddActor(bpy.types.Operator, Operator):
bl_idname = "bim.add_actor"
bl_label = "Add Actor"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
props = bpy.context.scene.BIMOwnerProperties
if props.actor:
core.add_actor(tool.Ifc, ifc_class=props.actor_class, actor=tool.Ifc.get().by_id(int(props.actor)))
class EnableEditingActor(bpy.types.Operator, Operator):
bl_idname = "bim.enable_editing_actor"
bl_label = "Enable Editing Actor"
bl_options = {"REGISTER", "UNDO"}
actor: bpy.props.IntProperty()
def _execute(self, context):
core.enable_editing_actor(tool.Owner, actor=tool.Ifc.get().by_id(self.actor))
class DisableEditingActor(bpy.types.Operator, Operator):
bl_idname = "bim.disable_editing_actor"
bl_label = "Disable Editing Actor"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
core.disable_editing_actor(tool.Owner)
class EditActor(bpy.types.Operator, Operator):
bl_idname = "bim.edit_actor"
bl_label = "Edit Actor"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
core.edit_actor(tool.Ifc, tool.Owner)
class RemoveActor(bpy.types.Operator, Operator):
bl_idname = "bim.remove_actor"
bl_label = "Remove Actor"
bl_options = {"REGISTER", "UNDO"}
actor: bpy.props.IntProperty()
def _execute(self, context):
core.remove_actor(tool.Ifc, actor=tool.Ifc.get().by_id(self.actor))
@@ -18,7 +18,7 @@
import bpy
from blenderbim.bim.prop import StrProperty, Attribute
from blenderbim.bim.module.owner.data import OwnerData
from blenderbim.bim.module.owner.data import OwnerData, ActorData
from bpy.types import PropertyGroup
from bpy.props import (
PointerProperty,
@@ -44,6 +44,20 @@ def get_user_organisation(self, context):
return OwnerData.data["user_organisation"]
def get_actor(self, context):
if not ActorData.is_loaded:
ActorData.load()
return ActorData.data["actor"]
def update_actor_type(self, context):
ActorData.data["actor"] = ActorData.actor()
def update_actor_class(self, context):
ActorData.data["actors"] = ActorData.actors()
class BIMOwnerProperties(PropertyGroup):
active_person_id: IntProperty(name="Active Person Id")
person_attributes: CollectionProperty(name="Person Attributes", type=Attribute)
@@ -64,3 +78,20 @@ class BIMOwnerProperties(PropertyGroup):
user_person: EnumProperty(items=get_user_person, name="Person")
user_organisation: EnumProperty(items=get_user_organisation, name="Organisation")
active_user_id: IntProperty(name="Active User Id")
active_actor_id: IntProperty(name="Active Actor Id")
actor_attributes: CollectionProperty(name="Actor Attributes", type=Attribute)
actor_class: EnumProperty(
items=(("IfcActor", "Actor", ""), ("IfcOccupant", "Occupant", "")),
name="Actor Type",
update=update_actor_class,
)
actor_type: EnumProperty(
items=(
("IfcPerson", "Person", ""),
("IfcOrganization", "Organisation", ""),
("IfcPersonAndOrganization", "User", ""),
),
name="Actor Type",
update=update_actor_type,
)
actor: EnumProperty(items=get_actor, name="Actor")
@@ -19,7 +19,7 @@
import bpy
import blenderbim.bim.helper
import blenderbim.tool as tool
from blenderbim.bim.module.owner.data import PeopleData, OrganisationsData, OwnerData
from blenderbim.bim.module.owner.data import PeopleData, OrganisationsData, OwnerData, ActorData
def draw_roles(box, parent):
@@ -227,3 +227,52 @@ class BIM_PT_owner(bpy.types.Panel):
row.operator("bim.set_user", icon="KEYFRAME_HLT", text="").user = user["id"]
op = row.operator("bim.remove_person_and_organisation", icon="X", text="")
op.person_and_organisation = user["id"]
class BIM_PT_actor(bpy.types.Panel):
bl_label = "IFC Actor"
bl_idname = "BIM_PT_actor"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_project_setup"
@classmethod
def poll(cls, context):
return tool.Ifc.get()
def draw(self, context):
if not ActorData.is_loaded:
ActorData.load()
self.props = context.scene.BIMOwnerProperties
self.layout.use_property_split = True
self.layout.use_property_decorate = False
row = self.layout.row(align=True)
row.prop(self.props, "actor_class", text="")
row.prop(self.props, "actor_type", text="")
if ActorData.data["actor"]:
row = self.layout.row(align=True)
row.prop(self.props, "actor", text="")
row.operator("bim.add_actor", icon="ADD", text="")
else:
self.layout.label(text="No users found.")
for actor in ActorData.data["actors"]:
self.draw_actor(actor)
def draw_actor(self, actor):
if actor["is_editing"]:
box = self.layout.box()
row = box.row(align=True)
row.operator("bim.edit_actor", icon="CHECKMARK")
row.operator("bim.disable_editing_actor", icon="CANCEL", text="")
blenderbim.bim.helper.draw_attributes(self.props.actor_attributes, box)
else:
row = self.layout.row(align=True)
row.label(text=actor["name"])
row.operator("bim.enable_editing_actor", icon="GREASEPENCIL", text="").actor = actor["id"]
row.operator("bim.remove_actor", icon="X", text="").actor = actor["id"]
@@ -20,9 +20,10 @@ import bpy
from . import ui, prop, operator
classes = (
operator.ExecuteIfcPatch,
operator.RunMigratePatch,
operator.SelectIfcPatchInput,
operator.SelectIfcPatchOutput,
operator.ExecuteIfcPatch,
operator.UpdateIfcPatchArguments,
prop.BIMPatchProperties,
ui.BIM_PT_patch,
@@ -19,6 +19,9 @@
import os
import bpy
import json
import blenderbim.tool as tool
import blenderbim.core.patch as core
import blenderbim.bim.handler
try:
import ifcpatch
@@ -114,3 +117,20 @@ class UpdateIfcPatchArguments(bpy.types.Operator):
new_attr.name = arg_name
new_attr.set_value(arg_info.get("default", new_attr.get_value_default()))
return {"FINISHED"}
class RunMigratePatch(bpy.types.Operator):
bl_idname = "bim.run_migrate_patch"
bl_label = "Run Migrate Patch"
infile: bpy.props.StringProperty()
outfile: bpy.props.StringProperty()
schema: bpy.props.StringProperty()
def execute(self, context):
core.run_migrate_patch(tool.Patch, infile=self.infile, outfile=self.outfile, schema=self.schema)
try:
bpy.ops.file.refresh()
except:
pass # Probably running in headless mode
blenderbim.bim.handler.refresh_ui_data()
return {"FINISHED"}
@@ -16,6 +16,7 @@
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import os
import bpy
import blenderbim.tool as tool
from blenderbim.bim.ifc import IfcStore
@@ -31,9 +32,32 @@ class ProjectData:
@classmethod
def load(cls):
cls.data = {"export_schema": cls.get_export_schema()}
cls.data = {
"export_schema": cls.get_export_schema(),
"template_file": cls.template_file(),
"last_saved": cls.last_saved(),
}
cls.is_loaded = True
@classmethod
def get_export_schema(cls):
return [(s, s, "") for s in IfcStore.schema_identifiers]
@classmethod
def template_file(cls):
files = os.listdir(os.path.join(bpy.context.scene.BIMProperties.data_dir, "libraries"))
results = [("0", "Blank Project", "")]
results.extend([(f, f.replace(".ifc", ""), "") for f in files if ".ifc" in f])
return results
@classmethod
def last_saved(cls):
ifc = tool.Ifc.get()
if not ifc:
return ""
try:
save_datetime = ifc.wrapped_data.header.file_name.time_stamp
save_date, save_time = save_datetime.split("T")
return f"{save_date} {':'.join(save_time.split(':')[0:2])}"
except:
return ""
@@ -27,6 +27,7 @@ import ifcopenshell.util.selector
import ifcopenshell.util.representation
import blenderbim.bim.handler
import blenderbim.tool as tool
import blenderbim.core.project as core
import blenderbim.core.context
import blenderbim.core.owner
from blenderbim.bim.ifc import IfcStore
@@ -45,80 +46,16 @@ class CreateProject(bpy.types.Operator):
def execute(self, context):
IfcStore.begin_transaction(self)
IfcStore.add_transaction_operation(self, rollback=self.rollback, commit=lambda data: True)
result = self._execute(context)
self.transaction_data = {"file": self.file}
self._execute(context)
self.transaction_data = {"file": tool.Ifc.get()}
IfcStore.add_transaction_operation(self, rollback=lambda data: True, commit=self.commit)
IfcStore.end_transaction(self)
return result
def _execute(self, context):
active_object = context.view_layer.objects.active
self.file = IfcStore.get_file()
if self.file:
return {"FINISHED"}
IfcStore.file = ifcopenshell.api.run(
"project.create_file", **{"version": context.scene.BIMProjectProperties.export_schema}
)
self.file = IfcStore.get_file()
if self.file.schema == "IFC2X3":
person = blenderbim.core.owner.add_person(tool.Ifc)
organisation = blenderbim.core.owner.add_organisation(tool.Ifc)
user = blenderbim.core.owner.add_person_and_organisation(tool.Ifc, person=person, organisation=organisation)
blenderbim.core.owner.set_user(tool.Owner, user=user)
project = bpy.data.objects.new(self.get_name("IfcProject", "My Project"), None)
site = bpy.data.objects.new(self.get_name("IfcSite", "My Site"), None)
building = bpy.data.objects.new(self.get_name("IfcBuilding", "My Building"), None)
building_storey = bpy.data.objects.new(self.get_name("IfcBuildingStorey", "My Storey"), None)
bpy.ops.bim.assign_class(obj=project.name, ifc_class="IfcProject")
blenderbim.core.unit.assign_scene_units(tool.Ifc, tool.Unit)
model = blenderbim.core.context.add_context(
tool.Ifc, context_type="Model", context_identifier="", target_view="", parent=0
)
body_context = blenderbim.core.context.add_context(
tool.Ifc, context_type="Model", context_identifier="Body", target_view="MODEL_VIEW", parent=model
)
blenderbim.core.context.add_context(
tool.Ifc, context_type="Model", context_identifier="Box", target_view="MODEL_VIEW", parent=model
)
plan = blenderbim.core.context.add_context(
tool.Ifc, context_type="Plan", context_identifier="", target_view="", parent=0
)
blenderbim.core.context.add_context(
tool.Ifc, context_type="Plan", context_identifier="Annotation", target_view="PLAN_VIEW", parent=plan
)
blenderbim.bim.handler.refresh_ui_data()
context.scene.BIMRootProperties.contexts = str(body_context.id())
bpy.ops.bim.assign_class(obj=site.name, ifc_class="IfcSite")
bpy.ops.bim.assign_class(obj=building.name, ifc_class="IfcBuilding")
bpy.ops.bim.assign_class(obj=building_storey.name, ifc_class="IfcBuildingStorey")
blenderbim.core.aggregate.assign_object(
tool.Ifc, tool.Aggregate, tool.Collector, relating_obj=project, related_obj=site
)
blenderbim.core.aggregate.assign_object(
tool.Ifc, tool.Aggregate, tool.Collector, relating_obj=site, related_obj=building
)
blenderbim.core.aggregate.assign_object(
tool.Ifc, tool.Aggregate, tool.Collector, relating_obj=building, related_obj=building_storey
)
context.view_layer.objects.active = active_object
return {"FINISHED"}
def get_name(self, ifc_class, name):
if not bpy.data.objects.get(f"{ifc_class}/{name}"):
return name
i = 2
while bpy.data.objects.get(f"{ifc_class}/{name} {i}"):
i += 1
return f"{name} {i}"
def _execute(self, context):
props = context.scene.BIMProjectProperties
template = None if props.template_file == "0" else props.template_file
core.create_project(tool.Ifc, tool.Project, schema=props.export_schema, template=template)
def rollback(self, data):
IfcStore.file = None
@@ -373,7 +310,11 @@ class AppendLibraryElement(bpy.types.Operator):
self.import_type_from_ifc(element, context)
elif element.is_a("IfcMaterial"):
self.import_material_from_ifc(element, context)
context.scene.BIMProjectProperties.library_elements[self.prop_index].is_appended = True
try:
context.scene.BIMProjectProperties.library_elements[self.prop_index].is_appended = True
except:
# TODO Remove this terrible code when I refactor this into the core
pass
blenderbim.bim.handler.purge_module_data()
return {"FINISHED"}
@@ -670,10 +611,14 @@ class LinkIfc(bpy.types.Operator):
bl_description = "Link a Blender file"
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
filter_glob: bpy.props.StringProperty(default="*.blend;*.blend1", options={"HIDDEN"})
use_relative_path: bpy.props.BoolProperty(name="Use Relative Path", default=False)
def execute(self, context):
new = context.scene.BIMProjectProperties.links.add()
new.name = self.filepath
filepath = self.filepath
if self.use_relative_path:
filepath = os.path.relpath(filepath, bpy.path.abspath("//"))
new.name = filepath
bpy.ops.bim.load_link(filepath=self.filepath)
return {"FINISHED"}
@@ -705,13 +650,16 @@ class UnloadLink(bpy.types.Operator):
filepath: bpy.props.StringProperty()
def execute(self, context):
filepath = self.filepath
if not os.path.isabs(filepath):
filepath = os.path.abspath(os.path.join(bpy.path.abspath("//"), filepath))
for collection in context.scene.collection.children:
if collection.library and collection.library.filepath == self.filepath:
if collection.library and collection.library.filepath == filepath:
context.scene.collection.children.unlink(collection)
for scene in bpy.data.scenes:
if scene.library and scene.library.filepath == self.filepath:
if scene.library and scene.library.filepath == filepath:
bpy.data.scenes.remove(scene)
link = context.scene.BIMProjectProperties.links.get(self.filepath)
link = context.scene.BIMProjectProperties.links.get(filepath)
link.is_loaded = False
return {"FINISHED"}
@@ -724,16 +672,19 @@ class LoadLink(bpy.types.Operator):
filepath: bpy.props.StringProperty()
def execute(self, context):
filepath = self.filepath
if not os.path.isabs(filepath):
filepath = os.path.abspath(os.path.join(bpy.path.abspath("//"), filepath))
with bpy.data.libraries.load(self.filepath, link=True) as (data_from, data_to):
data_to.scenes = data_from.scenes
for scene in bpy.data.scenes:
if not scene.library or scene.library.filepath != self.filepath:
if not scene.library or scene.library.filepath != filepath:
continue
for child in scene.collection.children:
if "IfcProject" not in child.name:
continue
bpy.data.scenes[0].collection.children.link(child)
link = context.scene.BIMProjectProperties.links.get(self.filepath)
link = context.scene.BIMProjectProperties.links.get(filepath)
link.is_loaded = True
return {"FINISHED"}
@@ -38,6 +38,12 @@ def get_export_schema(self, context):
return ProjectData.data["export_schema"]
def get_template_file(self, context):
if not ProjectData.is_loaded:
ProjectData.load()
return ProjectData.data["template_file"]
def update_filter_mode(self, context):
self.filter_categories.clear()
if self.filter_mode == "NONE":
@@ -132,6 +138,7 @@ class BIMProjectProperties(PropertyGroup):
links: CollectionProperty(name="Links", type=Link)
active_link_index: IntProperty(name="Active Link Index")
export_schema: EnumProperty(items=get_export_schema, name="IFC Schema")
template_file: EnumProperty(items=get_template_file, name="Template File")
def get_library_element_index(self, lib_element):
return next((i for i in range(len(self.library_elements)) if self.library_elements[i] == lib_element))
@@ -28,7 +28,7 @@ class BIM_PT_project(Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_project_setup"
bl_parent_id = "BIM_PT_project_info"
def draw(self, context):
if not ProjectData.is_loaded:
@@ -94,8 +94,7 @@ class BIM_PT_project(Panel):
props = context.scene.BIMProperties
pprops = context.scene.BIMProjectProperties
row = self.layout.row(align=True)
row.label(text="IFC Filename", icon="FILE")
row.label(text=os.path.basename(props.ifc_file) or "No File Found")
row.label(text=os.path.basename(props.ifc_file) or "No File Found", icon="FILE")
if IfcStore.get_file():
row.prop(pprops, "is_authoring", icon="MODIFIER", text="")
@@ -136,20 +135,21 @@ class BIM_PT_project(Panel):
row = self.layout.row(align=True)
row.label(text="File Not Loaded", icon="ERROR")
if props.ifc_file:
row = self.layout.row(align=True)
row.label(text="Saved", icon="EXPORT")
row.label(text=ProjectData.data["last_saved"])
row = self.layout.row(align=True)
row.prop(props, "ifc_file", text="")
row.operator("bim.reload_ifc_file", icon="FILE_REFRESH", text="")
row.operator("bim.select_ifc_file", icon="FILE_FOLDER", text="")
row = self.layout.row(align=True)
row.prop(props, "ifc_file", text="")
row.operator("bim.reload_ifc_file", icon="FILE_REFRESH", text="")
op = row.operator("export_ifc.bim", icon="FILE_TICK", text="")
op = row.operator("export_ifc.bim", icon="EXPORT", text="Save Project")
op.should_save_as = False
op = row.operator("export_ifc.bim", icon="FILE_TICK", text="Save As")
op.should_save_as = True
row.operator("bim.select_ifc_file", icon="FILE_FOLDER", text="")
row = self.layout.row(align=True)
row.prop(props, "schema_dir")
row.operator("bim.select_schema_dir", icon="FILE_FOLDER", text="")
row = self.layout.row(align=True)
row.prop(props, "data_dir")
row.operator("bim.select_data_dir", icon="FILE_FOLDER", text="")
def draw_create_project_ui(self, context):
props = context.scene.BIMProperties
@@ -164,6 +164,9 @@ class BIM_PT_project(Panel):
row.prop(props, "area_unit", text="Area Unit")
row = self.layout.row()
row.prop(props, "volume_unit", text="Volume Unit")
row = self.layout.row()
row.prop(pprops, "template_file", text="Template")
row = self.layout.row(align=True)
row.operator("bim.create_project")
row.operator("bim.load_project")
@@ -281,11 +284,12 @@ class BIM_UL_links(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
if item:
row = layout.row(align=True)
row.label(text=item.name)
if item.is_loaded:
row.label(text=item.name)
op = row.operator("bim.unload_link", text="", icon="UNLINKED")
op.filepath = item.name
else:
row.prop(item, "name", text="")
op = row.operator("bim.load_link", text="", icon="LINKED")
op.filepath = item.name
op = row.operator("bim.unlink_ifc", text="", icon="X")
@@ -192,7 +192,6 @@ class BIM_PT_material_psets(Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "material"
bl_parent_id = "BIM_PT_object_metadata"
@classmethod
def poll(cls, context):
@@ -20,13 +20,12 @@ import bpy
from . import ui, prop, operator
classes = (
operator.EnableReassignClass,
operator.DisableReassignClass,
operator.ReassignClass,
operator.AssignClass,
operator.UnassignClass,
operator.UnlinkObject,
operator.CopyClass,
operator.DisableReassignClass,
operator.EnableReassignClass,
operator.ReassignClass,
operator.UnlinkObject,
prop.BIMRootProperties,
ui.BIM_PT_class,
)
@@ -32,12 +32,47 @@ class IfcClassData:
@classmethod
def load(cls):
cls.is_loaded = True
cls.data = {
"contexts": cls.contexts(),
"has_entity": cls.has_entity(),
"name": cls.name(),
"ifc_class": cls.ifc_class(),
}
cls.data = {}
cls.data["ifc_products"] = cls.ifc_products()
cls.data["ifc_classes"] = cls.ifc_classes()
cls.data["contexts"] = cls.contexts()
cls.data["has_entity"] = cls.has_entity()
cls.data["name"] = cls.name()
cls.data["ifc_class"] = cls.ifc_class()
@classmethod
def ifc_products(cls):
products = [
"IfcElement",
"IfcElementType",
"IfcSpatialElement",
"IfcGroup",
"IfcStructuralItem",
"IfcContext",
"IfcAnnotation",
"IfcRelSpaceBoundary",
]
if tool.Ifc.get_schema() == "IFC2X3":
products = [
"IfcElement",
"IfcElementType",
"IfcSpatialStructureElement",
"IfcGroup",
"IfcStructuralItem",
"IfcAnnotation",
"IfcRelSpaceBoundary",
]
return [(e, e, "") for e in products]
@classmethod
def ifc_classes(cls):
ifc_product = bpy.context.scene.BIMRootProperties.ifc_product
declaration = tool.Ifc.schema().declaration_by_name(ifc_product)
declarations = ifcopenshell.util.schema.get_subtypes(declaration)
names = [d.name() for d in declarations]
if ifc_product == "IfcElementType":
names.extend(("IfcDoorStyle", "IfcWindowStyle"))
return [(c, c, "") for c in sorted(names)]
@classmethod
def contexts(cls):
@@ -113,7 +113,7 @@ class ReassignClass(bpy.types.Operator):
return {"FINISHED"}
class AssignClass(bpy.types.Operator):
class AssignClass(bpy.types.Operator, Operator):
bl_idname = "bim.assign_class"
bl_label = "Assign IFC Class"
bl_options = {"REGISTER", "UNDO"}
@@ -126,178 +126,29 @@ class AssignClass(bpy.types.Operator):
should_add_representation: bpy.props.BoolProperty(default=True)
ifc_representation_class: bpy.props.StringProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
objects = [bpy.data.objects.get(self.obj)] if self.obj else context.selected_objects
self.file = IfcStore.get_file()
if not self.ifc_class:
self.ifc_class = context.scene.BIMRootProperties.ifc_class
self.declaration = IfcStore.get_schema().declaration_by_name(self.ifc_class)
if self.predefined_type == "USERDEFINED":
self.predefined_type = self.userdefined_type
ifc_class = self.ifc_class or context.scene.BIMRootProperties.ifc_class
predefined_type = self.userdefined_type if self.predefined_type == "USERDEFINED" else self.predefined_type
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)
active_object = context.active_object
for obj in objects:
self.assign_class(context, obj)
return {"FINISHED"}
def assign_class(self, context, obj):
if obj.BIMObjectProperties.ifc_definition_id:
return
product = ifcopenshell.api.run(
"root.create_entity",
self.file,
**{
"ifc_class": self.ifc_class,
"predefined_type": self.predefined_type or None,
"name": obj.name,
},
)
obj.name = "{}/{}".format(product.is_a(), obj.name)
IfcStore.link_element(product, obj)
if self.should_add_representation:
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)
blenderbim.core.geometry.add_representation(
core.assign_class(
tool.Ifc,
tool.Geometry,
tool.Style,
tool.Surveyor,
tool.Collector,
tool.Root,
obj=obj,
ifc_class=ifc_class,
predefined_type=predefined_type,
should_add_representation=self.should_add_representation,
context=ifc_context,
ifc_representation_class=self.ifc_representation_class,
profile_set_usage=None,
)
if product.is_a("IfcElementType"):
tool.Collector.assign(obj)
elif product.is_a("IfcOpeningElement"):
obj.display_type = "WIRE"
tool.Collector.assign(obj)
elif (
product.is_a("IfcSpatialElement")
or product.is_a("IfcSpatialStructureElement")
or product.is_a("IfcProject")
or product.is_a("IfcContext")
):
self.place_in_spatial_collection(obj, context)
elif product.is_a("IfcStructuralItem"):
if product.is_a("IfcStructuralMember"):
self.place_in_structural_items_collection(obj, context, structural_collection="Members")
elif product.is_a("IfcStructuralConnection"):
self.place_in_structural_items_collection(obj, context, structural_collection="Connections")
else:
self.assign_potential_spatial_container(obj)
context.view_layer.objects.active = obj
def place_in_spatial_collection(self, obj, context):
for collection in obj.users_collection:
if collection.name == obj.name:
return
parent_collection = None
for collection in obj.users_collection:
collection.objects.unlink(obj)
if "Ifc" in collection.name:
parent_collection = collection
collection = bpy.data.collections.new(obj.name)
collection.objects.link(obj)
if parent_collection:
parent_collection.children.link(collection)
blenderbim.core.aggregate.assign_object(
tool.Ifc,
tool.Aggregate,
tool.Collector,
relating_obj=bpy.data.objects.get(parent_collection.name),
related_obj=obj,
)
else:
context.scene.collection.children.link(collection)
def place_in_structural_items_collection(self, obj, context, structural_collection):
for project in [c for c in context.view_layer.layer_collection.children if "IfcProject" in c.name]:
if not [c for c in project.children if "StructuralItems" in c.name]:
members = bpy.data.collections.new("Members")
connections = bpy.data.collections.new("Connections")
items = bpy.data.collections.new("StructuralItems")
items.children.link(members)
items.children.link(connections)
project.collection.children.link(items)
for coll in [c for c in project.children if "StructuralItems" in c.name]:
for collection in [c for c in coll.children if structural_collection in c.name]:
for user_collection in obj.users_collection:
user_collection.objects.unlink(obj)
collection.collection.objects.link(obj)
break
break
break
def assign_potential_spatial_container(self, obj):
for collection in obj.users_collection:
if "Ifc" not in collection.name or collection.name == obj.name:
continue
spatial_obj = bpy.data.objects.get(collection.name)
if spatial_obj and spatial_obj.BIMObjectProperties.ifc_definition_id:
element = self.file.by_id(spatial_obj.BIMObjectProperties.ifc_definition_id)
if self.file.schema != "IFC2X3" and not element.is_a("IfcSpatialElement"):
continue
elif self.file.schema == "IFC2X3" and not element.is_a("IfcSpatialStructureElement"):
continue
blenderbim.core.spatial.assign_container(
tool.Ifc,
tool.Collector,
tool.Spatial,
structure_obj=spatial_obj,
element_obj=obj,
)
break
class UnassignClass(bpy.types.Operator):
bl_idname = "bim.unassign_class"
bl_label = "Unassign IFC Class"
bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
self.file = IfcStore.get_file()
if self.obj:
objects = [bpy.data.objects.get(self.obj)]
else:
objects = context.selected_objects
for obj in objects:
if not obj.BIMObjectProperties.ifc_definition_id:
continue
product = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
self.remove_voids(product, obj)
IfcStore.unlink_element(product, obj)
if product.is_a("IfcGridAxis"):
ifcopenshell.api.run("grid.remove_grid_axis", self.file, **{"axis": product})
elif product.is_a("IfcGrid"):
grid_collection = bpy.data.collections.get(obj.name)
for axis_collection in grid_collection.children:
for axis_obj in axis_collection.objects:
bpy.ops.bim.unassign_class(obj=axis_obj.name)
else:
ifcopenshell.api.run("root.remove_product", self.file, **{"product": product})
if "/" in obj.name and obj.name[0:3] == "Ifc":
obj.name = "/".join(obj.name.split("/")[1:])
if obj.data and obj.data.name == "Void":
bpy.data.objects.remove(obj)
return {"FINISHED"}
def remove_voids(self, product, obj):
if product.id() not in VoidData.products:
VoidData.load(self.file, product.id())
for opening_id in VoidData.products[product.id()]:
bpy.ops.bim.remove_opening(opening_id=opening_id, obj=obj.name)
context.view_layer.objects.active = active_object
class UnlinkObject(bpy.types.Operator):
@@ -33,17 +33,11 @@ from bpy.props import (
CollectionProperty,
)
products_enum = []
classes_enum = []
types_enum = []
def purge():
global products_enum
global classes_enum
global types_enum
products_enum = []
classes_enum = []
types_enum = []
@@ -61,10 +55,9 @@ def getIfcPredefinedTypes(self, context):
return types_enum
def refreshClasses(self, context):
global classes_enum
classes_enum.clear()
enum = getIfcClasses(self, context)
def refresh_classes(self, context):
IfcClassData.load()
enum = get_ifc_classes(self, context)
context.scene.BIMRootProperties.ifc_class = enum[0][0]
@@ -76,38 +69,16 @@ def refreshPredefinedTypes(self, context):
context.scene.BIMRootProperties.ifc_predefined_type = enum[0][0]
def getIfcProducts(self, context):
global products_enum
file = IfcStore.get_file()
if len(products_enum) < 1:
products_enum.extend(
[
(e, e, "")
for e in [
"IfcElement",
"IfcElementType",
"IfcSpatialElement",
"IfcGroup",
"IfcStructuralItem",
"IfcContext",
"IfcAnnotation",
"IfcRelSpaceBoundary",
]
]
)
if file.schema == "IFC2X3":
products_enum[2] = ("IfcSpatialStructureElement", "IfcSpatialStructureElement", "")
return products_enum
def get_ifc_products(self, context):
if not IfcClassData.is_loaded:
IfcClassData.load()
return IfcClassData.data["ifc_products"]
def getIfcClasses(self, context):
global classes_enum
file = IfcStore.get_file()
if len(classes_enum) < 1 and file:
declaration = IfcStore.get_schema().declaration_by_name(context.scene.BIMRootProperties.ifc_product)
declarations = ifcopenshell.util.schema.get_subtypes(declaration)
classes_enum.extend([(c, c, "") for c in sorted([d.name() for d in declarations])])
return classes_enum
def get_ifc_classes(self, context):
if not IfcClassData.is_loaded:
IfcClassData.load()
return IfcClassData.data["ifc_classes"]
def get_contexts(self, context):
@@ -118,7 +89,7 @@ def get_contexts(self, context):
class BIMRootProperties(PropertyGroup):
contexts: EnumProperty(items=get_contexts, name="Contexts")
ifc_product: EnumProperty(items=getIfcProducts, name="Products", update=refreshClasses)
ifc_class: EnumProperty(items=getIfcClasses, name="Class", update=refreshPredefinedTypes)
ifc_product: EnumProperty(items=get_ifc_products, name="Products", update=refresh_classes)
ifc_class: EnumProperty(items=get_ifc_classes, name="Class", update=refreshPredefinedTypes)
ifc_predefined_type: EnumProperty(items=getIfcPredefinedTypes, name="Predefined Type", default=None)
ifc_userdefined_type: StringProperty(name="Userdefined Type")
@@ -65,10 +65,6 @@ class BIM_PT_class(Panel):
row.operator("bim.unlink_object", icon="UNLINKED", text="")
if IfcStore.get_file().by_id(props.ifc_definition_id).is_a("IfcRoot"):
row.operator("bim.enable_reassign_class", icon="GREASEPENCIL", text="")
if context.selected_objects:
row.operator("bim.unassign_class", icon="X", text="")
else:
row.operator("bim.unassign_class", icon="X", text="").obj = context.active_object.name
else:
ifc_predefined_types = root_prop.getIfcPredefinedTypes(context.scene.BIMRootProperties, context)
self.draw_class_dropdowns(context, ifc_predefined_types)
@@ -28,7 +28,7 @@ class BIM_PT_search(Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_utilities"
bl_parent_id = "BIM_PT_collaboration"
@classmethod
def poll(cls, context):
@@ -20,86 +20,88 @@ import bpy
from . import ui, prop, operator
classes = (
operator.AddWorkPlan,
operator.EditWorkPlan,
operator.RemoveWorkPlan,
operator.EnableEditingWorkPlan,
operator.DisableEditingWorkPlan,
operator.EnableEditingWorkPlanSchedules,
operator.AssignWorkSchedule,
operator.UnassignWorkSchedule,
operator.AddWorkSchedule,
operator.EditWorkSchedule,
operator.RemoveWorkSchedule,
operator.EnableEditingWorkSchedule,
operator.EnableEditingTasks,
operator.DisableEditingWorkSchedule,
operator.DisableEditingSequence,
operator.EditSequenceAttributes,
operator.EditSequenceTimeLag,
operator.EnableEditingSequenceAttributes,
operator.EnableEditingSequenceTimeLag,
operator.AddSummaryTask,
operator.AddTask,
operator.AddTaskColumn,
operator.AddTimePeriod,
operator.AddWorkCalendar,
operator.EditWorkCalendar,
operator.EditWorkTime,
operator.RemoveWorkCalendar,
operator.RemoveWorkTime,
operator.UnassignRecurrencePattern,
operator.UnassignLagTime,
operator.RemoveTimePeriod,
operator.EnableEditingWorkCalendar,
operator.EnableEditingWorkTime,
operator.EnableEditingWorkCalendarTimes,
operator.DisableEditingWorkCalendar,
operator.DisableEditingWorkTime,
operator.AddWorkPlan,
operator.AddWorkSchedule,
operator.AddWorkTime,
operator.AssignLagTime,
operator.AssignRecurrencePattern,
operator.AddTimePeriod,
operator.AddTask,
operator.AddSummaryTask,
operator.ExpandTask,
operator.ContractTask,
operator.RemoveTask,
operator.EnableEditingTask,
operator.DisableEditingTask,
operator.DisableEditingTaskTime,
operator.EditTask,
operator.CopyTaskAttribute,
operator.AssignPredecessor,
operator.AssignSuccessor,
operator.UnassignPredecessor,
operator.UnassignSuccessor,
operator.EnableEditingTaskTime,
operator.EnableEditingTaskCalendar,
operator.EnableEditingTaskSequence,
operator.EditTaskTime,
operator.EditTaskCalendar,
operator.RemoveTaskCalendar,
operator.AssignProduct,
operator.UnassignProduct,
operator.AssignProcess,
operator.UnassignProcess,
operator.GenerateGanttChart,
operator.ImportP6,
operator.ImportP6XER,
operator.ImportPP,
operator.ImportMSP,
operator.LoadTaskProperties,
operator.SelectTaskRelatedProducts,
operator.VisualiseWorkScheduleDate,
operator.VisualiseWorkScheduleDateRange,
operator.RecalculateSchedule,
operator.AssignProduct,
operator.AssignRecurrencePattern,
operator.AssignSuccessor,
operator.AssignWorkSchedule,
operator.BlenderBIM_DatePicker,
operator.BlenderBIM_DatePickerSetDate,
operator.BlenderBIM_RedrawDatePicker,
operator.AddTaskColumn,
operator.RemoveTaskColumn,
operator.SetTaskSortColumn,
operator.LoadTaskInputs,
operator.LoadTaskResources,
operator.LoadTaskOutputs,
operator.CalculateTaskDuration,
operator.ContractTask,
operator.CopyTaskAttribute,
operator.DisableEditingSequence,
operator.DisableEditingTask,
operator.DisableEditingTaskTime,
operator.DisableEditingWorkCalendar,
operator.DisableEditingWorkPlan,
operator.DisableEditingWorkSchedule,
operator.DisableEditingWorkTime,
operator.EditSequenceAttributes,
operator.EditSequenceTimeLag,
operator.EditTask,
operator.EditTaskCalendar,
operator.EditTaskTime,
operator.EditWorkCalendar,
operator.EditWorkPlan,
operator.EditWorkSchedule,
operator.EditWorkTime,
operator.EnableEditingSequenceAttributes,
operator.EnableEditingSequenceTimeLag,
operator.EnableEditingTask,
operator.EnableEditingTaskCalendar,
operator.EnableEditingTaskSequence,
operator.EnableEditingTaskTime,
operator.EnableEditingTasks,
operator.EnableEditingWorkCalendar,
operator.EnableEditingWorkCalendarTimes,
operator.EnableEditingWorkPlan,
operator.EnableEditingWorkPlanSchedules,
operator.EnableEditingWorkSchedule,
operator.EnableEditingWorkTime,
operator.ExpandTask,
operator.ExportMSP,
operator.ExportP6,
operator.GenerateGanttChart,
operator.ImportMSP,
operator.ImportP6,
operator.ImportP6XER,
operator.ImportPP,
operator.LoadTaskInputs,
operator.LoadTaskOutputs,
operator.LoadTaskProperties,
operator.LoadTaskResources,
operator.RecalculateSchedule,
operator.RemoveTask,
operator.RemoveTaskCalendar,
operator.RemoveTaskColumn,
operator.RemoveTimePeriod,
operator.RemoveWorkCalendar,
operator.RemoveWorkPlan,
operator.RemoveWorkSchedule,
operator.RemoveWorkTime,
operator.SelectTaskRelatedProducts,
operator.SetTaskSortColumn,
operator.UnassignLagTime,
operator.UnassignPredecessor,
operator.UnassignProcess,
operator.UnassignProduct,
operator.UnassignRecurrencePattern,
operator.UnassignSuccessor,
operator.UnassignWorkSchedule,
operator.VisualiseWorkScheduleDate,
operator.VisualiseWorkScheduleDateRange,
prop.WorkPlan,
prop.BIMWorkPlanProperties,
prop.Task,
@@ -124,6 +126,11 @@ classes = (
)
def menu_func_export(self, context):
self.layout.operator(operator.ExportP6.bl_idname, text="P6 (.xml)")
self.layout.operator(operator.ExportMSP.bl_idname, text="Microsoft Project (.xml)")
def menu_func_import(self, context):
self.layout.operator(operator.ImportP6.bl_idname, text="P6 (.xml)")
self.layout.operator(operator.ImportP6XER.bl_idname, text="P6 (.xer)")
@@ -138,6 +145,7 @@ def register():
bpy.types.Scene.BIMWorkCalendarProperties = bpy.props.PointerProperty(type=prop.BIMWorkCalendarProperties)
bpy.types.Scene.DatePickerProperties = bpy.props.PointerProperty(type=prop.DatePickerProperties)
bpy.types.TextCurve.BIMDateTextProperties = bpy.props.PointerProperty(type=prop.BIMDateTextProperties)
bpy.types.TOPBAR_MT_file_export.append(menu_func_export)
bpy.types.TOPBAR_MT_file_import.append(menu_func_import)
@@ -148,4 +156,5 @@ def unregister():
del bpy.types.Scene.BIMWorkCalendarProperties
del bpy.types.Scene.DatePickerProperties
del bpy.types.TextCurve.BIMDateTextProperties
bpy.types.TOPBAR_MT_file_export.remove(menu_func_export)
bpy.types.TOPBAR_MT_file_import.remove(menu_func_import)
@@ -39,6 +39,19 @@ from ifcopenshell.api.sequence.data import Data
from ifcopenshell.api.resource.data import Data as ResourceData
def animate_text(scene, context):
data = bpy.data.curves.get("Timeline")
if not data or not bpy.data.objects.get("Timeline"):
self.remove_text_animation_handler()
scene.frame_current
props = data.BIMDateTextProperties
start = parser.parse(props.start, dayfirst=True, fuzzy=True)
finish = parser.parse(props.finish, dayfirst=True, fuzzy=True)
duration = finish - start
frame_date = (((scene.frame_current - props.start_frame) / props.total_frames) * duration) + start
data.body = frame_date.date().isoformat()
class AddWorkPlan(bpy.types.Operator):
bl_idname = "bim.add_work_plan"
bl_label = "Add Work Plan"
@@ -976,6 +989,7 @@ class GenerateGanttChart(bpy.types.Operator):
data = {
"pID": task.id(),
"pName": task.Name,
"pCaption": task.Name,
"pStart": task.TaskTime.ScheduleStart if task.TaskTime else "",
"pEnd": task.TaskTime.ScheduleFinish if task.TaskTime else "",
"pPlanStart": task.TaskTime.ScheduleStart if task.TaskTime else "",
@@ -986,6 +1000,7 @@ class GenerateGanttChart(bpy.types.Operator):
"pParent": task.Nests[0].RelatingObject.id() if task.Nests else 0,
"pOpen": 1,
"pCost": 1,
"ifcduration": task.TaskTime.ScheduleDuration if task.TaskTime else "",
}
if task.TaskTime and task.TaskTime.IsCritical:
data["pClass"] = "gtaskred"
@@ -1196,6 +1211,65 @@ class ImportMSP(bpy.types.Operator, ImportHelper):
return {"FINISHED"}
class ExportMSP(bpy.types.Operator, ImportHelper):
bl_idname = "export_msp.bim"
bl_label = "Export MSP"
bl_options = {"REGISTER", "UNDO"}
filename_ext = ".xml"
filter_glob: bpy.props.StringProperty(default="*.xml", options={"HIDDEN"})
holiday_start_date: bpy.props.StringProperty(default="2022-01-01", name="Holiday Start Date")
holiday_finish_date: bpy.props.StringProperty(default="2023-01-01", name="Holiday Finish Date")
@classmethod
def poll(cls, context):
ifc_file = IfcStore.get_file()
return ifc_file is not None
def execute(self, context):
from ifc4d.ifc2msp import Ifc2Msp
self.file = IfcStore.get_file()
start = time.time()
ifc2msp = Ifc2Msp()
ifc2msp.work_schedule = self.file.by_type("IfcWorkSchedule")[0]
ifc2msp.xml = bpy.path.ensure_ext(self.filepath, ".xml")
ifc2msp.file = self.file
ifc2msp.holiday_start_date = parser.parse(self.holiday_start_date).date()
ifc2msp.holiday_finish_date = parser.parse(self.holiday_finish_date).date()
ifc2msp.execute()
print("Export finished in {:.2f} seconds".format(time.time() - start))
return {"FINISHED"}
class ExportP6(bpy.types.Operator, ImportHelper):
bl_idname = "export_p6.bim"
bl_label = "Export P6"
bl_options = {"REGISTER", "UNDO"}
filename_ext = ".xml"
filter_glob: bpy.props.StringProperty(default="*.xml", options={"HIDDEN"})
holiday_start_date: bpy.props.StringProperty(default="2022-01-01", name="Holiday Start Date")
holiday_finish_date: bpy.props.StringProperty(default="2023-01-01", name="Holiday Finish Date")
@classmethod
def poll(cls, context):
ifc_file = IfcStore.get_file()
return ifc_file is not None
def execute(self, context):
from ifc4d.ifc2p6 import Ifc2P6
self.file = IfcStore.get_file()
start = time.time()
ifc2p6 = Ifc2P6()
ifc2p6.xml = bpy.path.ensure_ext(self.filepath, ".xml")
ifc2p6.file = self.file
ifc2p6.holiday_start_date = parser.parse(self.holiday_start_date).date()
ifc2p6.holiday_finish_date = parser.parse(self.holiday_finish_date).date()
ifc2p6.execute()
print("Export finished in {:.2f} seconds".format(time.time() - start))
return {"FINISHED"}
class EnableEditingWorkCalendarTimes(bpy.types.Operator):
bl_idname = "bim.enable_editing_work_calendar_times"
bl_label = "Enable Editing Work Calendar Times"
@@ -1607,6 +1681,10 @@ class EnableEditingSequenceTimeLag(bpy.types.Operator):
def import_attributes(self, name, prop, data):
if name == "LagValue":
prop = self.props.time_lag_attributes.add()
prop.name = name
prop.is_null = data[name] is None
prop.is_optional = False
if isinstance(data[name], isodate.Duration):
prop.data_type = "string"
prop.string_value = (
@@ -1841,22 +1919,10 @@ class VisualiseWorkScheduleDateRange(bpy.types.Operator):
obj.data.BIMDateTextProperties.total_frames = int(self.total_frames)
obj.data.BIMDateTextProperties.start = self.props.visualisation_start
obj.data.BIMDateTextProperties.finish = self.props.visualisation_finish
bpy.app.handlers.frame_change_post.append(self.animate_text)
bpy.app.handlers.frame_change_post.append(animate_text)
def remove_text_animation_handler(self):
bpy.app.handlers.frame_change_post.remove(self.animate_text)
def animate_text(self, scene, context):
data = bpy.data.curves.get("Timeline")
if not data or not bpy.data.objects.get("Timeline"):
self.remove_text_animation_handler()
scene.frame_current
props = data.BIMDateTextProperties
start = parser.parse(props.start, dayfirst=True, fuzzy=True)
finish = parser.parse(props.finish, dayfirst=True, fuzzy=True)
duration = finish - start
frame_date = (((scene.frame_current - props.start_frame) / props.total_frames) * duration) + start
data.body = frame_date.date().isoformat()
bpy.app.handlers.frame_change_post.remove(animate_text)
def animate_input(self, obj, product_frame):
if product_frame["type"] in ["LOGISTIC", "MOVE", "DISPOSAL"]:
@@ -17,6 +17,7 @@
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
import isodate
import ifcopenshell.api
import ifcopenshell.util.attribute
from blenderbim.bim.ifc import IfcStore
@@ -184,6 +185,16 @@ def updateTaskDuration(self, context):
props = context.scene.BIMWorkScheduleProperties
if not props.is_task_update_enabled:
return
if self.duration == "-":
return
try:
isodate.parse_duration(self.duration),
except:
self.duration = "-"
return
self.file = IfcStore.get_file()
task = self.file.by_id(self.ifc_definition_id)
if task.TaskTime:
@@ -27,7 +27,7 @@ class BIM_PT_spatial(Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
bl_parent_id = "BIM_PT_geometry_object"
bl_parent_id = "BIM_PT_object_metadata"
@classmethod
def poll(cls, context):
@@ -21,21 +21,30 @@ from . import ui, prop, operator
classes = (
operator.AddStyle,
operator.EnableEditingStyle,
operator.DisableEditingStyle,
operator.DisableEditingStyles,
operator.EditStyle,
operator.EnableEditingStyle,
operator.LoadStyles,
operator.RemoveStyle,
operator.UpdateStyleColours,
operator.UnlinkStyle,
operator.UpdateStyleColours,
operator.UpdateStyleTextures,
prop.Style,
prop.BIMStylesProperties,
prop.BIMStyleProperties,
ui.BIM_PT_styles,
ui.BIM_PT_style,
ui.BIM_PT_style_attributes,
ui.BIM_UL_styles,
)
def register():
bpy.types.Scene.BIMStylesProperties = bpy.props.PointerProperty(type=prop.BIMStylesProperties)
bpy.types.Material.BIMStyleProperties = bpy.props.PointerProperty(type=prop.BIMStyleProperties)
def unregister():
del bpy.types.Scene.BIMStylesProperties
del bpy.types.Material.BIMStyleProperties
@@ -18,12 +18,33 @@
import bpy
import blenderbim.tool as tool
import ifcopenshell
def refresh():
StylesData.is_loaded = False
StyleAttributesData.is_loaded = False
class StylesData:
data = {}
is_loaded = False
@classmethod
def load(cls):
cls.data = {"style_types": cls.style_types(), "total_styles": cls.total_styles()}
@classmethod
def style_types(cls):
declaration = tool.Ifc.schema().declaration_by_name("IfcPresentationStyle")
declarations = ifcopenshell.util.schema.get_subtypes(declaration)
return [(c, c, "") for c in sorted([d.name() for d in declarations])]
@classmethod
def total_styles(cls):
return len(tool.Ifc.get().by_type("IfcPresentationStyle"))
class StyleAttributesData:
data = {}
is_loaded = False
@@ -20,6 +20,7 @@ import bpy
import blenderbim.bim.handler
import blenderbim.tool as tool
import blenderbim.core.style as core
import ifcopenshell.util.representation
from blenderbim.bim.ifc import IfcStore
@@ -39,6 +40,21 @@ class UpdateStyleColours(bpy.types.Operator, Operator):
core.update_style_colours(tool.Ifc, tool.Style, obj=context.active_object.active_material)
class UpdateStyleTextures(bpy.types.Operator, Operator):
bl_idname = "bim.update_style_textures"
bl_label = "Update Style Textures"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
representation = ifcopenshell.util.representation.get_representation(
tool.Ifc.get_entity(context.active_object), "Model", "Body", "MODEL_VIEW"
)
if representation:
core.update_style_textures(
tool.Ifc, tool.Style, obj=context.active_object.active_material, representation=representation
)
class RemoveStyle(bpy.types.Operator, Operator):
bl_idname = "bim.remove_style"
bl_label = "Remove Style"
@@ -91,3 +107,22 @@ class EditStyle(bpy.types.Operator, Operator):
def _execute(self, context):
core.edit_style(tool.Ifc, tool.Style, obj=context.active_object.active_material)
class DisableEditingStyles(bpy.types.Operator, Operator):
bl_idname = "bim.disable_editing_styles"
bl_options = {"REGISTER", "UNDO"}
bl_label = "Disable Editing Styles"
def _execute(self, context):
core.disable_editing_styles(tool.Style)
class LoadStyles(bpy.types.Operator, Operator):
bl_idname = "bim.load_styles"
bl_label = "Load Styles"
bl_options = {"REGISTER", "UNDO"}
style_type: bpy.props.StringProperty()
def _execute(self, context):
core.load_styles(tool.Style, style_type=self.style_type)
@@ -19,6 +19,7 @@
import bpy
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.prop import StrProperty, Attribute
from blenderbim.bim.module.style.data import StylesData
from bpy.types import PropertyGroup
from bpy.props import (
PointerProperty,
@@ -32,6 +33,25 @@ from bpy.props import (
)
def get_style_types(self, context):
if not StylesData.is_loaded:
StylesData.load()
return StylesData.data["style_types"]
class Style(PropertyGroup):
name: StringProperty(name="Name")
ifc_definition_id: IntProperty(name="IFC Definition ID")
total_elements: IntProperty(name="Total Elements")
class BIMStylesProperties(PropertyGroup):
is_editing: BoolProperty(name="Is Editing")
style_type: EnumProperty(items=get_style_types, name="Style Type")
styles: CollectionProperty(name="Styles", type=Style)
active_style_index: IntProperty(name="Active Style Index")
class BIMStyleProperties(PropertyGroup):
attributes: CollectionProperty(name="Attributes", type=Attribute)
is_editing: BoolProperty(name="Is Editing")
@@ -17,9 +17,51 @@
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import blenderbim.bim.helper
from bpy.types import Panel
from bpy.types import Panel, UIList
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.module.style.data import StyleAttributesData
from blenderbim.bim.module.style.data import StylesData, StyleAttributesData
class BIM_PT_styles(Panel):
bl_label = "IFC Styles"
bl_idname = "BIM_PT_styles"
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):
if not StylesData.is_loaded:
StylesData.load()
self.props = context.scene.BIMStylesProperties
row = self.layout.row(align=True)
row.label(text="{} Styles Found".format(StylesData.data["total_styles"]), icon="MATERIAL")
row = self.layout.row(align=True)
if self.props.is_editing:
row.operator("bim.disable_editing_styles", text="", icon="CANCEL")
else:
row.prop(self.props, "style_type", text="")
row.operator("bim.load_styles", text="", icon="IMPORT").style_type = self.props.style_type
return
#row = self.layout.row(align=True)
#row.alignment = "RIGHT"
#row.operator("bim.add_presentation_style", text="", icon="ADD")
#if self.props.styles and self.props.active_style_index < len(self.props.styles):
# style = self.props.styles[self.props.active_style_index]
# op = row.operator("bim.select_by_style", text="", icon="RESTRICT_SELECT_OFF")
# op.style = style.ifc_definition_id
# row.operator("bim.remove_style", text="", icon="X").style = style.ifc_definition_id
self.layout.template_list("BIM_UL_styles", "", self.props, "styles", self.props, "active_style_index")
class BIM_PT_style(Panel):
@@ -42,6 +84,7 @@ class BIM_PT_style(Panel):
row = self.layout.row(align=True)
if props.ifc_style_id:
row.operator("bim.update_style_colours", icon="GREASEPENCIL")
row.operator("bim.update_style_textures", icon="TEXTURE", text="")
row.operator("bim.unlink_style", icon="UNLINKED", text="")
row.operator("bim.remove_style", icon="X", text="")
else:
@@ -88,3 +131,13 @@ class BIM_PT_style_attributes(Panel):
row = self.layout.row(align=True)
row.label(text=attribute["name"])
row.label(text=attribute["value"])
class BIM_UL_styles(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
if item:
row = layout.row(align=True)
row.label(text=item.name)
#row2 = row.row()
#row2.alignment = "RIGHT"
#row2.label(text=str(item.total_elements))
@@ -20,21 +20,28 @@ import bpy
from . import ui, prop, operator
classes = (
operator.LoadSystems,
operator.DisableSystemEditingUI,
operator.AddPort,
operator.AddSystem,
operator.EditSystem,
operator.RemoveSystem,
operator.ToggleAssigningSystem,
operator.AssignSystem,
operator.UnassignSystem,
operator.EnableEditingSystem,
operator.ConnectPort,
operator.DisableEditingSystem,
operator.DisableSystemEditingUI,
operator.DisconnectPort,
operator.EditSystem,
operator.EnableEditingSystem,
operator.HidePorts,
operator.LoadSystems,
operator.RemoveSystem,
operator.SelectSystemProducts,
operator.SetFlowDirection,
operator.ShowPorts,
operator.UnassignSystem,
prop.System,
prop.BIMSystemProperties,
ui.BIM_PT_systems,
ui.BIM_PT_object_systems,
ui.BIM_PT_ports,
ui.BIM_PT_port,
ui.BIM_UL_systems,
ui.BIM_UL_object_systems,
)
@@ -0,0 +1,100 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2022 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BlenderBIM Add-on.
#
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BlenderBIM Add-on is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
import ifcopenshell
import ifcopenshell.util.schema
import blenderbim.tool as tool
def refresh():
SystemData.is_loaded = False
ObjectSystemData.is_loaded = False
PortData.is_loaded = False
class SystemData:
data = {}
is_loaded = False
@classmethod
def load(cls):
cls.data = {
"system_class": cls.system_class(),
"total_systems": cls.total_systems(),
}
cls.is_loaded = True
@classmethod
def system_class(cls):
declaration = tool.Ifc.schema().declaration_by_name("IfcSystem")
declarations = ifcopenshell.util.schema.get_subtypes(declaration)
# We're only interested in systems for services. Not sure why IFC groups these together.
return [
(c, c, "")
for c in sorted([d.name() for d in declarations])
if c not in ("IfcZone", "IfcStructuralAnalysisModel")
]
@classmethod
def total_systems(cls):
return len(tool.Ifc.get().by_type("IfcSystem"))
class ObjectSystemData:
data = {}
is_loaded = False
@classmethod
def load(cls):
cls.data = {
"systems": cls.systems(),
"total_systems": cls.total_systems(),
}
cls.is_loaded = True
@classmethod
def systems(cls):
results = []
element = tool.Ifc.get_entity(bpy.context.active_object)
if not element:
return results
for system in ifcopenshell.util.system.get_element_systems(element):
results.append({"id": system.id(), "name": system.Name or "Unnamed", "ifc_class": system.is_a()})
return results
@classmethod
def total_systems(cls):
return len(tool.Ifc.get().by_type("IfcSystem"))
class PortData:
data = {}
is_loaded = False
@classmethod
def load(cls):
cls.data = {
"total_ports": cls.total_ports(),
}
cls.is_loaded = True
@classmethod
def total_ports(cls):
element = tool.Ifc.get_entity(bpy.context.active_object)
return len(ifcopenshell.util.system.get_ports(element))
@@ -17,208 +17,181 @@
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
import ifcopenshell.util.attribute
import ifcopenshell.api
import blenderbim.bim.helper
import blenderbim.tool as tool
import blenderbim.core.system as core
import blenderbim.bim.handler
from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.system.data import Data
class LoadSystems(bpy.types.Operator):
class Operator:
def execute(self, context):
IfcStore.execute_ifc_operator(self, context)
blenderbim.bim.handler.refresh_ui_data()
return {"FINISHED"}
class LoadSystems(bpy.types.Operator, Operator):
bl_idname = "bim.load_systems"
bl_label = "Load Systems"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
props = context.scene.BIMSystemProperties
props.systems.clear()
for ifc_definition_id, system in Data.systems.items():
new = props.systems.add()
new.ifc_definition_id = ifc_definition_id
new.name = system["Name"]
props.is_editing = True
bpy.ops.bim.disable_editing_system()
return {"FINISHED"}
def _execute(self, context):
core.load_systems(tool.System)
class DisableSystemEditingUI(bpy.types.Operator):
class DisableSystemEditingUI(bpy.types.Operator, Operator):
bl_idname = "bim.disable_system_editing_ui"
bl_label = "Disable System Editing UI"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
context.scene.BIMSystemProperties.is_editing = False
context.scene.BIMSystemProperties.active_system_id = 0
return {"FINISHED"}
def _execute(self, context):
core.disable_system_editing_ui(tool.System)
class AddSystem(bpy.types.Operator):
class AddSystem(bpy.types.Operator, Operator):
bl_idname = "bim.add_system"
bl_label = "Add System"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
result = ifcopenshell.api.run("system.add_system", IfcStore.get_file())
Data.load(IfcStore.get_file())
bpy.ops.bim.load_systems()
bpy.ops.bim.enable_editing_system(system=result.id())
return {"FINISHED"}
core.add_system(tool.Ifc, tool.System, ifc_class=context.scene.BIMSystemProperties.system_class)
class EditSystem(bpy.types.Operator):
class EditSystem(bpy.types.Operator, Operator):
bl_idname = "bim.edit_system"
bl_label = "Edit System"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
props = context.scene.BIMSystemProperties
attributes = {}
for attribute in props.system_attributes:
if attribute.is_null:
attributes[attribute.name] = None
else:
attributes[attribute.name] = attribute.string_value
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"system.edit_system",
self.file,
**{"system": self.file.by_id(props.active_system_id), "attributes": attributes}
core.edit_system(
tool.Ifc, tool.System, system=tool.Ifc.get().by_id(context.scene.BIMSystemProperties.active_system_id)
)
Data.load(IfcStore.get_file())
bpy.ops.bim.load_systems()
return {"FINISHED"}
class RemoveSystem(bpy.types.Operator):
class RemoveSystem(bpy.types.Operator, Operator):
bl_idname = "bim.remove_system"
bl_label = "Remove System"
bl_options = {"REGISTER", "UNDO"}
system: bpy.props.IntProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
props = context.scene.BIMSystemProperties
self.file = IfcStore.get_file()
ifcopenshell.api.run("system.remove_system", self.file, **{"system": self.file.by_id(self.system)})
Data.load(IfcStore.get_file())
bpy.ops.bim.load_systems()
return {"FINISHED"}
core.remove_system(tool.Ifc, tool.System, system=tool.Ifc.get().by_id(self.system))
class EnableEditingSystem(bpy.types.Operator):
class EnableEditingSystem(bpy.types.Operator, Operator):
bl_idname = "bim.enable_editing_system"
bl_label = "Enable Editing System"
bl_options = {"REGISTER", "UNDO"}
system: bpy.props.IntProperty()
def execute(self, context):
props = context.scene.BIMSystemProperties
props.system_attributes.clear()
blenderbim.bim.helper.import_attributes("IfcSystem", props.system_attributes, Data.systems[self.system])
props.active_system_id = self.system
return {"FINISHED"}
def _execute(self, context):
core.enable_editing_system(tool.System, system=tool.Ifc.get().by_id(self.system))
class DisableEditingSystem(bpy.types.Operator):
class DisableEditingSystem(bpy.types.Operator, Operator):
bl_idname = "bim.disable_editing_system"
bl_label = "Disable Editing System"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
context.scene.BIMSystemProperties.active_system_id = 0
return {"FINISHED"}
def _execute(self, context):
core.disable_editing_system(tool.System)
class ToggleAssigningSystem(bpy.types.Operator):
bl_idname = "bim.toggle_assigning_system"
bl_label = "Toggle Assigning System"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
context.scene.BIMSystemProperties.is_adding = not context.scene.BIMSystemProperties.is_adding
return {"FINISHED"}
class AssignSystem(bpy.types.Operator):
class AssignSystem(bpy.types.Operator, Operator):
bl_idname = "bim.assign_system"
bl_label = "Assign System"
bl_options = {"REGISTER", "UNDO"}
product: bpy.props.StringProperty()
system: bpy.props.IntProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
self.file = IfcStore.get_file()
products = [bpy.data.objects.get(self.product)] if self.product else context.selected_objects
for product in products:
if not product.BIMObjectProperties.ifc_definition_id:
continue
ifcopenshell.api.run(
"system.assign_system",
self.file,
**{
"product": self.file.by_id(product.BIMObjectProperties.ifc_definition_id),
"system": self.file.by_id(self.system),
}
)
Data.load(self.file)
return {"FINISHED"}
for obj in context.selected_objects:
element = tool.Ifc.get_entity(obj)
if element:
core.assign_system(tool.Ifc, system=tool.Ifc.get().by_id(self.system), product=element)
class UnassignSystem(bpy.types.Operator):
class UnassignSystem(bpy.types.Operator, Operator):
bl_idname = "bim.unassign_system"
bl_label = "Unassign System"
bl_options = {"REGISTER", "UNDO"}
product: bpy.props.StringProperty()
system: bpy.props.IntProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
self.file = IfcStore.get_file()
products = [bpy.data.objects.get(self.product)] if self.product else context.selected_objects
for product in products:
props = product.BIMObjectProperties
if not props.ifc_definition_id:
continue
if not (props.ifc_definition_id in Data.products and self.system in Data.products[props.ifc_definition_id]):
continue
ifcopenshell.api.run(
"system.unassign_system",
self.file,
**{
"product": self.file.by_id(product.BIMObjectProperties.ifc_definition_id),
"system": self.file.by_id(self.system),
}
)
Data.load(self.file)
return {"FINISHED"}
for obj in context.selected_objects:
element = tool.Ifc.get_entity(obj)
if element:
core.unassign_system(tool.Ifc, system=tool.Ifc.get().by_id(self.system), product=element)
class SelectSystemProducts(bpy.types.Operator):
class SelectSystemProducts(bpy.types.Operator, Operator):
bl_idname = "bim.select_system_products"
bl_label = "Select System Products"
bl_options = {"REGISTER", "UNDO"}
system: bpy.props.IntProperty()
def execute(self, context):
for obj in context.visible_objects:
obj.select_set(False)
if not obj.BIMObjectProperties.ifc_definition_id:
continue
product_systems = Data.products.get(obj.BIMObjectProperties.ifc_definition_id, [])
if self.system in product_systems:
obj.select_set(True)
return {"FINISHED"}
def _execute(self, context):
core.select_system_products(tool.System, system=tool.Ifc.get().by_id(self.system))
class ShowPorts(bpy.types.Operator, Operator):
bl_idname = "bim.show_ports"
bl_label = "Show Ports"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
core.show_ports(tool.System, element=tool.Ifc.get_entity(context.active_object))
class HidePorts(bpy.types.Operator, Operator):
bl_idname = "bim.hide_ports"
bl_label = "Hide Ports"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
core.hide_ports(tool.System, element=tool.Ifc.get_entity(context.active_object))
class AddPort(bpy.types.Operator, Operator):
bl_idname = "bim.add_port"
bl_label = "Add Ports"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
core.add_port(tool.Ifc, tool.System, element=tool.Ifc.get_entity(context.active_object))
class ConnectPort(bpy.types.Operator, Operator):
bl_idname = "bim.connect_port"
bl_label = "Connect Ports"
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
return len(context.selected_objects) == 2
def _execute(self, context):
obj1 = context.active_object
obj2 = context.selected_objects[0] if context.selected_objects[1] == obj1 else context.selected_objects[1]
core.connect_port(tool.Ifc, port1=tool.Ifc.get_entity(obj1), port2=tool.Ifc.get_entity(obj2))
class DisconnectPort(bpy.types.Operator, Operator):
bl_idname = "bim.disconnect_port"
bl_label = "Disconnect Ports"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
core.disconnect_port(tool.Ifc, port=tool.Ifc.get_entity(context.active_object))
class SetFlowDirection(bpy.types.Operator, Operator):
bl_idname = "bim.set_flow_direction"
bl_label = "Set Flow Direction"
bl_options = {"REGISTER", "UNDO"}
direction: bpy.props.StringProperty()
def _execute(self, context):
core.set_flow_direction(
tool.Ifc, tool.System, port=tool.Ifc.get_entity(context.active_object), direction=self.direction
)
@@ -17,6 +17,7 @@
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
from blenderbim.bim.module.system.data import SystemData
from blenderbim.bim.prop import StrProperty, Attribute
from bpy.types import PropertyGroup
from bpy.props import (
@@ -31,8 +32,15 @@ from bpy.props import (
)
def get_system_class(self, context):
if not SystemData.is_loaded:
SystemData.load()
return SystemData.data["system_class"]
class System(PropertyGroup):
name: StringProperty(name="Name")
ifc_class: StringProperty(name="IFC Class")
ifc_definition_id: IntProperty(name="IFC Definition ID")
@@ -43,3 +51,4 @@ class BIMSystemProperties(PropertyGroup):
systems: CollectionProperty(name="Systems", type=System)
active_system_index: IntProperty(name="Active System Index")
active_system_id: IntProperty(name="Active System Id")
system_class: EnumProperty(items=get_system_class, name="Class")
+106 -26
View File
@@ -16,9 +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 blenderbim.tool as tool
from bpy.types import Panel, UIList
from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.system.data import Data
from blenderbim.bim.module.system.data import SystemData, ObjectSystemData, PortData
class BIM_PT_systems(Panel):
@@ -35,15 +36,18 @@ class BIM_PT_systems(Panel):
return IfcStore.get_file()
def draw(self, context):
if not Data.is_loaded:
Data.load(IfcStore.get_file())
if not SystemData.is_loaded:
SystemData.load()
self.props = context.scene.BIMSystemProperties
row = self.layout.row(align=True)
row.label(text="{} Systems Found".format(len(Data.systems)), icon="OUTLINER")
row.label(text="{} Systems Found".format(SystemData.data["total_systems"]), icon="OUTLINER")
if self.props.is_editing:
row.operator("bim.add_system", text="", icon="ADD")
row.operator("bim.disable_system_editing_ui", text="", icon="CANCEL")
row = self.layout.row(align=True)
row.prop(self.props, "system_class", text="")
row.operator("bim.add_system", text="", icon="ADD")
else:
row.operator("bim.load_systems", text="", icon="GREASEPENCIL")
@@ -84,13 +88,13 @@ class BIM_PT_object_systems(Panel):
return IfcStore.get_file() and context.active_object.BIMObjectProperties.ifc_definition_id
def draw(self, context):
if not Data.is_loaded:
Data.load(IfcStore.get_file())
if not ObjectSystemData.is_loaded:
ObjectSystemData.load()
self.props = context.scene.BIMSystemProperties
row = self.layout.row(align=True)
if self.props.is_adding:
row.label(text="Adding Systems", icon="OUTLINER")
row.operator("bim.toggle_assigning_system", text="", icon="CANCEL")
if self.props.is_editing:
row = self.layout.row()
row.alignment = "RIGHT"
row.operator("bim.disable_system_editing_ui", text="", icon="CANCEL")
self.layout.template_list(
"BIM_UL_object_systems",
"",
@@ -100,27 +104,98 @@ class BIM_PT_object_systems(Panel):
"active_system_index",
)
else:
row.label(text=f"{len(Data.systems)} Systems in IFC Project", icon="OUTLINER")
row.operator("bim.toggle_assigning_system", text="", icon="ADD")
systems_object = Data.products.get(context.active_object.BIMObjectProperties.ifc_definition_id, [])
for system_id in systems_object:
row = self.layout.row(align=True)
row.label(text=Data.systems[system_id].get("Name", "Unnamed"))
op = row.operator("bim.select_system_products", text="", icon="RESTRICT_SELECT_OFF")
op.system = system_id
op = row.operator("bim.unassign_system", text="", icon="X")
op.system = system_id
row.label(text=f"{ObjectSystemData.data['total_systems']} Systems in IFC Project", icon="OUTLINER")
row.operator("bim.load_systems", text="", icon="GREASEPENCIL")
if not systems_object:
system_icons = {
"IfcSystem": "EXTERNAL_DRIVE",
"IfcDistributionSystem": "NETWORK_DRIVE",
"IfcDistributionCircuit": "DRIVER",
"IfcBuildingSystem": "MOD_BUILD",
}
for system in ObjectSystemData.data["systems"]:
row = self.layout.row(align=True)
row.label(text=system["name"], icon=system_icons[system["ifc_class"]])
op = row.operator("bim.select_system_products", text="", icon="RESTRICT_SELECT_OFF")
op.system = system["id"]
op = row.operator("bim.unassign_system", text="", icon="X")
op.system = system["id"]
if not ObjectSystemData.data["systems"]:
self.layout.label(text="No System associated with Active Object")
class BIM_PT_ports(Panel):
bl_label = "IFC Ports"
bl_idname = "BIM_PT_ports"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
bl_parent_id = "BIM_PT_services_object"
@classmethod
def poll(cls, context):
if not context.active_object:
return False
element = tool.Ifc.get_entity(context.active_object)
if not element or not element.is_a("IfcDistributionElement"):
return False
return True
def draw(self, context):
if not PortData.is_loaded:
PortData.load()
self.props = context.scene.BIMSystemProperties
row = self.layout.row(align=True)
row.label(text=f"{PortData.data['total_ports']} Ports Found", icon="PLUGIN")
row.operator("bim.show_ports", icon="HIDE_OFF", text="")
row.operator("bim.hide_ports", icon="HIDE_ON", text="")
row.operator("bim.add_port", icon="ADD", text="")
class BIM_PT_port(Panel):
bl_label = "IFC Port"
bl_idname = "BIM_PT_port"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
bl_parent_id = "BIM_PT_services_object"
@classmethod
def poll(cls, context):
if not context.active_object:
return False
element = tool.Ifc.get_entity(context.active_object)
if not element or not element.is_a("IfcPort"):
return False
return True
def draw(self, context):
self.props = context.scene.BIMSystemProperties
row = self.layout.row(align=True)
row.operator("bim.connect_port", icon="PLUGIN", text="")
row.operator("bim.disconnect_port", icon="UNLINKED", text="")
row.operator("bim.set_flow_direction", icon="FORWARD", text="").direction = "SOURCE"
row.operator("bim.set_flow_direction", icon="BACK", text="").direction = "SINK"
row.operator("bim.set_flow_direction", icon="ARROW_LEFTRIGHT", text="").direction = "SOURCEANDSINK"
row.operator("bim.set_flow_direction", icon="RESTRICT_INSTANCED_ON", text="").direction = "NOTDEFINED"
class BIM_UL_systems(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
system_icons = {
"IfcSystem": "EXTERNAL_DRIVE",
"IfcDistributionSystem": "NETWORK_DRIVE",
"IfcDistributionCircuit": "DRIVER",
"IfcBuildingSystem": "MOD_BUILD",
}
if item:
row = layout.row(align=True)
row.label(text=item.name)
row.label(text=item.name, icon=system_icons[item.ifc_class])
system_id = item.ifc_definition_id
if context.scene.BIMSystemProperties.active_system_id == system_id:
op = row.operator("bim.select_system_products", text="", icon="RESTRICT_SELECT_OFF")
@@ -143,8 +218,13 @@ class BIM_UL_systems(UIList):
class BIM_UL_object_systems(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
system_icons = {
"IfcSystem": "EXTERNAL_DRIVE",
"IfcDistributionSystem": "NETWORK_DRIVE",
"IfcDistributionCircuit": "DRIVER",
"IfcBuildingSystem": "MOD_BUILD",
}
if item:
row = layout.row(align=True)
row.label(text=item.name)
op = row.operator("bim.assign_system", text="", icon="ADD")
op.system = item.ifc_definition_id
row.label(text=item.name, icon=system_icons[item.ifc_class])
row.operator("bim.assign_system", text="", icon="ADD").system = item.ifc_definition_id
+141 -77
View File
@@ -22,6 +22,7 @@ import json
import webbrowser
import ifcopenshell
import blenderbim.bim.handler
import blenderbim.tool as tool
from . import schema
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.ui import IFCFileSelector
@@ -39,6 +40,43 @@ class OpenUri(bpy.types.Operator):
return {"FINISHED"}
class SelectURIAttribute(bpy.types.Operator):
bl_idname = "bim.select_uri_attribute"
bl_label = "Select URI Attribute"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Select a local file"
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
data_path: bpy.props.StringProperty(name="Data Path")
use_relative_path: bpy.props.BoolProperty(name="Use Relative Path", default=False)
def execute(self, context):
# data_path contains the latter half of the path to the string_value property
# I have no idea how to find out the former half, so let's just use brute force.
data_path = self.data_path.replace(".string_value", "")
attribute = None
try:
attribute = eval(f"bpy.context.scene.{data_path}")
except:
try:
attribute = eval(f"bpy.context.active_object.{data_path}")
except:
try:
attribute = eval(f"bpy.context.active_object.active_material.{data_path}")
except:
# Do you know a better way?
pass
if attribute:
filepath = self.filepath
if self.use_relative_path:
filepath = os.path.relpath(filepath, os.path.dirname(tool.Ifc.get_path()))
attribute.string_value = filepath
return {"FINISHED"}
def invoke(self, context, event):
context.window_manager.fileselect_add(self)
return {"RUNNING_MODAL"}
class SelectIfcFile(bpy.types.Operator, IFCFileSelector):
bl_idname = "bim.select_ifc_file"
bl_label = "Select IFC File"
@@ -106,7 +144,9 @@ class OpenUpstream(bpy.types.Operator):
return {"FINISHED"}
class AddSectionPlane(bpy.types.Operator):
class BIM_OT_add_section_plane(bpy.types.Operator):
"""Add a temporary empty object as a section cutaway. Cull all geometry rendering below the empty's local Z axis"""
bl_idname = "bim.add_section_plane"
bl_label = "Add Temporary Section Cutaway"
bl_options = {"REGISTER", "UNDO"}
@@ -151,79 +191,100 @@ class AddSectionPlane(bpy.types.Operator):
def create_section_compare_node(self):
group = bpy.data.node_groups.new("Section Compare", type="ShaderNodeTree")
group_input = group.nodes.new(type="NodeGroupInput")
group_input.location = 0, 50
separate_xyz = group.nodes.new(type="ShaderNodeSeparateXYZ")
separate_xyz.location = 200, 0
greater = group.nodes.new(type="ShaderNodeMath")
greater.operation = "GREATER_THAN"
greater.inputs[1].default_value = 0
greater.location = 400, 0
multiply = group.nodes.new(type="ShaderNodeMath")
multiply.operation = "MULTIPLY"
multiply.inputs[0].default_value = 1
multiply.location = 600, 150
group_output = group.nodes.new(type="NodeGroupOutput")
separate_xyz_a = group.nodes.new(type="ShaderNodeSeparateXYZ")
separate_xyz_b = group.nodes.new(type="ShaderNodeSeparateXYZ")
gt_a = group.nodes.new(type="ShaderNodeMath")
gt_a.operation = "GREATER_THAN"
gt_a.inputs[1].default_value = 0
gt_b = group.nodes.new(type="ShaderNodeMath")
gt_b.operation = "GREATER_THAN"
gt_b.inputs[1].default_value = 0
add = group.nodes.new(type="ShaderNodeMath")
compare = group.nodes.new(type="ShaderNodeMath")
compare.operation = "COMPARE"
compare.inputs[1].default_value = 2
group.links.new(group_input.outputs[""], separate_xyz_a.inputs[0])
group.links.new(group_input.outputs[""], separate_xyz_b.inputs[0])
group.links.new(separate_xyz_a.outputs[2], gt_a.inputs[0])
group.links.new(separate_xyz_b.outputs[2], gt_b.inputs[0])
group.links.new(gt_a.outputs[0], add.inputs[0])
group.links.new(gt_b.outputs[0], add.inputs[1])
group.links.new(add.outputs[0], compare.inputs[0])
group.links.new(compare.outputs[0], group_output.inputs[""])
group_output.location = 800, 0
group.links.new(group_input.outputs[""], multiply.inputs[0])
group.links.new(group_input.outputs[""], separate_xyz.inputs[0])
group.links.new(separate_xyz.outputs[2], greater.inputs[0])
group.links.new(greater.outputs[0], multiply.inputs[1])
group.links.new(multiply.outputs[0], group_output.inputs[""])
def create_section_override_node(self, obj, context):
group = bpy.data.node_groups.new("Section Override", type="ShaderNodeTree")
links = group.links
nodes = group.nodes
group_input = group.nodes.new(type="NodeGroupInput")
group_output = group.nodes.new(type="NodeGroupOutput")
group_input = nodes.new(type="NodeGroupInput")
group_output = nodes.new(type="NodeGroupOutput")
group_output.location = 600, 250
backfacing = group.nodes.new(type="ShaderNodeNewGeometry")
backfacing_mix = group.nodes.new(type="ShaderNodeMixShader")
emission = group.nodes.new(type="ShaderNodeEmission")
backfacing_mix = nodes.new(type="ShaderNodeMixShader")
backfacing_mix.location = group_output.location - Vector((400, 350))
backfacing = nodes.new(type="ShaderNodeNewGeometry")
backfacing.location = backfacing_mix.location + Vector((-200, 200))
group_input.location = backfacing_mix.location - Vector((200, 50))
emission = nodes.new(type="ShaderNodeEmission")
emission.inputs[0].default_value = list(context.scene.BIMProperties.section_plane_colour) + [1]
emission.location = backfacing_mix.location - Vector((200, 150))
group.links.new(backfacing.outputs["Backfacing"], backfacing_mix.inputs[0])
group.links.new(group_input.outputs[""], backfacing_mix.inputs[1])
group.links.new(emission.outputs["Emission"], backfacing_mix.inputs[2])
transparent = nodes.new(type="ShaderNodeBsdfTransparent")
transparent.location = group_output.location - Vector((400, 100))
transparent = group.nodes.new(type="ShaderNodeBsdfTransparent")
section_mix = group.nodes.new(type="ShaderNodeMixShader")
section_mix.name = "Section Mix"
section_mix.inputs[0].default_value = 1 # Directly pass input shader when there is no cutaway
section_mix.location = group_output.location - Vector((200, 0))
group.links.new(transparent.outputs["BSDF"], section_mix.inputs[1])
group.links.new(backfacing_mix.outputs["Shader"], section_mix.inputs[2])
group.links.new(section_mix.outputs["Shader"], group_output.inputs[""])
cut_obj = group.nodes.new(type="ShaderNodeTexCoord")
cut_obj = nodes.new(type="ShaderNodeTexCoord")
cut_obj.object = obj
section_compare = group.nodes.new(type="ShaderNodeGroup")
cut_obj.location = group_output.location - Vector((800, 150))
section_compare = nodes.new(type="ShaderNodeGroup")
section_compare.node_tree = bpy.data.node_groups.get("Section Compare")
section_compare.name = "Last Section Compare"
value = group.nodes.new(type="ShaderNodeValue")
value.name = "Mock Section"
group.links.new(cut_obj.outputs["Object"], section_compare.inputs[0])
group.links.new(value.outputs[0], section_compare.inputs[1])
group.links.new(section_compare.outputs[0], section_mix.inputs[0])
section_compare.location = group_output.location - Vector((600, 0))
links.new(cut_obj.outputs["Object"], section_compare.inputs[1])
links.new(backfacing.outputs["Backfacing"], backfacing_mix.inputs[0])
links.new(group_input.outputs[""], backfacing_mix.inputs[1])
links.new(emission.outputs["Emission"], backfacing_mix.inputs[2])
links.new(section_compare.outputs[0], section_mix.inputs[0])
links.new(transparent.outputs["BSDF"], section_mix.inputs[1])
links.new(backfacing_mix.outputs["Shader"], section_mix.inputs[2])
links.new(section_mix.outputs["Shader"], group_output.inputs[""])
def append_obj_to_section_override_node(self, obj):
group = bpy.data.node_groups.get("Section Override")
cut_obj = group.nodes.new(type="ShaderNodeTexCoord")
cut_obj.object = obj
try:
last_section_node = next(
n
for n in group.nodes
if isinstance(n, bpy.types.ShaderNodeGroup)
and n.node_tree.name == "Section Compare"
and not n.inputs[0].links
)
offset = Vector((0, 0))
except StopIteration:
last_section_node = group.nodes.get("Section Mix")
offset = Vector((200, 0))
section_compare = group.nodes.new(type="ShaderNodeGroup")
section_compare.node_tree = bpy.data.node_groups.get("Section Compare")
section_compare.location = last_section_node.location - Vector((200, 0)) - offset
last_compare = group.nodes.get("Last Section Compare")
last_compare.name = "Section Compare"
mock_section = group.nodes.get("Mock Section")
section_mix = group.nodes.get("Section Mix")
cut_obj = group.nodes.new(type="ShaderNodeTexCoord")
cut_obj.object = obj
cut_obj.location = last_section_node.location - Vector((400, 150)) - offset
group.links.new(last_compare.outputs[0], section_compare.inputs[0])
group.links.new(mock_section.outputs[0], section_compare.inputs[1])
group.links.new(cut_obj.outputs["Object"], last_compare.inputs[1])
group.links.new(section_compare.outputs[0], section_mix.inputs[0])
group.links.new(section_compare.outputs[0], last_section_node.inputs[0])
group.links.new(cut_obj.outputs["Object"], section_compare.inputs[1])
section_compare.name = "Last Section Compare"
@@ -274,7 +335,9 @@ class AddSectionPlane(bpy.types.Operator):
return node
class RemoveSectionPlane(bpy.types.Operator):
class BIM_OT_remove_section_plane(bpy.types.Operator):
"""Remove selected section plane. No effect if executed on a regular object"""
bl_idname = "bim.remove_section_plane"
bl_label = "Remove Temporary Section Cutaway"
bl_options = {"REGISTER", "UNDO"}
@@ -286,34 +349,35 @@ class RemoveSectionPlane(bpy.types.Operator):
def execute(self, context):
name = context.active_object.name
section_override = bpy.data.node_groups.get("Section Override")
for node in section_override.nodes:
if node.type != "TEX_COORD" or node.object.name != name:
continue
section_compare = node.outputs["Object"].links[0].to_node
# If the tex coord links to section_compare.inputs[1], it is called 'Input_3'
if node.outputs["Object"].links[0].to_socket.identifier == "Input_3":
section_override.links.new(
section_compare.inputs[0].links[0].from_socket, section_compare.outputs[0].links[0].to_socket
)
else: # If it links to section_compare.inputs[0]
if section_compare.inputs[1].links[0].from_node.name == "Mock Section":
# Then it is the very last section. Purge everything.
self.purge_all_section_data(context)
return {"FINISHED"}
section_override.links.new(
section_compare.inputs[1].links[0].from_socket, section_compare.outputs[0].links[0].to_socket
)
tex_coords = next(
(
n
for n in section_override.nodes
if isinstance(n, bpy.types.ShaderNodeTexCoord) and n.object.name == name
),
None,
)
if tex_coords is not None:
section_compare = tex_coords.outputs["Object"].links[0].to_node
if section_compare.inputs[0].links:
previous_section_compare = section_compare.inputs[0].links[0].from_node
next_section_compare = section_compare.outputs[0].links[0].to_node
section_override.links.new(previous_section_compare.outputs[0], next_section_compare.inputs[0])
self.offset_previous_nodes(section_compare, offset_x=200)
section_override.nodes.remove(section_compare)
section_override.nodes.remove(node)
section_override.nodes.remove(tex_coords)
bpy.data.objects.remove(context.active_object)
old_last_compare = section_override.nodes.get("Last Section Compare")
old_last_compare.name = "Section Compare"
section_mix = section_override.nodes.get("Section Mix")
new_last_compare = section_mix.inputs[0].links[0].from_node
new_last_compare.name = "Last Section Compare"
bpy.ops.object.delete({"selected_objects": [context.active_object]})
return {"FINISHED"}
def offset_previous_nodes(self, section_compare, offset_x=0, offset_y=0):
if section_compare.inputs[0].links:
previous_section_compare = section_compare.inputs[0].links[0].from_node
previous_section_compare.location += Vector((offset_x, offset_y))
if previous_section_compare.inputs[1].links:
previous_section_compare.inputs[1].links[0].from_node.location += Vector((offset_x, offset_y))
self.offset_previous_nodes(previous_section_compare, offset_x, offset_y)
def purge_all_section_data(self, context):
bpy.data.materials.remove(bpy.data.materials.get("Section Override"))
for material in bpy.data.materials:

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