Compare commits

...

290 Commits

Author SHA1 Message Date
Dion Moult 21fee721be #1153 Implement adding and removing schedules from a sheet 2022-04-29 13:32:34 +10:00
Dion Moult 6bcf00ef31 #1153 Reimplement ability to build SVG tables from referenced ODS documents 2022-04-28 19:57:47 +10:00
Christian Martínez de la Rosa 952d1c3b79 [IfcConvert] Added IfcWorkPlan to xml export 2022-04-28 17:54:43 +10:00
Christian Martínez de la Rosa 2d7a90bd2b [IfcConvert] Fix crash when IfcWorkCalendar does not contain IfcWorkTime 2022-04-28 17:54:43 +10:00
Christian Martínez de la Rosa b14de15a68 [IfcConvert] Added Output objects in the XML file 2022-04-28 17:54:43 +10:00
Christian Martínez de la Rosa 29ecba293b [IfcConvert] Added calendars to xml output file 2022-04-28 17:54:43 +10:00
Christian Martínez de la Rosa 8396c68e75 Added task to property / quanty relationship 2022-04-28 17:54:43 +10:00
Christian Martínez de la Rosa 01c6a0d9be [IfcConver] Changed OperatesOn hierarchy level in xml task export 2022-04-28 17:54:43 +10:00
Christian Martínez de la Rosa 99d8a225a9 Get IfcTasks from IfcWorkSchedule 2022-04-28 17:54:43 +10:00
Christian Martínez de la Rosa 89f13aa1e4 [IfcConvert] Added IsSuccessorFrom and IsPredecessorTo in xml task export 2022-04-28 17:54:43 +10:00
Christian Martínez de la Rosa f089d03dc6 [ifcConvert] Add related objects in tasks 2022-04-28 17:54:43 +10:00
Christian Martínez de la Rosa a4366ef151 [IfcConvert] Export tasks in the xml conversion
The exportion in the XML format exports in a new tag, named "tasks", all the tasks from the file with a hierarchy. Each task contains the IfcTaskTime object if it has it.
2022-04-28 17:54:43 +10:00
Dion Moult 9483abcdd5 Revert "Refactor BIM_PT_gis"
This reverts commit 265ebaebb8.
2022-04-28 17:25:31 +10:00
Dion Moult 200a343746 Revert "Refactor BIM_PT_gis added files"
This reverts commit 445e77bac0.
2022-04-28 17:25:31 +10:00
Dion Moult 2313fa00cc Revert "minox fix"
This reverts commit dfb980e0d1.
2022-04-28 17:25:31 +10:00
Dion Moult 99c5f895fc Revert "Finish refactor georeference module"
This reverts commit fcd5908ac9.
2022-04-28 17:25:31 +10:00
maxfb87 fcd5908ac9 Finish refactor georeference module 2022-04-28 14:34:35 +10:00
maxfb87 dfb980e0d1 minox fix 2022-04-28 14:34:35 +10:00
maxfb87 445e77bac0 Refactor BIM_PT_gis added files 2022-04-28 14:34:35 +10:00
maxfb87 265ebaebb8 Refactor BIM_PT_gis 2022-04-28 14:34:35 +10:00
Dion Moult 07fa7003fb Minor fix 2022-04-28 14:34:18 +10:00
Ryan Schultz 3a90d664fa added 3"=1'-0" scale (a common scale), and corrected 6"=1'-0" scale 2022-04-28 14:26:37 +10:00
Dion Moult 3f28ae18c0 #1153 You can now add / remove / open schedules, implemented as IFC documents 2022-04-28 14:16:39 +10:00
Dion Moult 9ea6499b5a #2082 You can now filter by metadata existence in the selector syntax 2022-04-27 22:20:12 +10:00
Adrien SCHVALBERG 11c3a6e547 Fix ifcparse/Ifc____.h template 2022-04-27 14:08:05 +02:00
Adrien SCHVALBERG e7f3856d64 Fix shared libraries exports. 2022-04-27 14:08:05 +02:00
Dion Moult 7e7d576855 Basic WIP Australian project library with some materials and walls 2022-04-27 20:45:34 +10:00
Dion Moult cfa1428a75 Fix bug where material layer set usage axis was not consistent for non walls 2022-04-27 20:44:26 +10:00
Dion Moult d4ee74455c Show construction type descriptions as tooltips in BIM authoring tool 2022-04-27 20:36:32 +10:00
Dion Moult 07d2cc62c1 Fix #2163 bug where launching viewing apps could freeze Blender 2022-04-26 09:53:32 +10:00
Dion Moult 8aff4badf7 Minor housekeeping 2022-04-25 19:37:03 +10:00
Dion Moult aa3325c010 #1153 Implement diameter annotation 2022-04-25 10:32:33 +10:00
Dion Moult 9b13cd8d5f #1153 Dimensions can now have custom text 2022-04-25 10:02:00 +10:00
Dion Moult 0fe32c0c75 #1153 Add support for radius annotations 2022-04-25 09:54:08 +10:00
Thomas Krijnen efc43c0fe9 #1153 merge-cells option in draw 2022-04-24 14:54:49 +02:00
Thomas Krijnen 5bfaa32c7c #1153 add ray_distance to tree result 2022-04-24 14:51:24 +02:00
Thomas Krijnen 38e4ccdb0a #1153 submodule 2022-04-24 14:50:53 +02:00
Dion Moult 7b71a7a866 #1153 You can now include and exclude elements from a drawing using queries 2022-04-24 22:15:14 +10:00
Dion Moult b5ee20cb7a #1153 New global referencing property determines whether or not a drawing is mean to be referenced from all other drawings 2022-04-24 18:39:22 +10:00
Dion Moult cc8da4abf4 #1153 Symbol background option for text is now stored in a pset 2022-04-24 13:13:21 +10:00
Dion Moult f2c4b20187 #1153 Experimental support for curve bounded planes for 3D annotations 2022-04-24 12:25:04 +10:00
Dion Moult 7c460bcf29 #1153 Viewport decoration now supports miscellaneous linework and fills 2022-04-24 11:12:24 +10:00
Dion Moult 31ddb0f2c5 #1153 Add support for Annotation3D representations 2022-04-24 10:00:31 +10:00
Thomas Krijnen c2cad06cfa Default BUILD_QTVIEWER to off for the time being 2022-04-23 15:39:03 +02:00
Dushyant Basson 421b58850c Updated to use Qt5 2022-04-23 15:19:10 +02:00
Dushyant Basson 7c89aa1098 Enabled option to build QtViewer 2022-04-23 15:19:10 +02:00
Dushyant Basson be7e85fc2d Revert "Updated to use Qt5"
This reverts commit 06fe4abf6a.
2022-04-23 15:19:10 +02:00
Dushyant Basson d91442de59 Revert "Enabled option to build QtViewer"
This reverts commit b82dddf441.
2022-04-23 15:19:10 +02:00
Dushyant Basson 9cd3d73a23 Enabled option to build QtViewer 2022-04-23 15:19:10 +02:00
Dushyant Basson 0cf3c97eb2 Updated to use Qt5 2022-04-23 15:19:10 +02:00
Dushyant Basson d49095844a added output directories to .gitignore 2022-04-23 15:19:10 +02:00
Dion Moult be65f77311 #1153 Breaklines are now pure lines separate from fill areas and orient correctly 2022-04-23 18:56:06 +10:00
Thomas Krijnen 2081dc4c08 #2156 Don't try to convert unset placements in element hierarchy parsing 2022-04-23 10:51:16 +02:00
Dion Moult bd16345b76 #1153 Utility to convert an edge to an arc in preparation for angular dimensions 2022-04-23 17:25:39 +10:00
Dion Moult 3264f338c7 #1153 Section RLs now show storey names or custom descriptions 2022-04-23 11:10:02 +10:00
Dion Moult 0c06c67f2d #1153 Storey level annotations are now automatically generated for sections and elevations 2022-04-22 12:41:21 +10:00
Dion Moult a66ba6a6f9 #1153 Reimplement metadata keys so you can specify attribute-based classes in drawings 2022-04-22 10:06:28 +10:00
Massimo Fabbro 80d8665992 Insert the link to Reboot Addon 2022-04-21 11:13:32 +10:00
Dion Moult 337ce89919 Fix bug where "Not Georeferenced" showed up twice in the Georeferencing UI panel 2022-04-21 10:47:09 +10:00
Dion Moult 1237a8ce97 Improve georeferencing documentation 2022-04-21 10:41:47 +10:00
Dion Moult c3c9ed2975 #2150 Document how to manually reconcile large coordinate ranges 2022-04-21 09:58:28 +10:00
Dion Moult 87cb1c69f4 #2150 Fix bug where cartesian point offsets didn't consider object placement rotations 2022-04-21 09:09:34 +10:00
Dion Moult 3aa7f8e4c8 #2150 Fix bug where manual offsets and autodetected offset could stack 2022-04-21 09:08:08 +10:00
Dion Moult 2c301d734b #1153 You can now specify the ZIndex of 2D annotations 2022-04-20 16:28:10 +10:00
Thomas Krijnen 1705e6f565 Changes for mpfr toolset discussed in #1993 2022-04-19 15:33:09 +02:00
renjianqiang 2cd7b08c47 Fix the problem that build dependencies failed under Debug mode 2022-04-18 09:56:41 +02:00
renjianqiang 647a5105b5 Fix the bug that can't install python when IFCOS_INSTALL_PYTHON is true 2022-04-18 09:56:41 +02:00
Dion Moult 9fc0baf175 #1153 Add support for IfcAnnotationFillArea representation 2022-04-18 10:27:20 +10:00
Dion Moult ca502cc293 #1153 Fix bugs with importing existing annotations 2022-04-18 10:27:20 +10:00
CyrilWaechter f70ab060e3 Add default when getting space collection
A case has been encountered where space object and space collection did
not share the same name (eg. space.0011 and space.0021).
@Moult I wonder if it is expected or if there is a consistency issue
with space object/collection naming.
2022-04-17 15:10:18 +02:00
Dion Moult 6fb3c28f1a #1153 Switching representations for annotations and drawing cameras now works 2022-04-17 18:41:19 +10:00
Dion Moult c79eb60959 #1153 Drawings that are pure annotation now do not generate references 2022-04-17 12:29:34 +10:00
Dion Moult 538419a23f #1153 New markers and patterns for insulation batting, steel, and wood grain 2022-04-17 12:19:20 +10:00
Dion Moult eb19165ebc #1153 Predefined type is now also included in CSS classes for drawings. 2022-04-16 18:32:17 +10:00
Dion Moult 1138ffee7e #2139 Fix bug where some objects were invalidated during undo or redo 2022-04-15 19:45:59 +10:00
Dion Moult 0f3416774c #1153 Drawing layers (underlay, linework, annotation) are now per drawing instead of global 2022-04-15 11:45:23 +10:00
Dion Moult 1ba7977587 #1153 Fix newlines in text literals and smart tags in SVGs 2022-04-14 21:08:06 +10:00
Dion Moult 5b1b8e8f50 #1153 Annotation reference and sheet IDs now implemented in SVG. 2022-04-14 15:20:09 +10:00
Dion Moult 78811d331b #2136 Minor fix 2022-04-14 15:20:09 +10:00
Jesusbill 4aa91cfa7f add feature to toggle wireframe visibility for linked models - no testcases 2022-04-13 18:28:48 +02:00
Dion Moult c099a6cb75 #1153 Drawing titles now display IFC scale and identification data 2022-04-13 12:21:18 +10:00
Dion Moult 029b402d11 Minor fix 2022-04-12 18:30:03 +10:00
Dion Moult a243cdd6c6 Minor fix 2022-04-12 18:09:45 +10:00
Dion Moult 1fae835de9 Update installation docs to distinguish between package from source and developer setups 2022-04-12 18:07:39 +10:00
Dion Moult f0c7ff1e57 #1153. Titleblock revision / data is are derived from IfcDocumentInformation 2022-04-12 08:29:29 +10:00
Thomas Krijnen 460fd5ca87 #2133 faceset helper: compare index vectors by address instead of value 2022-04-11 14:12:17 +02:00
Thomas Krijnen 054c0a9a27 Improve faceset logging 2022-04-11 14:08:44 +02:00
Thomas Krijnen 5af1baa48b Update release.yml 2022-04-10 20:47:01 +02:00
Thomas Krijnen 0e5d2cc0c9 Update release.yml 2022-04-10 10:15:07 +02:00
Dion Moult 48148f00a3 #2129 Support some FreeCAD annotation types. 2022-04-09 10:22:12 +10:00
Thomas Krijnen 564c4b7644 #2126 reverse 1st operand inner wires in 2d opening subtraction 2022-04-08 10:45:28 +02:00
Bruno Postle 33c53a2c9a regenerate site library 2022-04-08 16:49:06 +10:00
Bruno Postle c049007ee8 add missing import 2022-04-08 16:49:06 +10:00
Bruno Postle 05345e8ddf Add missing demo library 2022-04-08 16:49:06 +10:00
Thomas Krijnen 461b78665a OCCT 7.6 compatibility 2022-04-04 08:29:29 +00:00
Boris Brangeon 6160d65d18 Update selector.py
filter_rule.children[2].children[0].lower() == 'true'
2022-03-31 20:50:26 +02:00
Thomas Krijnen d526314190 #2115 Let --strict-tolerance govern degeneracy of periodic curve trim 2022-03-31 11:28:30 +02:00
Massimo Fabbro 85bac37bed Fix two broken links
Fixed "./installation.rst" and "./running_tests.rst" links
2022-03-30 20:31:00 +11:00
Dion Moult e38184b152 Adding a document now auto adds a reference for convenience. 2022-03-30 18:15:26 +11:00
Dion Moult a0c8d87ad0 #1153. You can now assign and remove drawings from sheets. 2022-03-30 18:15:11 +11:00
Dion Moult 8686462eed #1153. Section markers are now supported in SVG. 2022-03-29 20:44:20 +11:00
Dion Moult 6cd4a2d78b #1153. Add support for elevation symbols in generated SVGs. 2022-03-29 20:30:10 +11:00
Dion Moult 2c6cb2fbb2 #1153. Section references are now auto generated when activating a drawing. 2022-03-29 17:04:50 +11:00
Dion Moult a60dfd8dd5 #1153. Elevation references are now auto generated when activating drawings. 2022-03-29 10:01:41 +11:00
Dion Moult 1b1fd1e6c5 #2048. Minor fix. 2022-03-28 12:52:29 +11:00
Dion Moult 1e60f23880 #2081. Bump IfcOpenShell build. 2022-03-28 11:58:38 +11:00
Dion Moult eb7babb1b0 #2053. Minor fix. Enable strict tolerance everywhere. 2022-03-27 19:05:05 +11:00
Dion Moult 5797937825 #1951. Fix bug where opening standard cases couldn't be added as voids. 2022-03-27 18:21:22 +11:00
Dion Moult 28336140ca #2048. Blender materials, IFC materials and IFC presentation style names are now bi-directional. 2022-03-27 14:43:05 +11:00
Dion Moult ca03a88601 #2104. Support for adding conversion based units to the project. 2022-03-27 10:39:01 +11:00
Thomas Krijnen 0303d217bf #2105 Fix fillets 2022-03-26 13:50:36 +01:00
Dion Moult 4184f2bf46 #2094. Removing styles now also purges Blender material object if possible. 2022-03-26 21:17:52 +11:00
Dion Moult 3b0429ace9 #2106. Fix bug where geometry cache was not cleared when openings were changed 2022-03-26 20:38:30 +11:00
Dion Moult baf75f552e New barebones presentation style manager to see the styles in your project. 2022-03-26 18:15:30 +11:00
Dion Moult 4ea0c73081 Fix bug where we now accommodate invalid durations in the scheduling tree 2022-03-25 19:37:52 +11:00
Dion Moult 33852be3f1 Fix bug where lag times couldn't be edited 2022-03-25 19:37:09 +11:00
Dion Moult 48d476c2b3 Gantt charts now show ISO durations and task captions 2022-03-25 16:09:22 +11:00
Dion Moult 9db07883b7 Scheduling calculations no longer calculate from midnight and now handle a single 9-5 day as a duration of 1 day, as per industry standard practice. 2022-03-25 16:09:22 +11:00
Dion Moult 7af53c543d Fix bug with schedule calculation on zero duration tasks 2022-03-25 16:09:22 +11:00
Dion Moult e7f6ed73ac Fix bug where recalculating a schedule with no time data would fail 2022-03-25 16:09:22 +11:00
Dion Moult 8afa1a4f7d Fix bug where counting working days on a task without a calendar would fail 2022-03-25 16:09:22 +11:00
Dion Moult 1dd85f6c48 Fix bug where removing a rooted or calendar constrained task would purge all calendar assignments 2022-03-25 16:09:22 +11:00
dependabot[bot] 8bd826fb20 Bump numpy in /src/ifcopenshell-python/ifcopenshell
Bumps [numpy](https://github.com/numpy/numpy) from 1.20.3 to 1.21.0.
- [Release notes](https://github.com/numpy/numpy/releases)
- [Changelog](https://github.com/numpy/numpy/blob/main/doc/HOWTO_RELEASE.rst.txt)
- [Commits](https://github.com/numpy/numpy/compare/v1.20.3...v1.21.0)

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

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

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

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

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

* Operator to add new storey to building

* Add new storey to building button in aggregates panel

* Minor fix

* add method to data class to retrieve ifc class name

* Extract aggregate creation logic

* Prevent aggregating non-ifc elements + display popup info

* Add tooltips + rename operators

* Rename function to get relating object

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

* Add utilities to get related objects

* Add utility function to get related objects from element

* display related object in Aggregates panel in object properties

* Revert "Rename function to get relating object"

This reverts commit 5248627a41.

* Update method names

* Add Operator to select parts of an element

* Ditch box layout and only display number of parts

* Add operator to select aggregate of an element

* Display operator to select aggregate of element

* Move logic to aggregate module

* Add utility methods to Blender tool

* Add buttons to create Ifcsite and IfcBuilding from object panel
2022-02-03 07:16:43 +01:00
Dion Moult 94ff8349d3 You can now connect, disconnect, and set flow directions of distribution ports in Blender. 2022-02-03 12:05:05 +11:00
Henri J. Norden d333cb1b78 Cleanup changes 2022-02-02 14:20:46 +01:00
Henri J. Norden 3f4c4a8e52 Hoist compiling mpir & mpfr before HDF5, since they are more likely to fail and HDF5 takes way too long 2022-02-02 14:20:46 +01:00
Henri J. Norden b103e0dde5 Fix build-deps.cmd for VS2019 2022-02-02 14:20:46 +01:00
Dion Moult 883b8a523c Material node trees compliant with basic X3D multitextures are now stored in IFC 2022-02-02 17:51:40 +11:00
Dion Moult 8c0b06b36e Different BSDFs are now recognised for different reflectance methods. 2022-02-02 17:50:38 +11:00
Dion Moult 72397f3aff Saving surface specular roughness values to IFC is now supported. 2022-02-02 15:09:37 +11:00
Dion Moult 77821caae4 Adding surface styles now respect mutually exclusive style types 2022-02-02 15:04:19 +11:00
Dion Moult 10138e28c9 You can now add ports to distribution elements in Blender. 2022-02-01 19:16:31 +11:00
Gorgious f9ce0903f7 Fix #2010 : Authoring data class throws error in BIM tool if no ifc file is selected 2022-02-01 09:12:42 +01:00
Dion Moult a2c0045a40 Support relative object placements for IFC2X3 ports. 2022-02-01 18:23:55 +11:00
Dion Moult 3e10f52965 Relative placements for nested elements are now supported. 2022-02-01 17:39:55 +11:00
Dion Moult fd8aa6d142 IfcOpenShell API now supports port connections and disconnections. 2022-02-01 16:51:42 +11:00
Dion Moult ddb38b2ea0 The IfcOpenShell API now supports adding a distribution port. 2022-01-31 18:28:31 +11:00
Dion Moult 74174b1e09 Fix bug where drawing objects could be added to the wrong context. 2022-01-30 15:09:54 +11:00
Dion Moult 96179db7ad IFC element names no longer treat the Blender name suffix as significant 2022-01-30 15:08:32 +11:00
Dion Moult 116252f857 Object names now guard against duplicate IFC prefixes when assigning. 2022-01-30 15:06:01 +11:00
Dion Moult 538024e035 All element's spatial containment collections are now synced when assigning a class. 2022-01-30 15:04:50 +11:00
Dion Moult e9670a6906 Fix bug where editing then switching to the current representation crashes. 2022-01-30 14:47:22 +11:00
Dion Moult c7428dd160 Fix bug where syncing an object removed from the collection tree breaks. 2022-01-30 14:44:21 +11:00
Dion Moult bf9d46b506 Structural items in the collection hierarchy are now auto synced. 2022-01-30 10:11:19 +11:00
Dion Moult 7ee589cfc2 Remove legacy unassign class feature in favour of just deleting objects. Thanks theoryshaw! 2022-01-30 08:56:43 +11:00
Dion Moult 3083b66f61 Fix #2012. Minor fix. 2022-01-30 08:49:46 +11:00
Dion Moult 074fe0d3a6 You can now specify a target view when creating a new drawing. See #1153. 2022-01-29 21:11:35 +11:00
Dion Moult 46e1a46dd2 Drawing collection hierarchy is now auto fixed if shuffled around manually. See #1153. 2022-01-29 21:11:35 +11:00
Thomas Krijnen 677a9d715c #2009 2022-01-29 09:43:33 +01:00
Dion Moult a210cf654e Drawings list now reads data from IFC, not Blender. See #1153. 2022-01-29 17:26:09 +11:00
Dion Moult 44e4ee70ae Removing sheets now works in IFC. See #1153. 2022-01-29 15:45:45 +11:00
Dion Moult 27e6d702e9 You can now create drawing sheets in IFC. See #1153. 2022-01-29 13:42:46 +11:00
Dion Moult fb76f985c7 Minor fix 2022-01-29 09:01:35 +11:00
Dion Moult 9279aeccf6 You can now show and hide distribution ports of an element. 2022-01-29 08:52:37 +11:00
Dion Moult 92a1739d93 Minor fix - sorry, really need a local Sphinx setup 2022-01-28 20:41:20 +11:00
Dion Moult dfba9827a6 Minor fix 2022-01-28 20:37:18 +11:00
Dion Moult 19e007a67c New diagrams on the developer guide. 2022-01-28 20:32:41 +11:00
Dion Moult d3ef3f1501 Finish building minimum viable product of the IFC2P6 converter. Thanks myoualid! 2022-01-28 18:14:16 +11:00
Dion Moult f2e9f76631 Minor tweaks. See #2006. 2022-01-28 10:48:22 +11:00
333 changed files with 24392 additions and 7673 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: |
+2 -2
View File
@@ -18,14 +18,14 @@ jobs:
- name: Checkout
uses: actions/checkout@v2
with:
lfs: true
submodules: recursive
-
name: Update ubuntu
run: |
sudo apt update
sudo apt-get install git cmake gcc g++ libboost1.67-all-dev python3-all-dev swig libpcre3-dev libxml2-dev
sudo apt-get install libocct-foundation-dev libocct-modeling-algorithms-dev libocct-modeling-data-dev libocct-ocaf-dev libocct-visualization-dev libocct-data-exchange-dev \
libhdf5-dev
libhdf5-dev libcgal-dev
- name: Build
env:
+10
View File
@@ -5,6 +5,15 @@
/_installed-vs*-x*/
/build/
# output directories
/cmake/out/
/docs/out/
/src/examples/out/
/src/ifcmax/out/
/src/ifcwrap/out/
/src/qtviewer/out/
/win/BuildDepsCache*.txt
# IfcExpressParser residue
/src/ifcexpressparser/express_parser.py
@@ -13,6 +22,7 @@ __pycache__
*.py.bak
# Visual Studio Code files
.vscode
.vs
# PyCharm files
.idea
+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**.
+18 -11
View File
@@ -62,8 +62,12 @@ endif()
OPTION(USERSPACE_PYTHON_PREFIX "Installs IfcPython for the current user only instead of system-wide." OFF)
OPTION(ADD_COMMIT_SHA "Add commit sha and branch in version number, warning results in many rebuilds, requires git" OFF)
# TODO QtViewer is deprecated ATM as it uses the 0.4 API
# OPTION(BUILD_QTVIEWER "Build IfcOpenShell Qt GUI Viewer (requires Qt 4 framework)." OFF)
IF(NOT CMAKE_BUILD_TYPE)
SET(CMAKE_BUILD_TYPE "Release")
ENDIF()
# QtViewer requires Qt5
OPTION(BUILD_QTVIEWER "Build IfcOpenShell Qt GUI Viewer" OFF)
include(GNUInstallDirs)
if((BUILD_CONVERT OR BUILD_GEOMSERVER OR BUILD_IFCPYTHON) AND (NOT BUILD_IFCGEOM))
message(STATUS "'IfcGeom' is required with current outputs")
@@ -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,
@@ -881,6 +885,9 @@ INSTALL(FILES ${SERIALIZERS_S_FILES}
)
endif()
IF(BUILD_QTVIEWER)
ADD_SUBDIRECTORY(../src/qtviewer qtviewer)
endif()
list(APPEND CPACK_SOURCE_IGNORE_FILES
.git
+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-4c40fc1-$(PLATFORM)64.zip
cd dist/working && unzip ifcblender*
cp -r dist/working/io_import_scene_ifc/ifcopenshell dist/blenderbim/libs/site/packages/
cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-$(PYNUMBER)-v0.7.0-2985bba-$(PLATFORM)64.zip
cd dist/working && unzip ifcopenshell-python*
cp -r dist/working/ifcopenshell dist/blenderbim/libs/site/packages/
# See bug #812
cd dist/working && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.7.0/src/blenderbim/occ_utils.py
@@ -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
+15 -14
View File
@@ -79,17 +79,18 @@ for name in modules.keys():
classes = [
operator.OpenUri,
operator.SelectDataDir,
operator.SelectSchemaDir,
operator.SelectIfcFile,
operator.OpenUpstream,
operator.AddIfcFile,
operator.BIM_OT_add_section_plane,
operator.BIM_OT_remove_section_plane,
operator.ReloadIfcFile,
operator.AddIfcFile,
operator.RemoveIfcFile,
operator.ConfigureVisibility,
operator.OpenUpstream,
operator.OpenUri,
operator.ReloadIfcFile,
operator.RemoveIfcFile,
operator.SelectDataDir,
operator.SelectIfcFile,
operator.SelectSchemaDir,
operator.SelectURIAttribute,
prop.StrProperty,
prop.ObjProperty,
prop.Attribute,
@@ -105,17 +106,17 @@ 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,
@@ -38,7 +38,7 @@
<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
@@ -50,6 +50,8 @@ g.setOptions({
vFormatArr: ['Day', 'Week', 'Month', 'Quarter'], // Even with setUseSingleCell using Hour format on such a large chart can cause issues in some browsers,
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,
});
File diff suppressed because one or more lines are too long
@@ -0,0 +1,13 @@
ISO-10303-21;
HEADER;
FILE_DESCRIPTION((),'2;1');
FILE_NAME('EPset_Annotation.ifc','2020-01-01T00:00:00',(),(),'EPset_Annotation','EPset_Annotation',$);
FILE_SCHEMA(('IFC4'));
ENDSEC;
DATA;
#1=IFCPROPERTYSETTEMPLATE('3VuPUwdCD2Qx3XDDRs0R1N',$,'EPset_Annotation','',.PSET_OCCURRENCEDRIVEN.,'IfcAnnotation',(#2,#3));
#2=IFCSIMPLEPROPERTYTEMPLATE('2P7JN79n96Q9pElZ83LKe4',$,'ZIndex','',.P_SINGLEVALUE.,'IfcInteger',$,$,$,$,$,.READWRITE.);
#3=IFCSIMPLEPROPERTYTEMPLATE('1Wpx_r2xj1_9w5JpI0QRJy',$,'Symbol','',.P_ENUMERATEDVALUE.,'IfcLabel',$,#4,$,$,$,.READWRITE.);
#4=IFCPROPERTYENUMERATION('Symbol',(IFCLABEL('rectangle-tag'), IFCLABEL('door-tag')),$);
ENDSEC;
END-ISO-10303-21;
@@ -0,0 +1,20 @@
ISO-10303-21;
HEADER;
FILE_DESCRIPTION((),'2;1');
FILE_NAME('EPset_Drawing.ifc','2020-01-01T00:00:00',(),(),'EPset_Drawing','EPset_Drawing',$);
FILE_SCHEMA(('IFC4'));
ENDSEC;
DATA;
#1=IFCPROPERTYSETTEMPLATE('2JhNIvqZrFnAgxfhK0XVQX',$,'EPset_Drawing','',.PSET_OCCURRENCEDRIVEN.,'IfcAnnotation',(#2,#3,#4,#5,#6,#7,#8,#9,#10,#11));
#2=IFCSIMPLEPROPERTYTEMPLATE('23JavTMk98ZxXhrUEnjAcf',$,'TargetView','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
#3=IFCSIMPLEPROPERTYTEMPLATE('1yVWUt5H9DAOuu0OaMMLpe',$,'Scale','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
#4=IFCSIMPLEPROPERTYTEMPLATE('3gsuPBtU93b8f0gg1pjkq6',$,'HumanScale','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
#5=IFCSIMPLEPROPERTYTEMPLATE('0AK5C2UpL4$eaac2LszAx$',$,'HasUnderlay','',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
#6=IFCSIMPLEPROPERTYTEMPLATE('2j2ZEZR8X5tONm7kli5hM6',$,'HasLinework','',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
#7=IFCSIMPLEPROPERTYTEMPLATE('1ttChRysH9UuEX2FeMj5Hu',$,'HasAnnotation','',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
#8=IFCSIMPLEPROPERTYTEMPLATE('2NPPxuABv1huDTVh32TFgw',$,'GlobalReferencing','',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
#9=IFCSIMPLEPROPERTYTEMPLATE('10hT_1zrzEbRRKMXYAWvtD',$,'Metadata','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
#10=IFCSIMPLEPROPERTYTEMPLATE('3Z0BXPSG5CWgtI33ioV7aj',$,'Include','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
#11=IFCSIMPLEPROPERTYTEMPLATE('1RVts_g3PAw98PJA2yL3bO',$,'Exclude','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
ENDSEC;
END-ISO-10303-21;
@@ -23,12 +23,26 @@ text { fill: black; stroke: none; }
.cut { fill: black; stroke: black; stroke-linecap: 'round'; stroke-width: 0.35; fill-rule: evenodd; }
.projection { fill: white; stroke: black; stroke-linecap: 'round'; stroke-width: 0.25; }
.annotation { fill: none; stroke: black; stroke-linecap: 'round'; stroke-width: 0.25; }
.hidden { stroke-dasharray: 3, 2; }
.solid {}
.leader { marker-end: url(#leader-marker); }
.stair { marker-start: url(#stair-marker-start); marker-end: url(#stair-marker-end); }
.break { fill: white; }
.breakline { fill: none; stroke: black; stroke-linecap: 'round'; stroke-width: 0.25; marker-mid: url(#breakline-marker); }
.IfcAnnotation { fill: none; stroke: black; stroke-linecap: 'round'; stroke-width: 0.25; }
.PredefinedType-LINEWORK { stroke: black; stroke-width: 0.25; }
.PredefinedType-BACKGROUND { stroke: black; stroke-width: 0.18; }
.PredefinedType-GRID { marker-start: url(#grid-marker); marker-end: url(#grid-marker); }
.PredefinedType-SECTION { stroke-dasharray: 12.5, 3, 3, 3; }
.PredefinedType-SECTIONLEVEL { marker-start: url(#section-level-marker); stroke-dasharray: 12.5, 3, 3, 3; }
.PredefinedType-PLANLEVEL { marker-end: url(#plan-level-marker); }
.PredefinedType-DIMENSION { marker-start: url(#dimension-marker-start); marker-end: url(#dimension-marker-end); }
.PredefinedType-RADIUS { marker-end: url(#radius-marker-end); }
.PredefinedType-DIAMETER { marker-start: url(#diameter-marker-start); marker-end: url(#diameter-marker-end); }
.PredefinedType-HIDDENLINE { stroke-dasharray: 3, 2; }
.PredefinedType-STAIR { marker-start: url(#stair-marker-start); marker-end: url(#stair-marker-end); }
.PredefinedType-BOUNDARY { fill: none; stroke: red; stroke-width: 1; stroke-dasharray: 12, 4, 3, 4, 3, 4; }
.PredefinedType-BATTING { marker-start: url(#batting); stroke: none; }
.PredefinedType-SEALANT { fill: url(#crosshatch1); stroke-width: 0.25; }
.PredefinedType-FILLAREA { fill: white; stroke: none; }
.PredefinedType-BREAKLINE { fill: none; stroke: black; stroke-width: 0.25; marker-mid: url(#breakline-marker); }
.PredefinedType-TEXT { fill: black; stroke: none; }
path.PredefinedType-TEXTLEADER { marker-end: url(#leader-marker); }
text.PredefinedType-TEXTLEADER { fill: black; stroke: none; }
.material-blank { fill: white; }
.material-diagonal1 { fill: url(#diagonal1); }
.material-diagonal2 { fill: url(#diagonal2); }
@@ -44,5 +58,9 @@ text { fill: black; stroke: none; }
.material-honeycomb { fill: url(#honeycomb); }
.material-sand { fill: url(#sand); }
.material-concrete { fill: url(#concrete); stroke-width: 0.5; }
.material-boundary { fill: none; stroke: red; stroke-width: 1; stroke-dasharray: 12,4,3,4,3,4; }
.IfcSpace { fill: none; stroke: none; }
.PredefinedType-STUD { stroke: black; stroke-width: 1; }
.PredefinedType-WOOD { fill: url(#wood); stroke: black; stroke-width: 0.5; }
.PredefinedType-STEEL { fill: url(#steel); stroke: black; stroke-width: 0.5; }
.PredefinedType-CONCRETE { fill: url(#concrete); stroke: black; stroke-width: 0.5; }
.PredefinedType-PLASTERBOARD { fill: url(#sand); stroke: black; stroke-width: 0.25; }
@@ -1,5 +1,9 @@
<?xml version="1.0" encoding="utf-8" ?>
<svg baseProfile="full" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:ev="http://www.w3.org/2001/xml-events" xmlns:xlink="http://www.w3.org/1999/xlink">
<marker id="batting" markerHeight="10" markerWidth="4" orient="auto" refX="0" refY="5" markerUnits="userSpaceOnUse">
<!-- Tip: https://svg-path-visualizer.netlify.app/ -->
<path style="fill: none; stroke:black; stroke-width:0.18" d="M 0 2 A 2 2 0 0 1 4 2 L 2 8 M 0 2 L 2 8 A 2 2 0 0 0 4 10 M 2 8 A 2 2 0 0 1 0 10" />
</marker>
<marker id="eng-dimension-marker-start" markerHeight="14" markerWidth="11" orient="auto" refX="1" refY="7">
<g>
<path d="M 11 3.5 L 11 10.5 L 1 7" class="annotation" style="fill:black;" />
@@ -12,6 +16,23 @@
<path d="M 10 0 L 10 14" class="annotation" style="stroke-width:1;" />
</g>
</marker>
<marker id="radius-marker-end" markerHeight="14" markerWidth="11" orient="auto" refX="10" refY="7">
<g>
<path d="M 0 3.5 L 0 10.5 L 10 7" class="annotation" style="fill:black;" />
</g>
</marker>
<marker id="diameter-marker-start" markerHeight="14" markerWidth="11" orient="auto" refX="1" refY="7">
<g>
<path d="M 11 3.5 L 11 10.5 L 1 7" class="annotation" style="fill:black;" />
<path d="M 1 0 L 1 14" class="annotation" style="stroke-width:1;" />
</g>
</marker>
<marker id="diameter-marker-end" markerHeight="14" markerWidth="11" orient="auto" refX="10" refY="7">
<g>
<path d="M 0 3.5 L 0 10.5 L 10 7" class="annotation" style="fill:black;" />
<path d="M 10 0 L 10 14" class="annotation" style="stroke-width:1;" />
</g>
</marker>
<marker id="dimension-marker-start" markerHeight="30" markerWidth="20" refX="10" refY="15" orient="auto">
<g>
<path d="M 0 15 L 20 15" class="annotation" style="stroke-width:1;" />
@@ -53,7 +74,7 @@
<path d="M 0 1 L 90 1" class="annotation" style="stroke-width:1;" />
</g>
</marker>
<marker id="breakline-marker" markerHeight="20" markerWidth="15" refX="7.5" refY="10">
<marker id="breakline-marker" markerHeight="20" markerWidth="15" refX="7.5" refY="10" orient="auto">
<g>
<path d="M 0 10 L 5 20 L 7.5 10" class="annotation" style="stroke-width:1; fill: white;" />
<path d="M 7.5 10 L 10 0 L 15 10" class="annotation" style="stroke-width:1; fill: white;" />

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

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

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 67 KiB

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

Before

Width:  |  Height:  |  Size: 572 B

After

Width:  |  Height:  |  Size: 1.2 KiB

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

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

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

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

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

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

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

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

+3 -1
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,7 +48,7 @@ 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))
@@ -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"
+9 -12
View File
@@ -26,9 +26,7 @@ from blenderbim.bim.module.drawing.prop import RasterStyleProperty
from bpy.app.handlers import persistent
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.module.owner.prop import get_user_person, get_user_organisation
from ifcopenshell.api.attribute.data import Data as AttributeData
from ifcopenshell.api.material.data import Data as MaterialData
from ifcopenshell.api.style.data import Data as StyleData
from ifcopenshell.api.type.data import Data as TypeData
@@ -68,11 +66,10 @@ def name_callback(obj, data):
if isinstance(obj, bpy.types.Material):
if obj.BIMObjectProperties.ifc_definition_id:
IfcStore.get_file().by_id(obj.BIMObjectProperties.ifc_definition_id).Name = obj.name
AttributeData.load(IfcStore.get_file(), obj.BIMObjectProperties.ifc_definition_id)
MaterialData.load_materials()
if obj.BIMMaterialProperties.ifc_style_id:
IfcStore.get_file().by_id(obj.BIMMaterialProperties.ifc_style_id).Name = obj.name
StyleData.load(IfcStore.get_file(), obj.BIMMaterialProperties.ifc_style_id)
refresh_ui_data()
return
if not obj.BIMObjectProperties.ifc_definition_id or "/" not in obj.name:
@@ -96,7 +93,7 @@ def name_callback(obj, data):
if element.is_a("IfcTypeProduct"):
TypeData.purge()
element.Name = "/".join(obj.name.split("/")[1:])
AttributeData.load(IfcStore.get_file(), obj.BIMObjectProperties.ifc_definition_id)
refresh_ui_data()
def color_callback(obj, data):
@@ -160,12 +157,12 @@ def loadIfcStore(scene):
if not IfcStore.get_file():
return
IfcStore.get_schema()
IfcStore.reload_linked_elements()
IfcStore.relink_all_objects()
@persistent
def undo_pre(scene):
IfcStore.update_undo_redo_stack_objects()
IfcStore.track_undo_redo_stack_object_map()
@persistent
@@ -174,13 +171,13 @@ def undo_post(scene):
IfcStore.last_transaction = bpy.context.scene.BIMProperties.last_transaction
IfcStore.undo()
purge_module_data()
IfcStore.update_undo_redo_stack_objects()
IfcStore.reload_linked_elements(objects=[bpy.data.objects.get(o) for o in IfcStore.undo_redo_stack_objects])
IfcStore.track_undo_redo_stack_selected_objects()
IfcStore.reload_undo_redo_stack_objects()
@persistent
def redo_pre(scene):
IfcStore.update_undo_redo_stack_objects()
IfcStore.track_undo_redo_stack_object_map()
@persistent
@@ -189,8 +186,8 @@ def redo_post(scene):
IfcStore.last_transaction = bpy.context.scene.BIMProperties.last_transaction
IfcStore.redo()
purge_module_data()
IfcStore.update_undo_redo_stack_objects()
IfcStore.reload_linked_elements(objects=[bpy.data.objects.get(o) for o in IfcStore.undo_redo_stack_objects])
IfcStore.track_undo_redo_stack_selected_objects()
IfcStore.reload_undo_redo_stack_objects()
@persistent
+5
View File
@@ -48,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):
@@ -78,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":
+69 -7
View File
@@ -43,6 +43,7 @@ class IfcStore:
library_file = None
element_listeners = set()
undo_redo_stack_objects = set()
undo_redo_stack_object_names = {}
current_transaction = ""
last_transaction = ""
history = []
@@ -86,6 +87,7 @@ class IfcStore:
ifc_hash = hashlib.md5(ifc_key.encode("utf-8")).hexdigest()
IfcStore.cache_path = os.path.join(bpy.context.scene.BIMProperties.data_dir, "cache", f"{ifc_hash}.h5")
cache_settings = ifcopenshell.geom.settings()
cache_settings.set(cache_settings.STRICT_TOLERANCE, True)
try:
IfcStore.cache = ifcopenshell.geom.serializers.hdf5(IfcStore.cache_path, cache_settings)
except:
@@ -146,14 +148,32 @@ class IfcStore:
IfcStore.element_listeners.add(callback)
@staticmethod
def update_undo_redo_stack_objects():
def track_undo_redo_stack_object_map():
"""Keeps track of currently mapped object names, typically during undo and redo
When any Blender object is stored outside a Blender PointerProperty, such as
in a regular Python list, there is the likely probability that the object
will be invalidated when undo or redo occurs. Object invalidation seems to
occur whenever an object is affected during an operation.
For example, if an operator deletes a modifier on o1, then o1 will be invalidated.
"""
for key, value in IfcStore.id_map.items():
try:
IfcStore.undo_redo_stack_object_names[key] = value.name
except:
continue
@staticmethod
def track_undo_redo_stack_selected_objects():
"""Keeps track of selected object names, typically during undo and redo
When any Blender object is stored outside a Blender PointerProperty, such as
in a regular Python list, there is the likely probability that the object
will be invalidated when undo or redo occurs. Object invalidation seems to
only occur for selected objects either pre/post undo/redo event, including
selected objects for consecutive undo/redos, and all children.
occur for selected objects either pre/post undo/redo event, including
selected objects for consecutive undo/redos, and all children. This is
important because selected objects are often deleted from the scene.
So if I first select o1, then o2, then o3, then press undo, o3 will be
invalidated. If instead I press undo twice, o3 and o2 will be invalidated.
@@ -168,14 +188,23 @@ class IfcStore:
IfcStore.undo_redo_stack_objects |= objects
@staticmethod
def reload_linked_elements(objects=None):
def reload_undo_redo_stack_objects():
"""Reloads any invalidated objects after undo or redo
After an undo or redo operation, objects may have been invalidated in
our id_map and guid_map. Invalidated objects are typically those that
have been manipulated or deleted. This checks the cache of mapped and
selected objects prior to the operation and ensures that if the object
is invalidated, they are reloaded based on the object name that was
tracked prior to the undo / redo.
"""
file = IfcStore.get_file()
if not file:
return
if objects is None:
objects = bpy.data.objects
for obj in objects:
# First, reload objects that were selected or active
for name in IfcStore.undo_redo_stack_objects:
obj = bpy.data.objects.get(name)
if not obj:
continue
if not obj.BIMObjectProperties.ifc_definition_id:
@@ -186,6 +215,38 @@ class IfcStore:
data["guid"] = element.GlobalId
IfcStore.commit_link_element(data)
# Scan for any straggling invalidated objects which were indirectly affected and reload them too.
for key, value in IfcStore.id_map.items():
try:
value.name
except:
obj = bpy.data.objects.get(IfcStore.undo_redo_stack_object_names[key])
if not obj or not obj.BIMObjectProperties.ifc_definition_id:
continue
element = file.by_id(obj.BIMObjectProperties.ifc_definition_id)
data = {"id": element.id(), "obj": obj.name}
if hasattr(element, "GlobalId"):
data["guid"] = element.GlobalId
IfcStore.commit_link_element(data)
@staticmethod
def relink_all_objects():
file = IfcStore.get_file()
if not file:
return
for obj in bpy.data.objects:
if not obj:
continue
if not obj.BIMObjectProperties.ifc_definition_id:
continue
element = file.by_id(obj.BIMObjectProperties.ifc_definition_id)
data = {"id": element.id(), "obj": obj.name}
if hasattr(element, "GlobalId"):
data["guid"] = element.GlobalId
IfcStore.commit_link_element(data)
@staticmethod
def delete_element(element):
IfcStore.deleted_ids.add(element.id())
@@ -311,6 +372,7 @@ class IfcStore:
@staticmethod
def begin_transaction(operator):
IfcStore.undo_redo_stack_objects = set()
IfcStore.undo_redo_stack_object_names = {}
IfcStore.current_transaction = str(uuid.uuid4())
operator.transaction_key = IfcStore.current_transaction
+397 -161
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
@@ -160,6 +163,7 @@ class IfcImporter:
self.settings = ifcopenshell.geom.settings()
self.settings.set_deflection_tolerance(self.ifc_import_settings.deflection_tolerance)
self.settings.set_angular_tolerance(self.ifc_import_settings.angular_tolerance)
self.settings.set(self.settings.STRICT_TOLERANCE, True)
self.settings_native = ifcopenshell.geom.settings()
self.settings_native.set(self.settings_native.INCLUDE_CURVES, True)
self.settings_2d = ifcopenshell.geom.settings()
@@ -279,6 +283,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
@@ -345,16 +357,17 @@ class IfcImporter:
# FacetedBreps (without voids) are meshes. See #841.
# Commented out as seems currently too slow.
# if self.is_native_faceted_brep(representations):
# self.native_data[element.GlobalId] = {
# "representations": representations,
# "representation": self.get_body_representation(element.Representation.Representations),
# "type": "IfcFacetedBrep",
# }
# return True
# self.native_data[element.GlobalId] = {
# "representations": representations,
# "representation": self.get_body_representation(element.Representation.Representations),
# "type": "IfcFacetedBrep",
# }
# return True
def is_native_swept_disk_solid(self, representations):
for representation in representations:
if len(representation["raw"].Items) == 1 and representation["raw"].Items[0].is_a("IfcSweptDiskSolid"):
items = representation["raw"].Items or [] # Be forgiving of invalid IFCs because Revit :(
if len(items) == 1 and items[0].is_a("IfcSweptDiskSolid"):
return True
return False
@@ -378,6 +391,8 @@ class IfcImporter:
props = bpy.context.scene.BIMGeoreferenceProperties
if props.has_blender_offset:
return
if self.ifc_import_settings.should_offset_model:
return self.set_manual_blender_offset()
if self.file.schema == "IFC2X3":
project = self.file.by_type("IfcProject")[0]
else:
@@ -390,6 +405,13 @@ class IfcImporter:
return self.guess_georeferencing(building)
return self.guess_absolute_coordinate()
def set_manual_blender_offset(self):
props = bpy.context.scene.BIMGeoreferenceProperties
props.blender_eastings = str(self.ifc_import_settings.model_offset_coordinates[0])
props.blender_northings = str(self.ifc_import_settings.model_offset_coordinates[1])
props.blender_orthogonal_height = str(self.ifc_import_settings.model_offset_coordinates[2])
props.has_blender_offset = True
def guess_georeferencing(self, element):
if not element.ObjectPlacement or not element.ObjectPlacement.is_a("IfcLocalPlacement"):
return
@@ -406,10 +428,12 @@ class IfcImporter:
props.has_blender_offset = True
def guess_absolute_coordinate(self):
# Civil BIM applications like to work in absolute coordinates, where the ObjectPlacement is 0,0,0 but each
# individual coordinate of the shape representation is in absolute values.
# Civil BIM applications like to work in absolute coordinates, where the
# ObjectPlacement is usually 0,0,0 (but not always, so we'll need to
# check for the actual transformation) but each individual coordinate of
# the shape representation is in absolute values.
offset_point = self.get_offset_point()
if not offset_point:
if offset_point is None:
return
props = bpy.context.scene.BIMGeoreferenceProperties
props.blender_eastings = str(offset_point[0])
@@ -419,39 +443,48 @@ class IfcImporter:
def get_offset_point(self):
elements_checked = 0
# If more than these points aren't far away, the file probably isn't absolutely positioned
element_checking_threshold = 100
if self.file.schema == "IFC2X3":
# IFC2X3 does not have IfcCartesianPointList3D
point_lists = []
else:
point_lists = self.file.by_type("IfcCartesianPointList3D")
for point_list in point_lists:
elements_checked += 1
if elements_checked > element_checking_threshold:
return
for i, point in enumerate(point_list.CoordList):
if len(point) == 3 and self.is_point_far_away(point, is_meters=False):
return point
for point in self.file.by_type("IfcCartesianPoint"):
is_used_in_placement = False
for inverse in self.file.get_inverse(point):
if inverse.is_a("IfcAxis2Placement3D"):
is_used_in_placement = True
break
if is_used_in_placement:
# If more than these elements aren't far away, the file probably isn't absolutely positioned
element_checking_threshold = 10
for element in self.file.by_type("IfcElement"):
if not element.Representation:
continue
elements_checked += 1
if elements_checked > element_checking_threshold:
return
if len(point.Coordinates) == 3 and self.is_point_far_away(point, is_meters=False):
return point.Coordinates
if not self.does_element_likely_have_geometry_far_away(element):
continue
shape = ifcopenshell.geom.create_shape(self.settings, element)
m = shape.transformation.matrix.data
mat = np.array(
([m[0], m[3], m[6], m[9]], [m[1], m[4], m[7], m[10]], [m[2], m[5], m[8], m[11]], [0, 0, 0, 1])
)
point = np.array(
(
shape.geometry.verts[0] / self.unit_scale,
shape.geometry.verts[1] / self.unit_scale,
shape.geometry.verts[2] / self.unit_scale,
0.0,
)
)
return mat @ point
def does_element_likely_have_geometry_far_away(self, element):
for representation in element.Representation.Representations:
for subelement in self.file.traverse(representation):
if subelement.is_a("IfcCartesianPointList3D"):
for point in subelement.CoordList:
if len(point) == 3 and self.is_point_far_away(point, is_meters=False):
return True
if subelement.is_a("IfcCartesianPoint"):
if len(subelement.Coordinates) == 3 and self.is_point_far_away(subelement, is_meters=False):
return True
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,
@@ -461,13 +494,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]
matrix[1, 3] += self.ifc_import_settings.model_offset_coordinates[1]
matrix[2, 3] += self.ifc_import_settings.model_offset_coordinates[2]
return mathutils.Matrix(matrix.tolist())
@@ -561,7 +587,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:
@@ -583,14 +608,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):
@@ -852,78 +869,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")
@@ -1234,25 +1311,172 @@ class IfcImporter:
name = style.Name or str(style.id())
blender_material = bpy.data.materials.new(name)
old_definition_id = blender_material.BIMObjectProperties.ifc_definition_id
if not old_definition_id:
self.link_element(style, blender_material)
blender_material.BIMObjectProperties.ifc_definition_id = old_definition_id
self.link_element(style, blender_material)
blender_material.BIMMaterialProperties.ifc_style_id = style.id()
self.material_creator.styles[style.id()] = blender_material
rendering_style = None
texture_style = None
for surface_style in style.Styles:
if surface_style.is_a("IfcSurfaceStyleShading"):
alpha = 1.0
# Transparency was added in IFC4
if hasattr(surface_style, "Transparency") and surface_style.Transparency:
alpha = 1 - surface_style.Transparency
blender_material.diffuse_color = (
surface_style.SurfaceColour.Red,
surface_style.SurfaceColour.Green,
surface_style.SurfaceColour.Blue,
alpha,
if surface_style.is_a() == "IfcSurfaceStyleShading":
self.create_surface_style_shading(blender_material, surface_style)
elif surface_style.is_a("IfcSurfaceStyleRendering"):
rendering_style = surface_style
self.create_surface_style_rendering(blender_material, surface_style)
elif surface_style.is_a("IfcSurfaceStyleWithTextures"):
texture_style = surface_style
if rendering_style and texture_style:
self.create_surface_style_with_textures(blender_material, rendering_style, texture_style)
def create_surface_style_shading(self, blender_material, surface_style):
alpha = 1.0
# Transparency was added in IFC4
if hasattr(surface_style, "Transparency") and surface_style.Transparency:
alpha = 1 - surface_style.Transparency
blender_material.diffuse_color = (
surface_style.SurfaceColour.Red,
surface_style.SurfaceColour.Green,
surface_style.SurfaceColour.Blue,
alpha,
)
def create_surface_style_rendering(self, blender_material, surface_style):
self.create_surface_style_shading(blender_material, surface_style)
if surface_style.ReflectanceMethod in ["PHYSICAL", "NOTDEFINED"]:
blender_material.use_nodes = True
bsdf = blender_material.node_tree.nodes["Principled BSDF"]
if surface_style.DiffuseColour and surface_style.DiffuseColour.is_a("IfcColourRgb"):
bsdf.inputs["Base Color"].default_value = (
surface_style.DiffuseColour.Red,
surface_style.DiffuseColour.Green,
surface_style.DiffuseColour.Blue,
1,
)
if surface_style.SpecularColour and surface_style.SpecularColour.is_a("IfcNormalisedRatioMeasure"):
bsdf.inputs["Metallic"].default_value = surface_style.SpecularColour.wrappedValue
if surface_style.SpecularHighlight and surface_style.SpecularHighlight.is_a("IfcSpecularRoughness"):
bsdf.inputs["Roughness"].default_value = surface_style.SpecularHighlight.wrappedValue
if hasattr(surface_style, "Transparency") and surface_style.Transparency:
bsdf.inputs["Alpha"].default_value = 1 - surface_style.Transparency
blender_material.blend_method = "BLEND"
elif surface_style.ReflectanceMethod == "FLAT":
blender_material.use_nodes = True
output = {n.type: n for n in blender_material.node_tree.nodes}.get("OUTPUT_MATERIAL", None)
bsdf = blender_material.node_tree.nodes["Principled BSDF"]
mix = blender_material.node_tree.nodes.new(type="ShaderNodeMixShader")
mix.location = bsdf.location
blender_material.node_tree.links.new(mix.outputs[0], output.inputs["Surface"])
blender_material.node_tree.nodes.remove(bsdf)
lightpath = blender_material.node_tree.nodes.new(type="ShaderNodeLightPath")
lightpath.location = mix.location - mathutils.Vector((200, -200))
blender_material.node_tree.links.new(lightpath.outputs[0], mix.inputs[0])
bsdf = blender_material.node_tree.nodes.new(type="ShaderNodeBsdfTransparent")
bsdf.location = mix.location - mathutils.Vector((200, 0))
blender_material.node_tree.links.new(bsdf.outputs[0], mix.inputs[1])
rgb = blender_material.node_tree.nodes.new(type="ShaderNodeRGB")
rgb.location = mix.location - mathutils.Vector((200, 200))
blender_material.node_tree.links.new(rgb.outputs[0], mix.inputs[2])
if surface_style.DiffuseColour and surface_style.DiffuseColour.is_a("IfcColourRgb"):
rgb.outputs[0].default_value = (
surface_style.DiffuseColour.Red,
surface_style.DiffuseColour.Green,
surface_style.DiffuseColour.Blue,
1,
)
def create_surface_style_with_textures(self, blender_material, rendering_style, texture_style):
for texture in texture_style.Textures:
mode = getattr(texture, "Mode", None)
node = None
if texture.is_a("IfcImageTexture"):
image_url = texture.URLReference
if not os.path.abspath(texture.URLReference) and tool.Ifc.get_path():
image_url = os.path.join(os.path.dirname(tool.Ifc.get_path()), texture.URLReference)
if rendering_style.ReflectanceMethod in ["PHYSICAL", "NOTDEFINED"]:
bsdf = blender_material.node_tree.nodes["Principled BSDF"]
if mode == "NORMAL":
normalmap = blender_material.node_tree.nodes.new(type="ShaderNodeNormalMap")
normalmap.location = bsdf.location - mathutils.Vector((200, 0))
blender_material.node_tree.links.new(normalmap.outputs[0], bsdf.inputs["Normal"])
node = blender_material.node_tree.nodes.new(type="ShaderNodeTexImage")
node.location = normalmap.location - mathutils.Vector((200, 0))
image = bpy.data.images.load(image_url)
image.colorspace_settings.name = "Non-Color"
node.image = image
blender_material.node_tree.links.new(node.outputs[0], normalmap.inputs["Color"])
elif mode == "EMISSIVE":
output = {n.type: n for n in blender_material.node_tree.nodes}.get("OUTPUT_MATERIAL", None)
add = blender_material.node_tree.nodes.new(type="ShaderNodeAddShader")
add.location = bsdf.location + mathutils.Vector((200, 0))
blender_material.node_tree.links.new(bsdf.outputs[0], add.inputs[1])
blender_material.node_tree.links.new(add.outputs[0], output.inputs[0])
emission = blender_material.node_tree.nodes.new(type="ShaderNodeEmission")
emission.location = add.location - mathutils.Vector((200, 0))
blender_material.node_tree.links.new(emission.outputs[0], add.inputs[0])
node = blender_material.node_tree.nodes.new(type="ShaderNodeTexImage")
node.location = emission.location - mathutils.Vector((200, 0))
image = bpy.data.images.load(image_url)
node.image = image
blender_material.node_tree.links.new(node.outputs[0], emission.inputs[0])
elif mode == "METALLICROUGHNESS":
separate = blender_material.node_tree.nodes.new(type="ShaderNodeSeparateRGB")
separate.location = bsdf.location - mathutils.Vector((200, 0))
blender_material.node_tree.links.new(separate.outputs[1], bsdf.inputs["Roughness"])
blender_material.node_tree.links.new(separate.outputs[2], bsdf.inputs["Metallic"])
node = blender_material.node_tree.nodes.new(type="ShaderNodeTexImage")
node.location = separate.location - mathutils.Vector((200, 0))
image = bpy.data.images.load(image_url)
image.colorspace_settings.name = "Non-Color"
node.image = image
blender_material.node_tree.links.new(node.outputs[0], separate.inputs[0])
elif mode == "OCCLUSION":
# TODO work out how to implement glTF settings here
# https://docs.blender.org/manual/en/dev/addons/import_export/scene_gltf2.html
pass
elif mode == "DIFFUSE":
node = blender_material.node_tree.nodes.new(type="ShaderNodeTexImage")
node.location = bsdf.location - mathutils.Vector((400, 0))
image = bpy.data.images.load(image_url)
node.image = image
blender_material.node_tree.links.new(node.outputs[0], bsdf.inputs["Base Color"])
blender_material.node_tree.links.new(node.outputs[1], bsdf.inputs["Alpha"])
blender_material.blend_method = "BLEND"
elif rendering_style.ReflectanceMethod == "FLAT":
bsdf = blender_material.node_tree.nodes["Mix Shader"]
if mode == "EMISSIVE":
node = blender_material.node_tree.nodes.new(type="ShaderNodeTexImage")
node.location = bsdf.location - mathutils.Vector((200, 0))
image = bpy.data.images.load(image_url)
node.image = image
blender_material.node_tree.links.new(node.outputs[0], bsdf.inputs[2])
if node and getattr(texture, "IsMappedBy", None):
coordinates = texture.IsMappedBy[0]
coord = blender_material.node_tree.nodes.new(type="ShaderNodeTexCoord")
coord.location = node.location - mathutils.Vector((200, 0))
if coordinates.is_a("IfcTextureCoordinateGenerator") and coordinates.Mode == "COORD":
blender_material.node_tree.links.new(coord.outputs["Generated"], node.inputs["Vector"])
elif coordinates.is_a("IfcTextureCoordinateGenerator") and coordinates.Mode == "COORD-EYE":
blender_material.node_tree.links.new(coord.outputs["Camera"], node.inputs["Vector"])
else:
blender_material.node_tree.links.new(coord.outputs["UV"], node.inputs["Vector"])
def get_name(self, element):
return "{}/{}".format(element.is_a(), element.Name)
@@ -1298,9 +1522,10 @@ class IfcImporter:
return self.structural_member_collection.objects.link(obj)
elif element.is_a("IfcStructuralConnection"):
return self.structural_connection_collection.objects.link(obj)
elif element.is_a("IfcAnnotation") and self.is_drawing_annotation(element):
group = [r for r in element.HasAssignments if r.is_a("IfcRelAssignsToGroup")][0].RelatingGroup
return self.collections[group.GlobalId].objects.link(obj)
elif element.is_a("IfcAnnotation"):
group = self.get_drawing_group(element)
if group:
return self.collections[group.GlobalId].objects.link(obj)
container = ifcopenshell.util.element.get_container(element)
if container:
@@ -1320,28 +1545,16 @@ class IfcImporter:
return element.ObjectType in [
"DIMENSION",
"EQUAL_DIMENSION",
"MISC",
"PLAN_LEVEL",
"SECTION_LEVEL",
"STAIR_ARROW",
"TEXT_LEADER",
]
def is_drawing_annotation(self, element):
return element.ObjectType in [
"BREAKLINE",
"DIMENSION",
"DRAWING",
"EQUAL_DIMENSION",
"GRID",
"HIDDEN_LINE",
"MISC",
"PLAN_LEVEL",
"SECTION_LEVEL",
"STAIR_ARROW",
"TEXT",
"TEXT_LEADER",
]
def get_drawing_group(self, element):
for rel in element.HasAssignments or []:
if rel.is_a("IfcRelAssignsToGroup"):
return rel.RelatingGroup
def get_element_matrix(self, element):
result = ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement)
@@ -1467,6 +1680,12 @@ class IfcImporter:
else:
camera.BIMCameraProperties.diagram_scale = "CUSTOM"
camera.BIMCameraProperties.custom_diagram_scale = pset["Scale"]
if "HasUnderlay" in pset:
camera.BIMCameraProperties.has_underlay = pset["HasUnderlay"]
if "HasLinework" in pset:
camera.BIMCameraProperties.has_linework = pset["HasLinework"]
if "HasAnnotation" in pset:
camera.BIMCameraProperties.has_annotation = pset["HasAnnotation"]
return camera
def create_curve(self, element, shape):
@@ -1509,20 +1728,34 @@ 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,
m = shape.transformation.matrix.data
mat = np.array(
([m[0], m[3], m[6], m[9]], [m[1], m[4], m[7], m[10]], [m[2], m[5], m[8], m[11]], [0, 0, 0, 1])
)
for i, vert in enumerate(geometry.verts):
if ordinate_index > 2:
ordinate_index = 0
verts[i] = vert - offset_point[ordinate_index]
ordinate_index += 1
offset_point = np.linalg.inv(mat) @ np.array(
(
float(props.blender_eastings),
float(props.blender_northings),
float(props.blender_orthogonal_height),
0.0,
)
)
verts = [None] * len(geometry.verts)
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],
offset_point[0] * self.unit_scale,
offset_point[1] * self.unit_scale,
offset_point[2] * self.unit_scale,
float(props.blender_x_axis_abscissa),
float(props.blender_x_axis_ordinate),
)
mesh["has_cartesian_point_offset"] = True
else:
verts = geometry.verts
mesh["has_cartesian_point_offset"] = False
if geometry.faces:
num_vertices = len(verts) // 3
@@ -1610,7 +1843,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
@@ -1637,11 +1871,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")
@@ -26,7 +26,7 @@ import blenderbim.bim.import_ifc as import_ifc
def get_boundaries_collection(blender_space):
space_collection = bpy.data.collections.get(blender_space.name)
space_collection = bpy.data.collections.get(blender_space.name, blender_space.users_collection[0])
collection_name = f"Boundaries/{blender_space.BIMObjectProperties.ifc_definition_id}"
boundaries_collection = space_collection.children.get(collection_name)
if not boundaries_collection:
@@ -44,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)
@@ -61,13 +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)
self.ifc_importer.link_mesh(shape, mesh)
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)
@@ -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
@@ -32,6 +32,7 @@
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 (
@@ -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)
@@ -33,9 +33,14 @@ classes = (
operator.AddSheet,
operator.BuildSchedule,
operator.CleanWireframes,
operator.ContractSheet,
operator.ConvertEdgeToArc,
operator.CopyGrid,
operator.CreateDrawing,
operator.CreateSheets,
operator.DisableEditingDrawings,
operator.DisableEditingSchedules,
operator.DisableEditingSheets,
operator.DisableEditingText,
operator.DisableEditingTextProduct,
operator.EditText,
@@ -43,12 +48,15 @@ classes = (
operator.EditVectorStyle,
operator.EnableEditingText,
operator.EnableEditingTextProduct,
operator.GenerateReferences,
operator.ExpandSheet,
operator.LoadDrawings,
operator.LoadSchedules,
operator.LoadSheets,
operator.OpenSchedule,
operator.OpenSheet,
operator.OpenView,
operator.OpenViewCamera,
operator.RefreshDrawingList,
operator.RemoveDrawing,
operator.RemoveDrawingFromSheet,
operator.RemoveDrawingStyle,
operator.RemoveDrawingStyleAttribute,
operator.RemoveSchedule,
@@ -56,7 +64,6 @@ classes = (
operator.ResizeText,
operator.SaveDrawingStyle,
operator.SelectDocIfcFile,
operator.SelectScheduleFile,
prop.Variable,
prop.Drawing,
prop.Schedule,
@@ -67,12 +74,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,13 +151,13 @@ 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)
return obj
elif object_type == "TEXT_LEADER":
elif object_type in ("TEXT_LEADER", "SECTION_LEVEL"):
data = bpy.data.curves.new(object_type, type="CURVE")
data.dimensions = "3D"
data.resolution_u = 2
@@ -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,9 @@ import blenderbim.tool as tool
def refresh():
TextData.is_loaded = False
SheetsData.is_loaded = False
SchedulesData.is_loaded = False
DrawingsData.is_loaded = False
class TextData:
@@ -54,3 +57,55 @@ 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"]]
class SchedulesData:
data = {}
is_loaded = False
@classmethod
def load(cls):
cls.data = {"total_schedules": cls.total_schedules()}
cls.is_loaded = True
@classmethod
def total_schedules(cls):
return len([d for d in tool.Ifc.get().by_type("IfcDocumentInformation") if d.Scope == "SCHEDULE"])
@@ -40,7 +40,7 @@ class BaseDecorator:
DEF_GLSL = """
#define PI 3.141592653589793
#define MAX_POINTS 32
#define MAX_POINTS 64
#define CIRCLE_SEGS 12
"""
@@ -170,12 +170,32 @@ class BaseDecorator:
returns: iterable of blender objects
"""
results = []
decoration_presets = (
"DIMENSION",
"TEXT_LEADER",
"STAIR_ARROW",
"HIDDEN_LINE",
"PLAN_LEVEL",
"SECTION_LEVEL",
"BREAKLINE",
"GRID",
"ELEVATION",
"SECTION",
"TEXT",
)
for obj in collection.all_objects:
element = tool.Ifc.get_entity(obj)
if not element:
continue
if element.is_a("IfcAnnotation") and element.ObjectType == self.objecttype:
results.append(obj)
if element.is_a("IfcAnnotation"):
if element.ObjectType == self.objecttype:
results.append(obj)
elif (
self.objecttype == "MISC"
and element.ObjectType not in decoration_presets
and isinstance(obj.data, bpy.types.Mesh)
):
results.append(obj)
return results
def get_path_geom(self, obj, topo=True):
@@ -247,7 +267,7 @@ class BaseDecorator:
"""perform actual drawing stuff"""
raise NotImplementedError()
def draw_lines(self, context, obj, vertices, indices, topology=None):
def draw_lines(self, context, obj, vertices, indices, topology=None, is_scale_dependant=True):
region = context.region
region3d = context.region_data
color = context.scene.DocProperties.decorations_colour
@@ -277,14 +297,15 @@ class BaseDecorator:
self.shader.uniform_float("winsize", (region.width, region.height))
self.shader.uniform_float("color", color)
# Horrific prototype code
factor = self.camera_zoom_to_factor(context.space_data.region_3d.view_camera_zoom)
camera_width_px = factor * context.region.width
mm_to_px = camera_width_px / self.camera_width_mm
# 0.00025 is a magic constant number I visually discovered to get the right number.
# It probably should be dynamically calculated using system.dpi or something.
viewport_drawing_scale = 0.00025 * mm_to_px
self.shader.uniform_float("viewportDrawingScale", viewport_drawing_scale)
if is_scale_dependant:
# Horrific prototype code
factor = self.camera_zoom_to_factor(context.space_data.region_3d.view_camera_zoom)
camera_width_px = factor * context.region.width
mm_to_px = camera_width_px / self.camera_width_mm
# 0.00025 is a magic constant number I visually discovered to get the right number.
# It probably should be dynamically calculated using system.dpi or something.
viewport_drawing_scale = 0.00025 * mm_to_px
self.shader.uniform_float("viewportDrawingScale", viewport_drawing_scale)
batch.draw(self.shader)
@@ -357,11 +378,11 @@ class BaseDecorator:
elif "/" in precision:
precision = int(precision.split("/")[1])
elif unit_system == "METRIC":
precision = 3
precision = 4
else:
return
return bpy.utils.units.to_string(unit_system, "LENGTH", value, precision=precision, split_unit=True)
return bpy.utils.units.to_string(unit_system, "LENGTH", value, precision=precision)
class DimensionDecorator(BaseDecorator):
@@ -459,27 +480,8 @@ class DimensionDecorator(BaseDecorator):
self.draw_label(context, text, p0 + (dir) * 0.5, dir)
class EqualityDecorator(DimensionDecorator):
"""Decorator for equality objects
- outlines each segment
- augments with arrows on both sides
- puts 'EQ' label
"""
objecttype = "EQUAL_DIMENSION"
def draw_labels(self, context, obj, vertices, indices):
region = context.region
region3d = context.region_data
for i0, i1 in indices:
v0 = Vector(vertices[i0])
v1 = Vector(vertices[i1])
p0 = location_3d_to_region_2d(region, region3d, v0)
p1 = location_3d_to_region_2d(region, region3d, v1)
dir = p1 - p0
if dir.length < 1:
continue
self.draw_label(context, "EQ", p0 + (dir) * 0.5, dir)
class DiameterDecorator(DimensionDecorator):
objecttype = "DIAMETER"
class LeaderDecorator(BaseDecorator):
@@ -568,6 +570,98 @@ class LeaderDecorator(BaseDecorator):
self.draw_label(context, obj.BIMTextProperties.value, pos, dir, gap=0, center=False, vcenter=False)
class RadiusDecorator(BaseDecorator):
"""Decorating text with arrows
- head point with arrow
"""
objecttype = "RADIUS"
DEF_GLSL = (
BaseDecorator.DEF_GLSL
+ """
#define ARROW_ANGLE PI / 12.0
#define ARROW_SIZE 16.0
"""
)
GEOM_GLSL = """
uniform vec2 winsize;
uniform float viewportDrawingScale;
layout(lines) in;
layout(line_strip, max_vertices=MAX_POINTS) out;
in uint type[];
void main() {
vec4 clip2win = matCLIP2WIN();
vec4 win2clip = matWIN2CLIP();
vec4 p0 = gl_in[0].gl_Position, p1 = gl_in[1].gl_Position;
uint t0 = type[0], t1 = type[1];
vec4 p0w = CLIP2WIN(p0), p1w = CLIP2WIN(p1);
vec4 edge = p1w - p0w, dir = normalize(edge);
vec4 gap1 = vec4(0);
vec4 p;
// end edge arrow for last segment
if (t1 == 2u) {
vec4 head[3];
arrow_head(dir, viewportDrawingScale * ARROW_SIZE, ARROW_ANGLE, head);
gl_Position = p1;
EmitVertex();
p = p1w - head[1];
gl_Position = WIN2CLIP(p);
EmitVertex();
p = p1w - head[2];
gl_Position = WIN2CLIP(p);
EmitVertex();
gl_Position = p1;
EmitVertex();
EndPrimitive();
gap1 = dir * viewportDrawingScale * ARROW_SIZE;
}
// stem, adjusted for and arrow
gl_Position = p0;
EmitVertex();
p = p1w - gap1;
gl_Position = WIN2CLIP(p);
EmitVertex();
EndPrimitive();
}
"""
def get_spline_end(self, obj):
spline = obj.data.splines[0]
spline_points = spline.bezier_points if spline.bezier_points else spline.points
if not spline_points:
return Vector((0, 0, 0))
return obj.matrix_world @ spline_points[0].co
def decorate(self, context, obj):
verts, idxs, topo = self.get_path_geom(obj)
self.draw_lines(context, obj, verts, idxs, topo)
self.draw_labels(context, obj)
def draw_labels(self, context, obj):
region = context.region
region3d = context.region_data
dir = Vector((1, 0))
pos = location_3d_to_region_2d(region, region3d, self.get_spline_end(obj))
spline = obj.data.splines[0]
spline_points = spline.bezier_points if spline.bezier_points else spline.points
if spline_points:
length = (spline_points[-1].co - spline_points[-2].co).length
text = self.format_value(context, length)
self.draw_label(context, text, pos, dir, gap=0, center=False, vcenter=False)
class StairDecorator(BaseDecorator):
"""Decorating stairs
- head point with arrow
@@ -671,6 +765,7 @@ class HiddenDecorator(BaseDecorator):
GEOM_GLSL = """
uniform vec2 winsize;
uniform float viewportDrawingScale;
layout(lines) in;
layout(line_strip, max_vertices=MAX_POINTS) out;
@@ -725,10 +820,61 @@ class HiddenDecorator(BaseDecorator):
self.draw_lines(context, obj, verts, idxs)
class MiscDecorator(HiddenDecorator):
class MiscDecorator(BaseDecorator):
objecttype = "MISC"
FRAG_GLSL = BaseDecorator.FRAG_GLSL
GEOM_GLSL = """
uniform vec2 winsize;
uniform float viewportDrawingScale;
layout(lines) in;
layout(line_strip, max_vertices=MAX_POINTS) out;
out float dist; // distance from starging point along segment
void main() {
vec4 clip2win = matCLIP2WIN();
vec4 win2clip = matWIN2CLIP();
vec4 p0 = gl_in[0].gl_Position, p1 = gl_in[1].gl_Position;
vec4 p0w = CLIP2WIN(p0), p1w = CLIP2WIN(p1);
vec4 edge = p1w - p0w, dir = normalize(edge);
vec4 gap = dir * 1;
vec4 p;
// NB: something should be used to affect position, otherwise compiler eliminates winsize
dist = 0;
p = p0w + gap;
gl_Position = WIN2CLIP(p);
EmitVertex();
dist = length(edge);
p = p1w - gap;
gl_Position = WIN2CLIP(p);
EmitVertex();
EndPrimitive();
}
"""
FRAG_GLSL = """
uniform vec4 color;
in vec2 gl_FragCoord;
in float dist;
out vec4 fragColor;
void main() {
fragColor = color;
}
"""
def decorate(self, context, obj):
if obj.data.is_editmode:
verts, idxs = self.get_editmesh_geom(obj)
else:
verts, idxs = self.get_mesh_geom(obj)
self.draw_lines(context, obj, verts, idxs, is_scale_dependant=False)
class LevelDecorator(BaseDecorator):
@@ -923,7 +1069,7 @@ class SectionLevelDecorator(LevelDecorator):
class BreakDecorator(BaseDecorator):
"""Decorator for dimension objects
"""Decorator for breakline objects
- first edge of a mesh with zigzag thingy in the middle
Uses first two vertices in verts list.
@@ -990,21 +1136,10 @@ class BreakDecorator(BaseDecorator):
def decorate(self, context, obj):
if obj.data.is_editmode:
verts = self.get_editmesh_geom(obj)
verts, idxs = self.get_editmesh_geom(obj)
else:
verts = self.get_mesh_geom(obj)
self.draw_lines(context, obj, verts, [(0, 1)])
def get_mesh_geom(self, obj):
# first vertices only
vertices = [obj.matrix_world @ obj.data.vertices[i].co for i in (0, 1)]
return vertices
def get_editmesh_geom(self, obj):
# first vertices only
mesh = bmesh.from_edit_mesh(obj.data)
vertices = [obj.matrix_world @ v.co for v in mesh.edges[0].verts]
return vertices
verts, idxs = self.get_mesh_geom(obj)
self.draw_lines(context, obj, verts, idxs)
class GridDecorator(BaseDecorator):
@@ -1125,15 +1260,15 @@ class GridDecorator(BaseDecorator):
self.draw_label(context, text, p1, dir, vcenter=True, gap=0)
class SectionViewDecorator(LevelDecorator):
objecttype = "SECTION"
class ElevationDecorator(LevelDecorator):
objecttype = "ELEVATION"
DEF_GLSL = (
BaseDecorator.DEF_GLSL
+ """
#define CIRCLE_SIZE 8.0
#define TRIANGLE_L 32.0
#define TRIANGLE_W 16.0
#define TRIANGLE_L 22.63
#define TRIANGLE_W 11.31
"""
)
@@ -1144,12 +1279,14 @@ class SectionViewDecorator(LevelDecorator):
layout(lines) in;
layout(line_strip, max_vertices=MAX_POINTS) out;
void triangle_head(in vec4 dir, in vec4 side, in float length, in float width, out vec4 head[3]) {
vec4 nose = dir * length;
vec4 ear = side * width;
head[0] = vec4(0);
head[1] = nose * .5 + ear;
head[2] = nose;
void triangle_head(in vec4 side, in vec4 dir, in float length, in float width, in float radius, out vec4 head[5]) {
vec4 nose = side * length;
vec4 ear = dir * width;
head[0] = side * -radius;
head[1] = nose * -.5;
head[2] = vec4(0) + ear;
head[3] = nose * .5;
head[4] = side * radius;
}
void main() {
@@ -1160,75 +1297,205 @@ class SectionViewDecorator(LevelDecorator):
vec4 p0w = CLIP2WIN(p0), p1w = CLIP2WIN(p1);
vec4 edge = p1w - p0w, dir = normalize(edge);
vec4 gap = dir * viewportDrawingScale * TRIANGLE_L * .5;
vec4 side = vec4(cross(vec3(dir.xy, 0), vec3(0, 0, 1)).xy, 0, 0);
vec4 p;
vec4 head[CIRCLE_SEGS];
circle_head(viewportDrawingScale * CIRCLE_SIZE, head);
vec4 head3[3];
vec4 head5[5];
// start edge circle
for(int i=0; i<CIRCLE_SEGS; i++) {
p = p0w + gap + head[i];
p = p0w + head[i];
gl_Position = WIN2CLIP(p);
EmitVertex();
}
p = p0w + gap + head[0];
p = p0w + head[0];
gl_Position = WIN2CLIP(p);
EmitVertex();
EndPrimitive();
// start edge triangle
triangle_head(dir, -side, viewportDrawingScale * TRIANGLE_L, viewportDrawingScale * TRIANGLE_W, head3);
p = p0w + head3[0];
// edge triangle
triangle_head(side, dir, viewportDrawingScale * TRIANGLE_L, viewportDrawingScale * TRIANGLE_W, viewportDrawingScale * CIRCLE_SIZE, head5);
p = p0w + head5[0];
gl_Position = WIN2CLIP(p);
EmitVertex();
p = p0w + head3[1];
p = p0w + head5[1];
gl_Position = WIN2CLIP(p);
EmitVertex();
p = p0w + head3[2];
p = p0w + head5[2];
gl_Position = WIN2CLIP(p);
EmitVertex();
p = p0w + head5[3];
gl_Position = WIN2CLIP(p);
EmitVertex();
p = p0w + head5[4];
gl_Position = WIN2CLIP(p);
EmitVertex();
EndPrimitive();
// end edge circle
for(int i=0; i<CIRCLE_SEGS; i++) {
p = p1w - gap + head[i];
gl_Position = WIN2CLIP(p);
EmitVertex();
}
p = p1w - gap + head[0];
// reference divider
p = p0w + normalize(vec4(-1, 0, 0, 0)) * viewportDrawingScale * CIRCLE_SIZE;
gl_Position = WIN2CLIP(p);
EmitVertex();
EndPrimitive();
// end edge triangle
triangle_head(-dir, -side, viewportDrawingScale * TRIANGLE_L, viewportDrawingScale * TRIANGLE_W, head3);
p = p1w + head3[0];
p = p0w + normalize(vec4(1, 0, 0, 0)) * viewportDrawingScale * CIRCLE_SIZE;
gl_Position = WIN2CLIP(p);
EmitVertex();
p = p1w + head3[1];
gl_Position = WIN2CLIP(p);
EmitVertex();
p = p1w + head3[2];
gl_Position = WIN2CLIP(p);
EmitVertex();
EndPrimitive();
// stem
gl_Position = p0;
EmitVertex();
gl_Position = p1;
EmitVertex();
EndPrimitive();
}
"""
def decorate(self, context, obj):
verts, _, _ = self.get_path_geom(obj, topo=False)
self.draw_lines(context, obj, verts, [(0, 1)])
center = obj.matrix_world.translation
v1 = obj.matrix_world @ Vector((0, 0, 0))
v2 = obj.matrix_world @ Vector((0, 0, -1))
self.draw_lines(context, obj, [v1, v2], [(0, 1)])
class SectionDecorator(LevelDecorator):
objecttype = "SECTION"
DEF_GLSL = (
BaseDecorator.DEF_GLSL
+ """
#define CIRCLE_SIZE 8.0
#define TRIANGLE_L 22.63
#define TRIANGLE_W 11.31
"""
)
GEOM_GLSL = """
uniform vec2 winsize;
uniform float viewportDrawingScale;
layout(lines) in;
layout(line_strip, max_vertices=MAX_POINTS) out;
void triangle_head(in vec4 side, in vec4 dir, in float length, in float width, in float radius, out vec4 head[5]) {
vec4 nose = side * length;
vec4 ear = dir * width;
head[0] = side * -radius;
head[1] = nose * -.5;
head[2] = vec4(0) + ear;
head[3] = nose * .5;
head[4] = side * radius;
}
void main() {
vec4 clip2win = matCLIP2WIN();
vec4 win2clip = matWIN2CLIP();
vec4 p0 = gl_in[0].gl_Position, p1 = gl_in[1].gl_Position;
vec4 p0w = CLIP2WIN(p0), p1w = CLIP2WIN(p1);
vec4 edge = p1w - p0w, side = normalize(edge);
vec4 gap = side * viewportDrawingScale * CIRCLE_SIZE;
vec4 dir = vec4(cross(vec3(side.xy, 0), vec3(0, 0, 1)).xy, 0, 0);
vec4 p;
vec4 head[CIRCLE_SEGS];
circle_head(viewportDrawingScale * CIRCLE_SIZE, head);
vec4 head5[5];
// start edge circle
for(int i=0; i<CIRCLE_SEGS; i++) {
p = p0w + head[i];
gl_Position = WIN2CLIP(p);
EmitVertex();
}
p = p0w + head[0];
gl_Position = WIN2CLIP(p);
EmitVertex();
EndPrimitive();
// start edge triangle
triangle_head(side, dir, viewportDrawingScale * TRIANGLE_L, viewportDrawingScale * TRIANGLE_W, viewportDrawingScale * CIRCLE_SIZE, head5);
p = p0w + head5[0];
gl_Position = WIN2CLIP(p);
EmitVertex();
p = p0w + head5[1];
gl_Position = WIN2CLIP(p);
EmitVertex();
p = p0w + head5[2];
gl_Position = WIN2CLIP(p);
EmitVertex();
p = p0w + head5[3];
gl_Position = WIN2CLIP(p);
EmitVertex();
p = p0w + head5[4];
gl_Position = WIN2CLIP(p);
EmitVertex();
EndPrimitive();
// start reference divider
p = p0w + normalize(vec4(-1, 0, 0, 0)) * viewportDrawingScale * CIRCLE_SIZE;
gl_Position = WIN2CLIP(p);
EmitVertex();
p = p0w + normalize(vec4(1, 0, 0, 0)) * viewportDrawingScale * CIRCLE_SIZE;
gl_Position = WIN2CLIP(p);
EmitVertex();
EndPrimitive();
// end edge circle
for(int i=0; i<CIRCLE_SEGS; i++) {
p = p1w - head[i];
gl_Position = WIN2CLIP(p);
EmitVertex();
}
p = p1w - head[0];
gl_Position = WIN2CLIP(p);
EmitVertex();
EndPrimitive();
// end edge triangle
triangle_head(side, dir, viewportDrawingScale * TRIANGLE_L, viewportDrawingScale * TRIANGLE_W, viewportDrawingScale * CIRCLE_SIZE, head5);
p = p1w + head5[0];
gl_Position = WIN2CLIP(p);
EmitVertex();
p = p1w + head5[1];
gl_Position = WIN2CLIP(p);
EmitVertex();
p = p1w + head5[2];
gl_Position = WIN2CLIP(p);
EmitVertex();
p = p1w + head5[3];
gl_Position = WIN2CLIP(p);
EmitVertex();
p = p1w + head5[4];
gl_Position = WIN2CLIP(p);
EmitVertex();
EndPrimitive();
// end reference divider
p = p1w + normalize(vec4(-1, 0, 0, 0)) * viewportDrawingScale * CIRCLE_SIZE;
gl_Position = WIN2CLIP(p);
EmitVertex();
p = p1w + normalize(vec4(1, 0, 0, 0)) * viewportDrawingScale * CIRCLE_SIZE;
gl_Position = WIN2CLIP(p);
EmitVertex();
EndPrimitive();
// stem
p = p0w + gap;
gl_Position = WIN2CLIP(p);
EmitVertex();
p = p1w - gap;
gl_Position = WIN2CLIP(p);
EmitVertex();
EndPrimitive();
}
"""
def decorate(self, context, obj):
if obj.data.is_editmode:
verts, idxs = self.get_editmesh_geom(obj)
else:
verts, idxs = self.get_mesh_geom(obj)
vert_map = {v.freeze(): (obj.matrix_world.inverted() @ v).x for v in verts}
verts = sorted(verts, key=lambda v: vert_map[v], reverse=True)
self.draw_lines(context, obj, verts, idxs)
class TextDecorator(BaseDecorator):
@@ -1318,16 +1585,18 @@ class TextDecorator(BaseDecorator):
class DecorationsHandler:
decorators_classes = [
DimensionDecorator,
EqualityDecorator,
GridDecorator,
HiddenDecorator,
LeaderDecorator,
RadiusDecorator,
DiameterDecorator,
MiscDecorator,
PlanLevelDecorator,
SectionLevelDecorator,
StairDecorator,
BreakDecorator,
SectionViewDecorator,
SectionDecorator,
ElevationDecorator,
TextDecorator,
]
@@ -45,6 +45,3 @@ def set_active_camera_resolution(scene):
scene.render.resolution_x = scene.camera.data.BIMCameraProperties.raster_x
scene.render.resolution_y = scene.camera.data.BIMCameraProperties.raster_y
current_drawing = scene.DocProperties.drawings[scene.DocProperties.current_drawing_index]
if scene.camera != current_drawing.camera:
scene.DocProperties.current_drawing_index = scene.DocProperties.drawings.find(scene.camera.name.split("/")[1])
bpy.ops.bim.activate_view(drawing_index=scene.DocProperties.current_drawing_index)
@@ -19,6 +19,7 @@
import bpy
import math
import mathutils.geometry
import blenderbim.tool as tool
from mathutils import Vector
# Code taken and updated from https://blenderartists.org/t/detecting-intersection-of-bounding-boxes/457520/2
@@ -259,13 +260,11 @@ def format_distance(value, isArea=False, hide_units=True):
def get_active_drawing(scene):
"""Get active drawing collection and camera"""
props = scene.DocProperties
if props.active_drawing_index is None or len(props.drawings) == 0:
return None, None
try:
drawing = props.active_drawing
return drawing.camera.users_collection[0], drawing.camera
except (KeyError, IndexError):
raise RuntimeError("missing drawing collection")
camera = tool.Ifc.get_object(tool.Ifc.get().by_id(props.active_drawing_id))
return camera.users_collection[0], camera
except:
return None, None
def get_project_collection(scene):
@@ -53,7 +53,7 @@ def open_with_user_command(user_command, path):
if user_command:
commands = eval(user_command)
for command in commands:
subprocess.run(command)
subprocess.Popen(command)
else:
webbrowser.open("file://" + path)
@@ -65,67 +65,28 @@ 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"}
try:
drawing = tool.Ifc.get().by_id(self.props.active_drawing_id)
core.sync_references(tool.Ifc, tool.Collector, tool.Drawing, drawing=drawing)
except:
pass
class CreateDrawing(bpy.types.Operator):
@@ -144,6 +105,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
@@ -157,9 +119,10 @@ class CreateDrawing(bpy.types.Operator):
start = time.time()
self.profile_code("Start drawing generation process")
self.props = context.scene.DocProperties
self.cprops = self.camera.data.BIMCameraProperties
self.drawing_name = self.file.by_id(self.camera.BIMObjectProperties.ifc_definition_id).Name
self.get_scale()
if self.camera.data.BIMCameraProperties.update_representation(self.camera):
if self.cprops.update_representation(self.camera):
bpy.ops.bim.update_representation(obj=self.camera.name, ifc_representation_class="")
underlay_svg = self.generate_underlay(context)
self.profile_code("Generate underlay")
@@ -224,13 +187,11 @@ class CreateDrawing(bpy.types.Operator):
return svg_path
def generate_underlay(self, context):
if not self.props.has_underlay:
if not self.cprops.has_underlay:
return
svg_path = os.path.join(context.scene.BIMProperties.data_dir, "cache", self.drawing_name + "-underlay.svg")
context.scene.render.filepath = svg_path[0:-4] + ".png"
drawing_style = context.scene.DocProperties.drawing_styles[
self.camera.data.BIMCameraProperties.active_drawing_style_index
]
drawing_style = context.scene.DocProperties.drawing_styles[self.cprops.active_drawing_style_index]
if drawing_style.render_type == "DEFAULT":
bpy.ops.render.render(write_still=True)
@@ -284,7 +245,7 @@ class CreateDrawing(bpy.types.Operator):
return svg_path
def generate_linework(self, context):
if not self.props.has_linework:
if not self.cprops.has_linework:
return
svg_path = os.path.join(context.scene.BIMProperties.data_dir, "cache", self.drawing_name + "-linework.svg")
if os.path.isfile(svg_path) and self.props.should_use_linework_cache:
@@ -328,9 +289,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"]:
@@ -340,15 +302,16 @@ class CreateDrawing(bpy.types.Operator):
body_contexts.append(rep_context.id())
continue
elements = set(ifc.by_type("IfcElement")) - set(ifc.by_type("IfcOpeningElement"))
elements = tool.Drawing.get_drawing_elements(self.camera_element)
self.setup_serialiser(ifc)
cache_settings = ifcopenshell.geom.settings(DISABLE_TRIANGULATION=True)
cache = IfcStore.get_cache()
[cache.remove(guid) for guid in invalidated_guids]
if target_view_contexts and elements:
geom_settings = ifcopenshell.geom.settings(DISABLE_TRIANGULATION=True, INCLUDE_CURVES=True)
geom_settings = ifcopenshell.geom.settings(
DISABLE_TRIANGULATION=True, STRICT_TOLERANCE=True, INCLUDE_CURVES=True
)
geom_settings.set_context_ids(target_view_contexts)
it = ifcopenshell.geom.iterator(geom_settings, ifc, multiprocessing.cpu_count(), include=elements)
it.set_cache(cache)
@@ -359,18 +322,14 @@ class CreateDrawing(bpy.types.Operator):
elements -= processed
if body_contexts and elements:
geom_settings = ifcopenshell.geom.settings(
DISABLE_TRIANGULATION=True,
)
geom_settings = ifcopenshell.geom.settings(DISABLE_TRIANGULATION=True, STRICT_TOLERANCE=True)
geom_settings.set_context_ids(body_contexts)
it = ifcopenshell.geom.iterator(geom_settings, ifc, multiprocessing.cpu_count(), include=elements)
it.set_cache(cache)
for elem in self.yield_from_iterator(it):
self.serialiser.write(elem)
geom_settings = ifcopenshell.geom.settings(
DISABLE_TRIANGULATION=True,
)
geom_settings = ifcopenshell.geom.settings(DISABLE_TRIANGULATION=True, STRICT_TOLERANCE=True)
it = ifcopenshell.geom.iterator(geom_settings, ifc, include=[self.camera_element])
for elem in self.yield_from_iterator(it):
self.serialiser.write(elem)
@@ -387,7 +346,9 @@ class CreateDrawing(bpy.types.Operator):
return svg_path
def setup_serialiser(self, ifc):
self.svg_settings = ifcopenshell.geom.settings(DISABLE_TRIANGULATION=True, INCLUDE_CURVES=True)
self.svg_settings = ifcopenshell.geom.settings(
DISABLE_TRIANGULATION=True, STRICT_TOLERANCE=True, INCLUDE_CURVES=True
)
self.svg_buffer = ifcopenshell.geom.serializers.buffer()
self.serialiser = ifcopenshell.geom.serializers.svg(self.svg_buffer, self.svg_settings)
self.serialiser.setFile(ifc)
@@ -441,13 +402,14 @@ 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)
def generate_annotation(self, context):
if not self.props.has_annotation:
if not self.cprops.has_annotation:
return
svg_path = os.path.join(context.scene.BIMProperties.data_dir, "cache", self.drawing_name + "-annotation.svg")
if os.path.isfile(svg_path) and self.props.should_use_annotation_cache:
@@ -456,9 +418,7 @@ class CreateDrawing(bpy.types.Operator):
camera = self.camera
svg_writer = svgwriter.SvgWriter()
drawing_style = context.scene.DocProperties.drawing_styles[
camera.data.BIMCameraProperties.active_drawing_style_index
]
drawing_style = context.scene.DocProperties.drawing_styles[self.cprops.active_drawing_style_index]
render = context.scene.render
if self.is_landscape(render):
@@ -478,40 +438,9 @@ class CreateDrawing(bpy.types.Operator):
svg_writer.camera_height = height
svg_writer.camera_projection = tuple(camera.matrix_world.to_quaternion() @ Vector((0, 0, -1)))
for obj in camera.users_collection[0].objects:
if "IfcAnnotation/" not in obj.name:
continue
element = tool.Ifc.get_entity(obj)
if element.ObjectType == "GRID":
svg_writer.annotations.setdefault("grid_objs", []).append(obj)
elif obj.type == "CAMERA":
continue
elif element.ObjectType == "TEXT_LEADER":
svg_writer.annotations.setdefault("leader_objs", []).append(obj)
elif element.ObjectType == "STAIR_ARROW":
svg_writer.annotations["stair_obj"] = obj
elif element.ObjectType == "EQUAL_DIMENSION":
svg_writer.annotations.setdefault("equal_objs", []).append(obj)
elif element.ObjectType == "DIMENSION":
svg_writer.annotations.setdefault("dimension_objs", []).append(obj)
elif element.ObjectType == "BREAKLINE":
svg_writer.annotations["break_obj"] = obj
elif element.ObjectType == "HIDDEN_LINE":
svg_writer.annotations.setdefault("hidden_objs", []).append((obj, obj.data))
elif element.ObjectType == "SOLID_LINE":
svg_writer.annotations.setdefault("solid_objs", []).append((obj, obj.data))
elif element.ObjectType == "PLAN_LEVEL":
svg_writer.annotations.setdefault("plan_level_objs", []).append(obj)
elif element.ObjectType == "SECTION_LEVEL":
svg_writer.annotations["section_level_obj"] = obj
elif element.ObjectType == "TEXT":
svg_writer.annotations.setdefault("text_objs", []).append(obj)
elif element.ObjectType == "MISC":
svg_writer.annotations.setdefault("misc_objs", []).append(obj)
svg_writer.annotations["attributes"] = [a.name for a in drawing_style.attributes]
# TODO: This was the old 2D annotation box checking system, prepare to deprecate
svg_writer.annotations["annotation_objs"] = []
elements = tool.Drawing.get_group_elements(tool.Drawing.get_drawing_group(self.camera_element))
svg_writer.annotations = sorted(elements, key=lambda a: tool.Drawing.get_annotation_z_index(a))
svg_writer.metadata = tool.Drawing.get_drawing_metadata(self.camera_element)
svg_writer.write("annotation")
return svg_writer.output
@@ -562,7 +491,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,92 +502,37 @@ 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):
bl_idname = "bim.add_drawing_to_sheet"
bl_label = "Add Drawing To Sheet"
bl_options = {"REGISTER", "UNDO"}
# TODO: check undo redo
@classmethod
def poll(cls, context):
@@ -667,9 +541,69 @@ class AddDrawingToSheet(bpy.types.Operator):
def execute(self, context):
props = context.scene.DocProperties
active_drawing = props.drawings[props.active_drawing_index]
active_sheet = props.sheets[props.active_sheet_index]
drawing = tool.Ifc.get().by_id(active_drawing.ifc_definition_id)
sheet = tool.Ifc.get().by_id(active_sheet.ifc_definition_id)
if not sheet.is_a("IfcDocumentInformation"):
return {"FINISHED"}
if tool.Ifc.get_schema() == "IFC2X3":
references = sheet.DocumentReferences or []
else:
references = sheet.HasDocumentReferences or []
has_drawing = False
for reference in references:
if tool.Ifc.get_schema() == "IFC2X3":
element = [r for r in tool.Ifc.by_type("IfcRelAssociatesDocument") if r.RelatingDocument == reference][
0
].RelatedObjects[0]
else:
element = reference.DocumentRefForObjects[0].RelatedObjects[0]
if element == drawing:
has_drawing = True
break
if has_drawing:
return {"FINISHED"}
reference = tool.Ifc.run("document.add_reference", information=sheet)
if tool.Ifc.get_schema() == "IFC2X3":
attributes = {"ItemReference": str(len(sheet.DocumentReferences or []))}
else:
attributes = {"Identification": str(len(sheet.HasDocumentReferences or []))}
tool.Ifc.run("document.edit_reference", reference=reference, attributes=attributes)
tool.Ifc.run("document.assign_document", product=drawing, document=reference)
sheet_builder = sheeter.SheetBuilder()
sheet_builder.data_dir = context.scene.BIMProperties.data_dir
sheet_builder.add_drawing(props.active_drawing.name, props.active_sheet.name)
sheet_builder.add_drawing(reference, drawing, sheet)
tool.Drawing.import_sheets()
return {"FINISHED"}
class RemoveDrawingFromSheet(bpy.types.Operator):
bl_idname = "bim.remove_drawing_from_sheet"
bl_label = "Remove Drawing From Sheet"
bl_options = {"REGISTER", "UNDO"}
reference: bpy.props.IntProperty()
def execute(self, context):
reference = tool.Ifc.get().by_id(self.reference)
sheet = tool.Drawing.get_reference_document(reference)
sheet_builder = sheeter.SheetBuilder()
sheet_builder.data_dir = context.scene.BIMProperties.data_dir
sheet_builder.remove_drawing(reference, sheet)
drawing = tool.Drawing.get_reference_element(reference)
if drawing:
tool.Ifc.run("document.unassign_document", product=drawing, document=reference)
tool.Ifc.run("document.remove_reference", reference=reference)
tool.Drawing.import_sheets()
return {"FINISHED"}
@@ -685,10 +619,16 @@ class CreateSheets(bpy.types.Operator):
def execute(self, context):
scene = context.scene
props = scene.DocProperties
name = props.active_sheet.name
active_sheet = props.sheets[props.active_sheet_index]
sheet = tool.Ifc.get().by_id(active_sheet.ifc_definition_id)
if not sheet.is_a("IfcDocumentInformation"):
return {"FINISHED"}
name = os.path.splitext(os.path.basename(tool.Drawing.get_document_uri(sheet)))[0]
sheet_builder = sheeter.SheetBuilder()
sheet_builder.data_dir = scene.BIMProperties.data_dir
sheet_builder.build(name)
sheet_builder.build(sheet)
svg2pdf_command = context.preferences.addons["blenderbim"].preferences.svg2pdf_command
svg2dxf_command = context.preferences.addons["blenderbim"].preferences.svg2dxf_command
@@ -738,40 +678,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")
@@ -798,7 +712,9 @@ 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.context.scene.DocProperties.active_drawing_id = self.drawing
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"}
@@ -819,68 +735,6 @@ class SelectDocIfcFile(bpy.types.Operator):
return {"RUNNING_MODAL"}
class GenerateReferences(bpy.types.Operator):
bl_idname = "bim.generate_references"
bl_label = "Generate References"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
self.camera = context.scene.camera
self.filter_potential_references()
if self.camera.data.BIMCameraProperties.target_view == "PLAN_VIEW":
self.generate_grids()
if self.camera.data.BIMCameraProperties.target_view == "ELEVATION_VIEW":
self.generate_grids()
self.generate_levels(context)
if self.camera.data.BIMCameraProperties.target_view == "SECTION_VIEW":
self.generate_grids()
self.generate_levels(context)
return {"FINISHED"}
def filter_potential_references(self):
for name in ["grids", "levels"]:
setattr(self, name, [])
for obj in bpy.data.objects:
if "IfcGridAxis/" in obj.name:
self.grids.append(obj)
if "IfcBuildingStorey/" in obj.name:
self.levels.append(obj)
def generate_grids(self):
# TODO
pass
def generate_levels(self, context):
if self.camera.data.BIMCameraProperties.raster_x > self.camera.data.BIMCameraProperties.raster_y:
width = self.camera.data.ortho_scale
height = (
width / self.camera.data.BIMCameraProperties.raster_x * self.camera.data.BIMCameraProperties.raster_y
)
else:
height = self.camera.data.ortho_scale
width = (
height / self.camera.data.BIMCameraProperties.raster_y * self.camera.data.BIMCameraProperties.raster_x
)
level_obj = annotation.Annotator.get_annotation_obj("Section Level", "curve", context)
width_in_mm = width * 1000
real_world_width_in_mm = width_in_mm * scale
offset_in_mm = 20
offset_percentage = offset_in_mm / real_world_width_in_mm
for obj in self.levels:
projection = self.project_point_onto_camera(obj.location)
co1 = self.camera.matrix_world @ Vector((width / 2 - (offset_percentage * width), projection[1], -1))
co2 = self.camera.matrix_world @ Vector((-(width / 2), projection[1], -1))
annotation.Annotator.add_line_to_annotation(level_obj, context, co1, co2)
def project_point_onto_camera(self, point):
projection = self.camera.matrix_world.to_quaternion() @ Vector((0, 0, -1))
return self.camera.matrix_world.inverted() @ geometry.intersect_line_plane(
point.xyz, point.xyz - projection, self.camera.location, projection
)
class ResizeText(bpy.types.Operator):
bl_idname = "bim.resize_text"
bl_label = "Resize Text"
@@ -894,21 +748,14 @@ 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()
drawing: bpy.props.IntProperty()
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"}
def _execute(self, context):
core.remove_drawing(tool.Ifc, tool.Drawing, drawing=tool.Ifc.get().by_id(self.drawing))
class AddDrawingStyle(bpy.types.Operator):
@@ -1057,75 +904,66 @@ 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()
sheet: bpy.props.IntProperty()
def execute(self, context):
props = context.scene.DocProperties
props.sheets.remove(self.index)
return {"FINISHED"}
def _execute(self, context):
core.remove_sheet(tool.Ifc, tool.Drawing, sheet=tool.Ifc.get().by_id(self.sheet))
class AddSchedule(bpy.types.Operator):
class AddSchedule(bpy.types.Operator, Operator):
bl_idname = "bim.add_schedule"
bl_label = "Add Schedule"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
new = context.scene.DocProperties.schedules.add()
new.name = "SCHEDULE {}".format(len(context.scene.DocProperties.schedules))
return {"FINISHED"}
class RemoveSchedule(bpy.types.Operator):
bl_idname = "bim.remove_schedule"
bl_label = "Remove Schedule"
bl_options = {"REGISTER", "UNDO"}
index: bpy.props.IntProperty()
def execute(self, context):
props = context.scene.DocProperties
props.schedules.remove(self.index)
return {"FINISHED"}
class SelectScheduleFile(bpy.types.Operator):
bl_idname = "bim.select_schedule_file"
bl_label = "Select Documentation IFC File"
bl_options = {"REGISTER", "UNDO"}
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
filter_glob: bpy.props.StringProperty(default="*.ods", options={"HIDDEN"})
index: bpy.props.IntProperty()
filter_glob: bpy.props.StringProperty(default="*.ods;*.xls;*.xlsx", options={"HIDDEN"})
use_relative_path: bpy.props.BoolProperty(name="Use Relative Path", default=False)
def execute(self, context):
props = context.scene.DocProperties
props.active_schedule.file = self.filepath
return {"FINISHED"}
def _execute(self, context):
filepath = self.filepath
if self.use_relative_path:
filepath = os.path.relpath(filepath, bpy.path.abspath("//"))
core.add_schedule(
tool.Ifc,
tool.Drawing,
uri=filepath,
)
def invoke(self, context, event):
context.window_manager.fileselect_add(self)
return {"RUNNING_MODAL"}
class BuildSchedule(bpy.types.Operator):
class RemoveSchedule(bpy.types.Operator, Operator):
bl_idname = "bim.remove_schedule"
bl_label = "Remove Schedule"
bl_options = {"REGISTER", "UNDO"}
schedule: bpy.props.IntProperty()
def _execute(self, context):
core.remove_schedule(tool.Ifc, tool.Drawing, schedule=tool.Ifc.get().by_id(self.schedule))
class OpenSchedule(bpy.types.Operator, Operator):
bl_idname = "bim.open_schedule"
bl_label = "Open Schedule"
bl_options = {"REGISTER", "UNDO"}
schedule: bpy.props.IntProperty()
def _execute(self, context):
core.open_schedule(tool.Drawing, schedule=tool.Ifc.get().by_id(self.schedule))
class BuildSchedule(bpy.types.Operator, Operator):
bl_idname = "bim.build_schedule"
bl_label = "Build Schedule"
schedule: bpy.props.IntProperty()
@classmethod
def poll(cls, context):
return context.scene.DocProperties.active_schedule.file
def execute(self, context):
props = context.scene.DocProperties
schedule = props.active_schedule
schedule_creator = scheduler.Scheduler()
outfile = os.path.join(context.scene.BIMProperties.data_dir, "schedules", schedule.name + ".svg")
schedule_creator.schedule(schedule.file, outfile)
open_with_user_command(context.preferences.addons["blenderbim"].preferences.svg_command, outfile)
return {"FINISHED"}
def _execute(self, context):
core.build_schedule(tool.Drawing, schedule=tool.Ifc.get().by_id(self.schedule))
class AddScheduleToSheet(bpy.types.Operator):
@@ -1141,9 +979,40 @@ class AddScheduleToSheet(bpy.types.Operator):
def execute(self, context):
props = context.scene.DocProperties
active_schedule = props.schedules[props.active_schedule_index]
active_sheet = props.sheets[props.active_sheet_index]
schedule = tool.Ifc.get().by_id(active_schedule.ifc_definition_id)
sheet = tool.Ifc.get().by_id(active_sheet.ifc_definition_id)
if not sheet.is_a("IfcDocumentInformation"):
return {"FINISHED"}
if tool.Ifc.get_schema() == "IFC2X3":
references = sheet.DocumentReferences or []
else:
references = sheet.HasDocumentReferences or []
has_schedule = False
for reference in references:
if reference.Location == tool.Drawing.get_schedule_location(schedule):
has_schedule = True
break
if has_schedule:
return {"FINISHED"}
reference = tool.Ifc.run("document.add_reference", information=sheet)
if tool.Ifc.get_schema() == "IFC2X3":
attributes = {"ItemReference": str(len(sheet.DocumentReferences or []))}
else:
attributes = {"Identification": str(len(sheet.HasDocumentReferences or []))}
attributes["Location"] = tool.Drawing.get_schedule_location(schedule)
tool.Ifc.run("document.edit_reference", reference=reference, attributes=attributes)
sheet_builder = sheeter.SheetBuilder()
sheet_builder.data_dir = context.scene.BIMProperties.data_dir
sheet_builder.add_schedule(props.active_schedule.name, props.active_sheet.name)
sheet_builder.add_schedule(reference, schedule, sheet)
tool.Drawing.import_sheets()
return {"FINISHED"}
@@ -1170,23 +1039,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 +1065,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 +1077,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 +1274,126 @@ 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 LoadSchedules(bpy.types.Operator, Operator):
bl_idname = "bim.load_schedules"
bl_label = "Load Schedules"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
core.load_schedules(tool.Drawing)
class DisableEditingSchedules(bpy.types.Operator, Operator):
bl_idname = "bim.disable_editing_schedules"
bl_label = "Disable Editing Text Product"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
core.disable_editing_schedules(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)
class ExpandSheet(bpy.types.Operator):
bl_idname = "bim.expand_sheet"
bl_label = "Expand Sheet"
bl_options = {"REGISTER", "UNDO"}
sheet: bpy.props.IntProperty()
def execute(self, context):
props = context.scene.DocProperties
for sheet in [s for s in props.sheets if s.ifc_definition_id == self.sheet]:
sheet.is_expanded = True
core.load_sheets(tool.Drawing)
return {"FINISHED"}
class ContractSheet(bpy.types.Operator):
bl_idname = "bim.contract_sheet"
bl_label = "Contract Sheet"
bl_options = {"REGISTER", "UNDO"}
sheet: bpy.props.IntProperty()
def execute(self, context):
props = context.scene.DocProperties
for sheet in [s for s in props.sheets if s.ifc_definition_id == self.sheet]:
sheet.is_expanded = False
core.load_sheets(tool.Drawing)
return {"FINISHED"}
class ConvertEdgeToArc(bpy.types.Operator):
bl_idname = "bim.convert_edge_to_arc"
bl_label = "Convert Edge to Arc"
bl_options = {"REGISTER", "UNDO"}
resolution: bpy.props.IntProperty(name="Arc Resolution", min=1, default=1)
should_flip: bpy.props.BoolProperty(name="Flip", description="Flip arc", default=False)
def draw(self, context):
layout = self.layout
for prop in self.__class__.__annotations__.keys():
layout.prop(self, prop)
def execute(self, context):
if bpy.context.mode != "EDIT_MESH":
return {"CANCELLED"}
obj = bpy.context.active_object
cursor = obj.matrix_world.inverted() @ bpy.context.scene.cursor.location
mesh = obj.data
bm = bmesh.from_edit_mesh(mesh)
selected_verts = [v for v in bm.verts if v.select]
if len(selected_verts) != 2:
return {"CANCELLED"}
l1 = selected_verts[0].co - cursor
l2 = selected_verts[1].co - cursor
angle = l1.xy.angle_signed(l2.xy)
if self.should_flip:
if angle > 0:
angle = -((pi * 2) - angle)
else:
angle = (pi * 2) + angle
v = selected_verts[0]
bm.verts.remove(selected_verts[1])
bmesh.ops.spin(bm, geom=[v], axis=(0, 0, 1), cent=cursor, steps=self.resolution * 4, angle=-angle)
bmesh.update_edit_mesh(mesh)
mesh.update()
return {"FINISHED"}
@@ -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,12 +58,24 @@ def purge():
vector_styles_enum = []
def update_target_view(self, context):
DrawingsData.data["location_hint"] = DrawingsData.location_hint()
def get_location_hint(self, context):
if not DrawingsData.is_loaded:
DrawingsData.load()
return DrawingsData.data["location_hint"]
def update_diagram_scale(self, context):
scale = self.diagram_scale
if scale == "CUSTOM":
scale = self.custom_diagram_scale
scale = scale.split("|")[1]
element = tool.Ifc.get_entity(context.scene.camera)
if "|" not in scale:
return
human_scale, scale = scale.split("|")
element = tool.Ifc.get_entity(context.active_object)
if not element:
return
pset = ifcopenshell.util.element.get_psets(element).get("EPset_Drawing")
@@ -69,7 +83,9 @@ def update_diagram_scale(self, context):
pset = tool.Ifc.get().by_id(pset["id"])
else:
pset = ifcopenshell.api.run("pset.add_pset", tool.Ifc.get(), product=element, name="EPset_Drawing")
ifcopenshell.api.run("pset.edit_pset", tool.Ifc.get(), pset=pset, properties={"Scale": scale})
ifcopenshell.api.run(
"pset.edit_pset", tool.Ifc.get(), pset=pset, properties={"Scale": scale, "HumanScale": human_scale}
)
def get_diagram_scales(self, context):
@@ -83,7 +99,8 @@ def get_diagram_scales(self, context):
diagram_scales_enum = [
("CUSTOM", "Custom", ""),
("1'=1'-0\"|1/1", "1'=1'-0\"", ""),
('6"=1\'-0"|1/6', '6"=1\'-0"', ""),
('6"=1\'-0"|1/2', '6"=1\'-0"', ""),
('3"=1\'-0"|1/4', '3"=1\'-0"', ""),
('1-1/2"=1\'-0"|1/8', '1-1/2"=1\'-0"', ""),
('1"=1\'-0"|1/12', '1"=1\'-0"', ""),
('3/4"=1\'-0"|1/16', '3/4"=1\'-0"', ""),
@@ -132,31 +149,47 @@ def get_diagram_scales(self, context):
return diagram_scales_enum
def updateDrawingName(self, context):
if not self.camera:
return
if self.camera.name == self.name:
return
self.camera.name = "IfcAnnotation/{}".format(self.name)
unique_name = "/".join(self.camera.name.split("/")[1:])
self.camera.users_collection[0].name = "IfcGroup/{}".format(unique_name)
if self.name != unique_name:
self.name = unique_name
def update_drawing_name(self, context):
drawing = tool.Ifc.get().by_id(self.ifc_definition_id)
core.update_drawing_name(tool.Ifc, tool.Drawing, drawing=drawing, name=self.name)
def refreshActiveDrawingIndex(self, context):
bpy.ops.bim.activate_view(drawing_index=context.scene.DocProperties.active_drawing_index)
def update_schedule_name(self, context):
schedule = tool.Ifc.get().by_id(self.ifc_definition_id)
core.update_schedule_name(tool.Ifc, tool.Drawing, schedule=schedule, name=self.name)
def update_has_underlay(self, context):
update_layer(self, context, "HasUnderlay", self.has_underlay)
def update_has_linework(self, context):
update_layer(self, context, "HasLinework", self.has_linework)
def update_has_annotation(self, context):
update_layer(self, context, "HasAnnotation", self.has_annotation)
def update_layer(self, context, name, value):
element = tool.Ifc.get_entity(context.active_object)
if not element:
return
pset = ifcopenshell.util.element.get_psets(element).get("EPset_Drawing")
if pset:
pset = tool.Ifc.get().by_id(pset["id"])
else:
pset = ifcopenshell.api.run("pset.add_pset", tool.Ifc.get(), product=element, name="EPset_Drawing")
ifcopenshell.api.run("pset.edit_pset", tool.Ifc.get(), pset=pset, properties={name: value})
def getTitleblocks(self, context):
global titleblocks_enum
if len(titleblocks_enum) < 1:
titleblocks_enum.clear()
for filename in Path(os.path.join(context.scene.BIMProperties.data_dir, "templates", "titleblocks")).glob(
"*.svg"
):
f = str(filename.stem)
titleblocks_enum.append((f, f, ""))
files = Path(os.path.join(context.scene.BIMProperties.data_dir, "templates", "titleblocks")).glob("*.svg")
files = sorted([str(f.stem) for f in files])
titleblocks_enum.extend([(f, f, "") for f in files])
return titleblocks_enum
@@ -198,13 +231,15 @@ class Variable(PropertyGroup):
class Drawing(PropertyGroup):
name: StringProperty(name="Name", update=updateDrawingName)
camera: PointerProperty(name="Camera", type=bpy.types.Object)
ifc_definition_id: IntProperty(name="IFC Definition ID")
name: StringProperty(name="Name", update=update_drawing_name)
target_view: StringProperty(name="Target View")
class Schedule(PropertyGroup):
name: StringProperty(name="Name")
file: StringProperty(name="File")
ifc_definition_id: IntProperty(name="IFC Definition ID")
name: StringProperty(name="Name", update=update_schedule_name)
identification: StringProperty(name="Identification")
class Sheet(PropertyGroup):
@@ -218,9 +253,12 @@ class Sheet(PropertyGroup):
def get_name(self):
return self.get("name")
ifc_definition_id: IntProperty(name="IFC Definition ID")
identification: StringProperty(name="Identification")
name: StringProperty(name="Name", get=get_name, set=set_name)
drawings: CollectionProperty(name="Drawings", type=Drawing)
active_drawing_index: IntProperty(name="Active Drawing Index")
is_sheet: BoolProperty(name="Is Sheet", default=False)
reference_type: StringProperty(name="Reference Type")
is_expanded: BoolProperty(name="Is Expanded", default=False)
class DrawingStyle(PropertyGroup):
@@ -269,19 +307,33 @@ class RasterStyleProperty(enum.Enum):
class DocProperties(PropertyGroup):
has_underlay: BoolProperty(name="Underlay", default=False)
has_linework: BoolProperty(name="Linework", default=True)
has_annotation: BoolProperty(name="Annotation", default=True)
should_use_underlay_cache: BoolProperty(name="Use Underlay Cache", default=False)
should_use_linework_cache: BoolProperty(name="Use Linework Cache", default=False)
should_use_annotation_cache: BoolProperty(name="Use Annotation Cache", default=False)
should_extract: BoolProperty(name="Should Extract", default=True)
is_editing_drawings: BoolProperty(name="Is Editing Drawings", default=False)
is_editing_schedules: BoolProperty(name="Is Editing Schedules", default=False)
target_view: EnumProperty(
items=[
("PLAN_VIEW", "Plan", ""),
("ELEVATION_VIEW", "Elevation", ""),
("SECTION_VIEW", "Section", ""),
("REFLECTED_PLAN_VIEW", "RCP", ""),
("MODEL_VIEW", "Model", ""),
],
name="Target View",
default="PLAN_VIEW",
update=update_target_view,
)
location_hint: EnumProperty(items=get_location_hint, name="Location Hint")
drawings: CollectionProperty(name="Drawings", type=Drawing)
active_drawing_index: IntProperty(name="Active Drawing Index", update=refreshActiveDrawingIndex)
active_drawing_id: IntProperty(name="Active Drawing Id")
active_drawing_index: IntProperty(name="Active Drawing Index")
current_drawing_index: IntProperty(name="Current Drawing Index")
schedules: CollectionProperty(name="Schedules", type=Schedule)
active_schedule_index: IntProperty(name="Active Schedule Index")
titleblock: EnumProperty(items=getTitleblocks, name="Titleblock", update=refreshTitleblocks)
is_editing_sheets: BoolProperty(name="Is Editing Sheets", default=False)
sheets: CollectionProperty(name="Sheets", type=Sheet)
active_sheet_index: IntProperty(name="Active Sheet Index")
ifc_files: CollectionProperty(name="IFCs", type=StrProperty)
@@ -291,51 +343,18 @@ class DocProperties(PropertyGroup):
name="Decorations Colour", subtype="COLOR", default=(1, 1, 1, 1), min=0.0, max=1.0, size=4
)
@property
def active_schedule(self):
return self.schedules[self.active_schedule_index]
@property
def active_drawing(self):
return self.drawings[self.active_drawing_index]
@property
def active_sheet(self):
return self.sheets[self.active_sheet_index]
class BIMCameraProperties(PropertyGroup):
has_underlay: BoolProperty(name="Underlay", default=False, update=update_has_underlay)
has_linework: BoolProperty(name="Linework", default=True, update=update_has_linework)
has_annotation: BoolProperty(name="Annotation", default=True, update=update_has_annotation)
representation: StringProperty(name="Representation")
view_name: StringProperty(name="View Name")
target_view: EnumProperty(
items=[
("PLAN_VIEW", "PLAN_VIEW", ""),
("ELEVATION_VIEW", "ELEVATION_VIEW", ""),
("SECTION_VIEW", "SECTION_VIEW", ""),
("REFLECTED_PLAN_VIEW", "REFLECTED_PLAN_VIEW", ""),
("MODEL_VIEW", "MODEL_VIEW", ""),
],
name="Target View",
default="PLAN_VIEW",
)
diagram_scale: EnumProperty(items=get_diagram_scales, name="Drawing Scale", update=update_diagram_scale)
custom_diagram_scale: StringProperty(name="Custom Scale", update=update_diagram_scale)
raster_x: IntProperty(name="Raster X", default=1000)
raster_y: IntProperty(name="Raster Y", default=1000)
is_nts: BoolProperty(name="Is NTS")
cut_objects: EnumProperty(
items=[
(
".IfcWall|.IfcSlab|.IfcCurtainWall|.IfcStair|.IfcStairFlight|.IfcColumn|.IfcBeam|.IfcMember|.IfcCovering|.IfcSpace",
"Overall Plan / Section",
"",
),
(".IfcElement", "Detail Drawing", ""),
("CUSTOM", "Custom", ""),
],
name="Cut Objects",
)
cut_objects_custom: StringProperty(name="Custom Cut")
active_drawing_style_index: IntProperty(name="Active Drawing Style Index")
# For now, this JSON dump are all the parameters that determine a camera's "Block representation"
@@ -373,13 +392,4 @@ class BIMTextProperties(PropertyGroup):
update=refreshFontSize,
name="Font Size",
)
symbol: EnumProperty(
items=[
("None", "None", ""),
("rectangle-tag", "Rectangle Tag", ""),
("door-tag", "Door Tag", ""),
],
update=refreshFontSize,
name="Symbol",
)
relating_product: PointerProperty(name="Relating Product", type=bpy.types.Object)
@@ -16,11 +16,12 @@
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import xml.etree.ElementTree as ET
import urllib.parse
import pystache
import ntpath
import os
import ntpath
import pystache
import urllib.parse
import xml.etree.ElementTree as ET
import blenderbim.tool as tool
from shutil import copy
from xml.dom import minidom
@@ -30,8 +31,7 @@ class SheetBuilder:
self.data_dir = None
self.scale = "NTS"
def create(self, name, titleblock_name):
sheet_path = os.path.join(self.data_dir, f"{name}.svg")
def create(self, sheet_path, titleblock_name):
root = ET.Element("svg")
root.attrib["xmlns"] = "http://www.w3.org/2000/svg"
root.attrib["xmlns:xlink"] = "http://www.w3.org/1999/xlink"
@@ -59,11 +59,16 @@ class SheetBuilder:
with open(sheet_path, "w") as f:
f.write(minidom.parseString(ET.tostring(root)).toprettyxml(indent=" "))
def add_drawing(self, view_name, sheet_name):
sheet_path = os.path.join(self.data_dir, "sheets", sheet_name + ".svg")
view_path = os.path.join(self.data_dir, "diagrams", view_name + ".svg")
def add_drawing(self, reference, drawing, sheet):
filename = drawing.Name
sheet_name = os.path.splitext(os.path.basename(tool.Drawing.get_document_uri(sheet)))[0]
sheet_dir = os.path.join(self.data_dir, "sheets")
drawing_dir = os.path.join(self.data_dir, "diagrams")
sheet_path = os.path.join(sheet_dir, sheet_name + ".svg")
drawing_path = os.path.join(drawing_dir, filename + ".svg")
underlay_path = os.path.join(drawing_dir, filename + "-underlay.png")
if not os.path.isfile(view_path):
if not os.path.isfile(sheet_path):
raise FileNotFoundError
ET.register_namespace("", "http://www.w3.org/2000/svg")
@@ -72,7 +77,7 @@ class SheetBuilder:
sheet_tree = ET.parse(sheet_path)
sheet_root = sheet_tree.getroot()
view_tree = ET.parse(view_path)
view_tree = ET.parse(drawing_path)
view_root = view_tree.getroot()
# The view is placed into a group with a background image element.
@@ -80,29 +85,50 @@ class SheetBuilder:
# here to accommodate browsers which do not nest images.
view = ET.SubElement(sheet_root, "g")
view.attrib["data-type"] = "drawing"
view.attrib["data-id"] = str(reference.id())
view.attrib["data-drawing"] = drawing.GlobalId
view_width = self.convert_to_mm(view_root.attrib.get("width"))
view_height = self.convert_to_mm(view_root.attrib.get("height"))
background = ET.SubElement(view, "image")
background.attrib["xlink:href"] = "../diagrams/{}.png".format(view_name)
background.attrib["x"] = "30"
background.attrib["y"] = "30"
background.attrib["width"] = str(view_width)
background.attrib["height"] = str(view_height)
if os.path.isfile(underlay_path):
background = ET.SubElement(view, "image")
background.attrib["data-type"] = "background"
background.attrib["xlink:href"] = os.path.relpath(underlay_path, sheet_dir)
background.attrib["x"] = "30"
background.attrib["y"] = "30"
background.attrib["width"] = str(view_width)
background.attrib["height"] = str(view_height)
foreground = ET.SubElement(view, "image")
foreground.attrib["xlink:href"] = "../diagrams/{}.svg".format(view_name)
foreground.attrib["x"] = "30"
foreground.attrib["y"] = "30"
foreground.attrib["width"] = str(view_width)
foreground.attrib["height"] = str(view_height)
if os.path.isfile(drawing_path):
foreground = ET.SubElement(view, "image")
foreground.attrib["data-type"] = "foreground"
foreground.attrib["xlink:href"] = os.path.relpath(drawing_path, sheet_dir)
foreground.attrib["x"] = "30"
foreground.attrib["y"] = "30"
foreground.attrib["width"] = str(view_width)
foreground.attrib["height"] = str(view_height)
self.add_view_title(30, view_height + 35, view)
sheet_tree.write(sheet_path)
def add_schedule(self, schedule_name, sheet_name):
sheet_path = os.path.join(self.data_dir, "sheets", sheet_name + ".svg")
view_path = os.path.join(self.data_dir, "schedules", schedule_name + ".svg")
def remove_drawing(self, reference, sheet):
ET.register_namespace("", "http://www.w3.org/2000/svg")
sheet_path = tool.Drawing.get_document_uri(sheet)
sheet_tree = ET.parse(sheet_path)
sheet_root = sheet_tree.getroot()
for g in sheet_root.findall('{http://www.w3.org/2000/svg}g'):
if g.attrib.get("data-id") == str(reference.id()):
sheet_root.remove(g)
break
sheet_tree.write(sheet_path)
def add_schedule(self, reference, schedule, sheet):
view_path = tool.Drawing.get_document_uri(schedule)
schedule_name = os.path.splitext(os.path.basename(view_path))[0]
sheet_path = tool.Drawing.get_document_uri(sheet)
ET.register_namespace("", "http://www.w3.org/2000/svg")
ET.register_namespace("xlink", "http://www.w3.org/1999/xlink")
@@ -115,30 +141,35 @@ class SheetBuilder:
view_width = self.convert_to_mm(view_root.attrib.get("width"))
view_height = self.convert_to_mm(view_root.attrib.get("height"))
group = ET.SubElement(sheet_root, "g")
group.attrib["data-type"] = "schedule"
view = ET.SubElement(sheet_root, "g")
view.attrib["data-type"] = "schedule"
view.attrib["data-id"] = str(reference.id())
view.attrib["data-schedule"] = str(schedule.id())
foreground = ET.SubElement(group, "image")
foreground = ET.SubElement(view, "image")
foreground.attrib["data-type"] = "table"
foreground.attrib["xlink:href"] = "../schedules/{}.svg".format(schedule_name)
foreground.attrib["x"] = "30"
foreground.attrib["y"] = "30"
foreground.attrib["width"] = str(view_width)
foreground.attrib["height"] = str(view_height)
self.add_view_title(30, view_height + 35, group)
self.add_view_title(30, view_height + 35, view)
sheet_tree.write(sheet_path)
def add_view_title(self, x, y, parent):
title_tree = ET.parse(os.path.join(self.data_dir, "templates", "view-title.svg"))
title_root = title_tree.getroot()
title = ET.SubElement(parent, "image")
title.attrib["data-type"] = "view-title"
title.attrib["xlink:href"] = "../templates/view-title.svg"
title.attrib["x"] = str(x)
title.attrib["y"] = str(y)
title.attrib["width"] = str(self.convert_to_mm(title_root.attrib.get("width")))
title.attrib["height"] = str(self.convert_to_mm(title_root.attrib.get("height")))
def build(self, sheet_name):
def build(self, sheet):
sheet_name = os.path.splitext(os.path.basename(tool.Drawing.get_document_uri(sheet)))[0]
os.makedirs(os.path.join(self.data_dir, "build", sheet_name), exist_ok=True)
sheet_path = os.path.join(self.data_dir, "sheets", f"{sheet_name}.svg")
@@ -149,68 +180,86 @@ class SheetBuilder:
tree = ET.parse(sheet_path)
root = tree.getroot()
titleblock = root.findall('{http://www.w3.org/2000/svg}g[@data-type="titleblock"]')[0]
image = titleblock.findall("{http://www.w3.org/2000/svg}image")[0]
titleblock.append(self.parse_embedded_svg(image, {"number": sheet_name, "revision": "A"}))
titleblock.remove(image)
self.group_number = 1
self.build_drawings(root.findall('{http://www.w3.org/2000/svg}g[@data-type="drawing"]'), sheet_name)
self.build_schedules(root.findall('{http://www.w3.org/2000/svg}g[@data-type="schedule"]'))
self.build_titleblock(root, sheet)
self.build_drawings(root, sheet)
self.build_schedules(root)
with open(os.path.join(self.data_dir, "build", sheet_name, f"{sheet_name}.svg"), "wb") as output:
tree.write(output)
def build_drawings(self, drawings, sheet_name):
for view in drawings:
def build_titleblock(self, root, sheet):
titleblock = root.findall('{http://www.w3.org/2000/svg}g[@data-type="titleblock"]')[0]
image = titleblock.findall("{http://www.w3.org/2000/svg}image")[0]
titleblock.append(self.parse_embedded_svg(image, sheet.get_info()))
titleblock.remove(image)
def build_drawings(self, root, sheet):
sheet_name = os.path.splitext(os.path.basename(tool.Drawing.get_document_uri(sheet)))[0]
for view in root.findall('{http://www.w3.org/2000/svg}g[@data-type="drawing"]'):
reference = tool.Ifc.get().by_id(int(view.attrib["data-id"]))
drawing = tool.Ifc.get().by_id(view.attrib["data-drawing"])
images = view.findall("{http://www.w3.org/2000/svg}image")
background = images[0]
foreground = images[1]
view_title = images[2]
self.scale = "NTS"
# Add foreground
view.append(self.parse_embedded_svg(foreground, {}))
background = None
foreground = None
view_title = None
# Add background
background_path = os.path.join(self.data_dir, "sheets", self.get_href(background))
for image in images:
if image.attrib["data-type"] == "background":
background = image
elif image.attrib["data-type"] == "foreground":
foreground = image
elif image.attrib["data-type"] == "view-title":
view_title = image
copy(background_path, os.path.join(self.data_dir, "build", sheet_name))
if foreground is not None:
view.append(self.parse_embedded_svg(foreground, {}))
# Add view title
foreground_path = self.get_href(foreground)
view.append(
self.parse_embedded_svg(
view_title,
{"no": self.group_number, "name": ntpath.basename(foreground_path)[0:-4], "scale": self.scale},
)
)
if background is not None:
background_path = os.path.join(self.data_dir, "sheets", self.get_href(background))
copy(background_path, os.path.join(self.data_dir, "build", sheet_name))
if view_title is not None:
foreground_path = self.get_href(foreground)
data = reference.get_info()
if not data["Name"]:
data["Name"] = ntpath.basename(foreground_path)[0:-4]
data["Scale"] = tool.Drawing.get_drawing_human_scale(drawing)
view.append(self.parse_embedded_svg(view_title, data))
for image in images:
view.remove(image)
self.group_number += 1
def build_schedules(self, root):
for view in root.findall('{http://www.w3.org/2000/svg}g[@data-type="schedule"]'):
reference = tool.Ifc.get().by_id(int(view.attrib["data-id"]))
schedule = tool.Ifc.get().by_id(int(view.attrib["data-schedule"]))
def build_schedules(self, schedules):
for group in schedules:
images = group.findall("{http://www.w3.org/2000/svg}image")
schedule = images[0]
group_title = images[1]
self.scale = "NTS"
images = view.findall("{http://www.w3.org/2000/svg}image")
group.append(self.parse_embedded_svg(schedule, {}))
path = self.get_href(schedule)
group.append(
self.parse_embedded_svg(
group_title, {"no": self.group_number, "name": ntpath.basename(path)[0:-4], "scale": self.scale}
)
)
table = None
view_title = None
for image in images:
group.remove(image)
if image.attrib["data-type"] == "table":
table = image
elif image.attrib["data-type"] == "view-title":
view_title = image
self.group_number += 1
if table is not None:
view.append(self.parse_embedded_svg(table, {}))
if view_title is not None:
path = self.get_href(table)
data = reference.get_info()
if not data["Name"]:
data["Name"] = schedule.Name or "Unnamed"
view.append(self.parse_embedded_svg(view_title, data))
for image in images:
view.remove(image)
def get_href(self, element):
return urllib.parse.unquote(element.attrib.get("{http://www.w3.org/1999/xlink}href"))
@@ -24,6 +24,7 @@ import pystache
import xml.etree.ElementTree as ET
import svgwrite
import ifcopenshell
import ifcopenshell.util.element
import ifcopenshell.util.representation
import blenderbim.tool as tool
import blenderbim.bim.module.drawing.helper as helper
@@ -64,7 +65,8 @@ class SvgWriter:
self.data_dir = None
self.vector_style = None
self.human_scale = "NTS"
self.annotations = {}
self.annotations = []
self.metadata = []
self.background_image = None
self.scale = 1 / 100 # 1:100
@@ -139,182 +141,169 @@ class SvgWriter:
self.draw_line(element, "background")
def draw_annotations(self):
x_offset = self.raw_width / 2
y_offset = self.raw_height / 2
for element in self.annotations:
obj = tool.Ifc.get_object(element)
if not obj or element.ObjectType == "DRAWING":
continue
elif element.ObjectType == "GRID":
self.draw_grid_annotation(obj)
elif element.ObjectType == "TEXT_LEADER":
self.draw_leader_annotation(obj)
elif element.ObjectType == "STAIR_ARROW":
self.draw_stair_annotation(obj)
elif element.ObjectType == "DIMENSION":
self.draw_dimension_annotations(obj)
elif element.ObjectType == "RADIUS":
self.draw_radius_annotations(obj)
elif element.ObjectType == "DIAMETER":
self.draw_diameter_annotations(obj)
elif element.ObjectType == "ELEVATION":
self.draw_elevation_annotation(obj)
elif element.ObjectType == "SECTION":
self.draw_section_annotation(obj)
elif element.ObjectType == "BREAKLINE":
self.draw_break_annotations(obj)
elif element.ObjectType == "HIDDEN_LINE":
self.draw_line_annotation(obj)
elif element.ObjectType == "PLAN_LEVEL":
self.draw_plan_level_annotation(obj)
elif element.ObjectType == "SECTION_LEVEL":
self.draw_section_level_annotation(obj)
elif element.ObjectType == "TEXT":
self.draw_text_annotation(obj, obj.location)
else:
self.draw_misc_annotation(obj)
for obj in self.annotations.get("equal_objs", []):
self.draw_dimension_annotations(obj, text_override="EQ")
for obj in self.annotations.get("dimension_objs", []):
self.draw_dimension_annotations(obj)
# Experimental integration with the MeasureIt-ARCH Add-on
self.draw_measureit_arch_dimension_annotations()
if self.annotations.get("break_obj"):
self.draw_break_annotations(self.annotations["break_obj"])
for grid_obj in self.annotations.get("grid_objs", []):
matrix_world = grid_obj.matrix_world
for edge in grid_obj.data.edges:
classes = ["annotation", "grid"]
v0_global = matrix_world @ grid_obj.data.vertices[edge.vertices[0]].co.xyz
v1_global = matrix_world @ grid_obj.data.vertices[edge.vertices[1]].co.xyz
v0 = self.project_point_onto_camera(v0_global)
v1 = self.project_point_onto_camera(v1_global)
start = Vector(((x_offset + v0.x), (y_offset - v0.y)))
end = Vector(((x_offset + v1.x), (y_offset - v1.y)))
vector = end - start
line = self.svg.add(
self.svg.line(
start=tuple(start * self.scale), end=tuple(end * self.scale), class_=" ".join(classes)
)
)
line["marker-start"] = "url(#grid-marker)"
line["marker-end"] = "url(#grid-marker)"
line["stroke-dasharray"] = "12.5, 3, 3, 3"
axis_tag = tool.Ifc.get_entity(grid_obj).Name
self.svg.add(
self.svg.text(
axis_tag,
insert=tuple(start * self.scale),
**{
"font-size": annotation.Annotator.get_svg_text_size(5.0),
"font-family": "OpenGost Type B TT",
"text-anchor": "middle",
"alignment-baseline": "middle",
"dominant-baseline": "middle",
},
)
)
self.svg.add(
self.svg.text(
axis_tag,
insert=tuple(end * self.scale),
**{
"font-size": annotation.Annotator.get_svg_text_size(5.0),
"font-family": "OpenGost Type B TT",
"text-anchor": "middle",
"alignment-baseline": "middle",
"dominant-baseline": "middle",
},
)
)
self.draw_ifc_annotation()
for obj in self.annotations.get("misc_objs", []):
self.draw_misc_annotation(obj, ["IfcAnnotation"])
for obj_data in self.annotations.get("hidden_objs", []):
self.draw_line_annotation(obj_data, ["hidden"])
for obj_data in self.annotations.get("solid_objs", []):
self.draw_line_annotation(obj_data, ["solid"])
self.draw_leader_annotations()
for obj in self.annotations.get("plan_level_objs", []):
self.draw_plan_level_annotation(obj)
if self.annotations.get("section_level_obj"):
matrix_world = self.annotations["section_level_obj"].matrix_world
for spline in self.annotations["section_level_obj"].data.splines:
classes = ["annotation", "section-level"]
points = self.get_spline_points(spline)
projected_points = [self.project_point_onto_camera(matrix_world @ p.co.xyz) for p in points]
d = " ".join(
[
"L {} {}".format((x_offset + p.x) * self.scale, (y_offset - p.y) * self.scale)
for p in projected_points
]
)
d = "M{}".format(d[1:])
path = self.svg.add(self.svg.path(d=d, class_=" ".join(classes)))
path["marker-start"] = "url(#section-level-marker)"
path["stroke-dasharray"] = "12.5, 3, 3, 3"
text_position = Vector(
(
(x_offset + projected_points[0].x) * self.scale,
((y_offset - projected_points[0].y) * self.scale) - 3.5,
)
)
# TODO: allow metric to be configurable
rl = (matrix_world @ points[0].co.xyz).z
if bpy.context.scene.unit_settings.system == "IMPERIAL":
rl = helper.format_distance(rl)
else:
rl = "{:.3f}m".format(rl)
self.svg.add(
self.svg.text(
"RL +{}".format(rl),
insert=tuple(text_position),
**{
"font-size": annotation.Annotator.get_svg_text_size(2.5),
"font-family": "OpenGost Type B TT",
"text-anchor": "start",
"alignment-baseline": "baseline",
"dominant-baseline": "baseline",
},
)
)
if self.annotations.get("stair_obj"):
matrix_world = self.annotations["stair_obj"].matrix_world
for spline in self.annotations["stair_obj"].data.splines:
classes = ["annotation", "stair"]
points = self.get_spline_points(spline)
projected_points = [self.project_point_onto_camera(matrix_world @ p.co.xyz) for p in points]
d = " ".join(
[
"L {} {}".format((x_offset + p.x) * self.scale, (y_offset - p.y) * self.scale)
for p in projected_points
]
)
d = "M{}".format(d[1:])
start = Vector(((x_offset + projected_points[0].x), (y_offset - projected_points[0].y)))
next_point = Vector(((x_offset + projected_points[1].x), (y_offset - projected_points[1].y)))
text_position = (start * self.scale) - ((next_point - start).normalized() * 5)
path = self.svg.add(self.svg.path(d=d, class_=" ".join(classes)))
self.svg.add(
self.svg.text(
"UP",
insert=tuple(text_position),
**{
"font-size": annotation.Annotator.get_svg_text_size(2.5),
"font-family": "OpenGost Type B TT",
"text-anchor": "middle",
"alignment-baseline": "middle",
"dominant-baseline": "middle",
},
)
)
self.draw_text_annotations()
def draw_ifc_annotation(self):
def draw_section_level_annotation(self, obj):
x_offset = self.raw_width / 2
y_offset = self.raw_height / 2
for annotation in self.annotations.get("annotation_objs", []):
for edge in annotation["edges"]:
v0_global = annotation["vertices"][edge[0]]
v1_global = annotation["vertices"][edge[1]]
v0 = self.project_point_onto_camera(v0_global)
v1 = self.project_point_onto_camera(v1_global)
start = Vector(((x_offset + v0.x), (y_offset - v0.y)))
end = Vector(((x_offset + v1.x), (y_offset - v1.y)))
vector = end - start
line = self.svg.add(
self.svg.line(
start=tuple(start * self.scale),
end=tuple(end * self.scale),
class_=" ".join(annotation["classes"]),
)
matrix_world = obj.matrix_world
classes = self.get_attribute_classes(obj)
element = tool.Ifc.get_entity(obj)
storey = tool.Drawing.get_annotation_element(element)
tag = storey.Name if storey else element.Description
for spline in obj.data.splines:
points = self.get_spline_points(spline)
projected_points = [self.project_point_onto_camera(matrix_world @ p.co.xyz) for p in points]
d = " ".join(
[
"L {} {}".format((x_offset + p.x) * self.scale, (y_offset - p.y) * self.scale)
for p in projected_points
]
)
d = "M{}".format(d[1:])
path = self.svg.add(self.svg.path(d=d, class_=" ".join(classes)))
text_position = Vector(
(
(x_offset + projected_points[0].x) * self.scale,
((y_offset - projected_points[0].y) * self.scale) - 3.5,
)
)
# TODO: allow metric to be configurable
rl = (matrix_world @ points[0].co.xyz).z
if bpy.context.scene.unit_settings.system == "IMPERIAL":
rl = helper.format_distance(rl)
else:
rl = "{:.3f}m".format(rl)
text_style = {
"font-size": annotation.Annotator.get_svg_text_size(2.5),
"font-family": "OpenGost Type B TT",
"text-anchor": "start",
"alignment-baseline": "baseline",
"dominant-baseline": "baseline",
}
self.svg.add(self.svg.text(f"RL +{rl}", insert=tuple(text_position), **text_style))
if tag:
self.svg.add(self.svg.text(tag, insert=(text_position[0], text_position[1] - 5), **text_style))
def draw_misc_annotation(self, obj, classes):
def draw_stair_annotation(self, obj):
x_offset = self.raw_width / 2
y_offset = self.raw_height / 2
matrix_world = obj.matrix_world
classes = self.get_attribute_classes(obj)
for spline in obj.data.splines:
points = self.get_spline_points(spline)
projected_points = [self.project_point_onto_camera(matrix_world @ p.co.xyz) for p in points]
d = " ".join(
[
"L {} {}".format((x_offset + p.x) * self.scale, (y_offset - p.y) * self.scale)
for p in projected_points
]
)
d = "M{}".format(d[1:])
start = Vector(((x_offset + projected_points[0].x), (y_offset - projected_points[0].y)))
next_point = Vector(((x_offset + projected_points[1].x), (y_offset - projected_points[1].y)))
text_position = (start * self.scale) - ((next_point - start).normalized() * 5)
path = self.svg.add(self.svg.path(d=d, class_=" ".join(classes)))
self.svg.add(
self.svg.text(
"UP",
insert=tuple(text_position),
**{
"font-size": annotation.Annotator.get_svg_text_size(2.5),
"font-family": "OpenGost Type B TT",
"text-anchor": "middle",
"alignment-baseline": "middle",
"dominant-baseline": "middle",
},
)
)
def draw_grid_annotation(self, obj):
x_offset = self.raw_width / 2
y_offset = self.raw_height / 2
matrix_world = obj.matrix_world
classes = self.get_attribute_classes(obj)
for edge in obj.data.edges:
v0_global = matrix_world @ obj.data.vertices[edge.vertices[0]].co.xyz
v1_global = matrix_world @ obj.data.vertices[edge.vertices[1]].co.xyz
v0 = self.project_point_onto_camera(v0_global)
v1 = self.project_point_onto_camera(v1_global)
start = Vector(((x_offset + v0.x), (y_offset - v0.y)))
end = Vector(((x_offset + v1.x), (y_offset - v1.y)))
vector = end - start
line = self.svg.add(
self.svg.line(start=tuple(start * self.scale), end=tuple(end * self.scale), class_=" ".join(classes))
)
line["stroke-dasharray"] = "12.5, 3, 3, 3"
axis_tag = tool.Ifc.get_entity(obj).Name
self.svg.add(
self.svg.text(
axis_tag,
insert=tuple(start * self.scale),
**{
"font-size": annotation.Annotator.get_svg_text_size(5.0),
"font-family": "OpenGost Type B TT",
"text-anchor": "middle",
"alignment-baseline": "middle",
"dominant-baseline": "middle",
},
)
)
self.svg.add(
self.svg.text(
axis_tag,
insert=tuple(end * self.scale),
**{
"font-size": annotation.Annotator.get_svg_text_size(5.0),
"font-family": "OpenGost Type B TT",
"text-anchor": "middle",
"alignment-baseline": "middle",
"dominant-baseline": "middle",
},
)
)
def draw_misc_annotation(self, obj):
# We have to decide whether this should come from Blender or from IFC.
# For the moment, for convenience of experimenting with ideas, it comes
# from Blender. In the future, it should probably come from IFC.
if not isinstance(obj.data, bpy.types.Mesh):
return
classes.extend(self.get_attribute_classes(obj))
classes = self.get_attribute_classes(obj)
if len(obj.data.polygons) == 0:
self.draw_edge_annotation(obj, classes)
return
@@ -324,6 +313,7 @@ class SvgWriter:
for polygon in obj.data.polygons:
points = [obj.data.vertices[v] for v in polygon.vertices]
projected_points = [self.project_point_onto_camera(matrix_world @ p.co.xyz) for p in points]
projected_points.append(projected_points[0])
d = " ".join(
[
"L {} {}".format((x_offset + p.x) * self.scale, (y_offset - p.y) * self.scale)
@@ -334,57 +324,31 @@ class SvgWriter:
path = self.svg.add(self.svg.path(d=d, class_=" ".join(classes)))
def get_attribute_classes(self, obj):
classes = [obj.name.split("/")[0]]
for slot in obj.material_slots:
if slot.material:
classes.append("material-{}".format(re.sub("[^0-9a-zA-Z]+", "", slot.material.name)))
global_id = IfcStore.get_file().by_id(obj.BIMObjectProperties.ifc_definition_id).GlobalId
classes.append("globalid-{}".format(global_id))
for attribute in self.annotations.get("attributes", []):
result = self.get_obj_value(obj, attribute)
if result:
classes.append(
"{}-{}".format(re.sub("[^0-9a-zA-Z]+", "", attribute), re.sub("[^0-9a-zA-Z]+", "", result))
)
element = tool.Ifc.get_entity(obj)
global_id = "GlobalId-{}".format(element.GlobalId)
predefined_type = "PredefinedType-" + self.canonicalise_class_name(
str(ifcopenshell.util.element.get_predefined_type(element))
)
classes = [global_id, element.is_a(), predefined_type]
for key in self.metadata:
value = ifcopenshell.util.selector.Selector.get_element_value(element, key)
if value:
classes.append(self.canonicalise_class_name(key) + "-" + self.canonicalise_class_name(str(value)))
return classes
def get_obj_value(self, obj, key):
# This is a duplicate implementation of the IFC selector key in Blender
# In the future if all this becomes purely IFC based this can be deleted
if "." in key and key.split(".")[0] == "type":
try:
obj = obj.BIMObjectProperties.relating_type
except:
return
key = ".".join(key.split(".")[1:])
result = obj.BIMObjectProperties.attributes.get(key)
if result:
return result.string_value
elif key == "Name":
return obj.name.split("/")[1]
elif "." in key:
pset_name, prop = key.split(".")
pset = obj.BIMObjectProperties.psets.get(pset_name)
if not pset:
pset = obj.BIMObjectProperties.qtos.get(pset_name)
if not pset:
return
result = pset.properties.get(prop)
if result:
return result.string_value
def canonicalise_class_name(self, name):
return re.sub("[^0-9a-zA-Z]+", "", name)
def draw_line_annotation(self, obj_data, classes):
def draw_line_annotation(self, obj):
# TODO: properly scope these offsets
x_offset = self.raw_width / 2
y_offset = self.raw_height / 2
obj, data = obj_data
classes.extend(["annotation"])
classes = self.get_attribute_classes(obj)
matrix_world = obj.matrix_world
if isinstance(data, bpy.types.Curve):
for spline in data.splines:
if isinstance(obj.data, bpy.types.Curve):
for spline in obj.data.splines:
points = self.get_spline_points(spline)
projected_points = [self.project_point_onto_camera(matrix_world @ p.co.xyz) for p in points]
d = " ".join(
@@ -395,7 +359,7 @@ class SvgWriter:
)
d = "M{}".format(d[1:])
path = self.svg.add(self.svg.path(d=d, class_=" ".join(classes)))
elif isinstance(data, bpy.types.Mesh):
elif isinstance(obj.data, bpy.types.Mesh):
self.draw_edge_annotation(obj, classes)
def draw_edge_annotation(self, obj, classes):
@@ -414,20 +378,100 @@ class SvgWriter:
self.svg.line(start=tuple(start * self.scale), end=tuple(end * self.scale), class_=" ".join(classes))
)
def draw_leader_annotations(self):
for obj in self.annotations.get("leader_objs", []):
self.draw_line_annotation((obj, obj.data), ["leader"])
spline = obj.data.splines[0]
spline_points = spline.bezier_points if spline.bezier_points else spline.points
if spline_points:
position = obj.matrix_world @ spline_points[0].co.xyz
else:
position = Vector((0, 0, 0))
self.draw_text_annotation(obj, position)
def draw_leader_annotation(self, obj):
self.draw_line_annotation(obj)
spline = obj.data.splines[0]
spline_points = spline.bezier_points if spline.bezier_points else spline.points
if spline_points:
position = obj.matrix_world @ spline_points[0].co.xyz
else:
position = Vector((0, 0, 0))
self.draw_text_annotation(obj, position)
def draw_text_annotations(self):
for text_obj in self.annotations.get("text_objs", []):
self.draw_text_annotation(text_obj, text_obj.location)
def draw_section_annotation(self, obj):
x_offset = self.raw_width / 2
y_offset = self.raw_height / 2
self.draw_line_annotation(obj)
v1 = self.project_point_onto_camera(obj.matrix_world @ Vector((0, 0, 0)))
v2 = self.project_point_onto_camera(obj.matrix_world @ Vector((0, 0, -1)))
angle = -math.degrees((v2 - v1).xy.angle_signed(Vector((0, 1))))
for vert in obj.data.vertices:
point = obj.matrix_world @ vert.co
symbol_position = self.project_point_onto_camera(point)
symbol_position = Vector(((x_offset + symbol_position.x), (y_offset - symbol_position.y)))
transform = "rotate({}, {}, {})".format(
angle,
(symbol_position * self.scale)[0],
(symbol_position * self.scale)[1],
)
self.svg.add(
self.svg.use("#section-arrow", insert=tuple(symbol_position * self.scale), transform=transform)
)
self.svg.add(self.svg.use("#section-tag", insert=tuple(symbol_position * self.scale)))
reference_id, sheet_id = self.get_reference_and_sheet_id_from_annotation(tool.Ifc.get_entity(obj))
text_position = list(symbol_position * self.scale)
text_style = {
"font-size": annotation.Annotator.get_svg_text_size(2.5),
"font-family": "OpenGost Type B TT",
"text-anchor": "middle",
"alignment-baseline": "middle",
"dominant-baseline": "middle",
}
self.svg.add(self.svg.text(reference_id, insert=(text_position[0], text_position[1] - 2.5), **text_style))
self.svg.add(self.svg.text(sheet_id, insert=(text_position[0], text_position[1] + 2.5), **text_style))
def draw_elevation_annotation(self, obj):
x_offset = self.raw_width / 2
y_offset = self.raw_height / 2
symbol_position = self.project_point_onto_camera(obj.location)
symbol_position = Vector(((x_offset + symbol_position.x), (y_offset - symbol_position.y)))
v1 = self.project_point_onto_camera(obj.matrix_world @ Vector((0, 0, 0)))
v2 = self.project_point_onto_camera(obj.matrix_world @ Vector((0, 0, -1)))
angle = -math.degrees((v2 - v1).xy.angle_signed(Vector((0, 1))))
transform = "rotate({}, {}, {})".format(
angle,
(symbol_position * self.scale)[0],
(symbol_position * self.scale)[1],
)
self.svg.add(self.svg.use("#elevation-arrow", insert=tuple(symbol_position * self.scale), transform=transform))
self.svg.add(self.svg.use("#elevation-tag", insert=tuple(symbol_position * self.scale)))
reference_id, sheet_id = self.get_reference_and_sheet_id_from_annotation(tool.Ifc.get_entity(obj))
text_position = list(symbol_position * self.scale)
text_style = {
"font-size": annotation.Annotator.get_svg_text_size(2.5),
"font-family": "OpenGost Type B TT",
"text-anchor": "middle",
"alignment-baseline": "middle",
"dominant-baseline": "middle",
}
self.svg.add(self.svg.text(reference_id, insert=(text_position[0], text_position[1] - 2.5), **text_style))
self.svg.add(self.svg.text(sheet_id, insert=(text_position[0], text_position[1] + 2.5), **text_style))
def get_reference_and_sheet_id_from_annotation(self, element):
reference_id = "-"
sheet_id = "-"
drawing = tool.Drawing.get_annotation_element(element)
reference = tool.Drawing.get_drawing_reference(drawing)
if reference:
sheet = tool.Drawing.get_reference_sheet(reference)
if sheet:
if tool.Ifc.get_schema() == "IFC2X3":
reference_id = reference.ItemReference or "-"
sheet_id = sheet.DocumentId or "-"
else:
reference_id = reference.Identification or "-"
sheet_id = sheet.Identification or "-"
return (reference_id, sheet_id)
return ("-", "-")
def draw_text_annotation(self, text_obj, position):
x_offset = self.raw_width / 2
@@ -453,10 +497,9 @@ class SvgWriter:
(text_position * self.scale)[1],
)
if text_obj.BIMTextProperties.symbol != "None":
self.svg.add(
self.svg.use("#{}".format(text_obj.BIMTextProperties.symbol), insert=tuple(text_position * self.scale))
)
symbol = tool.Drawing.get_annotation_symbol(element)
if symbol:
self.svg.add(self.svg.use(f"#{symbol}", insert=tuple(text_position * self.scale)))
if text_literal.BoxAlignment == "top-left":
alignment_baseline = "hanging"
@@ -486,11 +529,15 @@ class SvgWriter:
alignment_baseline = "baseline"
text_anchor = "end"
text_body = text_literal.Literal
if text_obj.name in self.annotations.get("template_variables", {}):
text_body = pystache.render(text_body, self.annotations["template_variables"][text_obj.name])
literal = text_literal.Literal
for line_number, text_line in enumerate(text_body.split("\n")):
product = tool.Drawing.get_text_product(element)
selector = ifcopenshell.util.selector.Selector
variables = {}
for variable in re.findall("{{.*?}}", literal):
literal = literal.replace(variable, selector.get_element_value(product, variable[2:-2]) or "")
for line_number, text_line in enumerate(literal.replace("\\n", "\n").split("\n")):
self.svg.add(
self.svg.text(
text_line,
@@ -507,14 +554,20 @@ class SvgWriter:
)
)
def draw_break_annotations(self, break_obj):
def draw_break_annotations(self, obj):
x_offset = self.raw_width / 2
y_offset = self.raw_height / 2
matrix_world = break_obj.matrix_world
for polygon in break_obj.data.polygons:
points = [break_obj.data.vertices[v] for v in polygon.vertices]
classes = self.get_attribute_classes(obj)
matrix_world = obj.matrix_world
for edge in obj.data.edges:
points = [obj.data.vertices[v] for v in edge.vertices]
projected_points = [self.project_point_onto_camera(matrix_world @ p.co.xyz) for p in points]
projected_points = [
projected_points[0],
(projected_points[0] + projected_points[1]) / 2,
projected_points[1],
]
d = " ".join(
[
"L {} {}".format((x_offset + p.x) * self.scale, (y_offset - p.y) * self.scale)
@@ -522,25 +575,14 @@ class SvgWriter:
]
)
d = "M{}".format(d[1:])
path = self.svg.add(self.svg.path(d=d, class_=" ".join(["break"])))
break_points = [
projected_points[0],
((projected_points[1] - projected_points[0]) / 2) + projected_points[0],
projected_points[1],
]
d = " ".join(
["L {} {}".format((x_offset + p.x) * self.scale, (y_offset - p.y) * self.scale) for p in break_points]
)
d = "M{}".format(d[1:])
path = self.svg.add(self.svg.path(d=d, class_=" ".join(["breakline"])))
path = self.svg.add(self.svg.path(d=d, class_=" ".join(classes)))
def draw_plan_level_annotation(self, obj):
x_offset = self.raw_width / 2
y_offset = self.raw_height / 2
matrix_world = obj.matrix_world
classes = self.get_attribute_classes(obj)
for spline in obj.data.splines:
classes = ["annotation", "plan-level"]
points = self.get_spline_points(spline)
projected_points = [self.project_point_onto_camera(matrix_world @ p.co.xyz) for p in points]
d = " ".join(
@@ -551,7 +593,6 @@ class SvgWriter:
)
d = "M{}".format(d[1:])
path = self.svg.add(self.svg.path(d=d, class_=" ".join(classes)))
path["marker-end"] = "url(#plan-level-marker)"
text_position = Vector(
(
(x_offset + projected_points[0].x) * self.scale,
@@ -582,16 +623,80 @@ class SvgWriter:
)
)
def draw_dimension_annotations(self, dimension_obj, text_override=None):
matrix_world = dimension_obj.matrix_world
for spline in dimension_obj.data.splines:
def draw_radius_annotations(self, obj):
x_offset = self.raw_width / 2
y_offset = self.raw_height / 2
classes = self.get_attribute_classes(obj)
element = tool.Ifc.get_entity(obj)
matrix_world = obj.matrix_world
for spline in obj.data.splines:
points = self.get_spline_points(spline)
projected_points = [self.project_point_onto_camera(matrix_world @ p.co.xyz) for p in points]
d = " ".join(
[
"L {} {}".format((x_offset + p.x) * self.scale, (y_offset - p.y) * self.scale)
for p in projected_points
]
)
d = "M{}".format(d[1:])
path = self.svg.add(self.svg.path(d=d, class_=" ".join(classes)))
p0 = Vector(
((x_offset + projected_points[0].x) * self.scale, (y_offset - projected_points[0].y) * self.scale)
)
p1 = Vector(
((x_offset + projected_points[1].x) * self.scale, (y_offset - projected_points[1].y) * self.scale)
)
text_offset = (p0 - p1).xy.normalized() * 5
text_position = projected_points[0]
text_position = Vector(
((x_offset + text_position.x) * self.scale, (y_offset - text_position.y) * self.scale)
)
text_position += text_offset
text_style = {
"font-size": annotation.Annotator.get_svg_text_size(2.5),
"font-family": "OpenGost Type B TT",
"text-anchor": "middle",
"alignment-baseline": "middle",
"dominant-baseline": "middle",
}
radius = ((matrix_world @ points[-1].co) - (matrix_world @ points[-2].co)).length
radius = helper.format_distance(radius)
tag = element.Description or f"R{radius}"
self.svg.add(self.svg.text(tag, insert=tuple(text_position), **text_style))
def draw_diameter_annotations(self, obj):
classes = self.get_attribute_classes(obj)
matrix_world = obj.matrix_world
element = tool.Ifc.get_entity(obj)
text_override = element.Description
for spline in obj.data.splines:
points = self.get_spline_points(spline)
for i, p in enumerate(points):
if i + 1 >= len(points):
continue
v0_global = matrix_world @ points[i].co.xyz
v1_global = matrix_world @ points[i + 1].co.xyz
self.draw_dimension_annotation(v0_global, v1_global, text_override)
self.draw_dimension_annotation(
v0_global, v1_global, classes, text_override, text_format=lambda x: "D" + x
)
def draw_dimension_annotations(self, obj):
classes = self.get_attribute_classes(obj)
matrix_world = obj.matrix_world
element = tool.Ifc.get_entity(obj)
text_override = element.Description
for spline in obj.data.splines:
points = self.get_spline_points(spline)
for i, p in enumerate(points):
if i + 1 >= len(points):
continue
v0_global = matrix_world @ points[i].co.xyz
v1_global = matrix_world @ points[i + 1].co.xyz
self.draw_dimension_annotation(v0_global, v1_global, classes, text_override)
def draw_measureit_arch_dimension_annotations(self):
try:
@@ -601,10 +706,11 @@ class SvgWriter:
except:
return
for coord in coords:
self.draw_dimension_annotation(Vector(coord[0]), Vector(coord[1]))
self.draw_dimension_annotation(
Vector(coord[0]), Vector(coord[1]), ["IfcAnnotation", "PredefinedType-DIMENSION"]
)
def draw_dimension_annotation(self, v0_global, v1_global, text_override=None):
classes = ["annotation", "dimension"]
def draw_dimension_annotation(self, v0_global, v1_global, classes, text_override=None, text_format=lambda x: x):
x_offset = self.raw_width / 2
y_offset = self.raw_height / 2
v0 = self.project_point_onto_camera(v0_global)
@@ -626,12 +732,11 @@ class SvgWriter:
line = self.svg.add(
self.svg.line(start=tuple(start * self.scale), end=tuple(end * self.scale), class_=" ".join(classes))
)
line["marker-start"] = "url(#dimension-marker-start)"
line["marker-end"] = "url(#dimension-marker-end)"
if text_override is not None:
text = text_override
else:
text = str(dimension)
text = text_format(text)
self.svg.add(
self.svg.text(
text,
@@ -646,6 +751,7 @@ class SvgWriter:
)
def project_point_onto_camera(self, point):
# TODO is this needlessly complex?
return self.camera.matrix_world.inverted() @ geometry.intersect_line_plane(
point.xyz,
point.xyz - Vector(self.camera_projection),
+157 -101
View File
@@ -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, SchedulesData, DrawingsData
class BIM_PT_camera(Panel):
@@ -47,13 +47,13 @@ class BIM_PT_camera(Panel):
col = layout.column(align=True)
row = col.row(align=True)
row.prop(dprops, "has_underlay", icon="OUTLINER_OB_IMAGE")
row.prop(props, "has_underlay", icon="OUTLINER_OB_IMAGE")
row.prop(dprops, "should_use_underlay_cache", text="", icon="FILE_REFRESH")
row = col.row(align=True)
row.prop(dprops, "has_linework", icon="IMAGE_DATA")
row.prop(props, "has_linework", icon="IMAGE_DATA")
row.prop(dprops, "should_use_linework_cache", text="", icon="FILE_REFRESH")
row = col.row(align=True)
row.prop(dprops, "has_annotation", icon="MOD_EDGESPLIT")
row.prop(props, "has_annotation", icon="MOD_EDGESPLIT")
row.prop(dprops, "should_use_annotation_cache", text="", icon="FILE_REFRESH")
row = layout.row()
@@ -62,20 +62,9 @@ class BIM_PT_camera(Panel):
row = layout.row()
row.prop(props, "is_nts")
row = layout.row()
row.operator("bim.generate_references")
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":
row = layout.row()
row.prop(props, "cut_objects_custom")
row = layout.row()
row.prop(props, "raster_x")
row = layout.row()
@@ -149,90 +138,130 @@ class BIM_PT_drawing_underlay(Panel):
class BIM_PT_drawings(Panel):
bl_label = "SVG Drawings"
bl_label = "Drawings"
bl_idname = "BIM_PT_drawings"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "output"
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
bl_category = "BIM Documentation"
def draw(self, context):
layout = self.layout
layout.use_property_split = True
props = context.scene.DocProperties
if not DrawingsData.is_loaded:
DrawingsData.load()
row = layout.row(align=True)
row.operator("bim.add_drawing")
row.operator("bim.refresh_drawing_list", icon="FILE_REFRESH", text="")
self.props = context.scene.DocProperties
if props.drawings:
if props.active_drawing_index < len(props.drawings):
if not self.props.is_editing_drawings:
row = self.layout.row(align=True)
row.label(text=f"{DrawingsData.data['total_drawings']} Drawings Found", icon="IMAGE_DATA")
row.operator("bim.load_drawings", text="", icon="IMPORT")
return
row = self.layout.row(align=True)
row.prop(self.props, "target_view", text="")
row.prop(self.props, "location_hint", text="")
row.operator("bim.add_drawing", text="", icon="ADD")
row.operator("bim.disable_editing_drawings", text="", icon="CANCEL")
if self.props.drawings:
if self.props.active_drawing_index < len(self.props.drawings):
active_drawing = self.props.drawings[self.props.active_drawing_index]
row = self.layout.row(align=True)
row.alignment = "RIGHT"
op = row.operator("bim.open_view", icon="URL", text="")
op.view = props.active_drawing.name
row.operator("bim.remove_drawing", icon="X", text="").index = props.active_drawing_index
layout.template_list("BIM_UL_drawinglist", "", props, "drawings", props, "active_drawing_index")
op.view = active_drawing.name
op = row.operator("bim.activate_view", icon="OUTLINER_OB_CAMERA", text="")
op.drawing = active_drawing.ifc_definition_id
row.operator("bim.create_drawing", text="", icon="OUTPUT")
row.operator("bim.remove_drawing", icon="X", text="").drawing = active_drawing.ifc_definition_id
self.layout.template_list(
"BIM_UL_drawinglist", "", self.props, "drawings", self.props, "active_drawing_index"
)
row = layout.row()
row.operator("bim.add_ifc_file")
# Commented out until federated drawing generation is rebuilt
# row = self.layout.row()
# row.operator("bim.add_ifc_file")
for index, ifc_file in enumerate(props.ifc_files):
row = layout.row(align=True)
row.prop(ifc_file, "name", text="IFC #{}".format(index + 1))
row.operator("bim.select_doc_ifc_file", icon="FILE_FOLDER", text="").index = index
row.operator("bim.remove_ifc_file", icon="X", text="").index = index
# for index, ifc_file in enumerate(self.props.ifc_files):
# row = self.layout.row(align=True)
# row.prop(ifc_file, "name", text="IFC #{}".format(index + 1))
# row.operator("bim.select_doc_ifc_file", icon="FILE_FOLDER", text="").index = index
# row.operator("bim.remove_ifc_file", icon="X", text="").index = index
class BIM_PT_schedules(Panel):
bl_label = "ODS Schedules"
bl_label = "Schedules"
bl_idname = "BIM_PT_schedules"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "output"
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
bl_category = "BIM Documentation"
def draw(self, context):
layout = self.layout
layout.use_property_split = True
props = context.scene.DocProperties
if not SchedulesData.is_loaded:
SchedulesData.load()
row = layout.row(align=True)
row.operator("bim.add_schedule")
self.props = context.scene.DocProperties
if props.schedules:
row.operator("bim.build_schedule", icon="LINENUMBERS_ON", text="")
row.operator("bim.remove_schedule", icon="X", text="").index = props.active_schedule_index
if not self.props.is_editing_schedules:
row = self.layout.row(align=True)
row.label(text=f"{SchedulesData.data['total_schedules']} Schedules Found", icon="LONGDISPLAY")
row.operator("bim.load_schedules", text="", icon="IMPORT")
return
layout.template_list("BIM_UL_generic", "", props, "schedules", props, "active_schedule_index")
row = self.layout.row(align=True)
row.operator("bim.add_schedule", icon="ADD")
row.operator("bim.disable_editing_schedules", text="", icon="CANCEL")
row = layout.row()
row.prop(props.active_schedule, "file")
row.operator("bim.select_schedule_file", icon="FILE_FOLDER", text="")
if self.props.schedules:
if self.props.active_schedule_index < len(self.props.schedules):
active_schedule = self.props.schedules[self.props.active_schedule_index]
row = self.layout.row(align=True)
row.alignment = "RIGHT"
row.operator("bim.open_schedule", icon="URL", text="").schedule = active_schedule.ifc_definition_id
row.operator("bim.build_schedule", icon="LINENUMBERS_ON", text="").schedule = active_schedule.ifc_definition_id
row.operator("bim.remove_schedule", icon="X", text="").schedule = active_schedule.ifc_definition_id
self.layout.template_list("BIM_UL_generic", "", self.props, "schedules", self.props, "active_schedule_index")
class BIM_PT_sheets(Panel):
bl_label = "SVG Sheets"
bl_label = "Sheets"
bl_idname = "BIM_PT_sheets"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "output"
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
bl_category = "BIM Documentation"
def draw(self, context):
layout = self.layout
props = context.scene.DocProperties
if not SheetsData.is_loaded:
SheetsData.load()
row = layout.row(align=True)
row.prop(props, "titleblock", text="")
row.operator("bim.add_sheet")
self.props = context.scene.DocProperties
if props.sheets:
if not self.props.is_editing_sheets:
row = self.layout.row(align=True)
row.label(text=f"{SheetsData.data['total_sheets']} Sheets Found", icon="FILE")
row.operator("bim.load_sheets", text="", icon="IMPORT")
return
row = self.layout.row(align=True)
row.prop(self.props, "titleblock", text="")
row.operator("bim.add_sheet", text="", icon="ADD")
row.operator("bim.disable_editing_sheets", text="", icon="CANCEL")
if self.props.sheets and self.props.active_sheet_index < len(self.props.sheets):
active_sheet = self.props.sheets[self.props.active_sheet_index]
row = self.layout.row(align=True)
row.alignment = "RIGHT"
row.operator("bim.open_sheet", icon="URL", text="")
row.operator("bim.remove_sheet", icon="X", text="").index = props.active_sheet_index
row.operator("bim.add_drawing_to_sheet", icon="IMAGE_PLANE", text="")
row.operator("bim.add_schedule_to_sheet", icon="PRESET_NEW", text="")
row.operator("bim.create_sheets", icon="FILE_REFRESH", text="")
if active_sheet.is_sheet:
row.operator("bim.remove_sheet", icon="X", text="").sheet = active_sheet.ifc_definition_id
else:
op = row.operator("bim.remove_drawing_from_sheet", icon="X", text="")
op.reference = active_sheet.ifc_definition_id
layout.template_list("BIM_UL_generic", "", props, "sheets", props, "active_sheet_index")
row = layout.row(align=True)
row.operator("bim.add_drawing_to_sheet")
row.operator("bim.add_schedule_to_sheet")
row = layout.row()
row.operator("bim.create_sheets")
self.layout.template_list("BIM_UL_sheets", "", self.props, "sheets", self.props, "active_sheet_index")
class BIM_PT_text(Panel):
@@ -289,28 +318,30 @@ 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")
op = row.operator("bim.add_annotation", text="Dimension", icon="FIXED_SIZE")
op.object_type = "DIMENSION"
op.data_type = "curve"
op = row.operator("bim.add_annotation", text="Dim (Eq)", icon="ARROW_LEFTRIGHT")
op.object_type = "EQUAL_DIMENSION"
op = row.operator("bim.add_annotation", text="Angle", icon="DRIVER_ROTATIONAL_DIFFERENCE")
op.object_type = "ANGLE"
op.data_type = "mesh"
row = layout.row(align=True)
op = row.operator("bim.add_annotation", text="Radius", icon="FORWARD")
op.object_type = "RADIUS"
op.data_type = "curve"
op = row.operator("bim.add_annotation", text="Diameter", icon="ARROW_LEFTRIGHT")
op.object_type = "DIAMETER"
op.data_type = "curve"
row = layout.row(align=True)
@@ -341,33 +372,58 @@ class BIM_PT_annotation_utilities(Panel):
op = row.operator("bim.add_annotation", text="Breakline", icon="FCURVE")
op.object_type = "BREAKLINE"
op.data_type = "mesh"
op = row.operator("bim.add_annotation", text="Misc", icon="MESH_MONKEY")
op.object_type = "MISC"
op = row.operator("bim.add_annotation", text="Line", icon="MESH_MONKEY")
op.object_type = "LINEWORK"
op.data_type = "mesh"
props = context.scene.DocProperties
row = layout.row(align=True)
op = row.operator("bim.add_annotation", text="Fill Area", icon="NODE_TEXTURE")
op.object_type = "FILL_AREA"
row = layout.row(align=True)
row.operator("bim.add_drawing")
row.operator("bim.refresh_drawing_list", icon="FILE_REFRESH", text="")
if props.drawings:
if props.active_drawing_index < len(props.drawings):
op = row.operator("bim.open_view", icon="URL", text="")
op.view = props.active_drawing.name
row.operator("bim.remove_drawing", icon="X", text="").index = props.active_drawing_index
layout.template_list("BIM_UL_drawinglist", "", props, "drawings", props, "active_drawing_index")
layout.prop(props, "should_draw_decorations")
layout.prop(props, "decorations_colour")
row.prop(self.props, "should_draw_decorations", text="Viewport Annotations")
class BIM_UL_drawinglist(bpy.types.UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
if item:
row = layout.row(align=True)
row.prop(item, "name", text="", emboss=False)
op = row.operator("bim.open_view_camera", icon="OUTLINER_OB_CAMERA", text="")
op.view_name = item.name
icon = "UV_FACESEL"
if item.target_view == "ELEVATION_VIEW":
icon = "UV_VERTEXSEL"
elif item.target_view == "SECTION_VIEW":
icon = "UV_EDGESEL"
elif item.target_view == "REFLECTED_PLAN_VIEW":
icon = "XRAY"
elif item.target_view == "MODEL_VIEW":
icon = "SNAP_VOLUME"
row.prop(item, "name", text="", icon=icon, emboss=False)
else:
layout.label(text="", translate=False)
class BIM_UL_sheets(bpy.types.UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
if item:
row = layout.row(align=True)
if item.is_sheet:
if item.is_expanded:
row.operator(
"bim.contract_sheet", text="", emboss=False, icon="DISCLOSURE_TRI_DOWN"
).sheet = item.ifc_definition_id
else:
row.operator(
"bim.expand_sheet", text="", emboss=False, icon="DISCLOSURE_TRI_RIGHT"
).sheet = item.ifc_definition_id
else:
row.label(text="", icon="BLANK1")
if item.reference_type == "DRAWING":
row.label(text="", icon="IMAGE_DATA")
elif item.reference_type == "SCHEDULE":
row.label(text="", icon="LONGDISPLAY")
name = "{} - {}".format(item.identification or "X", item.name or "Unnamed")
row.label(text=name)
else:
layout.label(text="", translate=False)
@@ -157,6 +157,9 @@ class UpdateRepresentation(bpy.types.Operator):
def update_obj_mesh_representation(self, context, obj):
product = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
if not product.is_a("IfcGridAxis"):
tool.Geometry.clear_cache(product)
if product.is_a("IfcGridAxis"):
ifcopenshell.api.run("grid.create_axis_curve", self.file, **{"axis_curve": obj, "grid_axis": product})
return
@@ -188,8 +191,9 @@ class UpdateRepresentation(bpy.types.Operator):
"geometry": obj.data,
"coordinate_offset": coordinate_offset,
"total_items": max(1, len(obj.material_slots)),
"should_force_faceted_brep": context.scene.BIMGeometryProperties.should_force_faceted_brep,
"should_force_triangulation": context.scene.BIMGeometryProperties.should_force_triangulation,
"should_force_faceted_brep": tool.Geometry.should_force_faceted_brep(),
"should_force_triangulation": tool.Geometry.should_force_triangulation(),
"should_generate_uvs": tool.Geometry.should_generate_uvs(obj),
"ifc_representation_class": self.ifc_representation_class,
}
@@ -202,25 +206,14 @@ class UpdateRepresentation(bpy.types.Operator):
new_representation = ifcopenshell.api.run("geometry.add_representation", self.file, **representation_data)
[
blenderbim.core.style.add_style(tool.Ifc, tool.Style, obj=s.material)
for s in obj.material_slots
if s.material and not s.material.BIMMaterialProperties.ifc_style_id
]
if isinstance(obj.data, bpy.types.Mesh) and len(obj.data.polygons):
if tool.Geometry.is_body_representation(new_representation):
[tool.Geometry.run_style_add_style(obj=mat) for mat in tool.Geometry.get_object_materials_without_styles(obj)]
ifcopenshell.api.run(
"style.assign_representation_styles",
self.file,
**{
"shape_representation": new_representation,
"styles": [
self.file.by_id(s.material.BIMMaterialProperties.ifc_style_id)
for s in obj.material_slots
if s.material
],
"should_use_presentation_style_assignment": context.scene.BIMGeometryProperties.should_use_presentation_style_assignment,
},
shape_representation=new_representation,
styles=tool.Geometry.get_styles(obj),
should_use_presentation_style_assignment=context.scene.BIMGeometryProperties.should_use_presentation_style_assignment,
)
tool.Geometry.record_object_materials(obj)
@@ -328,8 +321,8 @@ class CopyRepresentation(bpy.types.Operator, Operator):
class OverrideDeleteTrait:
def delete_ifc_object(self, obj):
if obj.BIMObjectProperties.ifc_definition_id:
element = IfcStore.get_file().by_id(obj.BIMObjectProperties.ifc_definition_id)
element = tool.Ifc.get_entity(obj)
if element:
IfcStore.delete_element(element)
if getattr(element, "FillsVoids", None):
self.remove_filling(element)
@@ -354,6 +347,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)
@@ -367,21 +361,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
@@ -404,7 +406,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)
@@ -452,6 +453,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"]:
@@ -119,9 +119,6 @@ class BIM_PT_gis(Panel):
)
)
)
elif IfcStore.get_file().schema == "IFC2X3":
row = self.layout.row()
row.label(text="Not Georeferenced")
if Data.projected_crs:
row = self.layout.row(align=True)
@@ -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
@@ -53,7 +58,7 @@ class AuthoringData:
if not ifc_class and ifc_classes:
ifc_class = ifc_classes[0][0]
if ifc_class:
elements = [(str(e.id()), e.Name, "") for e in tool.Ifc.get().by_type(ifc_class)]
elements = [(str(e.id()), e.Name, e.Description or "") for e in tool.Ifc.get().by_type(ifc_class)]
results.extend(sorted(elements, key=lambda s: s[1]))
return results
return []
@@ -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)
@@ -27,6 +27,7 @@ from bpy.app.handlers import persistent
@persistent
def load_post(*args):
ifcopenshell.api.add_pre_listener("attribute.edit_attributes", "BlenderBIM.Root.SyncName", root.sync_name)
ifcopenshell.api.add_pre_listener("style.edit_presentation_style", "BlenderBIM.Root.SyncStyleName", root.sync_name)
ifcopenshell.api.add_post_listener(
"geometry.add_representation", "BlenderBIM.Product.GenerateBox", product.generate_box
@@ -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:
@@ -53,5 +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_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")
@@ -17,24 +17,34 @@
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
import blenderbim.bim.handler
import blenderbim.tool as tool
from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.style.data import Data as StyleData
def sync_name(usecase_path, ifc_file, settings):
if usecase_path == "attribute.edit_attributes":
element = settings["product"]
elif usecase_path == "style.edit_presentation_style":
element = settings["style"]
if "Name" not in settings["attributes"]:
return
obj = IfcStore.get_element(settings["product"].id())
obj = tool.Ifc.get_object(element)
if not obj:
return
if isinstance(obj, bpy.types.Object):
new_name = "{}/{}".format(settings["product"].is_a(), settings["attributes"]["Name"] or "Unnamed")
new_name = "{}/{}".format(element.is_a(), settings["attributes"]["Name"] or "Unnamed")
elif isinstance(obj, bpy.types.Material):
new_name = settings["attributes"]["Name"] or "Unnamed"
if obj.BIMMaterialProperties.ifc_style_id:
IfcStore.get_file().by_id(obj.BIMMaterialProperties.ifc_style_id).Name = new_name
StyleData.load(IfcStore.get_file(), obj.BIMMaterialProperties.ifc_style_id)
material = tool.Ifc.get_entity(obj)
if material and material != element:
material.Name = new_name
style = tool.Style.get_style(obj)
if style and style != element:
style.Name = new_name
collection = bpy.data.collections.get(obj.name)
if collection:
collection.name = new_name
obj.name = new_name
blenderbim.bim.handler.refresh_ui_data()
@@ -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
@@ -306,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"]
@@ -28,6 +28,7 @@ classes = (
operator.UnlinkIfc,
operator.UnloadLink,
operator.LoadLink,
operator.ToggleLinkVisibility,
operator.SelectLibraryFile,
operator.ChangeLibraryElement,
operator.RefreshLibrary,
@@ -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,20 +672,44 @@ 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.collection_name = child.name
link.is_loaded = True
return {"FINISHED"}
class ToggleLinkVisibility(bpy.types.Operator):
bl_idname = "bim.toggle_link_visibility"
bl_label = "Toggle Link Visibility"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Toggle visibility between SOLID and WIREFRAME"
collection_name: bpy.props.StringProperty()
def execute(self, context):
collection_name = self.collection_name
objs = filter(lambda obj: "IfcOpeningElement" not in obj.name, bpy.data.collections[collection_name].all_objects)
for i,obj in enumerate(objs):
if i == 0:
if obj.display_type == "WIRE":
display_type = "TEXTURED"
else:
display_type = "WIRE"
obj.display_type = display_type
return {"FINISHED"}
class ExportIFC(bpy.types.Operator):
bl_idname = "export_ifc.bim"
bl_label = "Export IFC"
@@ -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":
@@ -79,6 +85,7 @@ class FilterCategory(PropertyGroup):
class Link(PropertyGroup):
name: StringProperty(name="Name")
collection_name: StringProperty(name="Collection Name")
is_loaded: BoolProperty(name="Is Loaded", default=False)
@@ -132,6 +139,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,14 @@ 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.toggle_link_visibility", text="", icon="HIDE_OFF")
op.collection_name = item.collection_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")
@@ -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):
@@ -71,6 +71,7 @@ classes = (
operator.EnableEditingWorkSchedule,
operator.EnableEditingWorkTime,
operator.ExpandTask,
operator.ExportMSP,
operator.ExportP6,
operator.GenerateGanttChart,
operator.ImportMSP,
@@ -127,6 +128,7 @@ 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):
@@ -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,12 +1211,44 @@ 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):
@@ -1214,10 +1261,12 @@ class ExportP6(bpy.types.Operator, ImportHelper):
self.file = IfcStore.get_file()
start = time.time()
ifc2p6 = Ifc2P6()
ifc2p6.xml = self.filepath
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("Import finished in {:.2f} seconds".format(time.time() - start))
print("Export finished in {:.2f} seconds".format(time.time() - start))
return {"FINISHED"}
@@ -1632,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 = (
@@ -1866,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,13 +40,29 @@ 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"
bl_options = {"REGISTER", "UNDO"}
style: bpy.props.IntProperty()
def _execute(self, context):
core.remove_style(tool.Ifc, tool.Style, obj=context.active_object.active_material)
core.remove_style(tool.Ifc, tool.Material, tool.Style, style=tool.Ifc.get().by_id(self.style))
class AddStyle(bpy.types.Operator, Operator):
@@ -91,3 +108,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,8 +84,9 @@ 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="")
row.operator("bim.remove_style", icon="X", text="").style = props.ifc_style_id
else:
row.operator("bim.add_style", icon="ADD")
@@ -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))

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