Compare commits

...

154 Commits

Author SHA1 Message Date
Thomas Krijnen 87296dbd11 Use BUILD_PACKAGE option 2021-03-08 14:28:28 +01:00
Thomas Krijnen 26f33842ad username 2021-03-08 14:27:56 +01:00
Thomas Krijnen 6fbf8f5cb6 BUILD_PACKAGE option 2021-03-08 14:25:28 +01:00
Thomas Krijnen a76cfc40ac Change repo name 2021-03-08 14:25:03 +01:00
Thomas Krijnen 359fdb1c2e Merge branch 'feelpp' of https://github.com/feelpp/IfcOpenShell into feelpp-feelpp 2021-03-08 14:20:05 +01:00
Thomas Krijnen e9c84d1c11 SVG Rotate dimension text 2021-03-08 14:19:07 +01:00
Dion Moult e62b7e94a4 Fix #1339. You can now search case insensitively for pset, prop and attribute names 2021-03-08 16:42:47 +11:00
Dion Moult e89d540236 Fix bug where assigning a new constituent or profile was borked. Also you can now reorder material lists and constituent set items. 2021-03-08 16:19:26 +11:00
Dion Moult fa23f64e9f Fix #1344. You can now reorder items in a material layer set 2021-03-08 14:20:43 +11:00
Dion Moult cfaa90b1fb Fix bug where material / style UI was showing without a loaded IFC 2021-03-08 12:11:29 +11:00
Dion Moult dc909f1fc6 Add support for adding material layer set usages to elements 2021-03-08 08:41:06 +11:00
Thomas Krijnen 30fc2ad580 SVG: Fix storey elevations 2021-03-07 12:07:40 +01:00
Thomas Krijnen c542fb2480 explicit cast 2021-03-07 10:22:22 +01:00
Dion Moult 25717d002f Fix bug where nested aggregates failed to import 2021-03-07 14:16:08 +11:00
Dion Moult 0181bd7e56 Add support for square and cubic decimetre units 2021-03-07 10:51:30 +11:00
Dion Moult 71c6bba36d Third time lucky. 2021-03-06 14:42:30 +11:00
Dion Moult eb539666d9 Second time lucky. 2021-03-06 14:02:22 +11:00
Dion Moult e43eec6203 Add support for Python 3.9 2021-03-06 09:36:42 +11:00
Thomas Krijnen 4ca64ac42e SVG: preliminary --draw-storey-heights 2021-03-05 21:50:32 +01:00
Priit Laes f51e0db7fe build: Check for libxml2
Fixes issue #1298

Signed-off-by: Priit Laes <plaes@plaes.org>
2021-03-05 21:36:18 +01:00
Thomas Krijnen b1e8e0a968 Update vs-cfg.cmd 2021-03-05 20:58:34 +01:00
Dion Moult be66a98cfa Implement support for showing IfcMaterialLayerSetUsage material types 2021-03-05 18:27:19 +11:00
Dion Moult 76487ea3bf Deprecate the "allow non-element aggregates" workaround for prostructures as the new aggregate system should make it obsolete. 2021-03-05 16:42:39 +11:00
Dion Moult 13fe011c75 Fix bug where sometimes cartesian point offsets wouldn't get detected correctly for poorly geolocated files 2021-03-05 16:38:51 +11:00
Dion Moult 266a341c6b You can now assign/unassign products to groups. Part of #1153 roadmap. 2021-03-05 15:37:40 +11:00
Dion Moult a5bcbe046f You can now view, add, edit, and remove IFC groups. Useful, but also needed for #1153. 2021-03-05 14:06:51 +11:00
Dion Moult ab057e3eff Fix bug where you couldn't do import profiling on Windows. Thanks erab! 2021-03-05 10:02:29 +11:00
Thomas Krijnen 6aa0651190 #1153 support IfcAnnotation with Box as drawing view 2021-03-04 14:39:32 +01:00
Thomas Krijnen 0f296c486f todo note 2021-03-04 14:38:36 +01:00
Dion Moult 29f64f7c11 Drawing cameras now save IfcBlock representations in IFC. See #1153. 2021-03-04 22:04:40 +11:00
Dion Moult d2d4a1b976 Bundle IfcConvert with the BlenderBIM Add-on. See #1153. 2021-03-04 20:54:31 +11:00
Dion Moult 249ff95920 Minor fix 2021-03-04 17:26:39 +11:00
Dion Moult 4c8d12d978 Fix bug where you could add psets of the same name multiple times 2021-03-04 17:02:05 +11:00
Dion Moult 04d02feb86 Null automatically toggles when you set a property in a pset now 2021-03-04 14:58:26 +11:00
Dion Moult e6700ea29b Fix bug where existing blender offsets weren't used when importing two offset models consecutively 2021-03-04 11:58:07 +11:00
Dion Moult 9c7c3d7078 Users can now toggle hiding of null / empty properties in case they are powerusers 2021-03-03 21:50:13 +11:00
Dion Moult 0d3175c6ef Improve pset UI to show if no properties are set 2021-03-03 21:42:00 +11:00
Dion Moult 04abadac22 You can now reassign classes in bulk 2021-03-03 21:16:51 +11:00
Jesusbill 0b39226869 update readme and changelog 2021-03-01 23:39:13 +01:00
Jesusbill 9e31991e75 add "bonded" case for mesh and run 2021-03-01 23:22:55 +01:00
QwiglyDee b40bd681b1 fixed disappearing extrusion gizmo 2021-03-01 23:46:52 +07:00
QwiglyDee 63384a730b WIP gizmo for parametreic extrusion, see #1324 2021-03-01 18:42:28 +07:00
Dion Moult b9f81b8014 Minor packaging fix 2021-03-01 10:57:48 +11:00
Dion Moult c34f357301 Minor fix 2021-03-01 10:52:21 +11:00
Dion Moult beb58b89fe Basic module for integration with Augin. Nice. 2021-03-01 10:52:09 +11:00
Thomas Krijnen 97df037c93 Reformat ids.py 2021-02-28 09:34:35 +01:00
Thomas Krijnen a7d8bffca5 Merge branch 'v0.6.0' of https://github.com/IfcOpenShell/IfcOpenShell into v0.6.0 2021-02-28 09:31:38 +01:00
Thomas Krijnen b5f58dc113 #1349 result formatting 2021-02-28 09:31:21 +01:00
Dion Moult 4e889b75d7 BIMTester can now accept both IDS .xml as well as Gherkin .feature files and generate HTML reports 2021-02-28 14:35:21 +11:00
Dion Moult 36cfb0d3f5 Meshes can now be automagically converted into circular and arbitrary profile extrusions (swept solids) 2021-02-28 12:04:30 +11:00
Dion Moult 5921bcb63d You can now automagically convert meshes into rectangular solid extrusions 2021-02-27 21:15:18 +11:00
Thomas Krijnen 23ad1a8005 Preliminary bSI IDS implementation 2021-02-27 11:04:45 +01:00
Thomas Krijnen f257aef8f4 #1348 special case profiledefwithvoids, needs more work 2021-02-27 10:58:22 +01:00
Thomas Krijnen 3eacd809b0 validate() docstring 2021-02-24 14:06:48 +01:00
Dion Moult e1b90ed3b2 Rebuild pset template UI to be less confusing and match the UX paradigm of other modules 2021-02-24 21:07:27 +11:00
Dion Moult 28c2d7248e Fix bug in where IfcCountMeasure and similar properties were not editable 2021-02-24 16:21:27 +11:00
Dion Moult 3bee746f49 Optimise aggregate creation on large file imports 2021-02-24 13:49:06 +11:00
Dion Moult eeb27bb1c7 Fix regression in last release where the file was loaded twice during import, which led to increased import times 2021-02-24 12:51:50 +11:00
Dion Moult d50c89abee Fix #1337. Changing the name of a Blender object now also auto changes its IFC name. 2021-02-23 21:15:27 +11:00
Dion Moult 15cf3f5afd Fix bug where undo operations may invalidate an ID map and break everything 2021-02-23 20:01:00 +11:00
Dion Moult 1e22dbca1e Meshes are auto updated in the IFC as well when edit mode is toggled now. 2021-02-23 19:19:06 +11:00
Dion Moult 28fbf0ea99 Minor fix 2021-02-23 18:55:42 +11:00
Dion Moult d874a0089d Add documentation for ifcpatch. 2021-02-22 22:04:23 +11:00
Dion Moult 441d842f67 You can now easily get and set the cursor location for local<->global coordinate conversions 2021-02-22 18:27:57 +11:00
Dion Moult 667ac307c3 Fix bug where cursor was not placed in the correct unit after a global->local coordinate conversion 2021-02-22 18:16:09 +11:00
Dion Moult c81031819c Fix bug where blender offsets were not considered in local<->global coordinate conversion 2021-02-22 18:15:22 +11:00
Dion Moult 7cbb87284c Fix bug where selecting high polygon meshes for debugging fails 2021-02-22 16:52:10 +11:00
RobinVanTol d0a9edd186 Update occ_utils.py 2021-02-21 16:04:42 +01:00
Dion Moult 425379aaad Bump IfcOpenShell build 2021-02-21 22:11:05 +11:00
Dion Moult 25d86a50c5 Minor fix for previous commit 2021-02-21 11:55:04 +11:00
Dion Moult 0286cd370d Improve UI to change spatial container to prevent accidentally selecting incorrect objects 2021-02-21 11:24:27 +11:00
Dion Moult 0178804feb Fix bug where removing mapped representations leaving you with nothing was a poor user experience 2021-02-20 18:03:03 +11:00
Dion Moult 1892e9cc86 Fix bug where copying a class did not enforce mapped representations 2021-02-20 17:23:58 +11:00
Dion Moult 1b46f5c83a Fix bug where you couldn't edit the geometry of a grid axis 2021-02-20 17:13:36 +11:00
Dion Moult 3a0edab8c1 Blender now helps prevent saving Blender without having an exported IFC. See #1323. 2021-02-20 15:15:44 +11:00
Dion Moult 7032ee9f11 STEP IDs are now shown for non-rooted elements, like grid axes. 2021-02-20 13:53:11 +11:00
Dion Moult fd19b2d730 Fix #1312. Allow null ownership histories in IFC4. 2021-02-20 10:26:58 +11:00
Dion Moult a65b9f2ba7 You can now add type instances, and mapped representations are enforced. 2021-02-20 10:11:27 +11:00
Dion Moult 2db4a6f89f Fix bug where you couldn't copy classes without representations. 2021-02-20 09:21:18 +11:00
Thomas Krijnen 517b819504 .bat script typo 2021-02-19 10:43:47 +01:00
Dion Moult a0f79cc0db Fix #1318 - unable to install add-on due to clash with Blender-OSM. 2021-02-18 17:45:06 +11:00
Thomas Krijnen 67666ed70e Attempt at https://github.com/IfcOpenShell/IfcOpenBot/issues/1 2021-02-17 16:06:50 +01:00
Dion Moult 6bcfa5946f New basic UI to view tasks in an IFC file 2021-02-17 22:54:11 +11:00
Dion Moult 52691b7ee5 You can now edit parameters of parametric profile definitions in geometry. 2021-02-17 22:03:41 +11:00
Dion Moult 780ea707b0 Highly experimental script to convert from IFC task trees into a Gantt chart JSON for jsgantt-improved 2021-02-17 21:17:02 +11:00
Dion Moult 7e7cfbdd5f Highly experimental script to convert from P6 XML into an IFC task tree. 2021-02-17 21:16:21 +11:00
Dion Moult ab102176cc IfcOpenShell date util now supports converting to IFC2X3 date entities 2021-02-17 21:12:04 +11:00
Thomas Krijnen d7f289bc8a Update stylesheet 2021-02-16 20:23:36 +01:00
Thomas Krijnen 95a34cb8d0 Update stylesheet 2021-02-16 12:13:59 +01:00
Dion Moult 8369818b88 Fix bug where geolocation was lost if you tried to edit an absolutely positioned file 2021-02-16 20:27:37 +11:00
Dion Moult 5251bdabc6 Fix bug #1315 - regression in the absolute coordinate offset feature where I forgot to convert non-meter units. 2021-02-16 18:50:04 +11:00
Dion Moult 83c33dd5b0 Fix bug where only a light removal occured when removing elements. Now, a full geometry purge is done as well. 2021-02-16 11:12:42 +11:00
Thomas Krijnen bfc695c756 #1311 better error handling during parse for schema and header 2021-02-15 11:11:14 +01:00
Thomas Krijnen 459faec01a #1311 normalize schema name case 2021-02-14 17:39:15 +01:00
Thomas Krijnen 4fb65199b8 Validate: better handling of boxed enum 2021-02-14 17:32:08 +01:00
Thomas Krijnen 08576e28d6 fixed boxed enum validation 2021-02-14 17:23:16 +01:00
Thomas Krijnen b3f581d78d Handle boxed enum creation in Python 2021-02-14 17:22:15 +01:00
Thomas Krijnen 89d6b3d21a Fixes for RC2 changes re IfcPoint and IfcSegment 2021-02-14 17:21:58 +01:00
Thomas Krijnen d2bf2cf46e Pick RC2 definitions where required 2021-02-14 17:21:34 +01:00
Thomas Krijnen 43b2982dbb fix char encoding issue in source 2021-02-14 17:20:55 +01:00
Thomas Krijnen e750a134d0 Add generated rc2 schemas 2021-02-14 17:20:34 +01:00
Thomas Krijnen 6bbbc528fe prep for 4x3_rc2 2021-02-14 12:43:27 +01:00
Thomas Krijnen 7f4a436708 regen schema 2021-02-14 12:43:09 +01:00
Thomas Krijnen e6592ca706 conversion rules for first citizin enum types #1276 2021-02-14 12:19:06 +01:00
Thomas Krijnen cc2138463e Merge branch 'v0.6.0' of https://github.com/IfcOpenShell/IfcOpenShell into v0.6.0 2021-02-14 09:49:03 +01:00
Thomas Krijnen 6de3e55d19 make wrapper import in schema_class optional 2021-02-14 09:43:10 +01:00
Dion Moult 595131e6e8 Fix #1313. Fix bug where you couldn't import two different IFC files in succession in the same Blender file. 2021-02-14 17:27:10 +11:00
Dion Moult 6966da3bdf Switching construction type now has an option to map representations 2021-02-14 16:50:27 +11:00
Thomas Krijnen 4e897a0c21 surface style ids on serialization 2021-02-13 16:03:57 +01:00
Long 73a9015801 ifcpatch make.py (#1319) 2021-02-13 20:23:59 +11:00
Dion Moult 1b2bebecb0 You can now assign types with a smart type filter, without needing to first import types into Blender 2021-02-13 18:29:52 +11:00
Dion Moult e4dee2258d Fix #1300. New IfcOpenShell.util.type module for checking applicable type classes. 2021-02-13 18:27:02 +11:00
Long 7de3eec1b1 make ifcpatch callable from command line (#1317) 2021-02-13 10:06:15 +11:00
rbertucat 20fe48bd6a BuildingSmart IFCRail changes to BIMTester (#1309)
* Add a way to validate the IFC file in BIMTester

* Fix an issue in Report where only the first error was shown

* Fix the usage of Schema in BIMTester

* Add Aggreation steps to BIMTester
2021-02-12 09:34:10 +11:00
Christophe Prud'homme 836a830989 Release V 0.6.0 alpha 3 2021-02-10 07:05:16 +00:00
Christophe Prud'homme f99283b572 add runtime depends 2021-02-10 07:04:43 +00:00
Christophe Prud'homme cad1e08da6 Release V 0.6.0 alpha.2 2021-02-09 15:08:22 +00:00
Christophe Prud'homme 28126e0c51 Release V0.6.0 alpha 2
install in standard python dir
bump up version
2021-02-09 14:26:07 +00:00
Christophe Prud'homme 2805cda64b Release v0.6.0 alpha.1
add EXTRA_VERSION to set prerelease (to be set to null string for final release)

add release workflow
2021-02-09 13:40:19 +00:00
Thomas Krijnen 1d6b2dcb37 SVG: IfcAnnotation objectype handling 2021-02-09 14:04:01 +01:00
Thomas Krijnen 45f3e677df Also process wires as part of geometric sets 2021-02-09 14:03:35 +01:00
Christophe Prud'homme 2e0fc73d02 fix python package dir
@aothms the selection of the directory needs to fixed but the current choice is reasonnable
2021-02-09 12:55:30 +00:00
Christophe Prud'homme 9557aec048 use GNUInstallDirs to define properly the installation directories
fix typo in name LIBARY -> LIBRARY
2021-02-09 12:54:05 +00:00
Jesse Vander Does 65160f9bb4 Removed new line 2021-02-09 13:43:29 +01:00
Jesse Vander Does 333ba08841 Added '--no-wire-intersection-tolerance' option 2021-02-09 13:43:29 +01:00
Jesse Vander Does 2cb3b0817f Updated description 2021-02-09 13:43:29 +01:00
Jesse Vander Does 4ea95960a2 Added '--strict-tolerance' option 2021-02-09 13:43:29 +01:00
Jesse Vander Does 10f4bca5d2 Moved no wire flag inside of wire_intersections method 2021-02-09 13:43:29 +01:00
Jesse Vander Does d4a01618cd Fixed white space 2021-02-09 13:43:29 +01:00
Jesse Vander Does c9c6529978 Added '--no-wire-intersection-check' 2021-02-09 13:43:29 +01:00
Jesse Vander Does 2829a0764b Remove wire intersection check to preserve faces 2021-02-09 13:43:29 +01:00
Christophe Prud'homme 66b0bdd01e up ci 2021-02-09 07:42:31 +00:00
Christophe Prud'homme 7bced89687 use host ubuntu 20.04 2021-02-09 00:25:28 +00:00
Christophe Prud'homme b17ef5d57d prepare for docker image of ifcopenshell 2021-02-09 00:23:14 +00:00
Christophe Prud'homme 16ad45440f add package using cpack and install properly 2021-02-09 00:17:27 +00:00
Christophe Prud'homme 93564d1de7 add gh action 2021-02-09 00:17:05 +00:00
Dion Moult 76d61cb4d4 Minor fix - map representation now only does that, and nothing more 2021-02-07 22:15:21 +11:00
Dion Moult e9d2a39166 Only IFC files are now shown in the export file dialog for a less confusing user experience 2021-02-07 20:00:26 +11:00
Dion Moult d08f4171e3 Type representations are now always imported (better for authoring), and also import super fast compared to before (before, enabling types being imported would significantly slow down import times) 2021-02-07 19:57:32 +11:00
Dion Moult 23483eafb3 Fix bug where removing a mapped representation did not properly considered all mapped usages 2021-02-07 19:13:36 +11:00
Dion Moult 686ba2536b Fix bug where you could create mapped representations without having a type product 2021-02-07 19:08:34 +11:00
Dion Moult 7d5ac99f34 Fix bug where representations wouldn't be purged correctly if they were part of a layer with many items 2021-02-07 19:05:56 +11:00
Dion Moult 9b8d224aca Minor fix 2021-02-07 13:27:23 +11:00
Dion Moult 8939d93333 Fix bug where product and type representations wouldn't be linked in Blender when importing 2021-02-07 12:53:41 +11:00
Dion Moult 04aad216ba Type representation maps are now shown in the IFC Representations UI panel 2021-02-07 11:44:14 +11:00
aothms 27eb9d340a sphinx theme 2021-02-06 15:24:17 +00:00
aothms fbb53f93d8 Merge branch 'v0.6.0' of https://github.com/IfcOpenshell/IfcOpenShell into v0.6.0 2021-02-06 15:11:00 +00:00
aothms af6aed80da Documentation changes 2021-02-06 15:10:41 +00:00
Dion Moult 92ba412c95 Fix bug where add grids didn't work. See #1284. 2021-02-06 15:24:53 +11:00
Dion Moult 110c382973 Fix ability to create grids which was totally broken 2021-02-06 15:14:38 +11:00
Dion Moult 977df60f05 Fix #1283 - dumb objects now have an assigned class 2021-02-06 08:01:29 +11:00
Thomas Krijnen 539d77b5ad #1247 Boost_NO_BOOST_CMAKE=On 2021-02-05 17:04:37 +01:00
aothms ea34c3de85 Changes to docs 2021-02-05 12:01:57 +00:00
Thomas Krijnen 9374594920 Merge pull request #1297 from IfcOpenShell/docs
Documentation generation by @johltn
2021-02-05 11:57:14 +01:00
205 changed files with 144317 additions and 9918 deletions
+101
View File
@@ -0,0 +1,101 @@
name: CI
on:
push:
jobs:
activate:
runs-on: ubuntu-latest
if: |
github.repository == 'IfcOpenShell/IfcOpenShell' &&
!startsWith(github.event.head_commit.message, 'Release ') &&
!contains(github.event.head_commit.message, 'ci skip')
steps:
- run: echo ok go
build:
runs-on: ubuntu-20.04
needs: activate
steps:
- uses: actions/checkout@v2
-
name: Update ubuntu
run: |
sudo apt update
sudo apt-get install git cmake gcc g++ libboost1.67-all-dev python3-all-dev swig libpcre3-dev libxml2-dev
sudo apt-get install liboce-foundation-dev liboce-modeling-dev liboce-ocaf-dev liboce-visualization-dev liboce-ocaf-lite-dev
-
name: Build ifcopenshell
run: |
mkdir build && cd build
cmake \
-DCMAKE_INSTALL_PREFIX=$PWD/install/ \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_PREFIX_PATH=/usr \
-DCMAKE_SYSTEM_PREFIX_PATH=/usr \
-DBUILD_PACKAGE=On \
-DOCC_INCLUDE_DIR=/usr/include/oce \
-DOCC_LIBRARY_DIR=/usr/lib/x86_64-linux-gnu \
-DPYTHON_EXECUTABLE:FILEPATH=/usr/bin/python3 \
-DPYTHON_INCLUDE_DIR:PATH=/usr/include/python3.8 \
-DPYTHON_LIBRARY:FILEPATH=/usr/lib/x86_64-linux-gnu/libpython3.8.so \
-DCOLLADA_SUPPORT=Off \
-DLIBXML2_INCLUDE_DIR=/usr/include/libxml2 \
-DLIBXML2_LIBRARIES=/usr/lib/x86_64-linux-gnu/libxml2.so \
../cmake
make -j10
make install
-
name: Package
run: |
make package
working-directory: build
- name: Upload
uses: actions/upload-artifact@v2
with:
# Artifact name
name: ifcos-artifacts
# Directory containing files to upload
path: build/assets/Ifc*
deliver:
runs-on: ubuntu-20.04
needs: build
name: Docker Build, Tag, Push
steps:
- uses: actions/checkout@v2
with:
lfs: true
- name: Download
uses: actions/download-artifact@v2
with:
# Artifact name
name: ifcos-artifacts
path: artifacts/
-
name: Set up QEMU
uses: docker/setup-qemu-action@v1
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
-
name: Login to GitHub Container Registry
uses: docker/login-action@v1
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.CR_PAT }}
-
name: Build container image
uses: docker/build-push-action@v2
with:
context: artifacts
repository: IfcOpenShell/ifcopenshell
tags: ghcr.io/IfcOpenShell/ifcopenshell:latest
file: ./Dockerfile
push: true
+96
View File
@@ -0,0 +1,96 @@
name: Release
on:
push:
tags:
- 'v[0-9].[0-9].[0-9]*'
jobs:
activate:
if: github.repository == 'IfcOpenShell/IfcOpenShell'
runs-on: ubuntu-latest
steps:
- run: echo ok go
build:
needs: activate
runs-on: ubuntu-20.04
steps:
- name: Checkout
uses: actions/checkout@v2
with:
lfs: true
-
name: Update ubuntu
run: |
sudo apt update
sudo apt-get install git cmake gcc g++ libboost1.67-all-dev python3-all-dev swig libpcre3-dev libxml2-dev
sudo apt-get install liboce-foundation-dev liboce-modeling-dev liboce-ocaf-dev liboce-visualization-dev liboce-ocaf-lite-dev
- name: Build
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CHANGELOG_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
mkdir build && cd build
cmake \
-DCMAKE_INSTALL_PREFIX=$PWD/install/ \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_PREFIX_PATH=/usr \
-DCMAKE_SYSTEM_PREFIX_PATH=/usr \
-DBUILD_PACKAGE=On \
-DOCC_INCLUDE_DIR=/usr/include/oce \
-DOCC_LIBRARY_DIR=/usr/lib/x86_64-linux-gnu \
-DPYTHON_EXECUTABLE:FILEPATH=/usr/bin/python3 \
-DPYTHON_INCLUDE_DIR:PATH=/usr/include/python3.8 \
-DPYTHON_LIBRARY:FILEPATH=/usr/lib/x86_64-linux-gnu/libpython3.8.so \
-DCOLLADA_SUPPORT=Off \
-DLIBXML2_INCLUDE_DIR=/usr/include/libxml2 \
-DLIBXML2_LIBRARIES=/usr/lib/x86_64-linux-gnu/libxml2.so \
../cmake
make -j10
make install
- name: Package
id: package
run: |
make package
echo ::set-output name=tgz::$( ls assets/*.tar.gz | head -n 1 | xargs basename )
echo ::set-output name=deb::$( ls assets/*.deb | head -n 1 | xargs basename )
working-directory: build
env:
CHANGELOG_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Release
id: release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref }}
release_name: Release ${{ github.ref }}
draft: false
prerelease: true
body: |
Changes to this release
- add github actions support #1306
- add boost depends to ifcopenshell deb packaging
- <add more changes here>
See more details in the documentation
# body_path: build/CHANGELOG.md
- name: Upload TGZ
id: upload-tgz
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps
asset_path: build/assets/${{ steps.package.outputs.tgz }}
asset_name: ${{ steps.package.outputs.tgz }}
asset_content_type: application/x-gzip
- name: Upload DEB
id: upload-deb
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps
asset_path: build/assets/${{ steps.package.outputs.deb }}
asset_name: ${{ steps.package.outputs.deb }}
asset_content_type: application/vnd.debian.binary-package
+37
View File
@@ -0,0 +1,37 @@
# -*- mode: Dockerfile -*-
FROM ubuntu:focal
ARG CHANNEL
ENV CHANNEL=${CHANNEL:-latest}
RUN apt-get update \
&& apt-get install -y \
vim \
ssh \
sudo \
wget \
software-properties-common ;\
rm -rf /var/lib/apt/lists/*
RUN useradd --user-group --create-home --shell /bin/bash IfcOpenShell ;\
echo "IfcOpenShell ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers
ENV DEBIAN_FRONTEND=noninteractive
RUN echo "deb http://archive.ubuntu.com/ubuntu focal-proposed main restricted" | tee -a /etc/apt/sources.list; \
echo "deb http://archive.ubuntu.com/ubuntu focal-proposed universe" | tee -a /etc/apt/sources.list; \
echo "deb http://archive.ubuntu.com/ubuntu focal-proposed multiverse" | tee -a /etc/apt/sources.list; \
apt-get -qq update; \
apt-get -y install tzdata dos2unix rsync; \
apt-get -y install python3 libxml2 liboce-foundation11 liboce-modeling11 liboce-ocaf11 liboce-visualization11 \
liboce-ocaf-lite11 libpython3.8 libboost-system1.67.0 libboost-program-options1.67.0 \
libboost-regex1.67.0 libboost-thread1.67.0 libboost-date-time1.67.0; \
rm -rf /var/lib/apt/lists/* ;
COPY . /home/IfcOpenShell/
RUN ls -lrtR /home/IfcOpenShell
RUN dpkg -i /home/IfcOpenShell/*.deb
USER IfcOpenShell
+55 -10
View File
@@ -21,7 +21,10 @@ cmake_minimum_required(VERSION 3.1.3)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON) # not necessary, but encouraged
project (IfcOpenShell)
project (IfcOpenShell VERSION 0.6.0)
# use extra version to make pre-release using eg semver
set( EXTRA_VERSION "-alpha.3")
foreach(max_year RANGE 2014 2030)
set(max_sdk "$ENV{ADSK_3DSMAX_SDK_${max_year}}")
@@ -35,6 +38,7 @@ OPTION(COLLADA_SUPPORT "Build IfcConvert with COLLADA support (requires OpenCOLL
OPTION(IFCXML_SUPPORT "Build IfcParse with ifcXML support (requires libxml2)." ON)
OPTION(ENABLE_BUILD_OPTIMIZATIONS "Enable certain compiler and linker optimizations on RelWithDebInfo and Release builds." OFF)
OPTION(IFCCONVERT_DOUBLE_PRECISION "IfcConvert: Use double precision floating-point numbers." ON)
OPTION(BUILD_PACKAGE "" OFF)
OPTION(BUILD_IFCGEOM "Build IfcGeom." ON)
OPTION(BUILD_DOCUMENTATION "Build IfcOpenShell Documentation." OFF)
OPTION(BUILD_IFCPYTHON "Build IfcPython." ON)
@@ -54,7 +58,7 @@ OPTION(USERSPACE_PYTHON_PREFIX "Installs IfcPython for the current user only ins
# TODO QtViewer is deprecated ATM as it uses the 0.4 API
# OPTION(BUILD_QTVIEWER "Build IfcOpenShell Qt GUI Viewer (requires Qt 4 framework)." OFF)
include(GNUInstallDirs)
if((BUILD_CONVERT OR BUILD_GEOMSERVER OR BUILD_IFCPYTHON) AND (NOT BUILD_IFCGEOM))
message(STATUS "'IfcGeom' is required with current outputs")
set(BUILD_IFCGEOM ON)
@@ -65,7 +69,7 @@ IF(NOT BINDIR)
set(BINDIR bin)
ENDIF()
IF(NOT IS_ABSOLUTE ${BINDIR})
set(BINDIR ${CMAKE_INSTALL_PREFIX}/${BINDIR})
set(BINDIR ${CMAKE_INSTALL_BINDIR})
ENDIF()
MESSAGE(STATUS "BINDIR: ${BINDIR}")
@@ -73,7 +77,7 @@ IF(NOT INCLUDEDIR)
set(INCLUDEDIR include)
ENDIF()
IF(NOT IS_ABSOLUTE ${INCLUDEDIR})
set(INCLUDEDIR ${CMAKE_INSTALL_PREFIX}/${INCLUDEDIR})
set(INCLUDEDIR ${CMAKE_INSTALL_INCLUDEDIR})
ENDIF()
MESSAGE(STATUS "INCLUDEDIR: ${INCLUDEDIR}")
@@ -81,11 +85,11 @@ IF(NOT LIBDIR)
set(LIBDIR lib)
ENDIF()
IF(NOT IS_ABSOLUTE ${LIBDIR})
set(LIBDIR ${CMAKE_INSTALL_PREFIX}/${LIBDIR})
set(LIBDIR ${CMAKE_INSTALL_LIBDIR})
ENDIF()
MESSAGE(STATUS "LIBDIR: ${LIBDIR}")
set(IFCOPENSHELL_LIBARY_DIR "") # for *nix rpaths
set(IFCOPENSHELL_LIBRARY_DIR "") # for *nix rpaths
if (BUILD_SHARED_LIBS)
add_definitions(-DIFC_SHARED_BUILD)
@@ -95,7 +99,7 @@ if (BUILD_SHARED_LIBS)
# There will be couple hundreds of these so suppress them away, https://msdn.microsoft.com/en-us/library/esew7y1w.aspx
add_definitions(-wd4251)
endif()
set(IFCOPENSHELL_LIBARY_DIR "${LIBDIR}")
set(IFCOPENSHELL_LIBRARY_DIR "${LIBDIR}")
endif()
# Create cache entries if absent for environment variables
@@ -175,6 +179,7 @@ if(USE_MMAP)
endif()
if (IFCXML_SUPPORT)
find_package(LibXml2 REQUIRED)
add_definitions(-DWITH_IFCXML)
endif()
@@ -473,7 +478,7 @@ function(files_for_ifc_version IFC_VERSION RESULT_NAME)
)
endfunction()
set(SCHEMA_VERSIONS "2x3" "4" "4x1" "4x2" "4x3_rc1")
set(SCHEMA_VERSIONS "2x3" "4" "4x1" "4x2" "4x3_rc1" "4x3_rc2")
if(COMPILE_SCHEMA)
# @todo, this appears to be untested at the moment
@@ -624,7 +629,7 @@ if ((NOT WIN32) AND BUILD_SHARED_LIBS)
# Only set RPATHs when building shared libraries (i.e. IfcParse and
# IfcGeom are dynamically linked). Not necessarily a perfect solution
# but probably a good indication of whether RPATHs are necessary.
SET_INSTALL_RPATHS(IfcConvert "${IFCOPENSHELL_LIBARY_DIR};${OCC_LIBRARY_DIR};${Boost_LIBRARY_DIRS};${OPENCOLLADA_LIBRARY_DIR}")
SET_INSTALL_RPATHS(IfcConvert "${IFCOPENSHELL_LIBRARY_DIR};${OCC_LIBRARY_DIR};${Boost_LIBRARY_DIRS};${OPENCOLLADA_LIBRARY_DIR}")
endif()
INSTALL(TARGETS IfcConvert
@@ -645,7 +650,7 @@ ADD_EXECUTABLE(IfcGeomServer ${SOURCE_FILES})
TARGET_LINK_LIBRARIES(IfcGeomServer ${IFCOPENSHELL_LIBRARIES} ${OPENCASCADE_LIBRARIES} ${Boost_LIBRARIES})
if ((NOT WIN32) AND BUILD_SHARED_LIBS)
SET_INSTALL_RPATHS(IfcGeomServer "${IFCOPENSHELL_LIBARY_DIR};${OCC_LIBRARY_DIR};${Boost_LIBRARY_DIRS}")
SET_INSTALL_RPATHS(IfcGeomServer "${IFCOPENSHELL_LIBRARY_DIR};${OCC_LIBRARY_DIR};${Boost_LIBRARY_DIRS}")
endif()
INSTALL(TARGETS IfcGeomServer
@@ -716,3 +721,43 @@ INSTALL(FILES ${SERIALIZERS_S_FILES}
DESTINATION ${INCLUDEDIR}/serializers/schema_dependent
)
endif()
list(APPEND CPACK_SOURCE_IGNORE_FILES
.git
.gitignore
)
set(CPACK_PACKAGE_NAME "${PROJECT_NAME}-${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}${EXTRA_VERSION}")
set(CPACK_SOURCE_PACKAGE_FILE_NAME "${PROJECT_NAME}-${PROJECT_VERSION}${EXTRA_VERSION}")
SET(CPACK_PACKAGE_FILE_NAME "${PROJECT_NAME}-${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}${EXTRA_VERSION}-${CMAKE_SYSTEM_NAME}")
set(CPACK_PACKAGE_DIRECTORY "${PROJECT_BINARY_DIR}/assets")
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "IfcOpenShell")
set(CPACK_PACKAGE_DESCRIPTION "IfcOpenShell.")
set(CPACK_PACKAGE_VENDOR "Cemosis")
set(CPACK_PACKAGE_CONTACT "Christophe Prud'homme <christophe.prudhomme@cemosis.fr>")
set(CPACK_PACKAGE_VERSION_MAJOR "${PROJECT_VERSION_MAJOR}")
set(CPACK_PACKAGE_VERSION_MINOR "${PROJECT_VERSION_MINOR}")
set(CPACK_PACKAGE_VERSION_PATCH "${PROJECT_VERSION_PATCH}")
set(CPACK_GENERATOR "TGZ;DEB")
set(CPACK_SOURCE_GENERATOR "TGZ")
FOREACH(COMPONENT IN ITEMS ${BOOST_COMPONENTS})
string(REPLACE "_" "-" COMP ${COMPONENT})
set(BOOST_DEPS "${BOOST_DEPS}, libboost-${COMP}1.67.0")
endforeach(COMPONENT)
set(CPACK_DEBIAN_PACKAGE_NAME "${PROJECT_NAME}")
set(CPACK_DEBIAN_PACKAGE_MAINTAINER "${CPACK_PACKAGE_CONTACT}")
set(CPACK_DEBIAN_PACKAGE_DEPENDS "python3, libxml2, liboce-foundation11, liboce-modeling11, liboce-ocaf11, liboce-visualization11, liboce-ocaf-lite11, libpython3.8 ${BOOST_DEPS}")
set(CPACK_DEBIAN_PACKAGE_DESCRIPTION_SUMMARY "${CPACK_PACKAGE_DESCRIPTION_SUMMARY}")
set(CPACK_DEBIAN_PACKAGE_DESCRIPTION "${CPACK_PACKAGE_DESCRIPTION}")
set(CPACK_DEBIAN_PACKAGE_PRIORITY "optional")
set(CPACK_DEBIAN_PACKAGE_SECTION "science")
set(CPACK_DEBIAN_PACKAGE_VERSION "${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}${EXTRA_VERSION}")
set(CPACK_DEBIAN_ARCHITECTURE "${CMAKE_SYSTEM_PROCESSOR}")
#set(CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA "${CMAKE_SOURCE_DIR}/cmake/debian/postinst")
include(CPack)
+2 -2
View File
@@ -32,7 +32,7 @@ DOXYFILE_ENCODING = UTF-8
# title of most generated pages and in a few other places.
# The default value is: My Project.
PROJECT_NAME = "My Project"
PROJECT_NAME = "IfcOpenShell"
# The PROJECT_NUMBER tag can be used to enter a project or revision number. This
# could be handy for archiving the generated documentation or if some version
@@ -829,7 +829,7 @@ WARN_LOGFILE =
# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
# Note: If this tag is empty the current directory is searched.
INPUT = ../src/ifcgeom ../src/serializers
INPUT = ../src/ifcgeom ../src/serializers ../src/ifcgeom_schema_agnostic ../src/ifcparse
# This tag can be used to specify the character encoding of the source files
# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
+2 -13
View File
@@ -22,7 +22,7 @@ exhale_args = {
# These arguments are required
"containmentFolder": "./rst_files",
"rootFileName": "library_root.rst",
"rootFileTitle": "IfcOpenShell",
"rootFileTitle": "IfcOpenShell C++",
"doxygenStripFromPath": "..",
# Suggested optional arguments
"createTreeView": True,
@@ -34,15 +34,4 @@ exhale_args = {
}
# html_theme = 'sphinx_rtd_theme'
# # Tell sphinx what the primary language being documented is.
# primary_domain = 'cpp'
# # Tell sphinx what the pygments highlight language should be.
# highlight_language = 'cpp'
cpp_id_attributes = ['IFC_PARSE_API', 'IFC_GEOM_API']
+1 -1
View File
@@ -7,4 +7,4 @@ IfcOpenShell
rst_files/library_root
`ifcopenshell-python documentation <_build/html/index.html>`_
* `ifcopenshell-python documentation <python/html/index.html>`_
+26 -39
View File
@@ -1,52 +1,39 @@
# This program requires doxygen, sphinx, breathe and exhale.
import os
import os.path
import sys
import shutil
import subprocess
import multiprocessing
from sys import platform
def copy_content(content, container):
with open(content) as f:
with open(container, "w") as f1:
for line in f:
f1.write(line)
conf_path = "./conf.py"
index_path = "./index.rst"
# some extra check to see if we can find sphinx in pypy bin dir
sphinx_build = os.path.join(os.path.dirname(sys.executable), 'sphinx-build')
if not os.path.exists(sphinx_build):
sphinx_build = "sphinx-build"
# else:
# os.environ['PATH'] = os.pathsep + os.path.dirname(sys.executable)
# not used, -j caused issues in my sphinx install
j = str(multiprocessing.cpu_count())
if not os.path.isdir("./output/doxygen/xml"):
os.makedirs("output/doxygen/xml")
if os.path.isfile(conf_path) and not os.path.isfile(index_path):
os.remove(conf_path)
if os.path.isfile(index_path) and not os.path.isfile(conf_path):
os.remove(index_path)
subprocess.check_call([sphinx_build, "-b", "html", ".", "output"])
if not (os.path.isfile(conf_path) and os.path.isfile(index_path)):
subprocess.run(["sphinx-quickstart", "-q", "-p", "IfcOpenShell"])
conf_copy = ["./conf_to_copy.py", "./conf.py"]
index_copy = ["./index_to_copy.rst", "./index.rst"]
copy_content(conf_copy[0], conf_copy[1])
copy_content(index_copy[0], index_copy[1])
if platform == "linux" or platform == "linux2":
subprocess.run(["make", "html"])
if not os.path.isdir("/output/_build"):
subprocess.run(["cd","../src/ifcblenderexport/docs"])
subprocess.run(["make", "html"])
subprocess.run(["cd", "../../../docs"])
subprocess.run(["mv" "../src/ifcblenderexport/docs/_build", "./output"])
if sys.platform == "linux" or sys.platform == "linux2":
if os.path.isdir("output/python"):
shutil.rmtree("output/python")
if not os.path.exists(os.path.join("../src/ifcblenderexport/docs", "contents.rst")):
subprocess.check_call(["ln", "index.rst", "contents.rst"], cwd="../src/ifcblenderexport/docs")
subprocess.check_call(["sed", "-i", "s/html_theme/# html_theme/g", "conf.py"], cwd="../src/ifcblenderexport/docs")
subprocess.check_call(["make", "html"], cwd="../src/ifcblenderexport/docs")
subprocess.check_call(["git", "checkout", "conf.py"], cwd="../src/ifcblenderexport/docs")
shutil.move("../src/ifcblenderexport/docs/_build", "./output")
os.rename("./output/_build", "./output/python")
elif platform == "win32" or platform == "win64":
subprocess.run(["make.bat", "html"])
elif sys.platform == "win32" or sys.platform == "win64":
subprocess.check_call(["make.bat", "html"])
+1 -1
View File
@@ -7,4 +7,4 @@ IfcOpenShell
rst_files/library_root
`ifcopenshell-python documentation <_build/html/index.html>`_
* `ifcopenshell-python documentation <python/html/index.html>`_
+9 -4
View File
@@ -1,17 +1,22 @@
# ifc2ca
Files and scripts for the use of [`Code_Aster`](https://code-aster.org) in IFC-driven FEM analyses
### File Organisation
#### Scripts:
## File Organisation
## Scripts:
- [`ifc2ca.py`](ifc2ca.py): a python script to extract and create a `json` file from an `ifc` file
- [`scriptSalome.py`](scriptSalome.py): a python script to run in the [`Salome-Meca`](https://www.code-aster.org/spip.php?article303) environment. Creates the geometry and the mesh of the structure
- [`scriptCodeAster.py`](scriptCodeAster.py): a python script to create the input file (`.comm`) for Code_Aster
- [`scriptSalomeBonded.py`](scriptSalomeBonded.py): a python script to run in the [`Salome-Meca`](https://www.code-aster.org/spip.php?article303) environment. Creates the geometry and the mesh of the structure by bonding together all structural elements (no connections are considered)
- [`scriptCodeAsterBonded.py`](scriptCodeAsterBonded.py): a python script to create the input file (`.comm`) for Code_Aster for the "bonded" case
#### Analysis Models
## Analysis Models
A number of analysis models with all the relative input/output and script files are provided in [this repository](https://github.com/IfcOpenShell/analysis-models) within the IfcOpenShell Organization
---
### Current Status
## Current Status
Read [Change Log](changelog.md)
+17 -6
View File
@@ -1,23 +1,32 @@
## A change log of the ifc2ca files
# A change log of the ifc2ca files
## 01/03/21
- Add salome and code_aster script files for a "bonded" case, meaning all connections between elements are considered fixed. This allows to consider exported ifc files from commercial software that do not include connections between surface elements (see building_02 example)
## 14/10/20
##### 14/10/20
- Add curve connections betweeen elements and as supports according to the defined orientation - stiffness is not yet considered
##### 17/08/20
## 17/08/20
- Add rigid links for point connection to elements
##### 14/07/20
## 14/07/20
- Add internal springs for point connection to elements according to the defined orientation
- Add external springs for point connections according to the defined orientation
##### 27/05/20
## 27/05/20
- Add orientation for point, curve and surface geometries
- Calculate final geometry and orientation based on object placement transformation
- Add warnings in the json file to show any corrections considered while parsing the ifc file from ETABS with a model with rigid links
- Reduce file size by adding material and profile db in the json file and referencing them in the element objects
- Implement internal releases for curve-to-point connections with any arbitrary orientation
##### 22/03/20
## 22/03/20
- Calculation of section properties for I symmetric profile if not provided (assuming fillet radius is zero)
- Added geometry of planar structural surface members
- Refactored the ifc2ca.py script
@@ -25,10 +34,12 @@
- Added advanced meshing script to correctly simulate connections and independent meshing of structural elements (no common nodes)
##### 26/02/20
- Changed section to profile and added I profile
- Modified material and profile schema with mechanical and common properties
- Added geometry identification for point connection from representation
- Added connections along with supports based on the number of elements a connection is applied to
##### 09/02/20
- First implementation for straight structural curve elements and point connections
+123 -41
View File
@@ -17,7 +17,9 @@ class IFC2CA:
self.file = ifcopenshell.open(self.filename)
for model in self.file.by_type("IfcStructuralAnalysisModel"):
elements = self.get_structural_items(model, item_type="IfcStructuralMember")
connections = self.get_structural_items(model, item_type="IfcStructuralConnection")
connections = self.get_structural_items(
model, item_type="IfcStructuralConnection"
)
materialdb = []
materials = list(dict.fromkeys([e["material"] for e in elements]))
@@ -25,16 +27,24 @@ class IFC2CA:
id = int(mat.split("|")[1])
material = self.get_material_properties(self.file.by_id(id))
material["relatedElements"] = [
e["ifcName"] for e in elements if "material" in e and e["material"] == mat
e["ifcName"]
for e in elements
if "material" in e and e["material"] == mat
]
materialdb.append(material)
profiledb = []
profiles = list(dict.fromkeys([e["profile"] for e in elements if "profile" in e]))
profiles = list(
dict.fromkeys([e["profile"] for e in elements if "profile" in e])
)
for prof in [prof for prof in profiles if prof]:
id = int(prof.split("|")[1])
profile = self.get_profile_properties(self.file.by_id(id))
profile["relatedElements"] = [e["ifcName"] for e in elements if "profile" in e and e["profile"] == prof]
profile["relatedElements"] = [
e["ifcName"]
for e in elements
if "profile" in e and e["profile"] == prof
]
profiledb.append(profile)
self.result = {
@@ -47,11 +57,11 @@ class IFC2CA:
"warnings": self.warnings,
}
print('Model "%s" converted' % model.Name)
print("Number of elements: ", len(elements))
print("Number of connections: ", len(connections))
print("Number of materials: ", len(materialdb))
print("Number of profiles: ", len(profiledb))
print(f"Model {model.Name} converted")
print(f"Number of elements: {len(elements)}")
print(f"Number of connections: {len(connections)}")
print(f"Number of materials: {len(materialdb)}")
print(f"Number of profiles: {len(profiledb)}")
print("")
break
@@ -75,12 +85,19 @@ class IFC2CA:
material_profile = self.get_material_profile(item)
if not representation:
self.warnings.append(
"No representation defined for %s. Member excluded" % (item.is_a() + "|" + str(item.id()))
"No representation defined for %s. Member excluded"
% (item.is_a() + "|" + str(item.id()))
)
return
if not material_profile:
self.warnings.append("No material defined for in %s" % (item.is_a() + "|" + str(item.id())))
self.warnings.append("No profile defined for in %s" % (item.is_a() + "|" + str(item.id())))
self.warnings.append(
"No material defined for in %s"
% (item.is_a() + "|" + str(item.id()))
)
self.warnings.append(
"No profile defined for in %s"
% (item.is_a() + "|" + str(item.id()))
)
materialId = None
profileId = None
else:
@@ -99,21 +116,32 @@ class IFC2CA:
length = np.linalg.norm(np.array(geometry[1]) - np.array(geometry[0]))
for c in connections:
if c["eccentricity"]:
if np.linalg.norm(np.array(c["eccentricity"]["pointOnElement"])) > length + self.tol:
print(np.linalg.norm(np.array(c["eccentricity"]["pointOnElement"])), ">", length)
self.warnings.append("Eccentricity in %s corrected" % (item.is_a() + "|" + str(item.id())))
if (
np.linalg.norm(np.array(c["eccentricity"]["pointOnElement"]))
> length + self.tol
):
print(
f"{np.linalg.norm(np.array(c['eccentricity']['pointOnElement']))} > {length}"
)
self.warnings.append(
f"Eccentricity in {item.is_a()}|{str(item.id())} corrected"
)
c["eccentricity"]["pointOnElement"][0] = length
# End <--
if transformation:
geometry = self.transform_vectors(geometry, transformation)
orientation = self.transform_vectors(orientation, transformation, include_translation=False)
orientation = self.transform_vectors(
orientation, transformation, include_translation=False
)
for c in connections:
c["orientation"] = self.transform_vectors(
c["orientation"], transformation, include_translation=False
)
if c["eccentricity"]:
c["eccentricity"]["vector"] = self.transform_vectors(
c["eccentricity"]["vector"], transformation, include_translation=False
c["eccentricity"]["vector"],
transformation,
include_translation=False,
)
return {
@@ -134,11 +162,15 @@ class IFC2CA:
material = self.get_material_profile(item)
if not representation:
self.warnings.append(
"No representation defined for %s. Member excluded" % (item.is_a() + "|" + str(item.id()))
"No representation defined for %s. Member excluded"
% (item.is_a() + "|" + str(item.id()))
)
return
if not material:
self.warnings.append("No material defined for in %s" % (item.is_a() + "|" + str(item.id())))
self.warnings.append(
"No material defined for in %s"
% (item.is_a() + "|" + str(item.id()))
)
materialId = None
else:
materialId = material.is_a() + "|" + str(material.id())
@@ -151,7 +183,9 @@ class IFC2CA:
conn["orientation"] = orientation
if transformation:
geometry = self.transform_vectors(geometry, transformation)
orientation = self.transform_vectors(orientation, transformation, include_translation=False)
orientation = self.transform_vectors(
orientation, transformation, include_translation=False
)
for c in connections:
c["orientation"] = self.transform_vectors(
c["orientation"], transformation, include_translation=False
@@ -174,7 +208,8 @@ class IFC2CA:
representation = self.get_representation(item, "Vertex")
if not representation:
self.warnings.append(
"No representation defined for %s. Connection excluded" % (item.is_a() + "|" + str(item.id()))
"No representation defined for %s. Connection excluded"
% (item.is_a() + "|" + str(item.id()))
)
return
@@ -184,7 +219,9 @@ class IFC2CA:
orientation = np.eye(3).tolist()
if transformation:
geometry = self.transform_vectors(geometry, transformation)
orientation = self.transform_vectors(orientation, transformation, include_translation=False)
orientation = self.transform_vectors(
orientation, transformation, include_translation=False
)
return {
"ifcName": item.is_a() + "|" + str(item.id()),
@@ -194,14 +231,18 @@ class IFC2CA:
"geometry": geometry,
"orientation": orientation,
"appliedCondition": self.get_connection_input(item, "point"),
"relatedElements": [con.is_a() + "|" + str(con.id()) for con in item.ConnectsStructuralMembers],
"relatedElements": [
con.is_a() + "|" + str(con.id())
for con in item.ConnectsStructuralMembers
],
}
elif item.is_a("IfcStructuralCurveConnection"):
representation = self.get_representation(item, "Edge")
if not representation:
self.warnings.append(
"No representation defined for %s. Connection excluded" % (item.is_a() + "|" + str(item.id()))
"No representation defined for %s. Connection excluded"
% (item.is_a() + "|" + str(item.id()))
)
return
@@ -211,7 +252,9 @@ class IFC2CA:
orientation = np.eye(3).tolist()
if transformation:
geometry = self.transform_vectors(geometry, transformation)
orientation = self.transform_vectors(orientation, transformation, include_translation=False)
orientation = self.transform_vectors(
orientation, transformation, include_translation=False
)
return {
"ifcName": item.is_a() + "|" + str(item.id()),
@@ -221,7 +264,10 @@ class IFC2CA:
"geometry": geometry,
"orientation": orientation,
"appliedCondition": self.get_connection_input(item, "line"),
"relatedElements": [con.is_a() + "|" + str(con.id()) for con in item.ConnectsStructuralMembers],
"relatedElements": [
con.is_a() + "|" + str(con.id())
for con in item.ConnectsStructuralMembers
],
}
def get_transformation(self, placement):
@@ -229,11 +275,15 @@ class IFC2CA:
return None
if placement.is_a("IfcLocalPlacement"):
if placement.PlacementRelTo:
print("Warning! Object Placement with PlacementRelTo attribute is not supported and will be neglected")
print(
"Warning! Object Placement with PlacementRelTo attribute is not supported and will be neglected"
)
axes = placement.RelativePlacement
location = np.array(self.get_coordinate(axes.Location))
if axes.Axis and axes.RefDirection:
xAxis = np.array(axes.RefDirection.DirectionRatios) # this can be not accurate (in the xz plane)
xAxis = np.array(
axes.RefDirection.DirectionRatios
) # this can be not accurate (in the xz plane)
zAxis = np.array(axes.Axis.DirectionRatios)
zAxis /= np.linalg.norm(zAxis)
yAxis = np.cross(zAxis, xAxis)
@@ -253,10 +303,13 @@ class IFC2CA:
and np.allclose(zAxis, np.array([0.0, 0.0, 1.0]))
):
return None
return {"location": location, "rotationMatrix": np.array([xAxis, yAxis, zAxis]).transpose()}
return {
"location": location,
"rotationMatrix": np.array([xAxis, yAxis, zAxis]).transpose(),
}
else:
print(
"Warning! Object Placement is of type %s, which is not supported. Default considered" % placement.is_a()
f"Warning! Object Placement is of type {placement.is_a()}, which is not supported. Default considered"
)
return None
@@ -264,11 +317,13 @@ class IFC2CA:
if not element.Representation:
return None
for representation in element.Representation.Representations:
rep = self.get_specific_representation(representation, "Reference", rep_type)
rep = self.get_specific_representation(
representation, "Reference", rep_type
)
if rep:
return rep
else:
# print('Trying without rep identifier')
# print("Trying without rep identifier")
for representation in element.Representation.Representations:
rep = self.get_specific_representation(representation, None, rep_type)
if rep:
@@ -281,7 +336,9 @@ class IFC2CA:
return representation
if representation.RepresentationType == "MappedRepresentation":
return self.get_specific_representation(
representation.Items[0].MappingSource.MappedRepresentation, rep_id, rep_type
representation.Items[0].MappingSource.MappedRepresentation,
rep_id,
rep_type,
)
def get_geometry(self, representation):
@@ -299,7 +356,9 @@ class IFC2CA:
edges = item.Bounds[0].Bound.EdgeList
coords = []
for edge in edges:
coords.append(self.get_coordinate(edge.EdgeElement.EdgeStart.VertexGeometry))
coords.append(
self.get_coordinate(edge.EdgeElement.EdgeStart.VertexGeometry)
)
return coords
elif item.is_a("IfcVertexPoint"):
@@ -328,7 +387,9 @@ class IFC2CA:
def get_1D_orientation(self, geometry, zAxis):
xAxis = np.array(geometry[1]) - np.array(geometry[0])
xAxis /= np.linalg.norm(xAxis)
zAxis = np.array(zAxis.DirectionRatios) # this can be not strictly perpendicular (in the xz plane)
zAxis = np.array(
zAxis.DirectionRatios
) # this can be not strictly perpendicular (in the xz plane)
yAxis = np.cross(zAxis, xAxis)
yAxis /= np.linalg.norm(yAxis)
zAxis = np.cross(xAxis, yAxis)
@@ -347,7 +408,9 @@ class IFC2CA:
return orientation
def transform_vectors(self, geometry, trsf, include_translation=True):
if not any(isinstance(el, list) for el in geometry): # single point which contains no list
if not any(
isinstance(el, list) for el in geometry
): # single point which contains no list
geometry = [geometry]
globalGeometry = []
@@ -453,13 +516,18 @@ class IFC2CA:
{
"ifcName": rel.is_a() + "|" + str(rel.id()),
"id": rel.GlobalId,
"relatingElement": rel.RelatingStructuralMember.is_a() + "|" + str(rel.RelatingStructuralMember.id()),
"relatingElement": rel.RelatingStructuralMember.is_a()
+ "|"
+ str(rel.RelatingStructuralMember.id()),
"relatedConnection": rel.RelatedStructuralConnection.is_a()
+ "|"
+ str(rel.RelatedStructuralConnection.id()),
"orientation": self.get_0D_orientation(rel.ConditionCoordinateSystem),
"appliedCondition": self.get_connection_input(
rel, self.get_geometry_type_from_connection(rel.RelatedStructuralConnection)
rel,
self.get_geometry_type_from_connection(
rel.RelatedStructuralConnection
),
),
"eccentricity": None
if not rel.is_a("IfcRelConnectsWithEccentricity")
@@ -475,7 +543,9 @@ class IFC2CA:
if not rel.ConnectionConstraint.EccentricityInZ
else rel.ConnectionConstraint.EccentricityInZ,
],
"pointOnElement": self.get_coordinate(rel.ConnectionConstraint.PointOnRelatingElement),
"pointOnElement": self.get_coordinate(
rel.ConnectionConstraint.PointOnRelatingElement
),
},
}
for rel in itemList
@@ -532,11 +602,23 @@ class IFC2CA:
Iz = (2 * tf) * (b ** 3) / 12 + (h - 2 * tf) * (tw ** 3) / 12
Jx = 1 / 3 * ((h - tf) * (tw ** 3) + 2 * b * (tf ** 3))
return {"crossSectionArea": A, "momentOfInertiaY": Iy, "momentOfInertiaZ": Iz, "torsionalConstantX": Jx}
return {
"crossSectionArea": A,
"momentOfInertiaY": Iy,
"momentOfInertiaZ": Iz,
"torsionalConstantX": Jx,
}
if __name__ == "__main__":
fileNames = ["cantilever_01", "portal_01", "grid_of_beams", "slab_01", "structure_01"]
fileNames = [
"cantilever_01",
"portal_01",
"grid_of_beams",
"slab_01",
"structure_01",
"building_02",
]
files = fileNames
for fileName in files:
+221 -54
View File
@@ -33,7 +33,9 @@ class COMMANDFILE:
self.calculateRestraints(conn)
for el in elements:
for rel in el["connections"]:
conn = [c for c in connections if c["ifcName"] == rel["relatedConnection"]][0]
conn = [
c for c in connections if c["ifcName"] == rel["relatedConnection"]
][0]
rel["conn_string"] = None
if conn["geometryType"] == "point":
rel["conn_string"] = "_0DC_"
@@ -61,7 +63,10 @@ class COMMANDFILE:
+ self.getGroupName(rel["relatingElement"])
)
rel["index"] = len(conn["relatedElements"]) + 1
rel["unifiedGroupName"] = self.getGroupName(rel["relatedConnection"]) + "_0DC_%g" % rel["index"]
rel["unifiedGroupName"] = (
self.getGroupName(rel["relatedConnection"])
+ "_0DC_%g" % rel["index"]
)
else:
rel["groupName2"] = self.getGroupName(rel["relatedConnection"])
self.calculateConstraints(rel)
@@ -71,25 +76,54 @@ class COMMANDFILE:
materials = data["db"]["materials"]
profiles = data["db"]["profiles"]
edgeGroupNames = tuple([self.getGroupName(el["ifcName"]) for el in elements if el["geometryType"] == "line"])
faceGroupNames = tuple([self.getGroupName(el["ifcName"]) for el in elements if el["geometryType"] == "surface"])
edgeGroupNames = tuple(
[
self.getGroupName(el["ifcName"])
for el in elements
if el["geometryType"] == "line"
]
)
faceGroupNames = tuple(
[
self.getGroupName(el["ifcName"])
for el in elements
if el["geometryType"] == "surface"
]
)
point0DGroupNames = tuple(
[self.getGroupName(el["ifcName"]) + "_0D" for el in connections if el["geometryType"] == "point"]
[
self.getGroupName(el["ifcName"]) + "_0D"
for el in connections
if el["geometryType"] == "point"
]
)
spring1DGroupNames = tuple(
flatten(
[[rel["springGroupName"] for rel in el["connections"] if rel["springGroupName"]] for el in elements]
[
[
rel["springGroupName"]
for rel in el["connections"]
if rel["springGroupName"]
]
for el in elements
]
)
)
point1DGroupNames = tuple(
[self.getGroupName(el["ifcName"]) + "_0D" for el in connections if el["geometryType"] == "line"]
[
self.getGroupName(el["ifcName"]) + "_0D"
for el in connections
if el["geometryType"] == "line"
]
)
unifiedConnection = False
rigidLinkGroupNames = []
for conn in connections:
conn["unifiedGroupNames"] = [
rel["unifiedGroupName"] for rel in conn["relatedElements"] if rel["eccentricity"]
rel["unifiedGroupName"]
for rel in conn["relatedElements"]
if rel["eccentricity"]
]
# if not conn['appliedCondition'] and len(conn['unifiedGroupNames']) == 1:
# conn['appliedCondition'] = {
@@ -103,7 +137,9 @@ class COMMANDFILE:
unifiedConnection = True
rigidLinkGroupNames.extend(
[
self.getGroupName(rel["relatingElement"]) + "_1DR_" + self.getGroupName(conn["ifcName"])
self.getGroupName(rel["relatingElement"])
+ "_1DR_"
+ self.getGroupName(conn["ifcName"])
for rel in conn["relatedElements"]
if rel["eccentricity"]
]
@@ -182,7 +218,9 @@ model = AFFE_MODELE(
MODELISATION = 'DIS_TR'
),"""
context = {"groupNames": tuple(flatten([point0DGroupNames, spring1DGroupNames]))}
context = {
"groupNames": tuple(flatten([point0DGroupNames, spring1DGroupNames]))
}
f.write(template.format(**context))
@@ -234,7 +272,9 @@ model = AFFE_MODELE(
else:
if "shearModulus" in material["mechProps"]:
poissonRatio = (
material["mechProps"]["youngModulus"] / 2.0 / material["mechProps"]["shearModulus"]
material["mechProps"]["youngModulus"]
/ 2.0
/ material["mechProps"]["shearModulus"]
) - 1
else:
poissonRatio = 0.0
@@ -263,7 +303,9 @@ material = AFFE_MATERIAU(
),"""
context = {
"groupNames": tuple([self.getGroupName(rel) for rel in material["relatedElements"]]),
"groupNames": tuple(
[self.getGroupName(rel) for rel in material["relatedElements"]]
),
"matNameID": "mat" + "_%s" % i,
}
@@ -296,7 +338,10 @@ element = AFFE_CARA_ELEM(
)
for profile in profiles:
if profile["profileShape"] == "rectangular" and profile["profileType"] == "AREA":
if (
profile["profileShape"] == "rectangular"
and profile["profileType"] == "AREA"
):
template = """
_F(
GROUP_MA = {groupNames},
@@ -306,13 +351,18 @@ element = AFFE_CARA_ELEM(
),"""
context = {
"groupNames": tuple([self.getGroupName(rel) for rel in profile["relatedElements"]]),
"groupNames": tuple(
[self.getGroupName(rel) for rel in profile["relatedElements"]]
),
"profileDimensions": (profile["xDim"], profile["yDim"]),
}
f.write(template.format(**context))
elif profile["profileShape"] == "iSymmetrical" and profile["profileType"] == "AREA":
elif (
profile["profileShape"] == "iSymmetrical"
and profile["profileType"] == "AREA"
):
template = """
_F(
GROUP_MA = {groupNames},
@@ -322,7 +372,9 @@ element = AFFE_CARA_ELEM(
),"""
context = {
"groupNames": tuple([self.getGroupName(rel) for rel in profile["relatedElements"]]),
"groupNames": tuple(
[self.getGroupName(rel) for rel in profile["relatedElements"]]
),
"profileProperties": (
profile["mechProps"]["crossSectionArea"],
profile["mechProps"]["momentOfInertiaY"],
@@ -388,7 +440,10 @@ element = AFFE_CARA_ELEM(
REPERE = 'LOCAL'
),"""
context = {"groupName": self.getGroupName(conn["ifcName"]) + "_0D", "stiffnesses": conn["stiffnesses"]}
context = {
"groupName": self.getGroupName(conn["ifcName"]) + "_0D",
"stiffnesses": conn["stiffnesses"],
}
f.write(template.format(**context))
@@ -402,7 +457,10 @@ element = AFFE_CARA_ELEM(
REPERE = 'LOCAL'
),"""
context = {"groupName": rel["springGroupName"], "stiffnesses": rel["stiffnesses"]}
context = {
"groupName": rel["springGroupName"],
"stiffnesses": rel["stiffnesses"],
}
f.write(template.format(**context))
@@ -416,7 +474,10 @@ element = AFFE_CARA_ELEM(
REPERE = 'LOCAL'
),"""
context = {"groupName": self.getGroupName(conn["ifcName"]) + "_0D", "stiffnesses": conn["stiffnesses"]}
context = {
"groupName": self.getGroupName(conn["ifcName"]) + "_0D",
"stiffnesses": conn["stiffnesses"],
}
f.write(template.format(**context))
@@ -439,7 +500,10 @@ element = AFFE_CARA_ELEM(
VALE = {localAxisY}
),"""
context = {"groupName": self.getGroupName(el["ifcName"]), "localAxisY": tuple(el["orientation"][1])}
context = {
"groupName": self.getGroupName(el["ifcName"]),
"localAxisY": tuple(el["orientation"][1]),
}
f.write(template.format(**context))
@@ -614,7 +678,9 @@ liaisons = AFFE_CHAR_MECA(
LIAISON_UNIF = ("""
)
for conn in [conn for conn in connections if len(conn["unifiedGroupNames"]) > 1]:
for conn in [
conn for conn in connections if len(conn["unifiedGroupNames"]) > 1
]:
template = """
_F(
GROUP_NO = {groupNames},
@@ -798,44 +864,103 @@ FIN()
gr1 = rel["groupName1"]
gr2 = rel["groupName2"]
o = np.array(rel["orientation"]).transpose().tolist()
liaisons = {"groupNames": (gr1, gr1, gr1, gr2, gr2, gr2), "coeffs": [], "dofs": []}
liaisons = {
"groupNames": (gr1, gr1, gr1, gr2, gr2, gr2),
"coeffs": [],
"dofs": [],
}
stiffnesses = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
if not rel["appliedCondition"]:
rel["appliedCondition"] = {"dx": True, "dy": True, "dz": True, "drx": True, "dry": True, "drz": True}
if isinstance(rel["appliedCondition"]["dx"], bool) and rel["appliedCondition"]["dx"]:
liaisons["coeffs"].append((o[0][0], o[1][0], o[2][0], -o[0][0], -o[1][0], -o[2][0]))
rel["appliedCondition"] = {
"dx": True,
"dy": True,
"dz": True,
"drx": True,
"dry": True,
"drz": True,
}
if (
isinstance(rel["appliedCondition"]["dx"], bool)
and rel["appliedCondition"]["dx"]
):
liaisons["coeffs"].append(
(o[0][0], o[1][0], o[2][0], -o[0][0], -o[1][0], -o[2][0])
)
liaisons["dofs"].append(("DX", "DY", "DZ", "DX", "DY", "DZ"))
elif isinstance(rel["appliedCondition"]["dx"], float) and rel["appliedCondition"]["dx"] > 0:
elif (
isinstance(rel["appliedCondition"]["dx"], float)
and rel["appliedCondition"]["dx"] > 0
):
stiffnesses[0] = rel["appliedCondition"]["dx"]
if isinstance(rel["appliedCondition"]["dy"], bool) and rel["appliedCondition"]["dy"]:
liaisons["coeffs"].append((o[0][1], o[1][1], o[2][1], -o[0][1], -o[1][1], -o[2][1]))
if (
isinstance(rel["appliedCondition"]["dy"], bool)
and rel["appliedCondition"]["dy"]
):
liaisons["coeffs"].append(
(o[0][1], o[1][1], o[2][1], -o[0][1], -o[1][1], -o[2][1])
)
liaisons["dofs"].append(("DX", "DY", "DZ", "DX", "DY", "DZ"))
elif isinstance(rel["appliedCondition"]["dy"], float) and rel["appliedCondition"]["dy"] > 0:
elif (
isinstance(rel["appliedCondition"]["dy"], float)
and rel["appliedCondition"]["dy"] > 0
):
stiffnesses[1] = rel["appliedCondition"]["dy"]
if isinstance(rel["appliedCondition"]["dz"], bool) and rel["appliedCondition"]["dz"]:
liaisons["coeffs"].append((o[0][2], o[1][2], o[2][2], -o[0][2], -o[1][2], -o[2][2]))
if (
isinstance(rel["appliedCondition"]["dz"], bool)
and rel["appliedCondition"]["dz"]
):
liaisons["coeffs"].append(
(o[0][2], o[1][2], o[2][2], -o[0][2], -o[1][2], -o[2][2])
)
liaisons["dofs"].append(("DX", "DY", "DZ", "DX", "DY", "DZ"))
elif isinstance(rel["appliedCondition"]["dz"], float) and rel["appliedCondition"]["dz"] > 0:
elif (
isinstance(rel["appliedCondition"]["dz"], float)
and rel["appliedCondition"]["dz"] > 0
):
stiffnesses[2] = rel["appliedCondition"]["dz"]
if isinstance(rel["appliedCondition"]["drx"], bool) and rel["appliedCondition"]["drx"]:
liaisons["coeffs"].append((o[0][0], o[1][0], o[2][0], -o[0][0], -o[1][0], -o[2][0]))
if (
isinstance(rel["appliedCondition"]["drx"], bool)
and rel["appliedCondition"]["drx"]
):
liaisons["coeffs"].append(
(o[0][0], o[1][0], o[2][0], -o[0][0], -o[1][0], -o[2][0])
)
liaisons["dofs"].append(("DRX", "DRY", "DRZ", "DRX", "DRY", "DRZ"))
elif isinstance(rel["appliedCondition"]["drx"], float) and rel["appliedCondition"]["drx"] > 0:
elif (
isinstance(rel["appliedCondition"]["drx"], float)
and rel["appliedCondition"]["drx"] > 0
):
stiffnesses[3] = rel["appliedCondition"]["drx"]
if isinstance(rel["appliedCondition"]["dry"], bool) and rel["appliedCondition"]["dry"]:
liaisons["coeffs"].append((o[0][1], o[1][1], o[2][1], -o[0][1], -o[1][1], -o[2][1]))
if (
isinstance(rel["appliedCondition"]["dry"], bool)
and rel["appliedCondition"]["dry"]
):
liaisons["coeffs"].append(
(o[0][1], o[1][1], o[2][1], -o[0][1], -o[1][1], -o[2][1])
)
liaisons["dofs"].append(("DRX", "DRY", "DRZ", "DRX", "DRY", "DRZ"))
elif isinstance(rel["appliedCondition"]["dry"], float) and rel["appliedCondition"]["dry"] > 0:
elif (
isinstance(rel["appliedCondition"]["dry"], float)
and rel["appliedCondition"]["dry"] > 0
):
stiffnesses[4] = rel["appliedCondition"]["dry"]
if isinstance(rel["appliedCondition"]["drz"], bool) and rel["appliedCondition"]["drz"]:
liaisons["coeffs"].append((o[0][2], o[1][2], o[2][2], -o[0][2], -o[1][2], -o[2][2]))
if (
isinstance(rel["appliedCondition"]["drz"], bool)
and rel["appliedCondition"]["drz"]
):
liaisons["coeffs"].append(
(o[0][2], o[1][2], o[2][2], -o[0][2], -o[1][2], -o[2][2])
)
liaisons["dofs"].append(("DRX", "DRY", "DRZ", "DRX", "DRY", "DRZ"))
elif isinstance(rel["appliedCondition"]["drz"], float) and rel["appliedCondition"]["drz"] > 0:
elif (
isinstance(rel["appliedCondition"]["drz"], float)
and rel["appliedCondition"]["drz"] > 0
):
stiffnesses[5] = rel["appliedCondition"]["drz"]
rel["liaisons"] = liaisons
@@ -852,40 +977,76 @@ FIN()
conn["stiffnesses"] = tuple(stiffnesses)
return
if isinstance(conn["appliedCondition"]["dx"], bool) and conn["appliedCondition"]["dx"]:
if (
isinstance(conn["appliedCondition"]["dx"], bool)
and conn["appliedCondition"]["dx"]
):
liaisons["coeffs"].append((o[0][0], o[1][0], o[2][0]))
liaisons["dofs"].append(("DX", "DY", "DZ"))
elif isinstance(conn["appliedCondition"]["dx"], float) and conn["appliedCondition"]["dx"] > 0:
elif (
isinstance(conn["appliedCondition"]["dx"], float)
and conn["appliedCondition"]["dx"] > 0
):
stiffnesses[0] = conn["appliedCondition"]["dx"]
if isinstance(conn["appliedCondition"]["dy"], bool) and conn["appliedCondition"]["dy"]:
if (
isinstance(conn["appliedCondition"]["dy"], bool)
and conn["appliedCondition"]["dy"]
):
liaisons["coeffs"].append((o[0][1], o[1][1], o[2][1]))
liaisons["dofs"].append(("DX", "DY", "DZ"))
elif isinstance(conn["appliedCondition"]["dy"], float) and conn["appliedCondition"]["dy"] > 0:
elif (
isinstance(conn["appliedCondition"]["dy"], float)
and conn["appliedCondition"]["dy"] > 0
):
stiffnesses[1] = conn["appliedCondition"]["dy"]
if isinstance(conn["appliedCondition"]["dz"], bool) and conn["appliedCondition"]["dz"]:
if (
isinstance(conn["appliedCondition"]["dz"], bool)
and conn["appliedCondition"]["dz"]
):
liaisons["coeffs"].append((o[0][2], o[1][2], o[2][2]))
liaisons["dofs"].append(("DX", "DY", "DZ"))
elif isinstance(conn["appliedCondition"]["dz"], float) and conn["appliedCondition"]["dz"] > 0:
elif (
isinstance(conn["appliedCondition"]["dz"], float)
and conn["appliedCondition"]["dz"] > 0
):
stiffnesses[2] = conn["appliedCondition"]["dz"]
if isinstance(conn["appliedCondition"]["drx"], bool) and conn["appliedCondition"]["drx"]:
if (
isinstance(conn["appliedCondition"]["drx"], bool)
and conn["appliedCondition"]["drx"]
):
liaisons["coeffs"].append((o[0][0], o[1][0], o[2][0]))
liaisons["dofs"].append(("DRX", "DRY", "DRZ"))
elif isinstance(conn["appliedCondition"]["drx"], float) and conn["appliedCondition"]["drx"] > 0:
elif (
isinstance(conn["appliedCondition"]["drx"], float)
and conn["appliedCondition"]["drx"] > 0
):
stiffnesses[3] = conn["appliedCondition"]["drx"]
if isinstance(conn["appliedCondition"]["dry"], bool) and conn["appliedCondition"]["dry"]:
if (
isinstance(conn["appliedCondition"]["dry"], bool)
and conn["appliedCondition"]["dry"]
):
liaisons["coeffs"].append((o[0][1], o[1][1], o[2][1]))
liaisons["dofs"].append(("DRX", "DRY", "DRZ"))
elif isinstance(conn["appliedCondition"]["dry"], float) and conn["appliedCondition"]["dry"] > 0:
elif (
isinstance(conn["appliedCondition"]["dry"], float)
and conn["appliedCondition"]["dry"] > 0
):
stiffnesses[4] = conn["appliedCondition"]["dry"]
if isinstance(conn["appliedCondition"]["drz"], bool) and conn["appliedCondition"]["drz"]:
if (
isinstance(conn["appliedCondition"]["drz"], bool)
and conn["appliedCondition"]["drz"]
):
liaisons["coeffs"].append((o[0][2], o[1][2], o[2][2]))
liaisons["dofs"].append(("DRX", "DRY", "DRZ"))
elif isinstance(conn["appliedCondition"]["drz"], float) and conn["appliedCondition"]["drz"] > 0:
elif (
isinstance(conn["appliedCondition"]["drz"], float)
and conn["appliedCondition"]["drz"] > 0
):
stiffnesses[5] = conn["appliedCondition"]["drz"]
conn["liaisons"] = liaisons
@@ -893,7 +1054,13 @@ FIN()
if __name__ == "__main__":
fileNames = ["cantilever_01", "portal_01", "grid_of_beams", "slab_01", "structure_01"]
fileNames = [
"cantilever_01",
"portal_01",
"grid_of_beams",
"slab_01",
"structure_01",
]
files = fileNames
for fileName in files:
+555
View File
@@ -0,0 +1,555 @@
import json
import numpy as np
import itertools
flatten = itertools.chain.from_iterable
ScaleFactor = 1.0
AccelOfGravity = 9.806 * 1000
class COMMANDFILE:
def __init__(self, dataFilename, asterFilename):
self.dataFilename = dataFilename
self.asterFilename = asterFilename
self.create()
def getGroupName(self, name):
info = name.split("|")
sortName = "".join(c for c in info[0] if c.isupper())
return str(sortName + "_" + info[1])
def create(self):
# Read data from input file
with open(self.dataFilename) as dataFile:
data = json.load(dataFile)
elements = data["elements"]
connections = data["connections"]
# --> Delete this reference data and repopulate it with the objects
# while going through elements
for conn in connections:
conn["relatedElements"] = []
for el in elements:
for rel in el["connections"]:
conn = [
c for c in connections if c["ifcName"] == rel["relatedConnection"]
][0]
conn["relatedElements"].append(rel)
# End <--
materials = data["db"]["materials"]
profiles = data["db"]["profiles"]
edgeGroupNames = tuple(
[
self.getGroupName(el["ifcName"])
for el in elements
if el["geometryType"] == "line"
]
)
faceGroupNames = tuple(
[
self.getGroupName(el["ifcName"])
for el in elements
if el["geometryType"] == "surface"
]
)
rigidLinkGroupNames = []
for conn in connections:
rigidLinkGroupNames.extend(
[
self.getGroupName(rel["relatingElement"])
+ "_1DR_"
+ self.getGroupName(conn["ifcName"])
for rel in conn["relatedElements"]
if rel["eccentricity"]
]
)
rigidLinkGroupNames = tuple(rigidLinkGroupNames)
# Define file to write command file for code_aster
f = open(self.asterFilename, "w")
f.write("# Command file generated by IfcOpenShell/ifc2ca scripts\n")
f.write("\n")
f.write("# Linear Static Analysis With Self-Weight\n")
f.write(
"""
# STEP: INITIALIZE STUDY
DEBUT(
PAR_LOT = 'NON'
)
"""
)
f.write(
"""
# STEP: READ MED FILE
mesh = LIRE_MAILLAGE(
FORMAT = 'MED',
UNITE = 20
)
"""
)
f.write(
"""
# STEP: DEFINE MODEL
model = AFFE_MODELE(
MAILLAGE = mesh,
AFFE = (
_F(
TOUT = 'OUI',
PHENOMENE = 'MECANIQUE',
MODELISATION = '3D'
),"""
)
if faceGroupNames:
template = """
_F(
GROUP_MA = {groupNames},
PHENOMENE = 'MECANIQUE',
MODELISATION = 'DKT'
),"""
context = {"groupNames": faceGroupNames}
f.write(template.format(**context))
if edgeGroupNames:
template = """
_F(
GROUP_MA = {groupNames},
PHENOMENE = 'MECANIQUE',
MODELISATION = 'POU_D_E'
),"""
context = {"groupNames": edgeGroupNames}
f.write(template.format(**context))
if rigidLinkGroupNames:
template = """
_F(
GROUP_MA = {groupNames},
PHENOMENE = 'MECANIQUE',
MODELISATION = 'POU_D_E'
),"""
context = {"groupNames": rigidLinkGroupNames}
f.write(template.format(**context))
f.write(
"""
)
)\n
"""
)
f.write("# STEP: DEFINE MATERIALS")
for i, material in enumerate(materials):
template = """
{matNameID} = DEFI_MATERIAU(
ELAS = _F(
E = {youngModulus},
NU = {poissonRatio},
RHO = {massDensity}
)
)
"""
if "poissonRatio" in material["mechProps"]:
poissonRatio = material["mechProps"]["poissonRatio"]
else:
if "shearModulus" in material["mechProps"]:
poissonRatio = (
material["mechProps"]["youngModulus"]
/ 2.0
/ material["mechProps"]["shearModulus"]
) - 1
else:
poissonRatio = 0.0
context = {
"matNameID": "mat" + "_%s" % i,
"youngModulus": float(material["mechProps"]["youngModulus"])
* ScaleFactor ** 2,
"poissonRatio": float(poissonRatio),
"massDensity": float(material["commonProps"]["massDensity"])
* ScaleFactor ** 3,
}
f.write(template.format(**context))
f.write(
"""
material = AFFE_MATERIAU(
MAILLAGE = mesh,
AFFE = ("""
)
for i, material in enumerate(materials):
template = """
_F(
GROUP_MA = {groupNames},
MATER = {matNameID},
),"""
context = {
"groupNames": tuple(
[self.getGroupName(rel) for rel in material["relatedElements"]]
),
"matNameID": "mat" + "_%s" % i,
}
f.write(template.format(**context))
if rigidLinkGroupNames:
template = """
_F(
GROUP_MA = {groupNames},
MATER = {matNameID},
),"""
context = {"groupNames": rigidLinkGroupNames, "matNameID": "mat_0"}
f.write(template.format(**context))
f.write(
"""
)
)
"""
)
f.write(
"""
# STEP: DEFINE ELEMENTS
element = AFFE_CARA_ELEM(
MODELE = model,
POUTRE = ("""
)
for profile in profiles:
if (
profile["profileShape"] == "rectangular"
and profile["profileType"] == "AREA"
):
template = """
_F(
GROUP_MA = {groupNames},
SECTION = 'RECTANGLE',
CARA = ('HY', 'HZ'),
VALE = {profileDimensions}
),"""
context = {
"groupNames": tuple(
[self.getGroupName(rel) for rel in profile["relatedElements"]]
),
"profileDimensions": (
profile["xDim"] / ScaleFactor,
profile["yDim"] / ScaleFactor,
),
}
f.write(template.format(**context))
elif (
profile["profileShape"] == "iSymmetrical"
and profile["profileType"] == "AREA"
):
template = """
_F(
GROUP_MA = {groupNames},
SECTION = 'GENERALE',
CARA = ('A', 'IY', 'IZ', 'JX'),
VALE = {profileProperties}
),"""
context = {
"groupNames": tuple(
[self.getGroupName(rel) for rel in profile["relatedElements"]]
),
"profileProperties": (
profile["mechProps"]["crossSectionArea"] / ScaleFactor ** 2,
profile["mechProps"]["momentOfInertiaY"] / ScaleFactor ** 4,
profile["mechProps"]["momentOfInertiaZ"] / ScaleFactor ** 4,
profile["mechProps"]["torsionalConstantX"] / ScaleFactor ** 4,
),
}
f.write(template.format(**context))
if rigidLinkGroupNames:
template = """
_F(
GROUP_MA = {groupNames},
SECTION = 'RECTANGLE',
CARA = ('HY', 'HZ'),
VALE = {profileDimensions}
),"""
context = {"groupNames": rigidLinkGroupNames, "profileDimensions": (1, 1)}
f.write(template.format(**context))
f.write(
"""
),
COQUE = ("""
)
for el in [el for el in elements if el["geometryType"] == "surface"]:
template = """
_F(
GROUP_MA = '{groupName}',
EPAIS = {thickness},
VECTEUR = {localAxisX}
),"""
context = {
"groupName": self.getGroupName(el["ifcName"]),
"thickness": el["thickness"] / ScaleFactor,
"localAxisX": tuple(el["orientation"][0]),
}
f.write(template.format(**context))
f.write(
"""
),"""
)
f.write(
"""
ORIENTATION = ("""
)
for el in [el for el in elements if el["geometryType"] == "line"]:
template = """
_F(
GROUP_MA = '{groupName}',
CARA = 'VECT_Y',
VALE = {localAxisY}
),"""
context = {
"groupName": self.getGroupName(el["ifcName"]),
"localAxisY": tuple(el["orientation"][1]),
}
f.write(template.format(**context))
f.write(
"""
),"""
)
f.write(
"""
)\n
"""
)
f.write("# STEP: DEFINE SUPPORTS AND CONSTRAINTS")
f.write(
"""
liaisons = AFFE_CHAR_MECA(
MODELE = model,
DDL_IMPO = (
_F(
GROUP_NO = 'grdSupps',
DX = 0.0,
DY = 0.0,
DZ = 0.0,
DRX = 0.0,
DRY = 0.0,
DRZ = 0.0
)
),"""
)
if rigidLinkGroupNames:
f.write(
"""
LIAISON_SOLIDE = ("""
)
for groupName in rigidLinkGroupNames:
template = """
_F(
GROUP_MA = '{groupName}'
),"""
context = {"groupName": groupName}
f.write(template.format(**context))
f.write(
"""
),"""
)
f.write(
"""
)
"""
)
template = """
# STEP: DEFINE LOAD
gravLoad = AFFE_CHAR_MECA(
MODELE = model,
PESANTEUR = _F(
GRAVITE = {AccelOfGravity},
DIRECTION = (0.0, 0.0, -1.0)
)
)
"""
context = {
"AccelOfGravity": AccelOfGravity,
}
f.write(template.format(**context))
f.write(
"""
# STEP: RUN ANALYSIS
res_Bld = MECA_STATIQUE(
MODELE = model,
CHAM_MATER = material,
CARA_ELEM = element,
EXCIT = (
_F(
CHARGE = liaisons
),
_F(
CHARGE = gravLoad
)
)
)
"""
)
# f.write(
# '''
# # STEP: POST-PROCESSING
# res_Bld = CALC_CHAMP(
# reuse = res_Bld,
# RESULTAT = res_Bld,
# # CONTRAINTE = ('SIEF_ELNO', 'SIGM_ELNO', 'EFGE_ELNO',),
# FORCE = ('REAC_NODA', 'FORC_NODA',)
# )
# '''
# )
#
# template = \
# '''
# # STEP: MASS EXTRACTION FOR EACH ASSEMBLE
# FaceMass = POST_ELEM(
# TITRE = 'TotMass',
# MODELE = model,
# CARA_ELEM = element,
# CHAM_MATER = material,
# MASS_INER = _F(
# GROUP_MA = {massList},
# ),
# )\n'''
#
# context = {
# 'massList': massList,
# }
#
# f.write(template.format(**context))
#
# f.write(
# '''
# IMPR_TABLE(
# UNITE = 10,
# TABLE = FaceMass,
# SEPARATEUR = ',',
# NOM_PARA = ('LIEU', 'MASSE', 'CDG_X', 'CDG_Y', 'CDG_Z'),
# # FORMAT_R = '1PE15.6',
# )
# '''
# )
#
# template = \
# '''
# # STEP: REACTION EXTRACTION AT THE BASE
# Reacs = POST_RELEVE_T(
# ACTION = _F(
# INTITULE = 'sumReac',
# GROUP_NO = {groupNames},
# RESULTAT = res_Bld,
# NOM_CHAM = 'REAC_NODA',
# RESULTANTE = ('DX','DY','DZ',),
# MOMENT = ('DRX','DRY','DRZ',),
# POINT = (0,0,0,),
# OPERATION = 'EXTRACTION'
# )
# )
# '''
#
# context = {
# 'groupNames': point0DGroupNames,
# }
#
# f.write(template.format(**context))
#
# f.write(
# '''
# IMPR_TABLE(
# UNITE = 10,
# TABLE = Reacs,
# SEPARATEUR = ',',
# # NOM_PARA = ('INTITULE', 'RESU', 'NOM_CHAM', 'INST', 'DX','DY','DZ'),
# FORMAT_R = '1PE12.3',
# )
# '''
# )
#
f.write(
"""
# STEP: DEFORMED SHAPE EXTRACTION
IMPR_RESU(
FORMAT = 'MED',
UNITE = 80,
RESU = _F(
RESULTAT = res_Bld,
NOM_CHAM = ('DEPL',), # 'REAC_NODA', 'FORC_NODA',
NOM_CHAM_MED = ('Bld_DISP',), # 'Bld_REAC', 'Bld_FORC'
)
)
"""
)
f.write(
"""
# STEP: CONCLUDE STUDY
FIN()
"""
)
f.close()
if __name__ == "__main__":
fileNames = ["building_02"]
files = fileNames
for fileName in files:
BASE_PATH = "/home/jesusbill/Dev-Projects/github.com/IfcOpenShell/analysis-models/models/"
DATAFILENAME = BASE_PATH + fileName + "/" + fileName + ".json"
ASTERFILENAME = BASE_PATH + fileName + "/" + fileName + ".comm"
COMMANDFILE(DATAFILENAME, ASTERFILENAME)
+127 -40
View File
@@ -75,7 +75,9 @@ class MODEL:
shapeType = "EDGE"
if geometryType == "surface":
shapeType = "FACE"
return self.geompy.MakePartition(objects, [], [], [], self.geompy.ShapeType[shapeType], 0, [], 1)
return self.geompy.MakePartition(
objects, [], [], [], self.geompy.ShapeType[shapeType], 0, [], 1
)
def getLinkGeometry(self, ecc, orientation, finalPoint):
vector = np.array(orientation).transpose().dot(ecc["vector"])
@@ -157,17 +159,25 @@ class MODEL:
el["linkObjs"] = [None for _ in el["connections"]]
el["linkPointObjs"] = [[None, None] for _ in el["connections"]]
for j, rel in enumerate(el["connections"]):
conn = [c for c in connections if c["ifcName"] == rel["relatedConnection"]][0]
conn = [
c for c in connections if c["ifcName"] == rel["relatedConnection"]
][0]
if rel["eccentricity"]:
rel["index"] = len(conn["relatedElements"]) + 1
conn["relatedElements"].append(rel)
if not rel["eccentricity"]:
el["connObjs"][j] = self.makeObject(conn["geometry"], conn["geometryType"])
el["connObjs"][j] = self.makeObject(
conn["geometry"], conn["geometryType"]
)
else:
if conn["geometryType"] == "point":
geometry = self.getLinkGeometry(rel["eccentricity"], el["orientation"], conn["geometry"])
el["connObjs"][j] = self.makeObject(geometry[0], conn["geometryType"])
geometry = self.getLinkGeometry(
rel["eccentricity"], el["orientation"], conn["geometry"]
)
el["connObjs"][j] = self.makeObject(
geometry[0], conn["geometryType"]
)
el["linkPointObjs"][j][0] = self.geompy.MakeVertex(
geometry[0][0], geometry[0][1], geometry[0][2]
@@ -179,9 +189,14 @@ class MODEL:
el["linkPointObjs"][j][0], el["linkPointObjs"][j][1]
)
else:
print("Eccentricity defined for a %s geometryType" % conn["geometryType"])
print(
"Eccentricity defined for a %s geometryType"
% conn["geometryType"]
)
el["partObj"] = self.makePartition([el["elemObj"]] + el["connObjs"], el["geometryType"])
el["partObj"] = self.makePartition(
[el["elemObj"]] + el["connObjs"], el["geometryType"]
)
el["elemObj"] = geompy.GetInPlace(el["partObj"], el["elemObj"])
for j, rel in enumerate(el["connections"]):
@@ -193,7 +208,9 @@ class MODEL:
# Make assemble of Building Object
bldObjs = []
bldObjs.extend([el["partObj"] for el in elements])
bldObjs.extend(flatten([[link for link in el["linkObjs"] if link] for el in elements]))
bldObjs.extend(
flatten([[link for link in el["linkObjs"] if link] for el in elements])
)
bldObjs.extend([conn["connObj"] for conn in connections])
bldComp = geompy.MakeCompound(bldObjs)
@@ -203,9 +220,13 @@ class MODEL:
# Loop 2
for el in elements:
# geompy.addToStudy(el['partObj'], self.getGroupName(el['ifcName']))
geompy.addToStudyInFather(el["partObj"], el["elemObj"], self.getGroupName(el["ifcName"]))
geompy.addToStudyInFather(
el["partObj"], el["elemObj"], self.getGroupName(el["ifcName"])
)
for j, rel in enumerate(el["connections"]):
conn = [c for c in connections if c["ifcName"] == rel["relatedConnection"]][0]
conn = [
c for c in connections if c["ifcName"] == rel["relatedConnection"]
][0]
rel["conn_string"] = None
if conn["geometryType"] == "point":
rel["conn_string"] = "_0DC_"
@@ -216,7 +237,9 @@ class MODEL:
geompy.addToStudyInFather(
el["partObj"],
el["connObjs"][j],
self.getGroupName(el["ifcName"]) + rel["conn_string"] + self.getGroupName(rel["relatedConnection"]),
self.getGroupName(el["ifcName"])
+ rel["conn_string"]
+ self.getGroupName(rel["relatedConnection"]),
)
if rel["eccentricity"]:
pass
@@ -226,7 +249,9 @@ class MODEL:
for conn in connections:
# geompy.addToStudy(conn['connObj'], self.getGroupName(conn['ifcName']))
geompy.addToStudyInFather(conn["connObj"], conn["connObj"], self.getGroupName(conn["ifcName"]))
geompy.addToStudyInFather(
conn["connObj"], conn["connObj"], self.getGroupName(conn["ifcName"])
)
elapsed_time = time.time() - init_time
init_time += elapsed_time
@@ -240,14 +265,18 @@ class MODEL:
# Define and add groups for all curve and surface members
if len([e for e in elements if e["geometryType"] == "line"]) > 0:
# Make compound of requested group
compoundTemp = geompy.MakeCompound([e["elemObj"] for e in elements if e["geometryType"] == "line"])
compoundTemp = geompy.MakeCompound(
[e["elemObj"] for e in elements if e["geometryType"] == "line"]
)
# Define group object and add to study
curveCompound = geompy.GetInPlace(bldComp, compoundTemp)
geompy.addToStudyInFather(bldComp, curveCompound, "CurveMembers")
if len([e for e in elements if e["geometryType"] == "surface"]) > 0:
# Make compound of requested group
compoundTemp = geompy.MakeCompound([e["elemObj"] for e in elements if e["geometryType"] == "surface"])
compoundTemp = geompy.MakeCompound(
[e["elemObj"] for e in elements if e["geometryType"] == "surface"]
)
# Define group object and add to study
surfaceCompound = geompy.GetInPlace(bldComp, compoundTemp)
geompy.addToStudyInFather(bldComp, surfaceCompound, "SurfaceMembers")
@@ -255,34 +284,45 @@ class MODEL:
# Loop 3
for el in elements:
# el['partObj'] = geompy.RestoreGivenSubShapes(bldComp, [el['partObj']], GEOM.FSM_GetInPlace, False, False)[0]
geompy.addToStudyInFather(bldComp, el["elemObj"], self.getGroupName(el["ifcName"]))
geompy.addToStudyInFather(
bldComp, el["elemObj"], self.getGroupName(el["ifcName"])
)
for j, rel in enumerate(el["connections"]):
geompy.addToStudyInFather(
bldComp,
el["connObjs"][j],
self.getGroupName(el["ifcName"]) + rel["conn_string"] + self.getGroupName(rel["relatedConnection"]),
self.getGroupName(el["ifcName"])
+ rel["conn_string"]
+ self.getGroupName(rel["relatedConnection"]),
)
if rel["eccentricity"]: # point geometry
geompy.addToStudyInFather(
bldComp,
el["linkObjs"][j],
self.getGroupName(el["ifcName"]) + "_1DR_" + self.getGroupName(rel["relatedConnection"]),
self.getGroupName(el["ifcName"])
+ "_1DR_"
+ self.getGroupName(rel["relatedConnection"]),
)
geompy.addToStudyInFather(
bldComp,
el["linkPointObjs"][j][0],
self.getGroupName(rel["relatedConnection"]) + "_0DC_" + self.getGroupName(el["ifcName"]),
self.getGroupName(rel["relatedConnection"])
+ "_0DC_"
+ self.getGroupName(el["ifcName"]),
)
geompy.addToStudyInFather(
bldComp,
el["linkPointObjs"][j][1],
self.getGroupName(rel["relatedConnection"]) + "_0DC_%g" % rel["index"],
self.getGroupName(rel["relatedConnection"])
+ "_0DC_%g" % rel["index"],
)
for conn in connections:
# conn['connObj'] = geompy.RestoreGivenSubShapes(bldComp, [conn['connObj']], GEOM.FSM_GetInPlace, False, False)[0]
geompy.addToStudyInFather(bldComp, conn["connObj"], self.getGroupName(conn["ifcName"]))
geompy.addToStudyInFather(
bldComp, conn["connObj"], self.getGroupName(conn["ifcName"])
)
elapsed_time = time.time() - init_time
init_time += elapsed_time
@@ -339,7 +379,9 @@ class MODEL:
smesh.SetName(tempgroup, "CurveMembers")
if len([e for e in elements if e["geometryType"] == "surface"]) > 0:
tempgroup = bldMesh.GroupOnGeom(surfaceCompound, "SurfaceMembers", SMESH.FACE)
tempgroup = bldMesh.GroupOnGeom(
surfaceCompound, "SurfaceMembers", SMESH.FACE
)
smesh.SetName(tempgroup, "SurfaceMembers")
# Define groups in Mesh
@@ -348,41 +390,59 @@ class MODEL:
shapeType = SMESH.EDGE
if el["geometryType"] == "surface":
shapeType = SMESH.FACE
tempgroup = bldMesh.GroupOnGeom(el["elemObj"], self.getGroupName(el["ifcName"]), shapeType)
tempgroup = bldMesh.GroupOnGeom(
el["elemObj"], self.getGroupName(el["ifcName"]), shapeType
)
smesh.SetName(tempgroup, self.getGroupName(el["ifcName"]))
for j, rel in enumerate(el["connections"]):
tempgroup = bldMesh.GroupOnGeom(
el["connObjs"][j],
self.getGroupName(el["ifcName"]) + rel["conn_string"] + self.getGroupName(rel["relatedConnection"]),
self.getGroupName(el["ifcName"])
+ rel["conn_string"]
+ self.getGroupName(rel["relatedConnection"]),
SMESH.NODE,
)
smesh.SetName(
tempgroup,
self.getGroupName(el["ifcName"]) + rel["conn_string"] + self.getGroupName(rel["relatedConnection"]),
self.getGroupName(el["ifcName"])
+ rel["conn_string"]
+ self.getGroupName(rel["relatedConnection"]),
)
rel["node"] = (bldMesh.GetIDSource(tempgroup.GetNodeIDs(), SMESH.NODE)).GetIDs()[0]
rel["node"] = (
bldMesh.GetIDSource(tempgroup.GetNodeIDs(), SMESH.NODE)
).GetIDs()[0]
if rel["eccentricity"]:
tempgroup = bldMesh.GroupOnGeom(
el["linkObjs"][j],
self.getGroupName(el["ifcName"]) + "_1DR_" + self.getGroupName(rel["relatedConnection"]),
self.getGroupName(el["ifcName"])
+ "_1DR_"
+ self.getGroupName(rel["relatedConnection"]),
SMESH.EDGE,
)
smesh.SetName(
tempgroup,
self.getGroupName(el["ifcName"]) + "_1DR_" + self.getGroupName(rel["relatedConnection"]),
self.getGroupName(el["ifcName"])
+ "_1DR_"
+ self.getGroupName(rel["relatedConnection"]),
)
tempgroup = bldMesh.GroupOnGeom(
el["linkPointObjs"][j][0],
self.getGroupName(rel["relatedConnection"]) + "_0DC_" + self.getGroupName(el["ifcName"]),
self.getGroupName(rel["relatedConnection"])
+ "_0DC_"
+ self.getGroupName(el["ifcName"]),
SMESH.NODE,
)
smesh.SetName(
tempgroup,
self.getGroupName(rel["relatedConnection"]) + "_0DC_" + self.getGroupName(el["ifcName"]),
self.getGroupName(rel["relatedConnection"])
+ "_0DC_"
+ self.getGroupName(el["ifcName"]),
)
rel["eccNode"] = (bldMesh.GetIDSource(tempgroup.GetNodeIDs(), SMESH.NODE)).GetIDs()[0]
rel["eccNode"] = (
bldMesh.GetIDSource(tempgroup.GetNodeIDs(), SMESH.NODE)
).GetIDs()[0]
tempgroup = bldMesh.GroupOnGeom(
el["linkPointObjs"][j][1],
@@ -391,38 +451,60 @@ class MODEL:
+ self.getGroupName(rel["relatedConnection"]),
SMESH.NODE,
)
smesh.SetName(tempgroup, self.getGroupName(rel["relatedConnection"]) + "_0DC_%g" % rel["index"])
smesh.SetName(
tempgroup,
self.getGroupName(rel["relatedConnection"])
+ "_0DC_%g" % rel["index"],
)
for conn in connections:
tempgroup = bldMesh.GroupOnGeom(conn["connObj"], self.getGroupName(conn["ifcName"]), SMESH.NODE)
tempgroup = bldMesh.GroupOnGeom(
conn["connObj"], self.getGroupName(conn["ifcName"]), SMESH.NODE
)
smesh.SetName(tempgroup, self.getGroupName(conn["ifcName"]))
nodesId = bldMesh.GetIDSource(tempgroup.GetNodeIDs(), SMESH.NODE)
tempgroup = bldMesh.Add0DElementsToAllNodes(nodesId, self.getGroupName(conn["ifcName"]))
tempgroup = bldMesh.Add0DElementsToAllNodes(
nodesId, self.getGroupName(conn["ifcName"])
)
smesh.SetName(tempgroup, self.getGroupName(conn["ifcName"] + "_0D"))
if conn["geometryType"] == "point":
conn["node"] = nodesId.GetIDs()[0]
if conn["geometryType"] == "line":
tempgroup = bldMesh.GroupOnGeom(conn["connObj"], self.getGroupName(conn["ifcName"]), SMESH.EDGE)
tempgroup = bldMesh.GroupOnGeom(
conn["connObj"], self.getGroupName(conn["ifcName"]), SMESH.EDGE
)
smesh.SetName(tempgroup, self.getGroupName(conn["ifcName"]))
if conn["geometryType"] == "surface":
tempgroup = bldMesh.GroupOnGeom(conn["connObj"], self.getGroupName(conn["ifcName"]), SMESH.FACE)
tempgroup = bldMesh.GroupOnGeom(
conn["connObj"], self.getGroupName(conn["ifcName"]), SMESH.FACE
)
smesh.SetName(tempgroup, self.getGroupName(conn["ifcName"]))
# create 1D SEG2 spring elements
for el in elements:
for j, rel in enumerate(el["connections"]):
conn = [c for c in connections if c["ifcName"] == rel["relatedConnection"]][0]
conn = [
c for c in connections if c["ifcName"] == rel["relatedConnection"]
][0]
if conn["geometryType"] == "point":
grpName = bldMesh.CreateEmptyGroup(
SMESH.EDGE,
self.getGroupName(el["ifcName"]) + "_1DS_" + self.getGroupName(rel["relatedConnection"]),
self.getGroupName(el["ifcName"])
+ "_1DS_"
+ self.getGroupName(rel["relatedConnection"]),
)
smesh.SetName(
grpName,
self.getGroupName(el["ifcName"]) + "_1DS_" + self.getGroupName(rel["relatedConnection"]),
self.getGroupName(el["ifcName"])
+ "_1DS_"
+ self.getGroupName(rel["relatedConnection"]),
)
if not rel["eccentricity"]:
conn = [conn for conn in connections if conn["ifcName"] == rel["relatedConnection"]][0]
conn = [
conn
for conn in connections
if conn["ifcName"] == rel["relatedConnection"]
][0]
grpName.Add([bldMesh.AddEdge([conn["node"], rel["node"]])])
else:
grpName.Add([bldMesh.AddEdge([rel["eccNode"], rel["node"]])])
@@ -437,7 +519,12 @@ class MODEL:
try:
if NEW_SALOME:
bldMesh.ExportMED(
self.medFilename, auto_groups=0, minor=40, overwrite=1, meshPart=None, autoDimension=0
self.medFilename,
auto_groups=0,
minor=40,
overwrite=1,
meshPart=None,
autoDimension=0,
)
else:
bldMesh.ExportMED(self.medFilename, 0, SMESH.MED_V2_2, 1, None, 0)
+415
View File
@@ -0,0 +1,415 @@
from __future__ import division
from __future__ import print_function
import os
import time
import json
import salome
import salome_notebook
import salome_version
import numpy as np
import itertools
flatten = itertools.chain.from_iterable
ScaleFactor = 1.0
decimals = 2
class MODEL:
def __init__(self, dataFilename, medFilename, meshSize, zGround):
self.dataFilename = dataFilename
self.medFilename = medFilename
self.meshSize = meshSize
self.zGround = zGround
self.tolLoc = 0
self.mesh = None
self.meshNodes = None
self.create()
def getGroupName(self, name):
info = name.split("|")
sortName = "".join(c for c in info[0] if c.isupper())
return str(sortName + "_" + info[1])
def makePoint(self, pl):
"""Function to define a Point from
a polyline (list of 1 point)"""
(x, y, z) = pl
return self.geompy.MakeVertex(x, y, z)
def makeLine(self, pl):
"""Function to define a Line from
a polyline (list of 2 points)"""
(x, y, z) = pl[0]
P1 = self.geompy.MakeVertex(x, y, z)
(x, y, z) = pl[1]
P2 = self.geompy.MakeVertex(x, y, z)
return self.geompy.MakeLineTwoPnt(P1, P2)
def makeFace(self, pl):
"""Function to define a Face from
a polyline (list of points)"""
pointList = [None for _ in range(len(pl))]
for ip, (x, y, z) in enumerate(pl):
pointList[ip] = self.geompy.MakeVertex(x, y, z)
LineList = [None for _ in range(len(pl))]
for ip, P2 in enumerate(pointList):
P1 = pointList[ip - 1]
LineList[ip] = self.geompy.MakeLineTwoPnt(P1, P2)
return self.geompy.MakeFaceWires(LineList, 1)
def makeObject(self, geometry, geometryType):
geometry = np.round(np.array(geometry), decimals=decimals) / ScaleFactor
geometry = geometry.tolist()
if geometryType == "point":
return self.makePoint(geometry)
if geometryType == "line":
return self.makeLine(geometry)
if geometryType == "surface":
return self.makeFace(geometry)
def makePartition(self, objects, geometryType):
if geometryType == "point":
shapeType = "VERTEX"
if geometryType == "line":
shapeType = "EDGE"
if geometryType == "surface":
shapeType = "FACE"
return self.geompy.MakePartition(
objects, [], [], [], self.geompy.ShapeType[shapeType], 0, [], 1
)
def getLinkGeometry(self, ecc, orientation, finalPoint):
vector = np.array(orientation).transpose().dot(ecc["vector"])
initialPoint = (np.array(finalPoint) - vector).tolist()
return [initialPoint, finalPoint]
def length(self, geometry):
return (
(geometry[1][0] - geometry[0][0]) ** 2
+ (geometry[1][1] - geometry[0][1]) ** 2
+ (geometry[1][2] - geometry[0][2]) ** 2
) ** 0.5
def select(self, elements):
zmin = -100
zmax = 126300
for el in elements:
include = True
for p in el["geometry"]:
if p[2] < zmin or p[2] > zmax:
include = False
break
el["include"] = include
return [el for el in elements if el["include"]]
def create(self):
# Read data from input file
with open(self.dataFilename) as dataFile:
data = json.load(dataFile)
# print(len(data['elements']))
# elements = self.select(data['elements'])
# print(len(elements))
elements = data["elements"]
connections = data["connections"]
# --> Delete this reference data and repopulate it with the objects
# while going through elements
for conn in connections:
conn["relatedElements"] = []
# End <--
meshSize = self.meshSize / ScaleFactor
zGround = self.zGround / ScaleFactor
dec = 5 # 4 decimals for length in mm
tol = 10 ** (-dec - 3 + 1)
self.tolLoc = tol * 10 * 2
tolLoc = self.tolLoc
NEW_SALOME = int(salome_version.getVersion()[0]) >= 9
salome.salome_init()
theStudy = salome.myStudy
notebook = salome_notebook.NoteBook(theStudy)
###
### GEOM component
###
import GEOM
from salome.geom import geomBuilder
import math
import SALOMEDS
gg = salome.ImportComponentGUI("GEOM")
if NEW_SALOME:
geompy = geomBuilder.New()
else:
geompy = geomBuilder.New(theStudy)
self.geompy = geompy
O = geompy.MakeVertex(0, 0, 0)
OX = geompy.MakeVectorDXDYDZ(1, 0, 0)
OY = geompy.MakeVectorDXDYDZ(0, 1, 0)
OZ = geompy.MakeVectorDXDYDZ(0, 0, 1)
geompy.addToStudy(O, "O")
geompy.addToStudy(OX, "OX")
geompy.addToStudy(OY, "OY")
geompy.addToStudy(OZ, "OZ")
if len([e for e in elements if e["geometryType"] == "line"]) > 0:
buildingShapeType = "EDGE"
if len([e for e in elements if e["geometryType"] == "surface"]) > 0:
buildingShapeType = "FACE"
### Define entities ###
start_time = time.time()
print("Defining Object Geometry")
init_time = start_time
# Loop 1
for el in elements:
el["elemObj"] = self.makeObject(el["geometry"], el["geometryType"])
el["linkObjs"] = [None for _ in el["connections"]]
for j, rel in enumerate(el["connections"]):
conn = [
c for c in connections if c["ifcName"] == rel["relatedConnection"]
][0]
if rel["eccentricity"]:
rel["index"] = len(conn["relatedElements"]) + 1
geometry = self.getLinkGeometry(
rel["eccentricity"], el["orientation"], conn["geometry"]
)
el["linkObjs"][j] = self.makeObject(geometry, "line")
conn["relatedElements"].append(rel)
# Make assemble of Building Object
bldObjs = []
bldObjs.extend([el["elemObj"] for el in elements])
bldObjs.extend(
flatten([[link for link in el["linkObjs"] if link] for el in elements])
)
# bldComp = geompy.MakeCompound(bldObjs)
bldComp = geompy.MakePartition(
bldObjs, [], [], [], self.geompy.ShapeType[buildingShapeType], 0, [], 1
)
geompy.addToStudy(bldComp, "bldComp")
elapsed_time = time.time() - init_time
init_time += elapsed_time
print("Building Geometry Defined in %g sec" % (elapsed_time))
if len([e for e in elements if e["geometryType"] == "line"]) > 0:
buildingShapeType = "EDGE"
if len([e for e in elements if e["geometryType"] == "surface"]) > 0:
buildingShapeType = "FACE"
# Define and add groups for all curve, surface and rigid members
if len([e for e in elements if e["geometryType"] == "line"]) > 0:
# Make compound of requested group
compoundTemp = geompy.MakeCompound(
[e["elemObj"] for e in elements if e["geometryType"] == "line"]
)
# Define group object and add to study
curveCompound = geompy.GetInPlace(bldComp, compoundTemp)
geompy.addToStudyInFather(bldComp, curveCompound, "CurveMembers")
if len([e for e in elements if e["geometryType"] == "surface"]) > 0:
# Make compound of requested group
compoundTemp = geompy.MakeCompound(
[e["elemObj"] for e in elements if e["geometryType"] == "surface"]
)
# Define group object and add to study
surfaceCompound = geompy.GetInPlace(bldComp, compoundTemp)
geompy.addToStudyInFather(bldComp, surfaceCompound, "SurfaceMembers")
linkObjs = list(
flatten([[obj for obj in el["linkObjs"] if obj] for el in elements])
)
if len(linkObjs) > 0:
# Make compound of requested group
compoundTemp = geompy.MakeCompound(linkObjs)
# Define group object and add to study
rigidCompound = geompy.GetInPlace(bldComp, compoundTemp)
geompy.addToStudyInFather(bldComp, rigidCompound, "RigidMembers")
for el in elements:
# el['partObj'] = geompy.RestoreGivenSubShapes(bldComp, [el['partObj']], GEOM.FSM_GetInPlace, False, False)[0]
el["elemObj"] = geompy.GetInPlace(bldComp, el["elemObj"])
geompy.addToStudyInFather(
bldComp, el["elemObj"], self.getGroupName(el["ifcName"])
)
for j, rel in enumerate(el["connections"]):
if rel["eccentricity"]: # point geometry
el["linkObjs"][j] = geompy.GetInPlace(bldComp, el["linkObjs"][j])
geompy.addToStudyInFather(
bldComp,
el["linkObjs"][j],
self.getGroupName(el["ifcName"])
+ "_1DR_"
+ self.getGroupName(rel["relatedConnection"]),
)
elapsed_time = time.time() - init_time
init_time += elapsed_time
print("Building Geometry Groups Defined in %g sec" % (elapsed_time))
###
### SMESH component
###
import SMESH
from salome.smesh import smeshBuilder
print("Defining Mesh Components")
if NEW_SALOME:
smesh = smeshBuilder.New()
else:
smesh = smeshBuilder.New(theStudy)
bldMesh = smesh.Mesh(bldComp)
Regular_1D = bldMesh.Segment()
Local_Length_1 = Regular_1D.LocalLength(meshSize, None, tolLoc)
if buildingShapeType == "FACE":
NETGEN2D_ONLY = bldMesh.Triangle(algo=smeshBuilder.NETGEN_2D)
NETGEN2D_Pars = NETGEN2D_ONLY.Parameters()
NETGEN2D_Pars.SetMaxSize(meshSize)
NETGEN2D_Pars.SetOptimize(1)
NETGEN2D_Pars.SetFineness(2)
NETGEN2D_Pars.SetMinSize(meshSize / 5.0)
NETGEN2D_Pars.SetUseSurfaceCurvature(1)
NETGEN2D_Pars.SetQuadAllowed(1)
NETGEN2D_Pars.SetSecondOrder(0)
NETGEN2D_Pars.SetFuseEdges(254)
isDone = bldMesh.Compute()
coincident_nodes_on_part = bldMesh.FindCoincidentNodesOnPart(
[bldMesh], tolLoc, [], 0
)
if coincident_nodes_on_part:
# bldMesh.MergeNodes(coincident_nodes_on_part, [], 0)
# print(f'{len(coincident_nodes_on_part)} Sets of Coincident Nodes Found and Merged')
print(f"{len(coincident_nodes_on_part)} Sets of Coincident Nodes Found")
print(f"{coincident_nodes_on_part}")
## Set names of Mesh objects
smesh.SetName(Regular_1D.GetAlgorithm(), "Regular_1D")
smesh.SetName(Local_Length_1, "Local_Length_1")
if buildingShapeType == "FACE":
smesh.SetName(NETGEN2D_ONLY.GetAlgorithm(), "NETGEN2D_ONLY")
smesh.SetName(NETGEN2D_Pars, "NETGEN2D_Pars")
smesh.SetName(bldMesh.GetMesh(), "bldMesh")
elapsed_time = time.time() - init_time
init_time += elapsed_time
print("Meshing Operations Completed in %g sec" % (elapsed_time))
# Define and add groups for all curve, surface and rigid members
if len([e for e in elements if e["geometryType"] == "line"]) > 0:
tempgroup = bldMesh.GroupOnGeom(curveCompound, "CurveMembers", SMESH.EDGE)
smesh.SetName(tempgroup, "CurveMembers")
if len([e for e in elements if e["geometryType"] == "surface"]) > 0:
tempgroup = bldMesh.GroupOnGeom(
surfaceCompound, "SurfaceMembers", SMESH.FACE
)
smesh.SetName(tempgroup, "SurfaceMembers")
if len(linkObjs) > 0:
tempgroup = bldMesh.GroupOnGeom(rigidCompound, "RigidMembers", SMESH.EDGE)
smesh.SetName(tempgroup, "RigidMembers")
# Define groups in Mesh
for el in elements:
if el["geometryType"] == "line":
shapeType = SMESH.EDGE
if el["geometryType"] == "surface":
shapeType = SMESH.FACE
tempgroup = bldMesh.GroupOnGeom(
el["elemObj"], self.getGroupName(el["ifcName"]), shapeType
)
smesh.SetName(tempgroup, self.getGroupName(el["ifcName"]))
for j, rel in enumerate(el["connections"]):
if rel["eccentricity"]:
tempgroup = bldMesh.GroupOnGeom(
el["linkObjs"][j],
self.getGroupName(el["ifcName"])
+ "_1DR_"
+ self.getGroupName(rel["relatedConnection"]),
SMESH.EDGE,
)
smesh.SetName(
tempgroup,
self.getGroupName(el["ifcName"])
+ "_1DR_"
+ self.getGroupName(rel["relatedConnection"]),
)
self.mesh = bldMesh
self.meshNodes = bldMesh.GetNodesId()
# Find ground supports and extract node coordinates
grdSupps = bldMesh.CreateEmptyGroup(SMESH.NODE, "grdSupps")
for node in self.meshNodes:
coords = bldMesh.GetNodeXYZ(node)
if abs(coords[2] - self.zGround) < tolLoc:
grdSupps.Add([node])
smesh.SetName(grdSupps, "grdSupps")
elapsed_time = time.time() - init_time
init_time += elapsed_time
print("Mesh Groups Defined in %g sec" % (elapsed_time))
try:
if NEW_SALOME:
bldMesh.ExportMED(
self.medFilename,
auto_groups=0,
minor=40,
overwrite=1,
meshPart=None,
autoDimension=0,
)
else:
bldMesh.ExportMED(self.medFilename, 0, SMESH.MED_V2_2, 1, None, 0)
except:
print("ExportMED() failed. Invalid file name?")
if salome.sg.hasDesktop():
if NEW_SALOME:
salome.sg.updateObjBrowser()
else:
salome.sg.updateObjBrowser(1)
elapsed_time = init_time - start_time
print("ALL Operations Completed in %g sec" % (elapsed_time))
if __name__ == "__main__":
fileNames = ["building_02"]
files = fileNames
meshSize = 500
zGround = 0
for fileName in files:
BASE_PATH = "/home/jesusbill/Dev-Projects/github.com/IfcOpenShell/analysis-models/models/"
DATAFILENAME = BASE_PATH + fileName + "/" + fileName + ".json"
MEDFILENAME = BASE_PATH + fileName + "/" + fileName + ".med"
model = MODEL(DATAFILENAME, MEDFILENAME, meshSize, zGround)
@@ -0,0 +1,191 @@
from behave import step, given, when, then, use_step_matcher
from bimtester.ifc import IfcStore
from bimtester.table import TableModel
from bimtester import util
use_step_matcher("parse")
@step('There must be exactly {number} "{ifc_class}" element')
@step('There must be exactly {number} "{ifc_class}" elements')
def step_impl(context, number, ifc_class):
num = len(IfcStore.file.by_type(ifc_class))
assert num == int(
number
), "Could not find {} elements of {}. \
Found {} element(s).".format(
number, ifc_class, num
)
@given('a set of (key,value) called ("{key_name}","{value_name}")')
def step_impl(context, key_name, value_name):
model = getattr(context, "model", None)
if not model:
context.model = TableModel(key_name, value_name)
for row in context.table:
context.model.add_row(row[key_name], row[value_name])
@given('a set of (key,value) called ("{key_name}","{value_name}") taken from the file "{path_file}"')
def step_impl(context, key_name, value_name, path_file):
import csv
import os
model = getattr(context, "model", None)
if not model:
context.model = TableModel(key_name, value_name)
if context.config.userdata.get("path"):
path_file = os.path.join(context.config.userdata.get("path"), path_file)
if not os.path.exists(path_file):
assert False, "File {} not found".format(path_file)
with open(path_file, "r", encoding="utf-8-sig") as csvfile:
reader = csv.DictReader(csvfile)
for row in reader:
context.model.add_row(row[key_name], row[value_name])
@then('there is an element of type key with an attribute "{attribute_name}" equals to value')
def step_impl(context, attribute_name):
if not context.model.key_name and not context.model.value_name:
assert False, "Missing (key,value)"
errors = []
rows = context.model.rows
for key, values in rows.items():
for value in values:
found = check_equals(attribute_name, value, IfcStore.file.by_type(key))
if not found:
errors.append(f"The row ({key}, {value}) was not found.")
assert not errors, "Errors occured:\n{}".format("\n".join(errors))
@then('there is an element of type key with an attribute "{attribute_name}" starting with value')
def step_impl(context, attribute_name):
# import re
if not context.model.key_name and not context.model.value_name:
assert False, "Missing (key,value)"
errors = []
rows = context.model.rows
for key, values in rows.items():
for value in values:
found = check_starts_with(attribute_name, value, IfcStore.file.by_type(key))
if not found:
errors.append(f"The row ({key}, {value}) was not found.")
assert not errors, "Errors occured:\n{}".format("\n".join(errors))
@then('there must be exactly a number of "{ifc_class}" equals to the number of distinct value')
def step_impl(context, ifc_class):
try:
context.execute_steps(
"""
then There must be exactly {number} "{ifc_class}" elements
""".format(
ifc_class=ifc_class, number=context.model.get_count_distinct_values()
)
)
except AssertionError as error:
str_error = str(error)
assert False, str_error[: str_error.find("Traceback")]
assert True
@then('there is a relationship "{ifc_class}" between the two elements of each row')
def step_impl(context, ifc_class):
if not context.model.key_name and not context.model.value_name:
assert False, "Missing (key,value)"
errors = []
elements = IfcStore.file.by_type(ifc_class)
# first, loop on the dataset to check if there are all in the relationships
rows = context.model.rows
for key, values in rows.items():
found = False
# value is actually a list of values
for value in values:
for element in elements:
if (
any(x.Name == key for x in getattr(element, context.model.key_name))
and getattr(element, context.model.value_name).Name == value
):
found = True
if not found:
errors.append(f"The row ({key}, {value}) does not have the relationship.")
# second, loop on the elements to check if there are not too many relationships
for element in elements:
if hasattr(element, context.model.key_name) and hasattr(element, context.model.value_name):
# list here
keys = getattr(element, context.model.key_name)
value = getattr(element, context.model.value_name).Name
for key in keys:
if not key.Name in rows or not value in rows[key.Name]:
errors.append(f"The element ({element}) has a relation that cannot be found in the dataset: {key}")
assert not errors, "Errors occured:\n{}".format("\n".join(errors))
use_step_matcher("re")
@step('all IfcGroup must be linked to a type "(?P<linked_ifc_classes>.*)"')
def step_impl(context, linked_ifc_classes):
groups = IfcStore.file.by_type("IfcGroup")
errors = []
for group in groups:
if not hasattr(group, "IsGroupedBy"):
errors.append(f'The element "{group.Name}" has no "IsGroupedBy" attribute.')
else:
for grouped_by in getattr(group, "IsGroupedBy"):
if not hasattr(grouped_by, "RelatedObjects"):
errors.append(f'The element "{grouped_by.Name}" has no "RelatedObjects" attribute.')
else:
for related_object in getattr(grouped_by, "RelatedObjects"):
found = False
for linked_ifc_class in linked_ifc_classes.split(","):
if related_object.is_a(linked_ifc_class):
found = True
if not found:
errors.append(
f'The element "{related_object.Name}" does not have the right associated type.'
)
assert not errors, "Errors occured:\n{}".format("\n".join(errors))
@then('there is an element of type "(?P<ifc_types>.*)" with an attribute "(?P<attribute_name>.*)" for each key')
def step_impl(context, ifc_types, attribute_name):
check_if_element_exists_by_types_with_attribute_name(ifc_types, attribute_name, context.model.rows.keys())
@then('there is an element of type "(?P<ifc_types>.*)" with an attribute "(?P<attribute_name>.*)" for each value')
def step_impl(context, ifc_types, attribute_name):
values = set(item for sublist in context.model.rows.values() for item in sublist)
check_if_element_exists_by_types_with_attribute_name(ifc_types, attribute_name, values)
def check_if_element_exists_by_types_with_attribute_name(ifc_types, attribute_name, attribute_values):
errors = []
# retrieve all elements of that type
elements = util.by_types(IfcStore.file, ifc_types)
# loop
for attribute_value in attribute_values:
found = False
for element in elements:
if hasattr(element, attribute_name) and getattr(element, attribute_name) == attribute_value:
found = True
if not found:
errors.append(f'An element with attribute "{attribute_name}" equals to "{attribute_value}" was not found.')
assert not errors, "Errors occured:\n{}".format("\n".join(errors))
def check_starts_with(attribute_name, value, elements):
"""Make sure at least an element of the list has a attribute starting with the value"""
if any(hasattr(x, attribute_name) and getattr(x, attribute_name).startswith(value) for x in elements):
return True
return False
def check_equals(attribute_name, value, elements):
"""Make sure at least an element of the list has a attribute equals to the value"""
if any(hasattr(x, attribute_name) and getattr(x, attribute_name) == value for x in elements):
return True
return False
@@ -1,15 +1,23 @@
use_step_matcher("parse")
from bimtester.features.steps.classification import en
use_step_matcher("parse")
from bimtester.features.steps.element_classes import en
use_step_matcher("parse")
from bimtester.features.steps.geocoding import en
use_step_matcher("parse")
from bimtester.features.steps.geolocation import en
use_step_matcher("parse")
from bimtester.features.steps.geometric_detail import en
use_step_matcher("parse")
from bimtester.features.steps.model_federation import en
use_step_matcher("parse")
from bimtester.features.steps.project_setup import de, en, fr, it, nl
use_step_matcher("parse")
from bimtester.features.steps.aggregation import en
@@ -10,6 +10,12 @@ def step_impl(context, schema):
assert real_schema == schema, _("We expected a schema of {} but instead got {}").format(schema, real_schema)
@step("The IFC file must be valid")
def step_impl(context):
errors = util.validate(IfcStore.file)
assert not errors, "Errors occured:\n{}".format("\n".join(errors))
@step('The IFC file "{file}" is exempt from being provided')
def step_impl(context, file):
pass
+8 -12
View File
@@ -21,12 +21,12 @@ class ReportGenerator:
self.generate_feature_report(feature, output_file)
def generate_feature_report(self, feature, output_file):
file_name = os.path.basename(feature["location"]).split(":")[0]
# file_name = os.path.basename(feature["location"]).split(":")[0]
data = {
"file_name": file_name,
# "file_name": file_name,
"time": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"name": feature["name"],
"description": feature["description"],
"description": feature.get("description", ""),
"is_success": feature["status"] == "passed",
"scenarios": [],
}
@@ -93,9 +93,7 @@ class ReportGenerator:
step["result"] = {}
step["result"]["status"] = "skipped"
step["result"]["duration"] = 0
step["result"][
"error_message"
] = "This requirement has been skipped due to a previous failing step."
step["result"]["error_message"] = "This requirement has been skipped due to a previous failing step."
elif step["result"]["status"] == "undefined":
step["result"] = {}
step["result"]["status"] = "undefined"
@@ -108,14 +106,12 @@ class ReportGenerator:
"is_success": step["result"]["status"] == "passed",
"is_unspecified": step["result"]["status"] == "undefined",
"is_skipped": step["result"]["status"] == "skipped",
"error_message": None
if step["result"]["status"] == "passed"
else step["result"]["error_message"],
"error_message": None if step["result"]["status"] == "passed" else step["result"]["error_message"],
}
# TODO: there is probably a better way of doing this
if isinstance(data["error_message"], list):
data["error_message"] = data["error_message"][1]
# Remove the first "Assertion Failed" message
if isinstance(data["error_message"], list) and data["error_message"]:
data["error_message"].pop(0)
return data
def get_template_strings(self):
@@ -35,11 +35,11 @@
<span class="{{#is_success}}success{{/is_success}}{{^is_success}}failure{{/is_success}}">{{#is_success}}{{_success}}{{/is_success}}{{^is_success}}{{_failure}}{{/is_success}}</span>
{{_tests_passed}}: <strong>{{total_passes}} / {{total_steps}}</strong> ({{pass_rate}}%)
<br />
{{#description}}
<p class="description">
{{#description}}
{{.}}<br />
{{/description}}
</p>
{{/description}}
<hr>
</header>
{{#scenarios}}
+71 -5
View File
@@ -1,21 +1,70 @@
import os
import sys
import json
import shutil
import logging
import tempfile
import ifcopenshell
try:
import ifcopenshell.express
except:
pass # They are using an old version of IfcOpenShell. Gracefully degrade for now.
pass # They are using an old version of IfcOpenShell. Gracefully degrade for now.
import behave.formatter.pretty # Needed for pyinstaller to package it
from bimtester.ifc import IfcStore
from distutils.dir_util import copy_tree
from behave.__main__ import main as behave_main
# TODO: refactor when this isn't super experimental
from logging import StreamHandler
class IDSHandler(StreamHandler):
def __init__(self):
StreamHandler.__init__(self)
self.results = {
"name": "Specification name",
"status": "passed",
"location": "filename.xml",
"elements": [
{
"keyword": "Scenario",
"name": "Checking IDS specifications",
"status": "passed",
"steps": []
}
]
}
def emit(self, record):
msg = self.format(record)
# Obviously, not a final product
is_fail = "is compliant" not in msg
if is_fail:
self.results["status"] = "failed"
self.results["elements"][0]["status"] = "failed"
self.results["elements"][0]["steps"].append({
"keyword": "*",
"match": {},
"name": msg,
"result": {
"duration": 0.0,
"error_message": "Assertion Failed",
"status": "failed" if is_fail else "passed"
},
"step_type": "given"
})
class TestRunner:
def __init__(self, ifc_path, ifc=None):
def __init__(self, ifc_path, schema_path=None, ifc=None):
IfcStore.path = ifc_path
# can't load the IFC file if the schema is not loaded before
if schema_path:
schema = ifcopenshell.express.parse(schema_path)
ifcopenshell.register_schema(schema)
IfcStore.file = ifc if ifc else ifcopenshell.open(ifc_path)
try:
@@ -27,10 +76,27 @@ class TestRunner:
self.locale_path = os.path.join(self.base_path, "locale")
def run(self, args):
if args["schema_file"]:
schema = ifcopenshell.express.parse(args["schema_file"])
ifcopenshell.register_schema(args["schema_name"])
if args["feature"][-4:].lower() == ".xml":
return self.test_ids(args)
return self.test_feature(args)
def test_ids(self, args):
# Local import whilst this is experimental
import ifcopenshell.ids
logger = logging.getLogger("IDS")
logging.basicConfig(level=logging.INFO, format="%(message)s")
ids_handler = IDSHandler()
logger.addHandler(ids_handler)
ids_file = ifcopenshell.ids.ids(args["feature"])
ids_file.validate(IfcStore.file, logger)
tmpdir = tempfile.mkdtemp()
report_json = os.path.join(tmpdir, "report.json")
json.dump([ids_handler.results], open(report_json, "w"))
return report_json
def test_feature(self, args):
tmpdir = tempfile.mkdtemp()
features_path = os.path.join(tmpdir, "features")
steps_path = os.path.join(features_path, "steps")
+19
View File
@@ -0,0 +1,19 @@
from collections import defaultdict
class TableModel:
"""This class represents a table of data"""
def __init__(self, key_name=None, value_name=None):
self.rows = defaultdict(list)
self.key_name = key_name
self.value_name = value_name
def add_row(self, related, relating):
self.rows[related].append(relating)
def get_count(self):
return len(self.rows)
def get_count_distinct_values(self):
return len(set(item for sublist in self.rows.values() for item in sublist))
+21
View File
@@ -1,8 +1,29 @@
import ifcopenshell
import ifcopenshell.util.element
import ifcopenshell.validate
import json
from bimtester.lang import _
def by_types(ifc, ifc_types):
elements = []
for ifc_type in ifc_types.split(","):
elements += ifc.by_type(ifc_type.strip())
return elements
def validate(ifc):
errors = []
logger = ifcopenshell.validate.json_logger()
try:
ifcopenshell.validate.validate(ifc, logger)
if logger.statements:
for statement in logger.statements:
errors.append(f"{json.dumps(statement, default=str)}")
except RuntimeError as error:
assert False, str(error)
def assert_guid(ifc, guid):
try:
return ifc.by_guid(guid)
+2 -2
View File
@@ -10,7 +10,7 @@ parser = argparse.ArgumentParser(description="Runs unit tests for BIM data")
parser.add_argument("-a", "--action", type=str, help="Action to perform, from run/purge", default="run")
parser.add_argument("--advanced-arguments", type=str, help="Specify arguments to Behave", default="")
parser.add_argument("-c", "--console", action="store_true", help="Show results in the console")
parser.add_argument("-f", "--feature", type=str, help="Specify a feature file to test", required=True)
parser.add_argument("-f", "--feature", type=str, help="Specify a feature file or IDS to test", required=True)
parser.add_argument("-i", "--ifc", type=str, help="Specify an IFC file to test", required=True)
parser.add_argument("-p", "--path", type=str, help="Define a path for use in test steps that use relative paths")
parser.add_argument("-r", "--report", type=str, help="Specify an output file for a HTML report")
@@ -22,7 +22,7 @@ parser.add_argument("--lang", type=str, help="Specify a language e.g. en/de/fr/i
args = vars(parser.parse_args())
if args["action"] == "run":
report_json = bimtester.run.TestRunner(args["ifc"]).run(args)
report_json = bimtester.run.TestRunner(args["ifc"], args["schema_file"]).run(args)
if args["report"]:
bimtester.reports.ReportGenerator().generate(report_json, args["report"])
elif args["action"] == "purge":
+91 -13
View File
@@ -1,19 +1,44 @@
VERSION:=`date '+%y%m%d'`
PYVERSION:=py37
ifeq ($(PLATFORM), win)
ifeq ($(PYVERSION), py37)
PYTHONOCC_URL:=https://anaconda.org/DLR-SC/pythonocc-core/0.17.3/download/win-64/pythonocc-core-0.17.3-py37he980bc4_10.tar.bz2
OCE_URL:=https://anaconda.org/DLR-SC/oce/0.17.2/download/win-64/oce-0.17.2-he980bc4_14.tar.bz2
TBB_URL:=https://anaconda.org/DLR-SC/tbb/2019.5/download/win-64/tbb-2019.5-he980bc4_0.tar.bz2
endif
ifeq ($(PYVERSION), py39)
PYTHONOCC_URL:=https://anaconda.org/conda-forge/pythonocc-core/7.4.1/download/win-64/pythonocc-core-7.4.1-py39h3d1c7c5_0.tar.bz2
OCE_URL:=https://anaconda.org/conda-forge/occt/7.4.0/download/win-64/occt-7.4.0-h823b557_3.tar.bz2
TBB_URL:=https://anaconda.org/conda-forge/tbb/2020.2/download/win-64/tbb-2020.2-h2d74725_4.tar.bz2
endif
endif
ifeq ($(PLATFORM), macos)
ifeq ($(PYVERSION), py37)
PYTHONOCC_URL:=https://anaconda.org/DLR-SC/pythonocc-core/0.17.3/download/osx-64/pythonocc-core-0.17.3-py37h04f5b5a_10.tar.bz2
OCE_URL:=https://anaconda.org/DLR-SC/oce/0.17.2/download/osx-64/oce-0.17.2-h04f5b5a_12.tar.bz2
TBB_URL:=https://anaconda.org/DLR-SC/tbb/4.3.6/download/osx-64/tbb-4.3.6-0.tar.bz2
endif
ifeq ($(PYVERSION), py39)
PYTHONOCC_URL:=https://anaconda.org/conda-forge/pythonocc-core/7.4.1/download/osx-64/pythonocc-core-7.4.1-py39h4d29fe3_0.tar.bz2
OCE_URL:=https://anaconda.org/conda-forge/occt/7.4.0/download/osx-64/occt-7.4.0-hb9b6dc7_3.tar.bz2
TBB_URL:=https://anaconda.org/conda-forge/tbb/2020.2/download/osx-64/tbb-2020.2-h940c156_4.tar.bz2
endif
endif
ifeq ($(PLATFORM), linux)
ifeq ($(PYVERSION), py37)
PYTHONOCC_URL:=https://anaconda.org/DLR-SC/pythonocc-core/0.17.3/download/linux-64/pythonocc-core-0.17.3-py37h6bb024c_10.tar.bz2
OCE_URL:=https://anaconda.org/DLR-SC/oce/0.17.2/download/linux-64/oce-0.17.2-h6bb024c_14.tar.bz2
TBB_URL:=https://anaconda.org/DLR-SC/tbb/4.3.6/download/linux-64/tbb-4.3.6-0.tar.bz2
endif
ifeq ($(PYVERSION), py39)
PYTHONOCC_URL:=https://anaconda.org/conda-forge/pythonocc-core/7.4.1/download/linux-64/pythonocc-core-7.4.1-py39h465cb30_0.tar.bz2
OCE_URL:=https://anaconda.org/conda-forge/occt/7.4.0/download/linux-64/occt-7.4.0-h9121d39_3.tar.bz2
TBB_URL:=https://anaconda.org/conda-forge/tbb/2020.2/download/linux-64/tbb-2020.2-h4bd325d_4.tar.bz2
endif
endif
.PHONY: dist
dist:
@@ -26,14 +51,31 @@ endif
cp -r blenderbim/* dist/blenderbim/
# Provides IfcOpenShell Python functionality
cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcblender-python-37-v0.6.0-3bbdc3f-$(PLATFORM)64.zip
ifeq ($(PYVERSION), py37)
cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcblender-python-37-v0.6.0-517b819-$(PLATFORM)64.zip
endif
ifeq ($(PYVERSION), py39)
cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcblender-python-39-v0.6.0-517b819-$(PLATFORM)64.zip
endif
cd dist/working && unzip ifcblender*
cp -r dist/working/io_import_scene_ifc/ifcopenshell dist/blenderbim/libs/site/packages/
# See bug #812
cd dist/working && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.6.0/src/ifcblenderexport/occ_utils.py
cd dist/working && mv occ_utils.py ../blenderbim/libs/site/packages/ifcopenshell/geom/occ_utils.py
rm -rf dist/working
# Provides IfcConvert for construction documentation
mkdir dist/working
cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/IfcConvert-v0.6.0-517b819-$(PLATFORM)64.zip
cd dist/working && unzip IfcConvert*
ifeq ($(PLATFORM), win)
cp -r dist/working/IfcConvert.exe dist/blenderbim/libs/
else
cp -r dist/working/IfcConvert dist/blenderbim/libs/
endif
rm -rf dist/working
# Provides Python OCC functionality for cutting IFC geometry for construction documentation
mkdir dist/working
cd dist/working && wget $(PYTHONOCC_URL)
@@ -41,16 +83,26 @@ endif
ifeq ($(PLATFORM), win)
cd dist/working && cp -r Lib/site-packages/OCC ../blenderbim/libs/site/packages/
else
ifeq ($(PYVERSION), py37)
cd dist/working && cp -r lib/python3.7/site-packages/OCC ../blenderbim/libs/site/packages/
endif
ifeq ($(PYVERSION), py39)
cd dist/working && cp -r lib/python3.9/site-packages/OCC ../blenderbim/libs/site/packages/
endif
endif
rm -rf dist/working
# Required by Python OCC
mkdir dist/working
cd dist/working && wget $(OCE_URL)
cd dist/working && tar -xjvf oce*
cd dist/working && tar -xjvf oc*
ifeq ($(PLATFORM), win)
ifeq ($(PYVERSION), py37)
cd dist/working && cp -r Library/bin/* ../blenderbim/libs/site/packages/OCC/
endif
ifeq ($(PYVERSION), py39)
cd dist/working && cp -r Library/bin/* ../blenderbim/libs/site/packages/OCC/Core/
endif
else
# Unix Conda builds of PythonOCC expect OCE libs to have a RPATH of ../../../
cd dist/working && cp -r lib/* ../blenderbim/libs/
@@ -65,7 +117,12 @@ endif
cd dist/working && wget $(TBB_URL)
cd dist/working && tar -xjvf tbb*
ifeq ($(PLATFORM), win)
ifeq ($(PYVERSION), py37)
cd dist/working && cp -r Library/bin/* ../blenderbim/libs/site/packages/OCC/
endif
ifeq ($(PYVERSION), py39)
cd dist/working && cp -r Library/bin/* ../blenderbim/libs/site/packages/OCC/Core/
endif
else
cd dist/working && cp -r lib/* ../blenderbim/libs/
endif
@@ -98,7 +155,7 @@ endif
# Provides IFCCSV functionality
cp -r dist/working/IfcOpenShell-0.6.0/src/ifccsv/* dist/blenderbim/libs/site/packages/
# Provides IFCPatch functionality
cp -r dist/working/IfcOpenShell-0.6.0/src/ifcpatch dist/blenderbim/libs/site/packages/
cp -r dist/working/IfcOpenShell-0.6.0/src/ifcpatch/ifcpatch dist/blenderbim/libs/site/packages/
rm -rf dist/working
# Provides Mustache templating in construction documentation
@@ -172,22 +229,15 @@ endif
rm -rf dist/working
# Required by IFCClash
mkdir dist/working
ifeq ($(PLATFORM), linux)
mkdir dist/working
cd dist/working && wget https://files.pythonhosted.org/packages/0c/fa/00d85f893b02289e2942849d97f8818dde8e2111182e825fb3a735677791/python_fcl-0.0.12-cp37-cp37m-manylinux1_x86_64.whl
cd dist/working && unzip python_fcl*
cp -r dist/working/fcl dist/blenderbim/libs/site/packages/
rm -rf dist/working
endif
# Required by IFCClash
ifeq ($(PLATFORM), win)
mkdir dist/working
cd dist/working && wget https://files.pythonhosted.org/packages/19/e6/6e9f33fb59e8f27c0e1592bd26e644bc92b85c942b072b2d3854105d5887/python_fcl_win32-0.0.12.post3-py3-none-win_amd64.whl
endif
cd dist/working && unzip python_fcl*
cp -r dist/working/fcl dist/blenderbim/libs/site/packages/
rm -rf dist/working
endif
# Required by BIMTester
mkdir dist/working
@@ -231,6 +281,34 @@ endif
cd dist/working/defusedxml-0.6.0/ && cp -r defusedxml ../../blenderbim/libs/site/packages/
rm -rf dist/working
# Required by augin
mkdir dist/working
cd dist/working && wget https://files.pythonhosted.org/packages/76/b4/b7baffbda025efd5dc8fcd8d2e953e3aa939c236a484084fa8f4c3588ee9/boto3-1.17.17.tar.gz
cd dist/working && tar -xzvf boto3*
cd dist/working/boto3-1.17.17/ && cp -r boto3 ../../blenderbim/libs/site/packages/
rm -rf dist/working
# Required by boto3
mkdir dist/working
cd dist/working && wget https://files.pythonhosted.org/packages/7f/2e/e02fdfd0f0377c4e44e61de27b05d5cfe93575770661ef9ded80ed90fa88/botocore-1.20.17.tar.gz
cd dist/working && tar -xzvf botocore*
cd dist/working/botocore-1.20.17/ && cp -r botocore ../../blenderbim/libs/site/packages/
rm -rf dist/working
# Required by boto3
mkdir dist/working
cd dist/working && wget https://files.pythonhosted.org/packages/3c/56/3f325b1eef9791759784aa5046a8f6a1aff8f7c898a2e34506771d3b99d8/jmespath-0.10.0.tar.gz
cd dist/working && tar -xzvf jmespath*
cd dist/working/jmespath-0.10.0/ && cp -r jmespath ../../blenderbim/libs/site/packages/
rm -rf dist/working
# Required by boto3
mkdir dist/working
cd dist/working && wget https://files.pythonhosted.org/packages/08/e1/3ee2096ebaeeb8c186d20ed16c8faf4a503913e5c9a0e14cd6b8ffc405a3/s3transfer-0.3.4.tar.gz
cd dist/working && tar -xzvf s3transfer*
cd dist/working/s3transfer-0.3.4/ && cp -r s3transfer ../../blenderbim/libs/site/packages/
rm -rf dist/working
# Provides IFCJSON functionality
mkdir dist/working
cd dist/working && wget https://github.com/IFCJSON-Team/IFC2JSON_python/archive/master.zip
@@ -239,7 +317,7 @@ endif
rm -rf dist/working
cd dist/blenderbim && sed -i "s/999999/$(VERSION)/" __init__.py
cd dist && zip -r blender28-bim-$(VERSION)-$(PLATFORM).zip ./*
cd dist && zip -r blender28-bim-$(VERSION)-$(PYVERSION)-$(PLATFORM).zip ./*
rm -rf dist/blenderbim
.PHONY: clean
@@ -6,7 +6,7 @@ bpy = sys.modules.get("bpy")
if bpy is not None:
import bpy
import importlib
from . import handler, ui, prop, operator
from . import handler, ui, prop, operator, gizmos
modules = {
"project": None,
@@ -16,6 +16,7 @@ if bpy is not None:
"unit": None,
"georeference": None,
"context": None,
"drawing": None,
"attribute": None,
"type": None,
"spatial": None,
@@ -23,6 +24,8 @@ if bpy is not None:
"aggregate": None,
"geometry": None,
"cobie": None,
"sequence": None,
"group": None,
"material": None,
"style": None,
"layer": None,
@@ -40,6 +43,7 @@ if bpy is not None:
"diff": None,
"patch": None,
"covetool": None,
"augin": None,
"debug": None,
}
@@ -86,7 +90,6 @@ if bpy is not None:
operator.RemoveVariable,
operator.PropagateTextData,
operator.SetOverrideColour,
operator.AddDrawing,
operator.RemoveDrawing,
operator.AddDrawingStyle,
operator.RemoveDrawingStyle,
@@ -143,6 +146,11 @@ if bpy is not None:
ui.BIM_UL_drawinglist,
ui.BIM_UL_topics,
ui.BIM_ADDON_preferences,
gizmos.UglyDotGizmo,
gizmos.DotGizmo,
gizmos.DimensionLabelGizmo,
gizmos.ExtrusionGuidesGizmo,
gizmos.ExtrusionWidget
]
for module in modules.values():
@@ -164,6 +172,7 @@ if bpy is not None:
bpy.app.handlers.depsgraph_update_post.append(on_register)
bpy.app.handlers.load_post.append(handler.setDefaultProperties)
bpy.app.handlers.load_post.append(handler.loadIfcStore)
bpy.app.handlers.save_pre.append(handler.ensureIfcExported)
bpy.app.handlers.save_pre.append(handler.storeIdMap)
bpy.types.TOPBAR_MT_file_export.append(menu_func_export)
bpy.types.TOPBAR_MT_file_import.append(menu_func_import)
@@ -174,6 +183,7 @@ if bpy is not None:
bpy.types.Collection.BIMObjectProperties = bpy.props.PointerProperty(type=prop.BIMObjectProperties) # Check if we need this
bpy.types.Material.BIMMaterialProperties = bpy.props.PointerProperty(type=prop.BIMMaterialProperties)
bpy.types.Mesh.BIMMeshProperties = bpy.props.PointerProperty(type=prop.BIMMeshProperties)
bpy.types.Camera.BIMMeshProperties = bpy.props.PointerProperty(type=prop.BIMMeshProperties)
bpy.types.Camera.BIMCameraProperties = bpy.props.PointerProperty(type=prop.BIMCameraProperties)
bpy.types.TextCurve.BIMTextProperties = bpy.props.PointerProperty(type=prop.BIMTextProperties)
bpy.types.SCENE_PT_unit.append(ui.ifc_units)
@@ -190,6 +200,7 @@ if bpy is not None:
bpy.app.handlers.load_post.remove(handler.setDefaultProperties)
bpy.app.handlers.load_post.remove(handler.loadIfcStore)
bpy.app.handlers.save_pre.remove(handler.storeIdMap)
bpy.app.handlers.save_pre.remove(handler.ensureIfcExported)
bpy.types.TOPBAR_MT_file_export.remove(menu_func_export)
bpy.types.TOPBAR_MT_file_import.remove(menu_func_import)
del bpy.types.Scene.BIMProperties
@@ -199,6 +210,7 @@ if bpy is not None:
del bpy.types.Collection.BIMObjectProperties # Check if we need this
del bpy.types.Material.BIMMaterialProperties
del bpy.types.Mesh.BIMMeshProperties
del bpy.types.Camera.BIMMeshProperties
del bpy.types.Camera.BIMCameraProperties
del bpy.types.TextCurve.BIMTextProperties
bpy.types.SCENE_PT_unit.remove(ui.ifc_units)
@@ -0,0 +1,400 @@
import bpy
import blf
from bpy import types
import math
from mathutils import Vector, Matrix
from mathutils import geometry
import gpu, bgl
from bpy_extras import view3d_utils
from .shaders import DotsGizmoShader, ExtrusionGuidesShader, BaseLinesShader
"""Gizmos under the hood
## Transforms:
source/blender/windowmanager/gizmo/WM_gizmo_types.h
matrix_basis -- "Transformation of this gizmo." = placement in scene
matrix_offset -- "Custom offset from origin." = local transforms according to state/value
matrix_space -- "The space this gizmo is being modified in." used by some gizmos for undefined purposes
matrix_world -- final matrix, scaled according to viewport zoom and custom scale_basis
source/blender/windowmanager/gizmo/intern/wm_gizmo.c:WM_gizmo_calc_matrix_final_params
final = space @ (autoscale * (basis @ offset))
final = space @ (basis @ offset) -- if gizmo.use_draw_scale == False
final = space @ ((autoscale * basis) @ offset) -- if gizmo.use_draw_offset_scale
source/blender/windowmanager/gizmo/intern/wm_gizmo.c:wm_gizmo_calculate_scale
autoscale = gizmo.scale_basis * magic(preferences, matrix_space, matrix_basis, context.region_data)
magic -- making 1.0 to match preferences.view.gizmo_size pixels (75 by default)
## Selection
select_id -- apparently, id of a selectable part
test_select -- expected to return id of selection, doesn't seem to work
draw_select -- fake-draw of selection geometry for gpu-side cursor tracking
"""
# some geometries for Gizmo.custom_shape shaders
CUBE = (
(+1, +1, +1), (-1, +1, +1), (+1, -1, +1), # top
(+1, -1, +1), (-1, +1, +1), (-1, -1, +1),
(+1, +1, +1), (+1, -1, +1), (+1, +1, -1), # right
(+1, +1, -1), (+1, -1, +1), (+1, -1, -1),
(+1, +1, +1), (+1, +1, -1), (-1, +1, +1), # back
(-1, +1, +1), (+1, +1, -1), (-1, +1, -1),
(-1, -1, -1), (-1, +1, -1), (+1, -1, -1), # bot
(+1, -1, -1), (-1, +1, -1), (+1, +1, -1),
(-1, -1, -1), (-1, -1, +1), (-1, +1, -1), # left
(-1, +1, -1), (-1, -1, +1), (-1, +1, +1),
(-1, -1, -1), (+1, -1, -1), (-1, -1, +1), # front
(-1, -1, +1), (+1, -1, -1), (+1, -1, +1)
)
DISC = (
(0.0, 0.0, 0.0), (1.0, 0.0, 0), (0.8660254037844387, 0.49999999999999994, 0),
(0.0, 0.0, 0.0), (0.8660254037844387, 0.49999999999999994, 0), (0.5000000000000001, 0.8660254037844386, 0),
(0.0, 0.0, 0.0), (0.5000000000000001, 0.8660254037844386, 0), (6.123233995736766e-17, 1.0, 0),
(0.0, 0.0, 0.0), (6.123233995736766e-17, 1.0, 0), (-0.4999999999999998, 0.8660254037844387, 0),
(0.0, 0.0, 0.0), (-0.4999999999999998, 0.8660254037844387, 0), (-0.8660254037844385, 0.5000000000000003, 0),
(0.0, 0.0, 0.0), (-0.8660254037844385, 0.5000000000000003, 0), (-1.0, 1.2246467991473532e-16, 0),
(0.0, 0.0, 0.0), (-1.0, 1.2246467991473532e-16, 0), (-0.8660254037844388, -0.4999999999999997, 0),
(0.0, 0.0, 0.0), (-0.8660254037844388, -0.4999999999999997, 0), (-0.5000000000000004, -0.8660254037844384, 0),
(0.0, 0.0, 0.0), (-0.5000000000000004, -0.8660254037844384, 0), (-1.8369701987210297e-16, -1.0, 0),
(0.0, 0.0, 0.0), (-1.8369701987210297e-16, -1.0, 0), (0.49999999999999933, -0.866025403784439, 0),
(0.0, 0.0, 0.0), (0.49999999999999933, -0.866025403784439, 0), (0.8660254037844384, -0.5000000000000004, 0),
(0.0, 0.0, 0.0), (0.8660254037844384, -0.5000000000000004, 0), (1.0, 0.0, 0),
)
X3DISC = (
(0.0, 0.0, 0.0), (1.0, 0.0, 0), (0.8660254037844387, 0.49999999999999994, 0),
(0.0, 0.0, 0.0), (0.8660254037844387, 0.49999999999999994, 0), (0.5000000000000001, 0.8660254037844386, 0),
(0.0, 0.0, 0.0), (0.5000000000000001, 0.8660254037844386, 0), (6.123233995736766e-17, 1.0, 0),
(0.0, 0.0, 0.0), (6.123233995736766e-17, 1.0, 0), (-0.4999999999999998, 0.8660254037844387, 0),
(0.0, 0.0, 0.0), (-0.4999999999999998, 0.8660254037844387, 0), (-0.8660254037844385, 0.5000000000000003, 0),
(0.0, 0.0, 0.0), (-0.8660254037844385, 0.5000000000000003, 0), (-1.0, 1.2246467991473532e-16, 0),
(0.0, 0.0, 0.0), (-1.0, 1.2246467991473532e-16, 0), (-0.8660254037844388, -0.4999999999999997, 0),
(0.0, 0.0, 0.0), (-0.8660254037844388, -0.4999999999999997, 0), (-0.5000000000000004, -0.8660254037844384, 0),
(0.0, 0.0, 0.0), (-0.5000000000000004, -0.8660254037844384, 0), (-1.8369701987210297e-16, -1.0, 0),
(0.0, 0.0, 0.0), (-1.8369701987210297e-16, -1.0, 0), (0.49999999999999933, -0.866025403784439, 0),
(0.0, 0.0, 0.0), (0.49999999999999933, -0.866025403784439, 0), (0.8660254037844384, -0.5000000000000004, 0),
(0.0, 0.0, 0.0), (0.8660254037844384, -0.5000000000000004, 0), (1.0, 0.0, 0),
(0.0, 0.0, 0.0), (0, 1.0, 0.0), (0, 0.8660254037844387, 0.49999999999999994),
(0.0, 0.0, 0.0), (0, 0.8660254037844387, 0.49999999999999994), (0, 0.5000000000000001, 0.8660254037844386),
(0.0, 0.0, 0.0), (0, 0.5000000000000001, 0.8660254037844386), (0, 6.123233995736766e-17, 1.0),
(0.0, 0.0, 0.0), (0, 6.123233995736766e-17, 1.0), (0, -0.4999999999999998, 0.8660254037844387),
(0.0, 0.0, 0.0), (0, -0.4999999999999998, 0.8660254037844387), (0, -0.8660254037844385, 0.5000000000000003),
(0.0, 0.0, 0.0), (0, -0.8660254037844385, 0.5000000000000003), (0, -1.0, 1.2246467991473532e-16),
(0.0, 0.0, 0.0), (0, -1.0, 1.2246467991473532e-16), (0, -0.8660254037844388, -0.4999999999999997),
(0.0, 0.0, 0.0), (0, -0.8660254037844388, -0.4999999999999997), (0, -0.5000000000000004, -0.8660254037844384),
(0.0, 0.0, 0.0), (0, -0.5000000000000004, -0.8660254037844384), (0, -1.8369701987210297e-16, -1.0),
(0.0, 0.0, 0.0), (0, -1.8369701987210297e-16, -1.0), (0, 0.49999999999999933, -0.866025403784439),
(0.0, 0.0, 0.0), (0, 0.49999999999999933, -0.866025403784439), (0, 0.8660254037844384, -0.5000000000000004),
(0.0, 0.0, 0.0), (0, 0.8660254037844384, -0.5000000000000004), (0, 1.0, 0.0),
(0.0, 0.0, 0.0), (0.0, 0, 1.0), (0.49999999999999994, 0, 0.8660254037844387),
(0.0, 0.0, 0.0), (0.49999999999999994, 0, 0.8660254037844387), (0.8660254037844386, 0, 0.5000000000000001),
(0.0, 0.0, 0.0), (0.8660254037844386, 0, 0.5000000000000001), (1.0, 0, 6.123233995736766e-17),
(0.0, 0.0, 0.0), (1.0, 0, 6.123233995736766e-17), (0.8660254037844387, 0, -0.4999999999999998),
(0.0, 0.0, 0.0), (0.8660254037844387, 0, -0.4999999999999998), (0.5000000000000003, 0, -0.8660254037844385),
(0.0, 0.0, 0.0), (0.5000000000000003, 0, -0.8660254037844385), (1.2246467991473532e-16, 0, -1.0),
(0.0, 0.0, 0.0), (1.2246467991473532e-16, 0, -1.0), (-0.4999999999999997, 0, -0.8660254037844388),
(0.0, 0.0, 0.0), (-0.4999999999999997, 0, -0.8660254037844388), (-0.8660254037844384, 0, -0.5000000000000004),
(0.0, 0.0, 0.0), (-0.8660254037844384, 0, -0.5000000000000004), (-1.0, 0, -1.8369701987210297e-16),
(0.0, 0.0, 0.0), (-1.0, 0, -1.8369701987210297e-16), (-0.866025403784439, 0, 0.49999999999999933),
(0.0, 0.0, 0.0), (-0.866025403784439, 0, 0.49999999999999933), (-0.5000000000000004, 0, 0.8660254037844384),
(0.0, 0.0, 0.0), (-0.5000000000000004, 0, 0.8660254037844384), (0.0, 0, 1.0),
)
class CustomGizmo():
# FIXME: highliting/selection doesnt work
def draw_very_custom_shape(self, ctx, custom_shape, select_id=None):
# similar to draw_custom_shape
shape, batch, shader = custom_shape
shader.bind()
if select_id is not None:
gpu.select.load_id(select_id)
else:
if self.is_highlight:
color = (*self.color_highlight, self.alpha_highlight)
else:
color = (*self.color, self.alpha)
shader.uniform_float('color', color)
shape.glenable()
shape.uniform_region(ctx)
# shader.uniform_float('modelMatrix', self.matrix_world)
with gpu.matrix.push_pop():
gpu.matrix.multiply_matrix(self.matrix_world)
batch.draw()
bgl.glDisable(bgl.GL_BLEND)
class OffsetHandle():
"""Handling mouse to offset gizmo from base along Z axis"""
# FIXME: works a bit weird for rotated objects
def invoke(self, ctx, event):
self.init_value = self.target_get_value('offset') / self.scale_value
coordz = self.project_mouse(ctx, event)
if coordz is None:
return {'CANCELLED'}
self.init_coordz = coordz
return {'RUNNING_MODAL'}
def modal(self, ctx, event, tweak):
coordz = self.project_mouse(ctx, event)
if coordz is None:
return {'CANCELLED'}
delta = coordz - self.init_coordz
if 'PRECISE' in tweak:
delta /= 10.0
value = max(0, self.init_value + delta)
value *= self.scale_value
# ctx.area.header_text_set(f"coords: {self.init_coordz} - {coordz}, delta: {delta}, value: {value}")
ctx.area.header_text_set(f"Depth: {value}")
self.target_set_value('offset', value)
return {'RUNNING_MODAL'}
def project_mouse(self, ctx, event):
"""Projecting mouse coords to local axis Z"""
# logic from source/blender/editors/gizmo_library/gizmo_types/arrow3d_gizmo.c:gizmo_arrow_modal
mouse = Vector((event.mouse_region_x, event.mouse_region_y))
region = ctx.region
region3d = ctx.region_data
ray_orig = view3d_utils.region_2d_to_origin_3d(region, region3d, mouse)
ray_norm = view3d_utils.region_2d_to_vector_3d(region, region3d, mouse)
# 'arrow' origin and direction
base = Vector((0, 0, 0))
axis = Vector((0, 0, 1))
# projecttion of the arrow to a plane, perpendicular to view ray
axis_proj = axis - ray_norm * axis.dot(ray_norm)
# intersection of the axis with the plane through view origin perpendicular to the arrow projection
coords = geometry.intersect_line_plane(base, axis, ray_orig, axis_proj)
return coords.z
def exit(self, ctx, cancel):
if cancel:
self.target_set_value('offset', self.init_value)
else:
self.group.update(ctx)
class UglyDotGizmo(OffsetHandle, types.Gizmo):
"""three orthogonal circles"""
bl_idname = "BIM_GT_uglydot_3d"
bl_target_properties = (
{'id': 'offset', 'type': 'FLOAT', 'array_length': 1},
)
__slots__ = (
'scale_value',
'custom_shape',
'init_value',
'init_coordz',
)
def setup(self):
self.custom_shape = self.new_custom_shape(type='TRIS', verts=X3DISC)
def refresh(self):
offset = self.target_get_value('offset') / self.scale_value
self.matrix_offset.col[3][2] = offset # z-shift
def draw(self, ctx):
self.refresh()
self.draw_custom_shape(self.custom_shape)
def draw_select(self, ctx, select_id):
self.refresh()
self.draw_custom_shape(self.custom_shape, select_id=select_id)
class DotGizmo(CustomGizmo, OffsetHandle, types.Gizmo):
"""Single dot viewport-aligned"""
# FIXME: make it selectable
bl_idname = "BIM_GT_dot_2d"
bl_target_properties = (
{'id': 'offset', 'type': 'FLOAT', 'array_length': 1},
)
__slots__ = (
'scale_value',
'custom_shape',
)
def setup(self):
shader = DotsGizmoShader()
self.custom_shape = shader, shader.batch(pos=((0, 0, 0),)), shader.prog
self.use_draw_scale = False
def refresh(self):
offset = self.target_get_value('offset') / self.scale_value
self.matrix_offset.col[3][2] = offset # z-shifted
def draw(self, ctx):
self.refresh()
self.draw_very_custom_shape(ctx, self.custom_shape)
def draw_select(self, ctx, select_id):
self.refresh()
self.draw_very_custom_shape(ctx, self.custom_shape, select_id=select_id)
# doesn't get called
# def test_select(self, ctx, location):
# pass
class ExtrusionGuidesGizmo(CustomGizmo, types.Gizmo):
"""Extrusion guides
Noninteractive gizmo to indicate extrusion depth and planes.
Draws main segment and orthogonal cross at endpoints.
"""
bl_idname = "BIM_GT_extrusion_guides"
bl_target_properties = (
{'id': 'depth', 'type': 'FLOAT', 'array_length': 1},
)
__slots__ = (
'scale_value',
'custom_shape'
)
def setup(self):
shader = ExtrusionGuidesShader()
self.custom_shape = shader, shader.batch(pos=((0, 0, 0), (0, 0, 1))), shader.prog
self.use_draw_scale = False
def refresh(self):
depth = self.target_get_value('depth') / self.scale_value
self.matrix_offset.col[2][2] = depth # z-scaled
def draw(self, ctx):
self.refresh()
self.draw_very_custom_shape(ctx, self.custom_shape)
class DimensionLabelGizmo(types.Gizmo):
"""Text label for a dimension"""
# does not work properly, fonts are totally screwed up
bl_idname = "BIM_GT_dimension_label"
bl_target_properties = (
{'id': 'value', 'type': 'FLOAT', 'array_length': 1},
)
__slots__ = (
'text_label'
)
def setup(self):
pass
def refresh(self, ctx):
value = self.target_get_value('value')
self.matrix_offset.col[3][2] = value * .5
unit_system = ctx.scene.unit_settings.system
self.text_label = bpy.utils.units.to_string(unit_system, 'LENGTH', value, 3, split_unit=False)
def draw(self, ctx):
self.refresh(ctx)
self.draw_text(ctx)
def draw_text(self, ctx):
font_id = 0
font_size = 16
dpi = ctx.preferences.system.dpi
# pos = self.matrix_world @ Vector((0, 0, 0, 1))
# pos = Vector((0, 0, 0.5))
# region = ctx.region
# region3d = ctx.region_data
# pos = view3d_utils.location_3d_to_region_2d(region, region3d, pos)
# text = self.text_label
blf.size(font_id, font_size, dpi)
blf.position(font_id, 0, 0, 0)
blf.color(font_id, *self.color, self.alpha)
blf.draw(font_id, "ABC")
class ExtrusionWidget(types.GizmoGroup):
bl_idname = "bim.extrusion_widget"
bl_label = "Extrusion Gizmos"
bl_space_type = 'VIEW_3D'
bl_region_type = 'WINDOW'
bl_options = {'3D', 'PERSISTENT', 'SHOW_MODAL_ALL'}
# FIXME: use proper scale from ifc value to blender units
@classmethod
def poll(cls, ctx):
obj = ctx.object
return (obj and obj.type == 'MESH'
and obj.data.BIMMeshProperties.ifc_parameters.get('IfcExtrudedAreaSolid/Depth') is not None)
def setup(self, ctx):
target = ctx.object
prop = target.data.BIMMeshProperties.ifc_parameters.get('IfcExtrudedAreaSolid/Depth')
basis = target.matrix_world.normalized()
theme = ctx.preferences.themes[0].user_interface
gz = self.handle = self.gizmos.new('BIM_GT_uglydot_3d')
gz.matrix_basis = basis
gz.scale_basis = 0.1
gz.color = gz.color_highlight = tuple(theme.gizmo_primary)
gz.alpha = 0.5
gz.alpha_highlight = 1.0
gz.use_draw_modal = True
gz.target_set_prop('offset', prop, 'value')
gz.scale_value = 1000
gz = self.guides = self.gizmos.new('BIM_GT_extrusion_guides')
gz.matrix_basis = basis
gz.color = gz.color_highlight = tuple(theme.gizmo_secondary)
gz.alpha = gz.alpha_highlight = 0.5
gz.use_draw_modal = True
gz.target_set_prop('depth', prop, 'value')
gz.scale_value = 1000
# gz = self.label = self.gizmos.new('GIZMO_GT_dimension_label')
# gz.matrix_basis = basis
# gz.color = tuple(theme.gizmo_secondary)
# gz.alpha = 0.5
# gz.use_draw_modal = True
# gz.target_set_prop('value', target.demo, 'depth')
def refresh(self, ctx):
"""updating gizmos"""
target = ctx.object
basis = target.matrix_world.normalized()
self.handle.matrix_basis = basis
self.guides.matrix_basis = basis
def update(self, ctx):
"""updating object"""
bpy.ops.bim.update_parametric_representation()
# parameters disappear after update
# need to retrieve and rebind them again
bpy.ops.bim.get_representation_ifc_parameters()
target = ctx.object
prop = target.data.BIMMeshProperties.ifc_parameters.get('IfcExtrudedAreaSolid/Depth')
self.handle.target_set_prop('offset', prop, 'value')
self.guides.target_set_prop('depth', prop, 'value')
+70 -4
View File
@@ -3,6 +3,48 @@ import json
import blenderbim.bim.decoration as decoration
from bpy.app.handlers import persistent
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.module.attribute.data import Data as AttributeData
def mode_callback(obj, data):
if (
obj.mode != "OBJECT"
or not obj.data
or not isinstance(obj.data, bpy.types.Mesh)
or not obj.data.BIMMeshProperties.ifc_definition_id
or not bpy.context.scene.BIMGeometryProperties.should_auto_update_mesh_representations
):
return
representation = IfcStore.get_file().by_id(obj.data.BIMMeshProperties.ifc_definition_id)
if representation.RepresentationType == "Tessellation" or representation.RepresentationType == "Brep":
bpy.ops.bim.update_mesh_representation(obj=obj.name)
def name_callback(obj, data):
# Blender material names are up to 63 UTF-8 bytes
if not obj.BIMObjectProperties.ifc_definition_id or "/" not in obj.name or len(bytes(obj.name, "utf-8")) >= 63:
return
element = IfcStore.get_file().by_id(obj.BIMObjectProperties.ifc_definition_id)
if not element.is_a("IfcRoot"):
return
element.Name = "/".join(obj.name.split("/")[1:])
AttributeData.load(obj.BIMObjectProperties.ifc_definition_id)
def subscribe_to(object, data_path, callback):
subscribe_to = object.path_resolve(data_path, False)
bpy.msgbus.subscribe_rna(
key=subscribe_to,
owner=object,
args=(
object,
data_path,
),
notify=callback,
options={
"PERSISTENT",
},
)
@persistent
@@ -10,26 +52,50 @@ def loadIfcStore(scene):
IfcStore.file = None
IfcStore.schema = None
props = bpy.context.scene.BIMProperties
IfcStore.id_map = {int(k): bpy.data.objects.get(v) for k, v in json.loads(props.id_map).items()} if props.id_map else {}
IfcStore.id_map = (
{int(k): bpy.data.objects.get(v) for k, v in json.loads(props.id_map).items()} if props.id_map else {}
)
IfcStore.guid_map = (
{k: bpy.data.objects.get(v) for k, v in json.loads(props.guid_map).items()} if props.id_map else {}
)
# Purge data cache
from blenderbim.bim import modules
for module in modules.values():
if not module:
continue
try:
getattr(getattr(module, 'data'), 'Data').purge()
getattr(getattr(module, "data"), "Data").purge()
except AttributeError:
pass
@persistent
def ensureIfcExported(scene):
if IfcStore.get_file() and not bpy.context.scene.BIMProperties.ifc_file:
# The invocation pops up a file select window.
# This is non-blocking, therefore the Blend file is saved before we export.
bpy.ops.export_ifc.bim("INVOKE_DEFAULT", should_force_resave=True)
@persistent
def storeIdMap(scene):
bpy.context.scene.BIMProperties.id_map = json.dumps({k: v.name for k, v in IfcStore.id_map.items()})
bpy.context.scene.BIMProperties.guid_map = json.dumps({k: v.name for k, v in IfcStore.guid_map.items()})
try:
bpy.context.scene.BIMProperties.id_map = json.dumps({k: v.name for k, v in IfcStore.id_map.items()})
bpy.context.scene.BIMProperties.guid_map = json.dumps({k: v.name for k, v in IfcStore.guid_map.items()})
except:
# Regenerate maps. Is there a better solution for this? It seems fragile.
file = IfcStore.get_file()
IfcStore.id_map = {
o.ifc_definition_id: o.name for o in bpy.data.objects if o.BIMObjectProperties.ifc_definition_id
}
IfcStore.guid_map = {
file.by_id(i).GlobalId: n for i, n in IfcStore.id_map.items() if file.by_id(i).is_a("IfcRoot")
}
# Then attempt to store it again
bpy.context.scene.BIMProperties.id_map = json.dumps({k: v.name for k, v in IfcStore.id_map.items()})
bpy.context.scene.BIMProperties.guid_map = json.dumps({k: v.name for k, v in IfcStore.guid_map.items()})
@persistent
+7 -2
View File
@@ -1,5 +1,6 @@
import bpy
import ifcopenshell
import blenderbim.bim.handler
class IfcStore:
@@ -33,12 +34,16 @@ class IfcStore:
@staticmethod
def link_element(element, obj):
IfcStore.id_map[element.id()] = obj
IfcStore.guid_map[element.GlobalId] = obj
if hasattr(element, "GlobalId"):
IfcStore.guid_map[element.GlobalId] = obj
obj.BIMObjectProperties.ifc_definition_id = element.id()
blenderbim.bim.handler.subscribe_to(obj, "mode", blenderbim.bim.handler.mode_callback)
blenderbim.bim.handler.subscribe_to(obj, "name", blenderbim.bim.handler.name_callback)
@staticmethod
def unlink_element(element, obj=None):
del IfcStore.id_map[element.id()]
del IfcStore.guid_map[element.GlobalId]
if hasattr(element, "GlobalId"):
del IfcStore.guid_map[element.GlobalId]
if obj:
obj.BIMObjectProperties.ifc_definition_id = 0
+81 -116
View File
@@ -21,6 +21,7 @@ import numpy as np
from pathlib import Path
from itertools import cycle
from datetime import datetime
from blenderbim.bim.module.context.data import Data as ContextData
from blenderbim.bim.ifc import IfcStore
from . import schema
@@ -284,7 +285,6 @@ class IfcImporter:
self.settings_native.set(self.settings_native.INCLUDE_CURVES, True)
self.settings_2d = ifcopenshell.geom.settings()
self.settings_2d.set(self.settings_2d.INCLUDE_CURVES, True)
self.existing_elements = {}
self.include_elements = []
self.exclude_elements = []
self.project = None
@@ -297,6 +297,7 @@ class IfcImporter:
self.mesh_shapes = {}
self.time = 0
self.unit_scale = 1
self.added_data = {}
self.native_elements = {}
self.native_data = {}
self.aggregates = {}
@@ -318,13 +319,8 @@ class IfcImporter:
self.profile_code("Purge diffs")
self.load_file()
self.profile_code("Loading file")
self.load_existing_rooted_elements()
self.profile_code("Load existing rooted elements")
self.set_ifc_file()
self.profile_code("Setting file")
if self.ifc_import_settings.should_auto_set_workarounds:
self.auto_set_workarounds()
self.profile_code("Set vendor worksarounds")
self.calculate_unit_scale()
self.profile_code("Calculate unit scale")
self.calculate_model_offset()
@@ -335,8 +331,6 @@ class IfcImporter:
self.profile_code("Create project")
self.create_spatial_hierarchy()
self.profile_code("Create spatial hierarchy")
self.create_type_products()
self.profile_code("Create type products")
self.create_aggregates()
self.profile_code("Create aggregates")
self.create_openings_collection()
@@ -347,14 +341,16 @@ class IfcImporter:
# self.parse_native_elements()
# self.profile_code("Parsing native elements")
self.create_grids()
self.profile_code("Creating grids")
self.profile_code("Create grids")
# TODO: Deprecate
# self.create_native_products()
# self.profile_code("Creating native products")
# self.profile_code("Create native products")
self.create_products()
self.profile_code("Creating meshified products")
self.profile_code("Create products")
self.create_type_products()
self.profile_code("Create type products")
self.create_annotation()
self.profile_code("Creating annotation")
self.profile_code("Create annotation")
self.place_objects_in_spatial_tree()
self.profile_code("Placing objects in spatial tree")
if self.ifc_import_settings.should_merge_by_class:
@@ -376,13 +372,6 @@ class IfcImporter:
self.set_default_context()
self.profile_code("Setting default context")
def auto_set_workarounds(self):
applications = self.file.by_type("IfcApplication")
if not applications:
return
if "prostructures" in applications[0].ApplicationFullName.lower():
self.ifc_import_settings.should_allow_non_element_aggregates = True
def is_element_far_away(self, element):
try:
return self.is_point_far_away(element.ObjectPlacement.RelativePlacement.Location)
@@ -478,6 +467,9 @@ class IfcImporter:
return products
def calculate_model_offset(self):
props = bpy.context.scene.BIMGeoreferenceProperties
if props.has_blender_offset:
return
project = self.file.by_type("IfcProject")[0]
site = self.find_decomposed_ifc_class(project, "IfcSite")
if site and self.is_element_far_away(site[0]):
@@ -531,9 +523,15 @@ class IfcImporter:
for i, point in enumerate(point_list.CoordList):
if len(point) == 3 and self.is_point_far_away(point):
return point[0]
# offset_point = (point[0], point[1], point[2])
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:
continue
elements_checked += 1
if elements_checked > element_checking_threshold:
return
@@ -543,17 +541,6 @@ class IfcImporter:
def apply_blender_offset_to_matrix(self, matrix):
props = bpy.context.scene.BIMGeoreferenceProperties
if props.has_blender_offset and props.blender_offset_type == "OBJECT_PLACEMENT":
test = mathutils.Matrix(
ifcopenshell.util.geolocation.global2local(
matrix,
float(props.blender_eastings) * self.unit_scale,
float(props.blender_northings) * self.unit_scale,
float(props.blender_orthogonal_height) * self.unit_scale,
float(props.blender_x_axis_abscissa),
float(props.blender_x_axis_ordinate),
).tolist()
)
return mathutils.Matrix(
ifcopenshell.util.geolocation.global2local(
matrix,
@@ -628,25 +615,22 @@ class IfcImporter:
def create_type_product(self, element):
self.ifc_import_settings.logger.info("Creating object %s", element)
if element.GlobalId in self.existing_elements:
self.type_products[element.GlobalId] = self.existing_elements[element.GlobalId]
return
representation_map = self.get_type_product_body_representation_map(element)
mesh = None
if self.ifc_import_settings.should_import_type_representations and representation_map:
try:
shape = ifcopenshell.geom.create_shape(self.settings, representation_map.MappedRepresentation)
mesh_name = f"mesh-{shape.id}"
mesh = self.meshes.get(mesh_name)
if mesh is None:
if representation_map:
representation = representation_map.MappedRepresentation
mesh_name = "{}/{}".format(representation.ContextOfItems.id(), representation.id())
mesh = self.meshes.get(mesh_name)
if mesh is None:
try:
shape = ifcopenshell.geom.create_shape(self.settings, representation_map.MappedRepresentation)
mesh = self.create_mesh(element, shape)
self.meshes[mesh_name] = mesh
except:
self.ifc_import_settings.logger.error("Failed to generate shape for %s", element)
except:
self.ifc_import_settings.logger.error("Failed to generate shape for %s", element)
obj = bpy.data.objects.new(self.get_name(element), mesh)
obj.BIMObjectProperties.ifc_definition_id = element.id()
self.link_element(element, obj)
self.material_creator.create(element, obj, mesh)
self.type_collection.objects.link(obj)
self.type_products[element.GlobalId] = obj
def get_type_product_body_representation_map(self, element):
@@ -755,14 +739,10 @@ class IfcImporter:
if not self.ifc_import_settings.should_import_spaces and element.is_a("IfcSpace"):
return
if element.GlobalId in self.existing_elements:
return self.existing_elements[element.GlobalId]
self.ifc_import_settings.logger.info("Creating object %s", element)
if shape:
# TODO: make names more meaningful
mesh_name = f"mesh-{shape.geometry.id}"
mesh_name = self.get_mesh_name(shape.geometry)
mesh = self.meshes.get(mesh_name)
if mesh is None:
if element.GlobalId in self.native_elements:
@@ -778,7 +758,7 @@ class IfcImporter:
mesh = None
obj = bpy.data.objects.new(self.get_name(element), mesh)
IfcStore.link_element(element, obj)
self.link_element(element, obj)
if shape:
m = shape.transformation.matrix.data
@@ -1021,7 +1001,7 @@ class IfcImporter:
def merge_by_class(self):
merge_set = {}
for obj in IfcStore.id_map.values():
for obj in self.added_data.values():
if "/" not in obj.name or "IfcRelAggregates" in obj.users_collection[0].name:
continue
merge_set.setdefault(obj.name.split("/")[0], []).append(obj)
@@ -1029,7 +1009,7 @@ class IfcImporter:
def merge_by_material(self):
merge_set = {}
for obj in IfcStore.id_map.values():
for obj in self.added_data.values():
if "/" not in obj.name or "IfcRelAggregates" in obj.users_collection[0].name:
continue
if not obj.material_slots:
@@ -1055,7 +1035,7 @@ class IfcImporter:
cleaned_material["material"] = bpy.data.materials.new("Merged Material")
cleaned_material["material"].diffuse_color = cleaned_material["diffuse_color"]
for obj in IfcStore.id_map.values():
for obj in self.added_data.values():
if not hasattr(obj, "material_slots") or not obj.material_slots:
continue
for slot in obj.material_slots:
@@ -1079,7 +1059,7 @@ class IfcImporter:
def clean_mesh(self):
obj = None
last_obj = None
for obj in IfcStore.id_map.values():
for obj in self.added_data.values():
if obj.type == "MESH":
obj.select_set(True)
last_obj = obj
@@ -1098,12 +1078,6 @@ class IfcImporter:
return
self.openings[element.GlobalId] = obj
def load_existing_rooted_elements(self):
# TODO: consider how this impacts file reloading, for now we assume you only ever load the same file again
for obj in bpy.data.objects:
if hasattr(obj, "BIMObjectProperties") and obj.BIMObjectProperties.ifc_definition_id:
self.existing_elements[self.file.by_id(obj.BIMObjectProperties.ifc_definition_id).GlobalId] = obj
def load_diff(self):
if not self.ifc_import_settings.diff_file:
return
@@ -1130,18 +1104,11 @@ class IfcImporter:
def set_ifc_file(self):
bpy.context.scene.BIMProperties.ifc_file = self.ifc_import_settings.input_file
IfcStore.path = "self.ifc_import_settings.input_file"
IfcStore.file = self.file
IfcStore.path = self.ifc_import_settings.input_file
def calculate_unit_scale(self):
units = self.file.by_type("IfcUnitAssignment")[0]
for unit in units.Units:
if not hasattr(unit, "UnitType") or unit.UnitType != "LENGTHUNIT":
continue
while unit.is_a("IfcConversionBasedUnit"):
self.unit_scale *= unit.ConversionFactor.ValueComponent.wrappedValue
unit = unit.ConversionFactor.UnitComponent
if unit.is_a("IfcSIUnit"):
self.unit_scale *= ifcopenshell.util.unit.get_prefix_multiplier(unit.Prefix)
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(self.file)
def set_units(self):
units = self.file.by_type("IfcUnitAssignment")[0]
@@ -1174,9 +1141,6 @@ class IfcImporter:
def create_project(self):
self.project = {"ifc": self.file.by_type("IfcProject")[0]}
if self.project["ifc"].GlobalId in self.existing_elements:
self.project["blender"] = self.existing_elements[self.project["ifc"].GlobalId].users_collection[0]
return
self.project["blender"] = bpy.data.collections.new("IfcProject/{}".format(self.project["ifc"].Name))
obj = self.create_product(self.project["ifc"])
if obj:
@@ -1192,35 +1156,19 @@ class IfcImporter:
if element.is_a("IfcSpace"):
continue
global_id = element.GlobalId
if global_id in self.existing_elements:
collection = self.existing_elements[global_id].users_collection[0]
self.spatial_structure_elements[global_id] = {"blender": collection}
else:
collection = bpy.data.collections.new(self.get_name(element))
self.spatial_structure_elements[global_id] = {"blender": collection}
parent.children.link(collection)
obj = self.create_product(element)
if obj:
self.spatial_structure_elements[global_id]["blender_obj"] = obj
collection.objects.link(obj)
collection = bpy.data.collections.new(self.get_name(element))
self.spatial_structure_elements[global_id] = {"blender": collection}
parent.children.link(collection)
obj = self.create_product(element)
if obj:
self.spatial_structure_elements[global_id]["blender_obj"] = obj
collection.objects.link(obj)
if element.IsDecomposedBy:
for rel_aggregate in element.IsDecomposedBy:
self.add_related_objects(collection, rel_aggregate.RelatedObjects)
def create_aggregates(self):
if self.ifc_import_settings.should_allow_non_element_aggregates:
if self.file.schema == "IFC2X3":
rel_aggregates = [
a
for a in self.file.by_type("IfcRelAggregates")
if not a.RelatingObject.is_a("IfcSpatialStructureElement")
]
else:
rel_aggregates = [
a for a in self.file.by_type("IfcRelAggregates") if not a.RelatingObject.is_a("IfcSpatialElement")
]
else:
rel_aggregates = [a for a in self.file.by_type("IfcRelAggregates") if a.RelatingObject.is_a("IfcElement")]
rel_aggregates = [a for a in self.file.by_type("IfcRelAggregates") if a.RelatingObject.is_a("IfcElement")]
for rel_aggregate in rel_aggregates:
self.create_aggregate(rel_aggregate)
@@ -1228,17 +1176,27 @@ class IfcImporter:
element = rel_aggregate.RelatingObject
obj = bpy.data.objects.new("{}/{}".format(element.is_a(), element.Name), None)
IfcStore.link_element(element, obj)
self.place_object_in_spatial_tree(element, obj)
self.link_element(element, obj)
container = self.get_aggregate_spatial_container(element)
container_collection = self.spatial_structure_elements[container.GlobalId]["blender"]
collection = bpy.data.collections.new(obj.name)
obj.users_collection[0].children.link(collection)
obj.users_collection[0].objects.unlink(obj)
container_collection.children.link(collection)
collection.objects.link(obj)
self.aggregates[element.GlobalId] = obj
self.aggregate_collections[rel_aggregate.id()] = collection
def get_aggregate_spatial_container(self, element):
if hasattr(element, "ContainedInStructure") and element.ContainedInStructure:
container = element.ContainedInStructure[0].RelatingStructure
elif hasattr(element, "Decomposes") and element.Decomposes:
return self.get_aggregate_spatial_container(element.Decomposes[0].RelatingObject)
if container.is_a("IfcSpace"):
return self.get_aggregate_spatial_container(container)
return container
def create_openings_collection(self):
self.opening_collection = bpy.data.collections.new("IfcOpeningElements")
self.project["blender"].children.link(self.opening_collection)
@@ -1259,12 +1217,14 @@ class IfcImporter:
bpy.ops.object.delete({"selected_objects": objects_to_purge})
def place_objects_in_spatial_tree(self):
for ifc_definition_id, obj in IfcStore.id_map.items():
self.place_object_in_spatial_tree(self.file.by_id(ifc_definition_id), obj)
for global_id, obj in self.added_data.items():
self.place_object_in_spatial_tree(self.file.by_guid(global_id), obj)
def place_object_in_spatial_tree(self, element, obj):
if element.is_a("IfcProject"):
return
elif element.is_a("IfcTypeObject"):
self.type_collection.objects.link(obj)
elif element.GlobalId in self.spatial_structure_elements:
if not obj.data:
return
@@ -1388,6 +1348,16 @@ class IfcImporter:
):
return representation.Items[0].MappingTarget
def get_mesh_name(self, geometry):
representation_id = geometry.id
if "-" in representation_id:
representation_id = int(re.sub(r"\D", "", representation_id.split("-")[0]))
else:
representation_id = int(re.sub(r"\D", "", representation_id))
representation = self.file.by_id(representation_id)
context_id = representation.ContextOfItems.id() if hasattr(representation, "ContextOfItems") else 0
return "{}/{}".format(context_id, representation_id)
def create_mesh(self, element, shape, is_curve=False):
try:
if hasattr(shape, "geometry"):
@@ -1398,23 +1368,16 @@ class IfcImporter:
if is_curve:
return self.create_curve(geometry)
representation_id = geometry.id
if "-" in representation_id:
representation_id = int(re.sub(r"\D", "", representation_id.split("-")[0]))
else:
representation_id = int(re.sub(r"\D", "", representation_id))
representation = self.file.by_id(representation_id)
context_id = representation.ContextOfItems.id() if hasattr(representation, "ContextOfItems") else 0
mesh = bpy.data.meshes.new("{}/{}".format(context_id, representation_id))
mesh = bpy.data.meshes.new(self.get_mesh_name(geometry))
props = bpy.context.scene.BIMGeoreferenceProperties
if props.has_blender_offset and props.blender_offset_type == "CARTESIAN_POINT":
ordinate_index = 0
verts = [None] * len(geometry.verts)
offset_point = (
float(props.blender_eastings),
float(props.blender_northings),
float(props.blender_orthogonal_height),
float(props.blender_eastings) * self.unit_scale,
float(props.blender_northings) * self.unit_scale,
float(props.blender_orthogonal_height) * self.unit_scale,
)
for i, vert in enumerate(geometry.verts):
if ordinate_index > 2:
@@ -1568,13 +1531,16 @@ class IfcImporter:
bpy.context.scene.BIMProperties.contexts = str(subcontext.id())
break
def link_element(self, element, obj):
self.added_data[element.GlobalId] = obj
IfcStore.link_element(element, obj)
class IfcImportSettings:
def __init__(self):
self.logger = None
self.input_file = None
self.diff_file = None
self.should_import_type_representations = False
self.should_import_spaces = False
self.should_auto_set_workarounds = True
self.should_use_cpu_multiprocessing = True
@@ -1584,7 +1550,6 @@ class IfcImportSettings:
self.should_clean_mesh = True
self.deflection_tolerance = 0.001
self.angular_tolerance = 0.5
self.should_allow_non_element_aggregates = False
self.should_offset_model = False
self.model_offset_coordinates = (0, 0, 0)
self.ifc_import_filter = "NONE"
@@ -50,6 +50,12 @@ def draw_ui(context, layout, obj_type):
op = row.operator("bim.enable_editing_attributes", icon="GREASEPENCIL", text="Edit")
op.obj_type = obj_type
op.obj = obj.name
if "GlobalId" not in [a["name"] for a in Data.products[oprops.ifc_definition_id]]:
row = layout.row(align=True)
row.label(text="STEP ID")
row.label(text=str(oprops.ifc_definition_id))
for attribute in Data.products[oprops.ifc_definition_id]:
if attribute["value"] is None or attribute["type"] == "entity":
continue
@@ -0,0 +1,18 @@
import bpy
from . import ui, prop, operator
classes = (
prop.AuginProperties,
ui.BIM_PT_augin,
operator.AuginLogin,
operator.AuginCreateNewModel,
operator.AuginReset,
)
def register():
bpy.types.Scene.AuginProperties = bpy.props.PointerProperty(type=prop.AuginProperties)
def unregister():
del bpy.types.Scene.AuginProperties
@@ -0,0 +1,133 @@
import os
import bpy
import json
import string
import random
import shutil
import requests
import tempfile
import datetime
import addon_utils
class AuginLogin(bpy.types.Operator):
bl_idname = "bim.augin_login"
bl_label = "Login to Augin"
def execute(self, context):
props = bpy.context.scene.AuginProperties
url = "https://server.auge.pro.br/API/v3/augin_rest.php/user_login"
payload = {"email": props.username, "password": props.password}
response = requests.request("POST", url, data=payload)
result = response.json()
if result["success_feedback"]["success"]:
props.token = result["token"]
else:
self.report({"ERROR"}, "Login failed :(")
return {"FINISHED"}
class AuginReset(bpy.types.Operator):
bl_idname = "bim.augin_reset"
bl_label = "Upload Another Project"
def execute(self, context):
props = bpy.context.scene.AuginProperties
props.is_success = False
return {"FINISHED"}
class AuginCreateNewModel(bpy.types.Operator):
bl_idname = "bim.augin_create_new_model"
bl_label = "Create New Augin Model"
def execute(self, context):
import boto3
from botocore.config import Config
props = bpy.context.scene.AuginProperties
# Create project
url = "https://server.auge.pro.br/API/v3/augin_rest.php/new_model"
chars = string.ascii_uppercase + string.ascii_lowercase + string.digits
props.project_filename = "BBM{}{}".format(
datetime.datetime.now().strftime("%Y%m%d%H%M%S"), "".join(random.choice(chars) for _ in range(6))
)
addon_version = ".".join(
[
str(x)
for x in [
addon.bl_info.get("version", (-1, -1, -1))
for addon in addon_utils.modules()
if addon.bl_info["name"] == "BlenderBIM"
][0]
]
)
payload = {
"user_token": props.token,
"partner_token": "Mmf2zpxdgrkHaUNRm6Cv7GLJdBAeb7Rn",
"project_name": props.project_name,
"model_filename": props.project_filename + ".ifc",
"thumb_filename": props.project_filename + ".png",
"app_name": "BlenderBIM",
"sw_version": bpy.app.version_string,
"plugin_version": addon_version,
}
response = requests.request("POST", url, data=payload)
result = response.json()
# Upload to S3
my_config = Config(
region_name=result["s3_region"],
)
client = boto3.client(
"s3",
aws_access_key_id=result["s3_key"],
aws_secret_access_key=result["s3_secret"],
aws_session_token=result["s3_token"],
config=my_config,
)
# Generate thumb
old_file_format = context.scene.render.image_settings.file_format
old_filepath = context.scene.render.filepath
cam = bpy.data.cameras.new("Camera")
cam_obj = bpy.data.objects.new("Camera", cam)
context.scene.collection.objects.link(cam_obj)
context.scene.camera = cam_obj
tmpdir = tempfile.mkdtemp()
thumb_path = os.path.join(tmpdir, "thumb.png")
context.scene.render.image_settings.file_format = "PNG"
context.scene.render.filepath = thumb_path
bpy.ops.render.opengl(write_still=True)
context.scene.render.image_settings.file_format = old_file_format
context.scene.render.filepath = old_filepath
client.upload_file(bpy.context.scene.BIMProperties.ifc_file, result["s3_bucket"], result["model_path"])
client.upload_file(thumb_path, result["s3_bucket"], result["thumb_path"])
# Notify done
url = "https://server.auge.pro.br/API/v3/augin_rest.php/files_uploaded"
payload = {
"user_token": props.token,
"ifc_filesize": os.path.getsize(bpy.context.scene.BIMProperties.ifc_file),
"model_filesize": os.path.getsize(bpy.context.scene.BIMProperties.ifc_file),
"thumb_filesize": os.path.getsize(thumb_path),
"model_upload_path": result["model_path"],
"thumb_upload_path": result["thumb_path"],
"model_id": result["model_id"],
}
headers = {"Content-Type": "application/x-www-form-urlencoded"}
response = requests.request("POST", url, headers=headers, data=payload)
shutil.rmtree(tmpdir)
props.is_success = True
return {"FINISHED"}
@@ -0,0 +1,11 @@
import bpy.props
import bpy.types
class AuginProperties(bpy.types.PropertyGroup):
username: bpy.props.StringProperty(name="Username")
password: bpy.props.StringProperty(name="Password")
token: bpy.props.StringProperty(name="Token")
project_name: bpy.props.StringProperty(name="Project Name")
project_filename: bpy.props.StringProperty(name="IFC Filename")
is_success: bpy.props.BoolProperty(name="Is Successful Upload", default=False)
@@ -0,0 +1,45 @@
import bpy.types
class BIM_PT_augin(bpy.types.Panel):
bl_label = "Augin"
bl_idname = "BIM_PT_augin"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
def draw(self, context):
layout = self.layout
layout.use_property_split = True
scene = context.scene
props = scene.AuginProperties
if not props.token:
row = layout.row()
row.prop(props, "username")
row = layout.row()
row.prop(props, "password")
row = layout.row()
row.operator("bim.augin_login")
return
row = layout.row()
row.label(text="Logged in as " + props.username)
if not bpy.context.scene.BIMProperties.ifc_file:
row = layout.row()
row.label(text="No IFC Found")
return
if props.is_success:
row = layout.row()
row.label(text="Upload Successful")
row = layout.row()
row.operator("bim.augin_reset")
else:
row = layout.row()
row.prop(props, "project_name")
row = layout.row()
row.operator("bim.augin_create_new_model")
@@ -3,9 +3,14 @@ import bpy
import tempfile
import webbrowser
import ifcopenshell
import bimtester
import bimtester.run
import bimtester.reports
try:
import bimtester
import bimtester.run
import bimtester.reports
except:
print("Failed to load BIMTester. Try disabling other add-ons, in particular Blender-OSM. See bug #1318.")
from pathlib import Path
from itertools import cycle
from blenderbim.bim.ifc import IfcStore
@@ -31,7 +36,7 @@ class ExecuteBIMTester(bpy.types.Operator):
"schema_file": "",
"schema_name": "",
"lang": "en",
"steps": props.steps
"steps": props.steps,
}
use_stored_ifc = props.should_load_from_memory and IfcStore.get_file()
runner = bimtester.run.TestRunner(args["ifc"], ifc=IfcStore.get_file() if use_stored_ifc else None)
@@ -59,9 +64,9 @@ class BIMTesterPurge(bpy.types.Operator):
class SelectFeature(bpy.types.Operator):
bl_idname = "bim.select_feature"
bl_label = "Select Feature"
bl_label = "Select Feature / IDS"
filename_ext = ".feature"
filter_glob: bpy.props.StringProperty(default="*.feature", options={"HIDDEN"})
filter_glob: bpy.props.StringProperty(default="*.feature;*.xml", options={"HIDDEN"})
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def execute(self, context):
@@ -1,6 +1,5 @@
import os
from pathlib import Path
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.prop import StrProperty
from blenderbim.bim.module.root.prop import getIfcClasses
from bpy.types import PropertyGroup
@@ -41,7 +40,7 @@ def refreshScenarios(self, context):
class BimTesterProperties(PropertyGroup):
feature: StringProperty(default="", name="Feature File", update=refreshScenarios)
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")
@@ -42,7 +42,6 @@ class BIM_PT_constraints(Panel):
if self.props.active_constraint_id:
self.draw_editable_ui(context)
return
def draw_editable_ui(self, context):
for attribute in self.props.constraint_attributes:
@@ -18,10 +18,7 @@ class Data:
cls.contexts = {}
for context in file.by_type("IfcGeometricRepresentationContext", include_subtypes=False):
subcontexts = {}
# See bug #1224 for why we don't use HasSubContexts
for subcontext in file.by_type("IfcGeometricRepresentationSubContext"):
if subcontext.ParentContext != context:
continue
for subcontext in context.HasSubContexts:
subcontexts[int(subcontext.id())] = {
"ContextType": subcontext.ContextType,
"ContextIdentifier": subcontext.ContextIdentifier,
@@ -13,8 +13,11 @@ class ProfileImportIFC(bpy.types.Operator):
import cProfile
import pstats
# For Windows
filepath = bpy.context.scene.BIMProperties.ifc_file.replace('\\', '\\\\')
cProfile.run(
f"import bpy; bpy.ops.import_ifc.bim(filepath='{bpy.context.scene.BIMProperties.ifc_file}')", "blender.prof"
f"import bpy; bpy.ops.import_ifc.bim(filepath='{filepath}')", "blender.prof"
)
p = pstats.Stats("blender.prof")
p.sort_stats("cumulative").print_stats(50)
@@ -84,9 +87,6 @@ class SelectHighPolygonMeshes(bpy.types.Operator):
except:
# If it is not in the view layer
pass
relating_type = obj.BIMObjectProperties.relating_type
if relating_type:
relating_type.select_set(True)
return {"FINISHED"}
@@ -41,12 +41,7 @@ class BIM_PT_documents(Panel):
if self.props.is_editing:
self.layout.template_list(
"BIM_UL_documents",
"",
self.props,
"documents",
self.props,
"active_document_index",
"BIM_UL_documents", "", self.props, "documents", self.props, "active_document_index"
)
if self.props.active_document_id:
@@ -0,0 +1,14 @@
import bpy
from . import operator
classes = (
operator.AddDrawing,
)
def register():
pass
def unregister():
pass
@@ -0,0 +1,33 @@
import bpy
from blenderbim.bim.ifc import IfcStore
class AddDrawing(bpy.types.Operator):
bl_idname = "bim.add_drawing"
bl_label = "Add Drawing"
def execute(self, context):
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.7) # The view shall be 1.7m 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")
bpy.ops.bim.activate_drawing_style()
return {"FINISHED"}
@@ -4,6 +4,7 @@ from . import ui, prop, operator
classes = (
operator.EditObjectPlacement,
operator.AddRepresentation,
operator.MapRepresentations,
operator.MapRepresentation,
operator.SwitchRepresentation,
operator.RemoveRepresentation,
@@ -2,6 +2,7 @@ import bpy
import bmesh
import ifcopenshell.util.unit
from mathutils import Vector
from blenderbim.bim.module.geometry.helper import Helper
class Usecase:
@@ -12,6 +13,7 @@ class Usecase:
"context": None, # IfcGeometricRepresentationContext
"blender_object": None, # This is (currently) a Blender object, hence this depends on Blender now
"geometry": None, # This is (currently) a Blender data object, hence this depends on Blender now
"coordinate_offset": None, # Optionally apply a vector offset to all coordinates
"total_items": 1, # How many representation items to create
"unit_scale": None, # A scale factor to apply for all vectors in case the unit is different
"should_force_faceted_brep": False, # If we should force faceted breps for meshes
@@ -19,13 +21,19 @@ class Usecase:
"is_wireframe": False, # If the geometry is a wireframe
"is_curve": False, # If the geometry is a Blender curve
"is_point_cloud": False, # If the geometry is a point cloud
# Possible IFC representation classes:
# IfcExtrudedAreaSolid/IfcRectangleProfileDef
# IfcExtrudedAreaSolid/IfcCircleProfileDef
# IfcExtrudedAreaSolid/IfcArbitraryClosedProfileDef
"ifc_representation_class": None, # Whether to cast a mesh into a particular class
}
self.ifc_vertices = []
for key, value in settings.items():
self.settings[key] = value
def execute(self):
self.evaluate_geometry()
if isinstance(self.settings["geometry"], bpy.types.Mesh):
self.evaluate_geometry()
if self.settings["unit_scale"] is None:
self.settings["unit_scale"] = ifcopenshell.util.unit.calculate_unit_scale(self.file)
if self.settings["context"].ContextType == "Model":
@@ -123,8 +131,52 @@ class Usecase:
return self.create_curve_representation()
elif self.settings["is_point_cloud"]:
return self.create_point_cloud_representation()
elif isinstance(self.settings["geometry"], bpy.types.Camera):
return self.create_camera_block_representation()
elif self.settings["ifc_representation_class"] == "IfcExtrudedAreaSolid/IfcRectangleProfileDef":
return self.create_rectangle_extrusion_representation()
elif self.settings["ifc_representation_class"] == "IfcExtrudedAreaSolid/IfcCircleProfileDef":
return self.create_circle_extrusion_representation()
elif self.settings["ifc_representation_class"] == "IfcExtrudedAreaSolid/IfcArbitraryClosedProfileDef":
return self.create_arbitrary_extrusion_representation()
return self.create_mesh_representation()
def create_camera_block_representation(self):
raster_x = self.settings["geometry"].BIMCameraProperties.raster_x
raster_y = self.settings["geometry"].BIMCameraProperties.raster_y
if self.is_camera_landscape():
width = self.settings["geometry"].ortho_scale
height = width / raster_x * raster_y
else:
height = self.settings["geometry"].ortho_scale
width = height / raster_y * raster_x
block = self.file.create_entity(
"IfcBlock",
**{
"Position": self.file.createIfcAxis2Placement3D(
self.create_cartesian_point(-width / 2, -height / 2, -self.settings["geometry"].clip_end)
),
"XLength": self.convert_si_to_unit(width),
"YLength": self.convert_si_to_unit(height),
"ZLength": self.convert_si_to_unit(self.settings["geometry"].clip_end),
}
)
return self.file.createIfcShapeRepresentation(
self.settings["context"],
self.settings["context"].ContextIdentifier,
"CSG",
[self.file.createIfcCsgSolid(block)],
)
def is_camera_landscape(self):
return (
self.settings["geometry"].BIMCameraProperties.raster_x
> self.settings["geometry"].BIMCameraProperties.raster_y
)
def create_curve3d_representation(self):
return self.file.createIfcShapeRepresentation(
self.settings["context"],
@@ -214,6 +266,42 @@ class Usecase:
results.append(self.file.createIfcPolyline(points))
return results
def create_rectangle_extrusion_representation(self):
helper = Helper(self.file)
indices = helper.auto_detect_rectangle_profile_extruded_area_solid(self.settings["geometry"])
profile_def = helper.create_rectangle_profile_def(self.settings["geometry"], indices["profile"])
item = helper.create_extruded_area_solid(self.settings["geometry"], indices["extrusion"], profile_def)
return self.file.createIfcShapeRepresentation(
self.settings["context"],
self.settings["context"].ContextIdentifier,
"SweptSolid",
[item],
)
def create_circle_extrusion_representation(self):
helper = Helper(self.file)
indices = helper.auto_detect_circle_profile_extruded_area_solid(self.settings["geometry"])
profile_def = helper.create_circle_profile_def(self.settings["geometry"], indices["profile"])
item = helper.create_extruded_area_solid(self.settings["geometry"], indices["extrusion"], profile_def)
return self.file.createIfcShapeRepresentation(
self.settings["context"],
self.settings["context"].ContextIdentifier,
"SweptSolid",
[item],
)
def create_arbitrary_extrusion_representation(self):
helper = Helper(self.file)
indices = helper.auto_detect_arbitrary_closed_profile_extruded_area_solid(self.settings["geometry"])
profile_def = helper.create_arbitrary_closed_profile_def(self.settings["geometry"], indices["profile"])
item = helper.create_extruded_area_solid(self.settings["geometry"], indices["extrusion"], profile_def)
return self.file.createIfcShapeRepresentation(
self.settings["context"],
self.settings["context"].ContextIdentifier,
"SweptSolid",
[item],
)
def create_mesh_representation(self):
if self.file.schema == "IFC2X3" or self.settings["should_force_faceted_brep"]:
return self.create_faceted_brep()
@@ -290,6 +378,8 @@ class Usecase:
return self.file.createIfcCartesianPointList3D([self.convert_si_to_unit(v.co) for v in vertices])
def convert_si_to_unit(self, co):
if self.settings["coordinate_offset"]:
return (co / self.settings["unit_scale"]) + self.settings["coordinate_offset"]
return co / self.settings["unit_scale"]
def create_geometric_curve_set_representation(self, is_2d=False):
@@ -17,9 +17,15 @@ class Data:
return
cls.products[product_id] = []
product = file.by_id(product_id)
if not hasattr(product, "Representation") or not product.Representation:
return
for representation in product.Representation.Representations:
representations = []
if product.is_a("IfcProduct"):
if product.Representation:
representations = product.Representation.Representations
else:
representations = []
elif product.is_a("IfcTypeProduct"):
representations = [rm.MappedRepresentation for rm in product.RepresentationMaps or []]
for representation in representations:
c = representation.ContextOfItems
rep_id = int(representation.id())
cls.representations[rep_id] = {
@@ -0,0 +1,253 @@
import bpy
import bmesh
import ifcopenshell
import ifcopenshell.util.unit
from math import pi
from mathutils import Vector, Matrix
class Helper:
def __init__(self, file):
self.file = file
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(self.file)
# We can detect a rectangular extrusion by picking any face, then find an
# edge that shares a single vertex only with that face to find the extrusion
# edge. A face with the normal facing down is prioritised. A limited
# dissolve ensure that faces are quads and not tris.
def auto_detect_rectangle_profile_extruded_area_solid(self, mesh):
bm = bmesh.new()
bm.from_mesh(mesh)
bmesh.ops.dissolve_limit(bm, angle_limit=pi / 180 * 5, verts=bm.verts, edges=bm.edges)
bm.faces.ensure_lookup_table()
face = None
for face in bm.faces:
if face.normal.z < -0.1:
break
profile = [l.vert.index for l in face.loops]
extrusion = self.detect_extrusion_edge(bm, face)
bm.to_mesh(mesh)
mesh.update()
bm.free()
return {"profile": profile, "extrusion": extrusion}
# After a limited dissolve, we detect the circle profile as it should be the
# only ngon (this assumes the circle has at least a facetation of > 4 edges.
# The extrusion direction is any edge that only shares a single vertex with
# the profile. We prioritise the profile that has a downwards normal.
def auto_detect_circle_profile_extruded_area_solid(self, mesh):
bm = bmesh.new()
bm.from_mesh(mesh)
bmesh.ops.dissolve_limit(bm, angle_limit=pi / 180 * 5, verts=bm.verts, edges=bm.edges)
bm.faces.ensure_lookup_table()
potential_faces = []
for face in bm.faces:
if len(face.verts) > 4:
potential_faces.append(face)
for face in potential_faces:
if face.normal.z < -0.1:
break
profile = [l.vert.index for l in face.loops]
extrusion = self.detect_extrusion_edge(bm, face)
bm.to_mesh(mesh)
mesh.update()
bm.free()
return {"profile": profile, "extrusion": extrusion}
# After a limited dissolve, the arbitrary profile is any ngon or tri.
# Failing that, it is equivalent to a rectangular profile. The extrusion
# direction is any edge that only shares a single vertex with the profile.
# We prioritise the profile that has a downwards normal.
def auto_detect_arbitrary_closed_profile_extruded_area_solid(self, mesh):
bm = bmesh.new()
bm.from_mesh(mesh)
bmesh.ops.dissolve_limit(bm, angle_limit=pi / 180 * 5, verts=bm.verts, edges=bm.edges)
bm.faces.ensure_lookup_table()
potential_faces = []
for face in bm.faces:
total_verts = len(face.verts)
if total_verts > 4 or total_verts == 3:
potential_faces.append(face)
if not potential_faces:
potential_faces = bm.faces
for face in potential_faces:
if face.normal.z < -0.1:
break
profile = [l.vert.index for l in face.loops]
extrusion = self.detect_extrusion_edge(bm, face)
bm.to_mesh(mesh)
mesh.update()
bm.free()
return {"profile": profile, "extrusion": extrusion}
def detect_extrusion_edge(self, bm, profile_face):
bm.edges.ensure_lookup_table()
extrusion = None
face_verts_set = set(profile_face.verts)
for edge in bm.edges:
unshared_verts = set(edge.verts) - face_verts_set
if len(unshared_verts) == 1:
if unshared_verts.pop() == edge.verts[1]:
return [edge.verts[0].index, edge.verts[1].index]
return [edge.verts[1].index, edge.verts[0].index]
def create_extruded_area_solid(self, mesh, extrusion_indices, profile_def):
position = self.create_ifc_axis_2_placement_3d(
profile_def["curve_ucs"]["center"], profile_def["curve_ucs"]["z_axis"], profile_def["curve_ucs"]["x_axis"]
)
direction = self.get_extrusion_direction(mesh, extrusion_indices, profile_def["curve_ucs"])
unit_direction = direction.normalized()
return self.file.createIfcExtrudedAreaSolid(
profile_def["curve"],
position,
self.file.createIfcDirection((unit_direction.x, unit_direction.y, unit_direction.z)),
self.convert_si_to_unit(direction.length),
)
def create_arbitrary_closed_profile_def(self, mesh, profile_indices):
curve_ucs = self.get_curve_profile_coordinate_system(mesh, profile_indices)
outer_curve = self.create_polyline_from_loop(mesh, profile_indices, curve_ucs)
curve = self.file.createIfcArbitraryClosedProfileDef("AREA", None, outer_curve)
return {"curve_ucs": curve_ucs, "curve": curve}
def create_rectangle_profile_def(self, mesh, profile_indices):
curve_ucs = self.get_curve_profile_coordinate_system(mesh, profile_indices)
xdim = self.convert_si_to_unit(
(mesh.vertices[profile_indices[0]].co - mesh.vertices[profile_indices[1]].co).length
)
ydim = self.convert_si_to_unit(
(mesh.vertices[profile_indices[1]].co - mesh.vertices[profile_indices[2]].co).length
)
curve = self.file.createIfcRectangleProfileDef("AREA", None, None, xdim, ydim)
return {"curve_ucs": curve_ucs, "curve": curve}
def create_circle_profile_def(self, mesh, profile_indices):
curve_ucs = self.get_curve_profile_coordinate_system(mesh, profile_indices)
radius = self.convert_si_to_unit(
abs((mesh.vertices[profile_indices[0]].co - mesh.vertices[profile_indices[int(len(profile_indices) / 2)]].co).length) / 2
)
center = Vector((0, 0))
position = self.create_ifc_axis_2_placement_2d(center, Vector((1, 0)))
curve = self.file.createIfcCircleProfileDef("AREA", None, position, radius)
return {"curve_ucs": curve_ucs, "curve": curve}
# Not used anywhere, but probably useful in the future
def get_loop_from_v_indices(self, obj, indices):
edges = self.get_edges_in_v_indices(obj, indices)
loop = self.get_loop_from_edges(edges)
loop.pop(-1)
return loop
def get_loop_from_edges(self, edges):
while edges:
currentEdge = edges.pop()
startVert = currentEdge.vertices[0]
endVert = currentEdge.vertices[1]
polyLine = [startVert, endVert]
ok = 1
while ok:
ok = 0
i = len(edges)
while i:
i -= 1
ed = edges[i]
if ed.vertices[0] == endVert:
polyLine.append(ed.vertices[1])
endVert = polyLine[-1]
ok = 1
del edges[i]
elif ed.vertices[1] == endVert:
polyLine.append(ed.vertices[0])
endVert = polyLine[-1]
ok = 1
del edges[i]
elif ed.vertices[0] == startVert:
polyLine.insert(0, ed.vertices[1])
startVert = polyLine[0]
ok = 1
del edges[i]
elif ed.vertices[1] == startVert:
polyLine.insert(0, ed.vertices[0])
startVert = polyLine[0]
ok = 1
del edges[i]
return polyLine
def get_edges_in_v_indices(self, obj, indices):
return [e for e in obj.data.edges if (e.vertices[0] in indices and e.vertices[1] in indices)]
def get_curve_profile_coordinate_system(self, mesh, loop):
profile_face = bpy.data.meshes.new("profile_face")
profile_verts = [(mesh.vertices[p].co.x, mesh.vertices[p].co.y, mesh.vertices[p].co.z) for p in loop]
profile_faces = [tuple(range(0, len(profile_verts)))]
profile_face.from_pydata(profile_verts, [], profile_faces)
center = profile_face.polygons[0].center
if (mesh.vertices[loop[1]].co - mesh.vertices[loop[0]].co).length < 0.01:
x_axis = (mesh.vertices[loop[0]].co - center).normalized()
else:
x_axis = (mesh.vertices[loop[1]].co - mesh.vertices[loop[0]].co).normalized()
z_axis = profile_face.polygons[0].normal.normalized()
y_axis = z_axis.cross(x_axis).normalized()
matrix = Matrix((x_axis, y_axis, z_axis))
matrix.normalize()
return {
"center": center,
"x_axis": x_axis,
"y_axis": y_axis,
"z_axis": z_axis,
"matrix": matrix.to_4x4() @ Matrix.Translation(-center),
}
def convert_si_to_unit(self, co):
return co / self.unit_scale
def create_polyline_from_loop(self, mesh, loop, curve_ucs):
points = []
for point in loop:
transformed_point = curve_ucs["matrix"] @ mesh.vertices[point].co
points.append(self.create_cartesian_point(transformed_point.x, transformed_point.y))
points.append(points[0])
return self.file.createIfcPolyline(points)
def create_cartesian_point(self, x, y, z=None):
x = self.convert_si_to_unit(x)
y = self.convert_si_to_unit(y)
if z is None:
return self.file.createIfcCartesianPoint((x, y))
z = self.convert_si_to_unit(z)
return self.file.createIfcCartesianPoint((x, y, z))
def get_extrusion_direction(self, mesh, extrusion_indices, curve_ucs):
return curve_ucs["matrix"] @ (
curve_ucs["center"] + (mesh.vertices[extrusion_indices[1]].co - mesh.vertices[extrusion_indices[0]].co)
)
def get_start_and_end_of_extrusion(self, profile_points, extrusion_edge):
if extrusion_edge.vertices[0] in profile_points:
return (extrusion_edge.vertices[0], extrusion_edge.vertices[1])
return (extrusion_edge.vertices[1], extrusion_edge.vertices[0])
def create_ifc_axis_2_placement_2d(self, point, forward):
return self.file.createIfcAxis2Placement2D(
self.create_cartesian_point(point.x, point.y), self.file.createIfcDirection((forward.x, forward.y))
)
def create_ifc_axis_2_placement_3d(self, point, up, forward):
return self.file.createIfcAxis2Placement3D(
self.create_cartesian_point(point.x, point.y, point.z),
self.file.createIfcDirection((up.x, up.y, up.z)),
self.file.createIfcDirection((forward.x, forward.y, forward.z)),
)
@@ -1,26 +1,24 @@
class Usecase:
def __init__(self, file, settings=None):
self.file = file
self.settings = {
"product": None,
"representation": None,
}
self.settings = {"representation": None}
self.ifc_vertices = []
for key, value in settings.items():
self.settings[key] = value
def execute(self):
self.zero = self.file.createIfcCartesianPoint((0.0, 0.0, 0.0))
self.x_axis = self.file.createIfcDirection((1.0, 0.0, 0.0))
self.y_axis = self.file.createIfcDirection((0.0, 1.0, 0.0))
self.z_axis = self.file.createIfcDirection((0.0, 0.0, 1.0))
mapping_source = self.get_mapping_source()
self.get_mapping_source()
if not mapping_source:
return
mapping_target = self.file.createIfcCartesianTransformationOperator3D(
self.x_axis, self.y_axis, self.zero, 1, self.z_axis
)
mapped_item = self.file.createIfcMappedItem(self.get_mapping_source(), mapping_target)
zero = self.file.createIfcCartesianPoint((0.0, 0.0, 0.0))
x_axis = self.file.createIfcDirection((1.0, 0.0, 0.0))
y_axis = self.file.createIfcDirection((0.0, 1.0, 0.0))
z_axis = self.file.createIfcDirection((0.0, 0.0, 1.0))
mapping_target = self.file.createIfcCartesianTransformationOperator3D(x_axis, y_axis, zero, 1, z_axis)
mapped_item = self.file.createIfcMappedItem(mapping_source, mapping_target)
return self.file.create_entity(
"IfcShapeRepresentation",
**{
@@ -32,12 +30,6 @@ class Usecase:
)
def get_mapping_source(self):
if self.settings["representation"].RepresentationMap:
return self.settings["representation"].RepresentationMap[0]
return self.file.create_entity(
"IfcRepresentationMap",
**{
"MappingOrigin": self.file.createIfcAxis2Placement3D(self.zero, self.z_axis, self.x_axis),
"MappedRepresentation": self.settings["representation"],
}
)
for inverse in self.file.get_inverse(self.settings["representation"]):
if inverse.is_a("IfcRepresentationMap"):
return inverse
@@ -8,19 +8,22 @@ import blenderbim.bim.module.geometry.add_representation as add_representation
import blenderbim.bim.module.geometry.map_representation as map_representation
import blenderbim.bim.module.geometry.assign_styles as assign_styles
import blenderbim.bim.module.geometry.assign_representation as assign_representation
import blenderbim.bim.module.geometry.unassign_representation as unassign_representation
import blenderbim.bim.module.geometry.remove_representation as remove_representation
import blenderbim.bim.module.grid.create_axis_curve as create_axis_curve
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim import import_ifc
from blenderbim.bim.module.geometry.data import Data
from blenderbim.bim.module.context.data import Data as ContextData
from blenderbim.bim.module.void.data import Data as VoidData
from mathutils import Vector
def get_box_context_id():
def get_context_id(context_type, context_identifier, target_view):
for context in ContextData.contexts.values():
if context["ContextType"] == "Model":
if context["ContextType"] == context_type:
for i, subcontext in context["HasSubContexts"].items():
if subcontext["ContextIdentifier"] == "Box" and subcontext["TargetView"] == "MODEL_VIEW":
if subcontext["ContextIdentifier"] == context_identifier and subcontext["TargetView"] == target_view:
return i
@@ -90,10 +93,22 @@ class AddRepresentation(bpy.types.Operator):
product = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
context_of_items = self.file.by_id(context_id)
gprop = context.scene.BIMGeoreferenceProperties
coordinate_offset = None
if gprop.has_blender_offset and gprop.blender_offset_type == "CARTESIAN_POINT":
coordinate_offset = Vector(
(
float(gprop.blender_eastings),
float(gprop.blender_northings),
float(gprop.blender_orthogonal_height),
)
)
representation_data = {
"context": context_of_items,
"blender_object": obj,
"geometry": obj.data,
"coordinate_offset": coordinate_offset,
"total_items": max(1, len(obj.material_slots)),
"should_force_faceted_brep": context.scene.BIMGeometryProperties.should_force_faceted_brep,
"should_force_triangulation": context.scene.BIMGeometryProperties.should_force_triangulation,
@@ -105,7 +120,7 @@ class AddRepresentation(bpy.types.Operator):
print("Failed to write shape representation")
return {"FINISHED"}
box_context_id = get_box_context_id()
box_context_id = get_context_id("Model", "Box", "MODEL_VIEW")
if (
box_context_id
and context_of_items.ContextType == "Model"
@@ -202,13 +217,22 @@ class SwitchRepresentation(bpy.types.Operator):
class RemoveRepresentation(bpy.types.Operator):
bl_idname = "bim.remove_representation"
bl_label = "Remove Representation"
ifc_definition_id: bpy.props.IntProperty()
obj: bpy.props.StringProperty()
representation_id: bpy.props.IntProperty()
def execute(self, context):
self.file = IfcStore.get_file()
representation = self.file.by_id(self.ifc_definition_id)
obj = bpy.context.active_object
mesh = bpy.data.meshes.get("{}/{}".format(representation.ContextOfItems.id(), representation.id()))
representation = self.file.by_id(self.representation_id)
obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
product = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
is_mapped_representation = representation.RepresentationType == "MappedRepresentation"
if is_mapped_representation:
mesh_name = "{}/{}".format(
representation.ContextOfItems.id(), representation.Items[0].MappingSource.MappedRepresentation.id()
)
else:
mesh_name = "{}/{}".format(representation.ContextOfItems.id(), representation.id())
mesh = bpy.data.meshes.get(mesh_name)
if mesh:
if obj.data == mesh:
# TODO we can do better than this
@@ -216,9 +240,39 @@ class RemoveRepresentation(bpy.types.Operator):
if not void_mesh:
void_mesh = bpy.data.meshes.new("Void")
obj.data = void_mesh
bpy.data.meshes.remove(mesh)
if not is_mapped_representation:
bpy.data.meshes.remove(mesh)
product = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
unassign_representation.Usecase(self.file, {"product": product, "representation": representation}).execute()
remove_representation.Usecase(self.file, {"representation": representation}).execute()
Data.load(obj.BIMObjectProperties.ifc_definition_id)
Data.load(product.id())
return {"FINISHED"}
class MapRepresentations(bpy.types.Operator):
bl_idname = "bim.map_representations"
bl_label = "Map Representations"
product_id: bpy.props.IntProperty()
type_product_id: bpy.props.IntProperty()
def execute(self, context):
related_object = IfcStore.id_map[self.product_id]
if self.product_id not in Data.products:
Data.load(self.product_id)
for representation_id in Data.products[self.product_id]:
bpy.ops.bim.remove_representation(obj=related_object.name, representation_id=representation_id)
if self.type_product_id not in Data.products:
Data.load(self.type_product_id)
for representation_id in Data.products[self.type_product_id]:
bpy.ops.bim.map_representation(
obj=related_object.name,
representation_id=representation_id,
obj_data=IfcStore.id_map[self.type_product_id].data.name,
)
return {"FINISHED"}
@@ -226,33 +280,24 @@ class MapRepresentation(bpy.types.Operator):
bl_idname = "bim.map_representation"
bl_label = "Map Representation"
obj: bpy.props.StringProperty()
representation_id: bpy.props.IntProperty()
obj_data: bpy.props.StringProperty()
def execute(self, context):
objs = [bpy.data.objects.get(self.obj)] if self.obj else bpy.context.selected_objects
obj_data = bpy.data.meshes.get(self.obj_data) if self.obj_data else bpy.context.active_object.data
objs = [o for o in objs if o.data != obj_data]
obj_data = bpy.data.meshes.get(self.obj_data) if self.obj_data else None
self.file = IfcStore.get_file()
for obj in objs:
bpy.ops.bim.edit_object_placement(obj=obj.name)
product = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
if obj.data.BIMMeshProperties.ifc_definition_id:
old_representation = self.file.by_id(obj.data.BIMMeshProperties.ifc_definition_id)
else:
old_representation = None
target_representation = self.file.by_id(obj_data.BIMMeshProperties.ifc_definition_id)
obj.data = obj_data
if obj_data:
obj.data = obj_data
result = map_representation.Usecase(
self.file,
{
"product": product,
"representation": target_representation,
},
self.file, {"representation": self.file.by_id(self.representation_id)}
).execute()
assign_representation.Usecase(self.file, {"product": product, "representation": result}).execute()
if old_representation:
bpy.ops.bim.remove_representation(ifc_definition_id=old_representation.id())
Data.load(obj.BIMObjectProperties.ifc_definition_id)
return {"FINISHED"}
@@ -261,6 +306,7 @@ class UpdateMeshRepresentation(bpy.types.Operator):
bl_idname = "bim.update_mesh_representation"
bl_label = "Update Mesh Representation"
obj: bpy.props.StringProperty()
ifc_representation_class: bpy.props.StringProperty()
def execute(self, context):
if not ContextData.is_loaded:
@@ -270,64 +316,98 @@ class UpdateMeshRepresentation(bpy.types.Operator):
self.file = IfcStore.get_file()
for obj in objs:
bpy.ops.bim.edit_object_placement(obj=obj.name)
product = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
old_representation = self.file.by_id(obj.data.BIMMeshProperties.ifc_definition_id)
context_of_items = old_representation.ContextOfItems
representation_data = {
"context": context_of_items,
"blender_object": obj,
"geometry": obj.data,
"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,
}
new_representation = add_representation.Usecase(self.file, representation_data).execute()
if not new_representation:
print("Failed to write shape representation")
return {"FINISHED"}
box_context_id = get_box_context_id()
old_box = ifcopenshell.util.element.get_representation(product, "Model", "Box", "MODEL_VIEW")
if (
box_context_id
and old_box
and context_of_items.ContextType == "Model"
and context_of_items.ContextIdentifier
and context_of_items.ContextIdentifier == "Body"
):
representation_data["context"] = self.file.by_id(box_context_id)
new_box = add_representation.Usecase(self.file, representation_data).execute()
for inverse in self.file.get_inverse(old_box):
ifcopenshell.util.element.replace_attribute(inverse, old_box, new_box)
assign_styles.Usecase(
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,
},
).execute()
# TODO: move this into a replace_representation usecase or something
for inverse in self.file.get_inverse(old_representation):
ifcopenshell.util.element.replace_attribute(inverse, old_representation, new_representation)
obj.data.BIMMeshProperties.ifc_definition_id = int(new_representation.id())
obj.data.name = f"{old_representation.ContextOfItems.id()}/{new_representation.id()}"
bpy.ops.bim.remove_representation(ifc_definition_id=old_representation.id())
Data.load(obj.BIMObjectProperties.ifc_definition_id)
self.update_obj_mesh_representation(context, obj)
return {"FINISHED"}
def update_obj_mesh_representation(self, context, obj):
product = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
if product.is_a("IfcGridAxis"):
create_axis_curve.Usecase(self.file, {"AxisCurve": obj, "grid_axis": product}).execute()
return
bpy.ops.bim.edit_object_placement(obj=obj.name)
old_representation = self.file.by_id(obj.data.BIMMeshProperties.ifc_definition_id)
context_of_items = old_representation.ContextOfItems
gprop = context.scene.BIMGeoreferenceProperties
coordinate_offset = None
if gprop.has_blender_offset and gprop.blender_offset_type == "CARTESIAN_POINT":
coordinate_offset = Vector(
(
float(gprop.blender_eastings),
float(gprop.blender_northings),
float(gprop.blender_orthogonal_height),
)
)
representation_data = {
"context": context_of_items,
"blender_object": obj,
"geometry": obj.data,
"coordinate_offset": coordinate_offset,
"total_items": max(1, len(obj.material_slots)),
"should_force_faceted_brep": context.scene.BIMGeometryProperties.should_force_faceted_brep,
"should_force_triangulation": context.scene.BIMGeometryProperties.should_force_triangulation,
"ifc_representation_class": self.ifc_representation_class
}
new_representation = add_representation.Usecase(self.file, representation_data).execute()
#if product.is_a("IfcWall"):
# # Generate axis representation
# axis_context_id = get_context_id("Model", "Axis", "MODEL_VIEW")
# old_axis = ifcopenshell.util.element.get_representation(product, "Model", "Axis", "MODEL_VIEW")
# if (
# axis_context_id
# and old_axis
# and context_of_items.ContextType == "Model"
# and context_of_items.ContextIdentifier
# and context_of_items.ContextIdentifier == "Body"
# ):
# has_axis_generator = False
# if has_axis_generator:
# # TODO, just pseudocode for now
# representation_data["geometry"] = axis_generator_function_call
# pass
box_context_id = get_context_id("Model", "Box", "MODEL_VIEW")
old_box = ifcopenshell.util.element.get_representation(product, "Model", "Box", "MODEL_VIEW")
if (
box_context_id
and old_box
and context_of_items.ContextType == "Model"
and context_of_items.ContextIdentifier
and context_of_items.ContextIdentifier == "Body"
):
representation_data["context"] = self.file.by_id(box_context_id)
new_box = add_representation.Usecase(self.file, representation_data).execute()
for inverse in self.file.get_inverse(old_box):
ifcopenshell.util.element.replace_attribute(inverse, old_box, new_box)
assign_styles.Usecase(
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,
},
).execute()
# TODO: move this into a replace_representation usecase or something
for inverse in self.file.get_inverse(old_representation):
ifcopenshell.util.element.replace_attribute(inverse, old_representation, new_representation)
obj.data.BIMMeshProperties.ifc_definition_id = int(new_representation.id())
obj.data.name = f"{old_representation.ContextOfItems.id()}/{new_representation.id()}"
bpy.ops.bim.remove_representation(representation_id=old_representation.id())
Data.load(obj.BIMObjectProperties.ifc_definition_id)
class UpdateParametricRepresentation(bpy.types.Operator):
bl_idname = "bim.update_parametric_representation"
@@ -354,15 +434,14 @@ class GetRepresentationIfcParameters(bpy.types.Operator):
props = obj.data.BIMMeshProperties
elements = IfcStore.get_file().traverse(IfcStore.get_file().by_id(props.ifc_definition_id))
for element in elements:
if not element.is_a("IfcRepresentationItem"):
continue
for i in range(0, len(element)):
if element.attribute_type(i) == "DOUBLE":
new = props.ifc_parameters.add()
new.name = "{}/{}".format(element.is_a(), element.attribute_name(i))
new.step_id = element.id()
new.type = element.attribute_type(i)
new.index = i
if element[i]:
new.value = element[i]
if element.is_a("IfcRepresentationItem") or element.is_a("IfcParameterizedProfileDef"):
for i in range(0, len(element)):
if element.attribute_type(i) == "DOUBLE":
new = props.ifc_parameters.add()
new.name = "{}/{}".format(element.is_a(), element.attribute_name(i))
new.step_id = element.id()
new.type = element.attribute_type(i)
new.index = i
if element[i]:
new.value = element[i]
return {"FINISHED"}
@@ -14,6 +14,7 @@ from bpy.props import (
class BIMGeometryProperties(PropertyGroup):
should_auto_update_mesh_representations: BoolProperty(name="Should Auto Update Representations", default=False)
# Revit workaround
should_use_presentation_style_assignment: BoolProperty(name="Force Presentation Style Assignment", default=False)
# RIB iTwo, DESITE BIM workaround
@@ -1,5 +1,6 @@
import ifcopenshell.util.element
class Usecase:
def __init__(self, file, settings=None):
self.file = file
@@ -8,6 +9,19 @@ class Usecase:
self.settings[key] = value
def execute(self):
if self.settings["representation"].RepresentationType == "MappedRepresentation":
return self.remove_mapped_representation_portion_only()
return self.remove_entire_representation_tree()
def remove_mapped_representation_portion_only(self):
dummy_context = self.file.create_entity("IfcRepresentationContext")
dummy_representation_map = self.file.createIfcRepresentationMap()
self.settings["representation"].ContextOfItems = dummy_context
for item in self.settings["representation"].Items:
item.MappingSource = dummy_representation_map
ifcopenshell.util.element.remove_deep(self.file, self.settings["representation"])
def remove_entire_representation_tree(self):
dummy_context = self.file.create_entity("IfcRepresentationContext")
for subelement in self.file.traverse(self.settings["representation"]):
if subelement.is_a("IfcRepresentationItem") and subelement.StyledByItem:
@@ -26,11 +40,4 @@ class Usecase:
self.file.remove(inverse)
else:
assigned_items.remove(element)
inverse.AssignedItems == list(assigned_items)
elif inverse.is_a("IfcProductRepresentation"):
representations = set(inverse.Representations)
if len(representations) == 1:
self.file.remove(inverse)
else:
representations.remove(element)
inverse.Representations = list(representations)
inverse.AssignedItems = list(assigned_items)
@@ -41,7 +41,7 @@ class BIM_PT_representations(Panel):
op = row.operator("bim.switch_representation", icon="OUTLINER_DATA_MESH", text="")
op.ifc_definition_id = ifc_definition_id
op.disable_opening_subtractions = False
row.operator("bim.remove_representation", icon="X", text="").ifc_definition_id = ifc_definition_id
row.operator("bim.remove_representation", icon="X", text="").representation_id = ifc_definition_id
class BIM_PT_mesh(Panel):
@@ -66,8 +66,9 @@ class BIM_PT_mesh(Panel):
layout = self.layout
props = context.active_object.data.BIMMeshProperties
row = layout.row()
row.operator("bim.map_representation")
sprops = context.scene.BIMGeometryProperties
row = self.layout.row()
row.prop(sprops, "should_auto_update_mesh_representations")
row = layout.row(align=True)
op = row.operator("bim.switch_representation", text="Bake Voids", icon="SELECT_SUBTRACT")
@@ -79,6 +80,19 @@ class BIM_PT_mesh(Panel):
row = layout.row()
row.operator("bim.update_mesh_representation")
row = layout.row()
op = row.operator("bim.update_mesh_representation", text="Update Mesh As Rectangle Extrusion")
op.ifc_representation_class = "IfcExtrudedAreaSolid/IfcRectangleProfileDef"
row = layout.row()
op = row.operator("bim.update_mesh_representation", text="Update Mesh As Circle Extrusion")
op.ifc_representation_class = "IfcExtrudedAreaSolid/IfcCircleProfileDef"
row = layout.row()
op = row.operator("bim.update_mesh_representation", text="Update Mesh As Arbitrary Extrusion")
op.ifc_representation_class = "IfcExtrudedAreaSolid/IfcArbitraryClosedProfileDef"
row = layout.row()
row.operator("bim.get_representation_ifc_parameters")
for index, ifc_parameter in enumerate(props.ifc_parameters):
@@ -0,0 +1,56 @@
import blenderbim.bim.module.geometry.remove_representation as remove_representation
import ifcopenshell.util.element
class Usecase:
def __init__(self, file, settings=None):
self.file = file
self.settings = {"product": None, "representation": None}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
if self.settings["product"].is_a("IfcProduct"):
self.unassign_product_representation(self.settings["product"], self.settings["representation"])
elif self.settings["product"].is_a("IfcTypeProduct"):
self.unassign_type_representation()
def unassign_product_representation(self, product, representation):
representations = list(product.Representation.Representations or [])
if representation not in representations:
return
representations.remove(representation)
if not representations:
self.file.remove(product.Representation)
else:
product.Representation.Representations = representations
def unassign_type_representation(self):
for representation_map in self.settings["product"].RepresentationMaps or []:
if representation_map.MappedRepresentation == self.settings["representation"]:
self.unassign_products_using_mapped_representation(representation_map)
self.remove_representation_map_only(representation_map)
break
self.settings["product"].RepresentationMaps = self.settings["product"].RepresentationMaps or None
def remove_representation_map_only(self, representation_map):
dummy_representation = self.file.createIfcShapeRepresentation()
representation_map.MappedRepresentation = dummy_representation
ifcopenshell.util.element.remove_deep(self.file, representation_map)
self.file.remove(representation_map)
def unassign_products_using_mapped_representation(self, representation_map):
mapped_representations = []
just_representations = []
for map_usage in representation_map.MapUsage or []:
for inverse in self.file.get_inverse(map_usage):
if not inverse.is_a("IfcShapeRepresentation"):
continue
for definition in inverse.OfProductRepresentation or []:
for product in definition.ShapeOfProduct or []:
mapped_representations.append({"product": product, "representation": inverse})
just_representations.append(inverse)
for item in mapped_representations:
self.unassign_product_representation(item["product"], item["representation"])
for representation in just_representations:
remove_representation.Usecase(self.file, {"representation": representation}).execute()
@@ -11,6 +11,8 @@ classes = (
operator.AddGeoreferencing,
operator.ConvertLocalToGlobal,
operator.ConvertGlobalToLocal,
operator.GetCursorLocation,
operator.SetCursorLocation,
prop.BIMGeoreferenceProperties,
ui.BIM_PT_gis,
ui.BIM_PT_gis_utilities,
@@ -136,11 +136,9 @@ class EditGeoreferencing(bpy.types.Operator):
if not props.is_map_unit_null:
map_unit = props.map_unit_si if props.map_unit_type == "IfcSIUnit" else props.map_unit_imperial
edit_georeferencing.Usecase(self.file, {
"map_conversion": map_conversion,
"projected_crs": projected_crs,
"map_unit": map_unit
}).execute()
edit_georeferencing.Usecase(
self.file, {"map_conversion": map_conversion, "projected_crs": projected_crs, "map_unit": map_unit}
).execute()
Data.load()
bpy.ops.bim.disable_editing_georeferencing()
return {"FINISHED"}
@@ -154,7 +152,7 @@ class SetNorthOffset(bpy.types.Operator):
context.scene.sun_pos_properties.north_offset = -radians(
ifcopenshell.util.geolocation.xy2angle(
context.scene.BIMGeoreferenceProperties.map_conversion.get("XAxisAbscissa").float_value,
context.scene.BIMGeoreferenceProperties.map_conversion.get("XAxisOrdinate").float_value
context.scene.BIMGeoreferenceProperties.map_conversion.get("XAxisOrdinate").float_value,
)
)
return {"FINISHED"}
@@ -200,17 +198,42 @@ class ConvertLocalToGlobal(bpy.types.Operator):
Data.load()
props = context.scene.BIMGeoreferenceProperties
x, y, z = [float(co) for co in props.coordinate_input.split(",")]
results = ifcopenshell.util.geolocation.xyz2enh(
x,
y,
z,
Data.map_conversion["Eastings"],
Data.map_conversion["Northings"],
Data.map_conversion["OrthogonalHeight"],
Data.map_conversion.get("XAxisAbscissa", 1.0),
Data.map_conversion.get("XAxisOrdinate", 0.0),
Data.map_conversion.get("Scale", 1.0),
)
if props.has_blender_offset and props.blender_offset_type == "CARTESIAN_POINT":
x -= float(props.blender_eastings)
y -= float(props.blender_northings)
z -= float(props.blender_orthogonal_height)
elif props.has_blender_offset and props.blender_offset_type == "OBJECT_PLACEMENT":
results = ifcopenshell.util.geolocation.xyz2enh(
x,
y,
z,
float(props.blender_eastings),
float(props.blender_northings),
float(props.blender_orthogonal_height),
float(props.blender_x_axis_abscissa),
float(props.blender_x_axis_ordinate),
1.0,
)
x, y, z = results
# TODO: what if the project CRS units and the project units are different?
if Data.map_conversion:
results = ifcopenshell.util.geolocation.xyz2enh(
x,
y,
z,
Data.map_conversion["Eastings"],
Data.map_conversion["Northings"],
Data.map_conversion["OrthogonalHeight"],
Data.map_conversion.get("XAxisAbscissa", 1.0),
Data.map_conversion.get("XAxisOrdinate", 0.0),
Data.map_conversion.get("Scale", 1.0),
)
else:
results = (x, y, z)
props.coordinate_output = ",".join([str(r) for r in results])
bpy.context.scene.cursor.location = results
return {"FINISHED"}
@@ -225,18 +248,64 @@ class ConvertGlobalToLocal(bpy.types.Operator):
Data.load()
props = context.scene.BIMGeoreferenceProperties
x, y, z = [float(co) for co in props.coordinate_input.split(",")]
results = ifcopenshell.util.geolocation.enh2xyz(
x,
y,
z,
Data.map_conversion["Eastings"],
Data.map_conversion["Northings"],
Data.map_conversion["OrthogonalHeight"],
Data.map_conversion.get("XAxisAbscissa", 1.0),
Data.map_conversion.get("XAxisOrdinate", 0.0),
Data.map_conversion.get("Scale", 1.0),
)
if Data.map_conversion:
results = ifcopenshell.util.geolocation.enh2xyz(
x,
y,
z,
Data.map_conversion["Eastings"],
Data.map_conversion["Northings"],
Data.map_conversion["OrthogonalHeight"],
Data.map_conversion.get("XAxisAbscissa", 1.0),
Data.map_conversion.get("XAxisOrdinate", 0.0),
Data.map_conversion.get("Scale", 1.0),
)
else:
results = (x, y, z)
if props.has_blender_offset and props.blender_offset_type == "CARTESIAN_POINT":
results[0] += float(props.blender_eastings)
results[1] += float(props.blender_northings)
results[2] += float(props.blender_orthogonal_height)
elif props.has_blender_offset and props.blender_offset_type == "OBJECT_PLACEMENT":
results = ifcopenshell.util.geolocation.enh2xyz(
results[0],
results[1],
results[2],
float(props.blender_eastings),
float(props.blender_northings),
float(props.blender_orthogonal_height),
float(props.blender_x_axis_abscissa),
float(props.blender_x_axis_ordinate),
1.0,
)
props.coordinate_output = ",".join([str(r) for r in results])
bpy.context.scene.cursor.location = results
scale = ifcopenshell.util.unit.calculate_unit_scale(IfcStore.get_file())
bpy.context.scene.cursor.location = [o * scale for o in results]
return {"FINISHED"}
class GetCursorLocation(bpy.types.Operator):
bl_idname = "bim.get_cursor_location"
bl_label = "Get Cursor Location"
def execute(self, context):
props = context.scene.BIMGeoreferenceProperties
scale = ifcopenshell.util.unit.calculate_unit_scale(IfcStore.get_file())
project_coordinates = [o / scale for o in bpy.context.scene.cursor.location]
props.coordinate_input = ",".join([str(o) for o in project_coordinates])
return {"FINISHED"}
class SetCursorLocation(bpy.types.Operator):
bl_idname = "bim.set_cursor_location"
bl_label = "Set Cursor Location"
def execute(self, context):
props = context.scene.BIMGeoreferenceProperties
scale = ifcopenshell.util.unit.calculate_unit_scale(IfcStore.get_file())
bpy.context.scene.cursor.location = [float(co) * scale for co in props.coordinate_output.split(",")]
return {"FINISHED"}
@@ -143,10 +143,12 @@ class BIM_PT_gis_utilities(Panel):
def draw(self, context):
props = context.scene.BIMGeoreferenceProperties
row = self.layout.row()
row = self.layout.row(align=True)
row.prop(props, "coordinate_input", text="Input")
row = self.layout.row()
row.operator("bim.get_cursor_location", text="", icon="TRACKER")
row = self.layout.row(align=True)
row.prop(props, "coordinate_output", text="Output")
row.operator("bim.set_cursor_location", text="", icon="TRACKER")
row = self.layout.row(align=True)
row.operator("bim.convert_local_to_global", text="Local to Global")
@@ -0,0 +1,44 @@
import ifcopenshell
import ifcopenshell.util.unit
import ifcopenshell.util.placement
from mathutils import Matrix # For now, we depend on Blender
class Usecase:
def __init__(self, file, settings=None):
self.file = file
self.settings = {
"AxisCurve": None, # A Blender object
"grid_axis": None,
}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
if self.settings["grid_axis"].AxisCurve:
ifcopenshell.util.element.remove_deep(self.file, self.settings["grid_axis"].AxisCurve)
self.settings["unit_scale"] = ifcopenshell.util.unit.calculate_unit_scale(self.file)
grid = [i for i in self.file.get_inverse(self.settings["grid_axis"]) if i.is_a("IfcGrid")][0]
points = [
Matrix(ifcopenshell.util.placement.get_local_placement(grid.ObjectPlacement)).inverted()
@ (self.settings["AxisCurve"].matrix_world @ v.co)
for v in self.settings["AxisCurve"].data.vertices[0:2]
]
self.settings["grid_axis"].AxisCurve = self.file.createIfcPolyline(
[
self.create_cartesian_point(points[0][0], points[0][1], points[0][2]),
self.create_cartesian_point(points[1][0], points[1][1], points[1][2]),
]
)
def create_cartesian_point(self, x, y, z=None):
x = self.convert_si_to_unit(x)
y = self.convert_si_to_unit(y)
if z is None:
return self.file.createIfcCartesianPoint((x, y))
z = self.convert_si_to_unit(z)
return self.file.createIfcCartesianPoint((x, y, z))
def convert_si_to_unit(self, co):
return co / self.settings["unit_scale"]
@@ -0,0 +1,21 @@
class Usecase:
def __init__(self, file, settings=None):
self.file = file
self.settings = {
"AxisTag": "A",
"SameSense": True,
"UVWAxes": "UAxes", # Choose which axes
"Grid": None,
}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
element = self.file.create_entity("IfcGridAxis", **{
"AxisTag": self.settings["AxisTag"],
"SameSense": self.settings["SameSense"]
})
axes = list(getattr(self.settings["Grid"], self.settings["UVWAxes"]) or [])
axes.append(element)
setattr(self.settings["Grid"], self.settings["UVWAxes"], axes)
return element
@@ -0,0 +1,26 @@
import bpy
from . import ui, prop, operator
classes = (
operator.LoadGroups,
operator.DisableGroupEditingUI,
operator.AddGroup,
operator.EditGroup,
operator.RemoveGroup,
operator.AssignGroup,
operator.UnassignGroup,
operator.EnableEditingGroup,
operator.DisableEditingGroup,
prop.Group,
prop.BIMGroupProperties,
ui.BIM_PT_groups,
ui.BIM_UL_groups,
)
def register():
bpy.types.Scene.BIMGroupProperties = bpy.props.PointerProperty(type=prop.BIMGroupProperties)
def unregister():
del bpy.types.Scene.BIMGroupProperties
@@ -0,0 +1,17 @@
import ifcopenshell
from blenderbim.bim.module.owner.api import create_owner_history
class Usecase:
def __init__(self, file, settings={}):
self.file = file
self.settings = {}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
return self.file.create_entity("IfcGroup", **{
"GlobalId": ifcopenshell.guid.new(),
"OwnerHistory": create_owner_history(),
"Name": "Unnamed"
})
@@ -0,0 +1,28 @@
import ifcopenshell
from blenderbim.bim.module.owner.api import create_owner_history
from blenderbim.bim.module.owner.api import update_owner_history
class Usecase:
def __init__(self, file, settings=None):
self.file = file
self.settings = {
"product": None,
"group": None,
}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
if not self.settings["group"].IsGroupedBy:
return self.file.create_entity("IfcRelAssignsToGroup", **{
"GlobalId": ifcopenshell.guid.new(),
"OwnerHistory": create_owner_history(),
"RelatedObjects": [self.settings["product"]],
"RelatingGroup": self.settings["group"]
})
rel = self.settings["group"].IsGroupedBy[0]
related_objects = set(rel.RelatedObjects) or set()
related_objects.add(self.settings["product"])
rel.RelatedObjects = list(related_objects)
update_owner_history(rel)
@@ -0,0 +1,27 @@
from blenderbim.bim.ifc import IfcStore
class Data:
is_loaded = False
products = {}
groups = {}
@classmethod
def purge(cls):
cls.is_loaded = False
cls.products = {}
cls.groups = {}
@classmethod
def load(cls):
cls.products = {}
cls.groups = {}
for group in IfcStore.get_file().by_type("IfcGroup", include_subtypes=False):
if group.IsGroupedBy:
for rel in group.IsGroupedBy:
for product in rel.RelatedObjects:
cls.products.setdefault(product.id(), []).append(group.id())
data = group.get_info()
del data["OwnerHistory"]
cls.groups[group.id()] = data
cls.is_loaded=True
@@ -0,0 +1,13 @@
class Usecase:
def __init__(self, file, settings=None):
self.file = file
self.settings = {
"group": None,
"attributes": {}
}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
for name, value in self.settings["attributes"].items():
setattr(self.settings["group"], name, value)
@@ -0,0 +1,149 @@
import bpy
import blenderbim.bim.module.group.add_group as add_group
import blenderbim.bim.module.group.edit_group as edit_group
import blenderbim.bim.module.group.remove_group as remove_group
import blenderbim.bim.module.group.assign_group as assign_group
import blenderbim.bim.module.group.unassign_group as unassign_group
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.module.group.data import Data
class LoadGroups(bpy.types.Operator):
bl_idname = "bim.load_groups"
bl_label = "Load Groups"
def execute(self, context):
props = context.scene.BIMGroupProperties
while len(props.groups) > 0:
props.groups.remove(0)
for ifc_definition_id, group in Data.groups.items():
new = props.groups.add()
new.ifc_definition_id = ifc_definition_id
new.name = group["Name"]
props.is_editing = True
bpy.ops.bim.disable_editing_group()
return {"FINISHED"}
class DisableGroupEditingUI(bpy.types.Operator):
bl_idname = "bim.disable_group_editing_ui"
bl_label = "Disable Group Editing UI"
def execute(self, context):
context.scene.BIMGroupProperties.is_editing = False
return {"FINISHED"}
class AddGroup(bpy.types.Operator):
bl_idname = "bim.add_group"
bl_label = "Add Group"
def execute(self, context):
result = add_group.Usecase(IfcStore.get_file()).execute()
Data.load()
bpy.ops.bim.load_groups()
bpy.ops.bim.enable_editing_group(group=result.id())
return {"FINISHED"}
class EditGroup(bpy.types.Operator):
bl_idname = "bim.edit_group"
bl_label = "Edit Group"
def execute(self, context):
props = context.scene.BIMGroupProperties
attributes = {}
for attribute in props.group_attributes:
if attribute.is_null:
attributes[attribute.name] = None
else:
attributes[attribute.name] = attribute.string_value
self.file = IfcStore.get_file()
edit_group.Usecase(
self.file, {"group": self.file.by_id(props.active_group_id), "attributes": attributes}
).execute()
Data.load()
bpy.ops.bim.load_groups()
return {"FINISHED"}
class RemoveGroup(bpy.types.Operator):
bl_idname = "bim.remove_group"
bl_label = "Remove Group"
group: bpy.props.IntProperty()
def execute(self, context):
props = context.scene.BIMGroupProperties
self.file = IfcStore.get_file()
remove_group.Usecase(self.file, {"group": self.file.by_id(self.group)}).execute()
Data.load()
bpy.ops.bim.load_groups()
return {"FINISHED"}
class EnableEditingGroup(bpy.types.Operator):
bl_idname = "bim.enable_editing_group"
bl_label = "Enable Editing Group"
group: bpy.props.IntProperty()
def execute(self, context):
props = context.scene.BIMGroupProperties
while len(props.group_attributes) > 0:
props.group_attributes.remove(0)
data = Data.groups[self.group]
for attribute in IfcStore.get_schema().declaration_by_name("IfcGroup").all_attributes():
data_type = str(attribute.type_of_attribute)
if "<entity" in data_type:
continue
new = props.group_attributes.add()
new.name = attribute.name()
new.is_null = data[attribute.name()] is None
new.is_optional = attribute.optional()
new.string_value = "" if new.is_null else data[attribute.name()]
props.active_group_id = self.group
return {"FINISHED"}
class DisableEditingGroup(bpy.types.Operator):
bl_idname = "bim.disable_editing_group"
bl_label = "Disable Editing Group"
def execute(self, context):
context.scene.BIMGroupProperties.active_group_id = 0
return {"FINISHED"}
class AssignGroup(bpy.types.Operator):
bl_idname = "bim.assign_group"
bl_label = "Assign Group"
product: bpy.props.StringProperty()
group: bpy.props.IntProperty()
def execute(self, context):
product = bpy.data.objects.get(self.product) if self.product else context.active_object
self.file = IfcStore.get_file()
assign_group.Usecase(self.file, {
"product": self.file.by_id(product.BIMObjectProperties.ifc_definition_id),
"group": self.file.by_id(self.group)
}).execute()
Data.load()
return {"FINISHED"}
class UnassignGroup(bpy.types.Operator):
bl_idname = "bim.unassign_group"
bl_label = "Unassign Group"
product: bpy.props.StringProperty()
group: bpy.props.IntProperty()
def execute(self, context):
product = bpy.data.objects.get(self.product) if self.product else context.active_object
self.file = IfcStore.get_file()
unassign_group.Usecase(self.file, {
"product": self.file.by_id(product.BIMObjectProperties.ifc_definition_id),
"group": self.file.by_id(self.group)
}).execute()
Data.load()
return {"FINISHED"}
@@ -0,0 +1,26 @@
import bpy
from blenderbim.bim.prop import StrProperty, Attribute
from bpy.types import PropertyGroup
from bpy.props import (
PointerProperty,
StringProperty,
EnumProperty,
BoolProperty,
IntProperty,
FloatProperty,
FloatVectorProperty,
CollectionProperty,
)
class Group(PropertyGroup):
name: StringProperty(name="Name")
ifc_definition_id: IntProperty(name="IFC Definition ID")
class BIMGroupProperties(PropertyGroup):
group_attributes: CollectionProperty(name="Group Attributes", type=Attribute)
is_editing: BoolProperty(name="Is Editing", default=False)
groups: CollectionProperty(name="Groups", type=Group)
active_group_index: IntProperty(name="Active Group Index")
active_group_id: IntProperty(name="Active Group Id")
@@ -0,0 +1,11 @@
class Usecase:
def __init__(self, file, settings=None):
self.file = file
self.settings = {"group": None}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
for rel in self.settings["group"].IsGroupedBy or []:
self.file.remove(rel)
self.file.remove(self.settings["group"])
@@ -0,0 +1,79 @@
from bpy.types import Panel, UIList
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.module.group.data import Data
class BIM_PT_groups(Panel):
bl_label = "IFC Groups"
bl_idname = "BIM_PT_groups"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
@classmethod
def poll(cls, context):
return IfcStore.get_file()
def draw(self, context):
if not Data.is_loaded:
Data.load()
self.props = context.scene.BIMGroupProperties
row = self.layout.row(align=True)
row.label(text="{} Groups Found".format(len(Data.groups)), icon="OUTLINER")
if self.props.is_editing:
row.operator("bim.add_group", text="", icon="ADD")
row.operator("bim.disable_group_editing_ui", text="", icon="CHECKMARK")
else:
row.operator("bim.load_groups", text="", icon="GREASEPENCIL")
if self.props.is_editing:
self.layout.template_list(
"BIM_UL_groups",
"",
self.props,
"groups",
self.props,
"active_group_index",
)
if self.props.active_group_id:
self.draw_editable_ui(context)
def draw_editable_ui(self, context):
for attribute in self.props.group_attributes:
row = self.layout.row(align=True)
row.prop(attribute, "string_value", text=attribute.name)
if attribute.is_optional:
row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
class BIM_UL_groups(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 context.active_object:
oprops = context.active_object.BIMObjectProperties
if (
oprops.ifc_definition_id in Data.products
and item.ifc_definition_id in Data.products[oprops.ifc_definition_id]
):
op = row.operator("bim.unassign_group", text="", icon="KEYFRAME_HLT", emboss=False)
op.group = item.ifc_definition_id
else:
op = row.operator("bim.assign_group", text="", icon="KEYFRAME", emboss=False)
op.group = item.ifc_definition_id
if context.scene.BIMGroupProperties.active_group_id == item.ifc_definition_id:
row.operator("bim.edit_group", text="", icon="CHECKMARK")
row.operator("bim.disable_editing_group", text="", icon="X")
elif context.scene.BIMGroupProperties.active_group_id:
row.operator("bim.remove_group", text="", icon="X").group = item.ifc_definition_id
else:
op = row.operator("bim.enable_editing_group", text="", icon="GREASEPENCIL")
op.group = item.ifc_definition_id
row.operator("bim.remove_group", text="", icon="X").group = item.ifc_definition_id
@@ -0,0 +1,25 @@
import ifcopenshell
from blenderbim.bim.module.owner.api import update_owner_history
class Usecase:
def __init__(self, file, settings=None):
self.file = file
self.settings = {
"product": None,
"group": None,
}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
if not self.settings["group"].IsGroupedBy:
return
rel = self.settings["group"].IsGroupedBy[0]
related_objects = set(rel.RelatedObjects) or set()
related_objects.remove(self.settings["product"])
if len(related_objects):
rel.RelatedObjects = list(related_objects)
update_owner_history(rel)
else:
self.file.remove(rel)
@@ -1,5 +1,3 @@
import ifcopenshell
class Usecase:
def __init__(self, file, settings={}):
self.file = file
@@ -16,7 +16,7 @@ class Data:
cls.layers = {}
@classmethod
def load(cls, item_id=None):
def load(cls):
cls._file = IfcStore.get_file()
if not cls._file:
return
@@ -10,6 +10,7 @@ classes = (
operator.RemoveConstituent,
operator.AddLayer,
operator.RemoveLayer,
operator.ReorderMaterialSetItem,
operator.AddListItem,
operator.RemoveListItem,
operator.EnableEditingAssignedMaterial,
@@ -9,7 +9,7 @@ class Usecase:
self.settings[key] = value
def execute(self):
constituents = list(self.settings["constituent_set"].MaterialConstituents)
constituents = list(self.settings["constituent_set"].MaterialConstituents or [])
constituent = self.file.create_entity("IfcMaterialConstituent", **{"Material": self.settings["material"]})
constituents.append(constituent)
self.settings["constituent_set"].MaterialConstituents = constituents
@@ -9,7 +9,7 @@ class Usecase:
self.settings[key] = value
def execute(self):
layers = list(self.settings["layer_set"].MaterialLayers)
layers = list(self.settings["layer_set"].MaterialLayers or [])
layer = self.file.create_entity("IfcMaterialLayer", **{
"Material": self.settings["material"],
"LayerThickness": 0.
@@ -9,6 +9,6 @@ class Usecase:
self.settings[key] = value
def execute(self):
materials = list(self.settings["material_list"].Materials)
materials = list(self.settings["material_list"].Materials or [])
materials.append(self.settings["material"])
self.settings["material_list"].Materials = materials
@@ -13,21 +13,30 @@ class Usecase:
self.assign_ifc_material()
elif self.settings["type"] == "IfcMaterialConstituentSet":
material_set = self.file.create_entity(self.settings["type"])
material_set.MaterialConstituents = [self.settings["material"]]
self.create_material_association(material_set)
elif self.settings["type"] == "IfcMaterialLayerSet":
material_set = self.file.create_entity(self.settings["type"])
material_set.MaterialLayers = [self.settings["material"]]
self.create_material_association(material_set)
elif self.settings["type"] == "IfcMaterialLayerSetUsage":
material_set = self.file.create_entity("IfcMaterialLayerSet")
material_set_usage = self.create_layer_set_usage(material_set)
self.create_material_association(material_set_usage)
elif self.settings["type"] == "IfcMaterialProfileSet":
material_set = self.file.create_entity(self.settings["type"])
material_set.MaterialProfiles = [self.settings["material"]]
self.create_material_association(material_set)
elif self.settings["type"] == "IfcMaterialList":
material_set = self.file.create_entity(self.settings["type"])
material_set.Materials = [self.settings["material"]]
self.create_material_association(material_set)
def create_layer_set_usage(self, material_set):
return self.file.create_entity("IfcMaterialLayerSetUsage", **{
"ForLayerSet": material_set,
"LayerSetDirection": "AXIS2" if self.settings["product"].is_a("IfcWall") else "AXIS3",
"DirectionSense": "POSITIVE",
"OffsetFromReferenceLine": 0
})
def assign_ifc_material(self):
rel = self.get_rel_associates_material(self.settings["material"])
if not rel:
@@ -21,6 +21,7 @@ class Data:
cls.materials = {}
cls.constituent_sets = {}
cls.constituents = {}
cls.layer_set_usages = {}
cls.layer_sets = {}
cls.layers = {}
cls.profile_sets = {}
@@ -39,6 +40,7 @@ class Data:
cls.load_materials()
cls.load_constituents()
cls.load_layers()
cls.load_layer_usages()
cls.load_profiles()
cls.load_lists()
cls.is_loaded = True
@@ -62,6 +64,11 @@ class Data:
cls.load_element("IfcMaterialLayer", cls.layers)
cls.load_element("IfcMaterialLayerSet", cls.layer_sets)
@classmethod
def load_layer_usages(cls):
cls.layer_set_usages = {}
cls.load_element("IfcMaterialLayerSetUsage", cls.layer_set_usages)
@classmethod
def load_profiles(cls):
cls.profile_sets = {}
@@ -103,8 +110,6 @@ class Data:
@classmethod
def load_association(cls, association, product_id):
material_select = association.RelatingMaterial
if material_select.is_a("IfcMaterialLayerSetUsage"): # TODO: implement usages
material_select = material_select.ForLayerSet
elif material_select.is_a("IfcMaterialProfileSetUsage"): # TODO: implement usages
if material_select.is_a("IfcMaterialProfileSetUsage"): # TODO: implement usages
material_select = material_select.ForProfileSet
cls.products[product_id] = {"type": material_select.is_a(), "id": material_select.id()}
@@ -8,6 +8,7 @@ import blenderbim.bim.module.material.remove_constituent as remove_constituent
import blenderbim.bim.module.material.add_layer as add_layer
import blenderbim.bim.module.material.edit_layer as edit_layer
import blenderbim.bim.module.material.remove_layer as remove_layer
import blenderbim.bim.module.material.reorder_set_item as reorder_set_item
import blenderbim.bim.module.material.add_list_item as add_list_item
import blenderbim.bim.module.material.remove_list_item as remove_list_item
import blenderbim.bim.module.material.edit_assigned_material as edit_assigned_material
@@ -63,6 +64,7 @@ class AssignMaterial(bpy.types.Operator):
"material": self.file.by_id(int(obj.BIMObjectMaterialProperties.material)),
},
).execute()
Data.load()
Data.load(obj.BIMObjectProperties.ifc_definition_id)
return {"FINISHED"}
@@ -136,6 +138,37 @@ class AddLayer(bpy.types.Operator):
return {"FINISHED"}
class ReorderMaterialSetItem(bpy.types.Operator):
bl_idname = "bim.reorder_material_set_item"
bl_label = "Reorder Material Set Item"
obj: bpy.props.StringProperty()
old_index: bpy.props.IntProperty()
new_index: bpy.props.IntProperty()
material_set: bpy.props.IntProperty()
def execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
self.file = IfcStore.get_file()
material_set = self.file.by_id(self.material_set)
reorder_set_item.Usecase(
self.file,
{
"material_set": material_set,
"old_index": self.old_index,
"new_index": self.new_index,
},
).execute()
if material_set.is_a("IfcMaterialConstituentSet"):
Data.load_constituents()
elif material_set.is_a("IfcMaterialLayerSet"):
Data.load_layers()
elif material_set.is_a("IfcMaterialProfileSet"):
Data.load_profiles()
elif material_set.is_a("IfcMaterialList"):
Data.load_lists()
return {"FINISHED"}
class RemoveLayer(bpy.types.Operator):
bl_idname = "bim.remove_layer"
bl_label = "Remove Layer"
@@ -206,10 +239,15 @@ class EnableEditingAssignedMaterial(bpy.types.Operator):
props.material = str(product_data["id"])
return {"FINISHED"}
material_set_class = product_data["type"]
if product_data["type"] == "IfcMaterialConstituentSet":
material_set_data = Data.constituent_sets[product_data["id"]]
elif product_data["type"] == "IfcMaterialLayerSet":
material_set_data = Data.layer_sets[product_data["id"]]
elif product_data["type"] == "IfcMaterialLayerSetUsage":
layer_set_usage = Data.layer_set_usages[product_data["id"]]
material_set_data = Data.layer_sets[layer_set_usage["ForLayerSet"]]
material_set_class = "IfcMaterialLayerSet"
elif product_data["type"] == "IfcMaterialProfileSet":
material_set_data = Data.profile_sets[product_data["id"]]
elif product_data["type"] == "IfcMaterialList":
@@ -220,7 +258,7 @@ class EnableEditingAssignedMaterial(bpy.types.Operator):
while len(props.material_set_attributes) > 0:
props.material_set_attributes.remove(0)
for attribute in IfcStore.get_schema().declaration_by_name(product_data["type"]).all_attributes():
for attribute in IfcStore.get_schema().declaration_by_name(material_set_class).all_attributes():
if "<string>" not in str(attribute.type_of_attribute):
continue
if attribute.name() in material_set_data:
@@ -254,8 +292,7 @@ class EditAssignedMaterial(bpy.types.Operator):
obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
props = obj.BIMObjectMaterialProperties
product_data = Data.products[obj.BIMObjectProperties.ifc_definition_id]
material_set_id = self.material_set or product_data["id"]
material_set = self.file.by_id(material_set_id)
material_set = self.file.by_id(self.material_set)
if product_data["type"] == "IfcMaterial":
bpy.ops.bim.unassign_material(obj=obj.name)
@@ -301,7 +338,7 @@ class EnableEditingMaterialSetItem(bpy.types.Operator):
if product_data["type"] == "IfcMaterialConstituentSet":
material_set_item_data = Data.constituents[self.material_set_item]
elif product_data["type"] == "IfcMaterialLayerSet":
elif product_data["type"] == "IfcMaterialLayerSet" or product_data["type"] == "IfcMaterialLayerSetUsage":
material_set_item_data = Data.layers[self.material_set_item]
elif product_data["type"] == "IfcMaterialProfileSet":
material_set_item_data = Data.profiles[self.material_set_item]
@@ -382,7 +419,7 @@ class EditMaterialSetItem(bpy.types.Operator):
},
).execute()
Data.load_constituents()
elif product_data["type"] == "IfcMaterialLayerSet":
elif product_data["type"] == "IfcMaterialLayerSet" or product_data["type"] == "IfcMaterialLayerSetUsage":
edit_layer.Usecase(
self.file,
{
@@ -34,11 +34,12 @@ def getMaterialTypes(self, context):
"IfcMaterial",
"IfcMaterialConstituentSet",
"IfcMaterialLayerSet",
"IfcMaterialLayerSetUsage",
"IfcMaterialProfileSet",
"IfcMaterialList",
]
if IfcStore.get_file().schema == "IFC2X3":
material_types = ["IfcMaterial", "IfcMaterialLayerSet", "IfcMaterialList"]
material_types = ["IfcMaterial", "IfcMaterialLayerSet", "IfcMaterialLayerSetUsage", "IfcMaterialList"]
materialtypes_enum.clear()
materialtypes_enum = [(m, m, "") for m in material_types]
return materialtypes_enum
@@ -0,0 +1,19 @@
class Usecase:
def __init__(self, file, settings=None):
self.file = file
self.settings = {"material_set": None, "old_index": 0, "new_index": 0}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
if self.settings["material_set"].is_a("IfcMaterialConstituentSet"):
set_name = "MaterialConstituents"
elif self.settings["material_set"].is_a("IfcMaterialLayerSet"):
set_name = "MaterialLayers"
elif self.settings["material_set"].is_a("IfcMaterialProfileSet"):
set_name = "MaterialProfiles"
elif self.settings["material_set"].is_a("IfcMaterialList"):
set_name = "Materials"
items = list(getattr(self.settings["material_set"], set_name) or [])
items.insert(self.settings["new_index"], items.pop(self.settings["old_index"]))
setattr(self.settings["material_set"], set_name, items)
@@ -10,6 +10,10 @@ class BIM_PT_material(Panel):
bl_region_type = "WINDOW"
bl_context = "material"
@classmethod
def poll(cls, context):
return IfcStore.get_file() and context.active_object and context.active_object.active_material
def draw(self, context):
row = self.layout.row()
if bool(context.active_object.active_material.BIMObjectProperties.ifc_definition_id):
@@ -51,23 +55,34 @@ class BIM_PT_object_material(Panel):
if self.product_data:
if self.product_data["type"] == "IfcMaterialConstituentSet":
self.material_set_data = Data.constituent_sets[self.product_data["id"]]
self.set_items = self.material_set_data["MaterialConstituents"]
self.material_set_id = self.product_data["id"]
self.material_set_data = Data.constituent_sets[self.material_set_id]
self.set_items = self.material_set_data["MaterialConstituents"] or []
self.set_data = Data.constituents
self.set_item_name = "constituent"
elif self.product_data["type"] == "IfcMaterialLayerSet":
self.material_set_data = Data.layer_sets[self.product_data["id"]]
self.set_items = self.material_set_data["MaterialLayers"]
self.material_set_id = self.product_data["id"]
self.material_set_data = Data.layer_sets[self.material_set_id]
self.set_items = self.material_set_data["MaterialLayers"] or []
self.set_data = Data.layers
self.set_item_name = "layer"
elif self.product_data["type"] == "IfcMaterialLayerSetUsage":
self.material_set_usage = Data.layer_set_usages[self.product_data["id"]]
self.material_set_id = self.material_set_usage["ForLayerSet"]
self.material_set_data = Data.layer_sets[self.material_set_id]
self.set_items = self.material_set_data["MaterialLayers"] or []
self.set_data = Data.layers
self.set_item_name = "layer"
elif self.product_data["type"] == "IfcMaterialProfileSet":
self.material_set_data = Data.profile_sets[self.product_data["id"]]
self.set_items = self.material_set_data["MaterialProfiles"]
self.material_set_id = self.product_data["id"]
self.material_set_data = Data.profile_sets[self.material_set_id]
self.set_items = self.material_set_data["MaterialProfiles"] or []
self.set_data = Data.profiles
self.set_item_name = "profile"
elif self.product_data["type"] == "IfcMaterialList":
self.material_set_data = Data.lists[self.product_data["id"]]
self.set_items = self.material_set_data["Materials"]
self.material_set_id = self.product_data["id"]
self.material_set_data = Data.lists[self.material_set_id]
self.set_items = self.material_set_data["Materials"] or []
self.set_item_name = "list_item"
return self.draw_material_ui()
@@ -82,7 +97,8 @@ class BIM_PT_object_material(Panel):
row.label(text=self.product_data["type"])
if self.props.is_editing:
row.operator("bim.edit_assigned_material", icon="CHECKMARK", text="")
op = row.operator("bim.edit_assigned_material", icon="CHECKMARK", text="")
op.material_set = self.material_set_id
row.operator("bim.disable_editing_assigned_material", icon="X", text="")
else:
row.operator("bim.enable_editing_assigned_material", icon="GREASEPENCIL", text="")
@@ -122,14 +138,16 @@ class BIM_PT_object_material(Panel):
row.prop(self.props, "material", text="")
op = row.operator(f"bim.add_{self.set_item_name}", icon="ADD", text="")
setattr(op, f"{self.set_item_name}_set", self.product_data["id"])
setattr(op, f"{self.set_item_name}_set", self.material_set_id)
total_items = len(self.set_items)
for index, set_item_id in enumerate(self.set_items):
if self.props.active_material_set_item_id == set_item_id:
self.draw_editable_set_item_ui(set_item_id)
else:
self.draw_read_only_set_item_ui(set_item_id, is_first=index == 0, is_last=index == total_items - 1)
self.draw_read_only_set_item_ui(
set_item_id, index, is_first=index == 0, is_last=index == total_items - 1
)
def draw_editable_set_item_ui(self, set_item_id):
item = self.set_data[set_item_id]
@@ -154,7 +172,7 @@ class BIM_PT_object_material(Panel):
row.prop(attribute, "bool_value", text=attribute.name)
row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
def draw_read_only_set_item_ui(self, set_item_id, is_first=False, is_last=False):
def draw_read_only_set_item_ui(self, set_item_id, index, is_first=False, is_last=False):
if self.product_data["type"] == "IfcMaterialList":
item = Data.materials[set_item_id]
row = self.layout.row(align=True)
@@ -163,26 +181,38 @@ class BIM_PT_object_material(Panel):
else:
item = self.set_data[set_item_id]
row = self.layout.row(align=True)
row.label(text=item.get("Name", "Unnamed"), icon="ALIGN_CENTER")
row.label(text=item.get("Name", "Unnamed") or "Unnamed", icon="ALIGN_CENTER")
row.label(text=Data.materials[item["Material"]]["Name"], icon="MATERIAL")
if not is_first:
row.operator("bim.edit_attributes", icon="TRIA_UP", text="")
op = row.operator(f"bim.reorder_material_set_item", icon="TRIA_UP", text="")
op.old_index = index
op.new_index = index - 1
setattr(op, "material_set", self.material_set_id)
if not is_last:
row.operator("bim.edit_attributes", icon="TRIA_DOWN", text="")
op = row.operator(f"bim.reorder_material_set_item", icon="TRIA_DOWN", text="")
op.old_index = index
op.new_index = index + 1
setattr(op, "material_set", self.material_set_id)
if not self.props.active_material_set_item_id and self.product_data["type"] != "IfcMaterialList":
op = row.operator("bim.enable_editing_material_set_item", icon="GREASEPENCIL", text="")
op.material_set_item = set_item_id
op = row.operator(f"bim.remove_{self.set_item_name}", icon="X", text="")
if self.product_data["type"] == "IfcMaterialList":
setattr(op, "list_item_set", self.product_data["id"])
setattr(op, "list_item_set", self.material_set_id)
setattr(op, self.set_item_name, item["id"])
def draw_read_only_set_ui(self):
name_attr = "LayerSetName" if self.product_data["type"] == "IfcMaterialLayerSet" else "Name"
if (
self.product_data["type"] == "IfcMaterialLayerSetUsage"
or self.product_data["type"] == "IfcMaterialLayerSet"
):
name_attr = "LayerSetName"
else:
name_attr = "Name"
row = self.layout.row(align=True)
row.label(text=name_attr)
row.label(text=self.material_set_data.get(name_attr, "Unnamed"))
row.label(text=self.material_set_data.get(name_attr, "Unnamed") or "Unnamed")
if hasattr(self.material_set_data, "Description") and self.material_set_data["Description"]:
row = self.layout.row(align=True)
row.label(text="Description")
@@ -196,5 +226,5 @@ class BIM_PT_object_material(Panel):
else:
item = self.set_data[item_id]
row = self.layout.row(align=True)
row.label(text=item.get("Name", "Unnamed"), icon="ALIGN_CENTER")
row.label(text=item.get("Name", "Unnamed") or "Unnamed", icon="ALIGN_CENTER")
row.label(text=Data.materials[item["Material"]]["Name"], icon="MATERIAL")
@@ -3,8 +3,8 @@ import math
import ifcopenshell
from bpy.types import Operator
from bpy.props import FloatProperty
from bpy_extras.object_utils import AddObjectHelper, object_data_add
from mathutils import Vector
from blenderbim.bim.ifc import IfcStore
def add_object(self, context):
@@ -79,7 +79,11 @@ def add_object(self, context):
faces = [[0, 1, 2, 3, 4, 5]]
mesh = bpy.data.meshes.new(name="Dumb Door Profile")
mesh.from_pydata(verts, edges, faces)
obj = object_data_add(context, mesh, operator=self)
obj = bpy.data.objects.new("Door Profile", mesh)
context.view_layer.active_layer_collection.collection.objects.link(obj)
bpy.context.view_layer.objects.active = obj
obj.select_set(True)
bpy.ops.object.convert(target="CURVE")
# Door lining sweep
@@ -93,7 +97,11 @@ def add_object(self, context):
faces = []
mesh = bpy.data.meshes.new(name="Dumb Door")
mesh.from_pydata(verts, edges, faces)
obj2 = object_data_add(context, mesh, operator=self)
obj2 = bpy.data.objects.new("Door", mesh)
context.view_layer.active_layer_collection.collection.objects.link(obj2)
bpy.context.view_layer.objects.active = obj2
obj2.select_set(True)
bpy.ops.object.convert(target="CURVE")
obj2.data.dimensions = "2D"
@@ -116,10 +124,14 @@ def add_object(self, context):
faces = [[0, 1, 2, 3]]
mesh = bpy.data.meshes.new(name="Dumb Door Panel")
mesh.from_pydata(verts, edges, faces)
obj3 = object_data_add(context, mesh, operator=self)
obj3 = bpy.data.objects.new("Door Panel", mesh)
modifier = obj3.modifiers.new("Panel Height", "SOLIDIFY")
modifier.offset = 1
modifier.thickness = self.overall_height - 0.045
context.view_layer.active_layer_collection.collection.objects.link(obj3)
bpy.context.view_layer.objects.active = obj3
obj3.select_set(True)
bpy.ops.object.convert(target="MESH")
ctx = bpy.context.copy()
@@ -138,11 +150,16 @@ def add_object(self, context):
faces = [[0, 1, 2, 3]]
mesh = bpy.data.meshes.new(name="Dumb Door Opening")
mesh.from_pydata(verts, edges, faces)
obj4 = object_data_add(context, mesh, operator=self)
obj4 = bpy.data.objects.new("Door Opening", mesh)
modifier = obj4.modifiers.new("Panel Height", "SOLIDIFY")
modifier.offset = -1
modifier.thickness = self.overall_height + 0.1
context.view_layer.active_layer_collection.collection.objects.link(obj4)
bpy.context.view_layer.objects.active = obj4
obj4.select_set(True)
bpy.ops.object.convert(target="MESH")
obj4.display_type = "WIRE"
obj4.parent = obj2
obj4.matrix_parent_inverse = obj2.matrix_world.inverted()
@@ -150,6 +167,11 @@ def add_object(self, context):
obj4.name = "Door Opening"
obj2.name = "Door"
if IfcStore.get_file():
bpy.ops.bim.assign_class(obj=obj2.name, ifc_class="IfcDoor")
obj2.location = context.scene.cursor.location
#obj2.data.name = "Model/Body/MODEL_VIEW/" + guid
#obj2.data.use_fake_user = True
@@ -165,10 +187,9 @@ def add_object(self, context):
#rep.target_view = "PLAN_VIEW"
class BIM_OT_add_object(Operator, AddObjectHelper):
class BIM_OT_add_object(Operator):
bl_idname = "mesh.add_door"
bl_label = "Dumb Door"
bl_options = {"REGISTER", "UNDO"}
overall_width: FloatProperty(name="Overall Width", default=0.85)
overall_height: FloatProperty(name="Overall Height", default=2.1)
@@ -1,19 +1,17 @@
import bpy
import blenderbim.bim.module.grid.create_grid_axis as create_grid_axis
import blenderbim.bim.module.grid.create_axis_curve as create_axis_curve
from bpy.types import Operator
from bpy.props import FloatProperty, IntProperty
from bpy_extras.object_utils import AddObjectHelper, object_data_add
from mathutils import Vector
from blenderbim.bim.ifc import IfcStore
def add_object(self, context):
obj = object_data_add(context, None, operator=self)
obj.name = "IfcGrid/Grid"
name = obj.name.split("/")[1]
obj = bpy.data.objects.new("Grid", None)
obj.name = "Grid"
default_collection = obj.users_collection[0]
default_collection.objects.unlink(obj)
collection = bpy.data.collections.new("IfcGrid/" + name)
collection = bpy.data.collections.new(obj.name)
has_site_collection = False
for child in bpy.context.view_layer.layer_collection.children:
if "IfcProject/" not in child.name:
@@ -28,6 +26,17 @@ def add_object(self, context):
bpy.context.view_layer.active_layer_collection.collection.children.link(collection)
collection.objects.link(obj)
self.file = IfcStore.get_file()
if self.file:
bpy.ops.bim.assign_class(obj=obj.name, ifc_class="IfcGrid")
grid = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
if has_site_collection:
site_obj = bpy.data.objects.get(grandchild.name)
if site_obj and site_obj.BIMObjectProperties.ifc_definition_id:
bpy.ops.bim.assign_container(
relating_structure=site_obj.BIMObjectProperties.ifc_definition_id, related_element=obj.name
)
axes_collection = bpy.data.collections.new("UAxes")
collection.children.link(axes_collection)
for i in range(0, self.total_u):
@@ -39,14 +48,17 @@ def add_object(self, context):
faces = []
mesh = bpy.data.meshes.new(name="Grid Axis")
mesh.from_pydata(verts, edges, faces)
obj = object_data_add(context, mesh, operator=self)
tag = chr(ord("A") + i)
obj.name = "IfcGridAxis/" + tag
default_collection.objects.unlink(obj)
obj = bpy.data.objects.new(f"IfcGridAxis/{tag}", mesh)
axes_collection.objects.link(obj)
attribute = obj.BIMObjectProperties.attributes.add()
attribute.name = "AxisTag"
attribute.string_value = tag
if self.file:
result = create_grid_axis.Usecase(
self.file, {"AxisTag": tag, "AxisCurve": obj, "UVWAxes": "UAxes", "Grid": grid}
).execute()
create_axis_curve.Usecase(self.file, {"AxisCurve": obj, "grid_axis": result}).execute()
obj.BIMObjectProperties.ifc_definition_id = result.id()
axes_collection = bpy.data.collections.new("VAxes")
collection.children.link(axes_collection)
@@ -59,17 +71,20 @@ def add_object(self, context):
faces = []
mesh = bpy.data.meshes.new(name="Grid Axis")
mesh.from_pydata(verts, edges, faces)
obj = object_data_add(context, mesh, operator=self)
tag = str(i + 1).zfill(2)
obj.name = "IfcGridAxis/" + tag
default_collection.objects.unlink(obj)
obj = bpy.data.objects.new(f"IfcGridAxis/{tag}", mesh)
axes_collection.objects.link(obj)
attribute = obj.BIMObjectProperties.attributes.add()
attribute.name = "AxisTag"
attribute.string_value = tag
if IfcStore.get_file():
result = create_grid_axis.Usecase(
self.file, {"AxisTag": tag, "AxisCurve": obj, "UVWAxes": "VAxes", "Grid": grid}
).execute()
create_axis_curve.Usecase(self.file, {"AxisCurve": obj, "grid_axis": result}).execute()
obj.BIMObjectProperties.ifc_definition_id = result.id()
class BIM_OT_add_object(Operator, AddObjectHelper):
class BIM_OT_add_object(Operator):
bl_idname = "mesh.add_grid"
bl_label = "Grid"
bl_options = {"REGISTER", "UNDO"}
@@ -98,13 +98,13 @@ class PieUpdateContainer(bpy.types.Operator):
for obj in context.selected_objects:
if not obj.BIMObjectProperties.ifc_definition_id:
continue
relating_structure = None
for collection in obj.users_collection:
relating_structure_obj = bpy.data.objects.get(collection.name)
if not relating_structure_obj or not relating_structure_obj.BIMObjectProperties.ifc_definition_id:
continue
relating_structure = relating_structure_obj
bpy.ops.bim.assign_container(relating_structure=relating_structure.name, related_element=obj.name)
spatial_obj = bpy.data.objects.get(collection.name)
if spatial_obj and spatial_obj.BIMObjectProperties.ifc_definition_id:
bpy.ops.bim.assign_container(
relating_structure=spatial_obj.BIMObjectProperties.ifc_definition_id, related_element=obj.name
)
break
return {"FINISHED"}
@@ -115,7 +115,6 @@ class VIEW3D_MT_PIE_bim(bpy.types.Menu):
pie = self.layout.menu_pie()
pie.operator("bim.edit_object_placement")
pie.operator("bim.update_mesh_representation")
pie.operator("bim.map_representation")
pie.operator("bim.pie_add_opening")
pie.operator("bim.pie_update_container")
pie.operator("bim.open_pie_class", text="Assign IFC Class")
@@ -1,8 +1,8 @@
import bpy
from bpy.types import Operator
from bpy.props import FloatProperty
from bpy_extras.object_utils import AddObjectHelper, object_data_add
from mathutils import Vector
from blenderbim.bim.ifc import IfcStore
def add_object(self, context):
@@ -17,18 +17,21 @@ def add_object(self, context):
mesh = bpy.data.meshes.new(name="Dumb Slab")
mesh.from_pydata(verts, edges, faces)
obj = object_data_add(context, mesh, operator=self)
obj = bpy.data.objects.new("Slab", mesh)
modifier = obj.modifiers.new("Slab Depth", "SOLIDIFY")
modifier.use_even_offset = True
modifier.offset = 1
modifier.thickness = self.depth
obj.name = "Slab"
context.view_layer.active_layer_collection.collection.objects.link(obj)
if IfcStore.get_file():
bpy.ops.bim.assign_class(obj=obj.name, ifc_class="IfcSlab", predefined_type="FLOOR")
obj.location = context.scene.cursor.location
class BIM_OT_add_object(Operator, AddObjectHelper):
class BIM_OT_add_object(Operator):
bl_idname = "mesh.add_slab"
bl_label = "Dumb Slab"
bl_options = {"REGISTER", "UNDO"}
length: FloatProperty(name="Length", default=2)
width: FloatProperty(name="Width", default=2)
@@ -1,8 +1,8 @@
import bpy
from bpy.types import Operator
from bpy.props import FloatProperty, BoolProperty
from bpy_extras.object_utils import AddObjectHelper, object_data_add
from mathutils import Vector
from blenderbim.bim.ifc import IfcStore
def add_object(self, context):
@@ -25,7 +25,8 @@ def add_object(self, context):
mesh = bpy.data.meshes.new(name="Dumb Wall")
mesh.from_pydata(verts, edges, faces)
obj = object_data_add(context, mesh, operator=self)
obj = bpy.data.objects.new("Wall", mesh)
context.view_layer.active_layer_collection.collection.objects.link(obj)
if not self.use_plane:
modifier = obj.modifiers.new("Wall Height", "SCREW")
modifier.angle = 0
@@ -39,13 +40,15 @@ def add_object(self, context):
modifier.use_even_offset = True
modifier.thickness = self.width
obj.name = "Wall"
if IfcStore.get_file():
bpy.ops.bim.assign_class(obj=obj.name, ifc_class="IfcWall")
obj.location = context.scene.cursor.location
return obj
class BIM_OT_add_object(Operator, AddObjectHelper):
class BIM_OT_add_object(Operator):
bl_idname = "mesh.add_wall"
bl_label = "Dumb Wall"
bl_options = {"REGISTER", "UNDO"}
height: FloatProperty(name="Height", default=3)
length: FloatProperty(name="Length", default=1)
@@ -3,8 +3,8 @@ import math
import ifcopenshell
from bpy.types import Operator
from bpy.props import FloatProperty
from bpy_extras.object_utils import AddObjectHelper, object_data_add
from mathutils import Vector
from blenderbim.bim.ifc import IfcStore
def add_object(self, context):
@@ -64,7 +64,11 @@ def add_object(self, context):
faces = [[0, 1, 2, 3]]
mesh = bpy.data.meshes.new(name="Dumb Window Profile")
mesh.from_pydata(verts, edges, faces)
obj = object_data_add(context, mesh, operator=self)
obj = bpy.data.objects.new("Window Profile", mesh)
context.view_layer.active_layer_collection.collection.objects.link(obj)
bpy.context.view_layer.objects.active = obj
obj.select_set(True)
bpy.ops.object.convert(target="CURVE")
# Window lining sweep
@@ -78,7 +82,11 @@ def add_object(self, context):
faces = []
mesh = bpy.data.meshes.new(name="Dumb Window")
mesh.from_pydata(verts, edges, faces)
obj2 = object_data_add(context, mesh, operator=self)
obj2 = bpy.data.objects.new("Window", mesh)
context.view_layer.active_layer_collection.collection.objects.link(obj2)
bpy.context.view_layer.objects.active = obj2
obj2.select_set(True)
bpy.ops.object.convert(target="CURVE")
obj2.data.splines[0].use_cyclic_u = True
@@ -102,10 +110,14 @@ def add_object(self, context):
faces = [[0, 1, 2, 3]]
mesh = bpy.data.meshes.new(name="Dumb Window Panel")
mesh.from_pydata(verts, edges, faces)
obj3 = object_data_add(context, mesh, operator=self)
obj3 = bpy.data.objects.new("Window Panel", mesh)
modifier = obj3.modifiers.new("Panel Height", "SOLIDIFY")
modifier.offset = 1
modifier.thickness = self.overall_height - 0.08
context.view_layer.active_layer_collection.collection.objects.link(obj3)
bpy.context.view_layer.objects.active = obj3
obj3.select_set(True)
bpy.ops.object.convert(target="MESH")
ctx = bpy.context.copy()
@@ -124,10 +136,14 @@ def add_object(self, context):
faces = [[0, 1, 2, 3]]
mesh = bpy.data.meshes.new(name="Dumb Window Opening")
mesh.from_pydata(verts, edges, faces)
obj4 = object_data_add(context, mesh, operator=self)
obj4 = bpy.data.objects.new("Window Opening", mesh)
modifier = obj4.modifiers.new("Panel Height", "SOLIDIFY")
modifier.offset = -1
modifier.thickness = self.overall_height
context.view_layer.active_layer_collection.collection.objects.link(obj4)
bpy.context.view_layer.objects.active = obj4
obj4.select_set(True)
bpy.ops.object.convert(target="MESH")
obj4.display_type = "WIRE"
obj4.parent = obj2
@@ -135,7 +151,10 @@ def add_object(self, context):
obj4.hide_render = True
obj4.name = "Window Opening"
if IfcStore.get_file():
bpy.ops.bim.assign_class(obj=obj2.name, ifc_class="IfcWindow")
obj2.name = "Window"
obj2.location = context.scene.cursor.location
#obj2.data.name = "Model/Body/MODEL_VIEW/" + guid
#obj2.data.use_fake_user = True
@@ -150,10 +169,9 @@ def add_object(self, context):
#rep.target_view = "PLAN_VIEW"
class BIM_OT_add_object(Operator, AddObjectHelper):
class BIM_OT_add_object(Operator):
bl_idname = "mesh.add_window"
bl_label = "Dumb Window"
bl_options = {"REGISTER", "UNDO"}
overall_width: FloatProperty(name="Overall Width", default=0.7)
overall_height: FloatProperty(name="Overall Height", default=1)
@@ -27,6 +27,15 @@ def update_owner_history(element, change_action=None):
def create_owner_history(change_action=None):
file = IfcStore.get_file()
if (
not bpy.context.scene.BIMOwnerProperties.user_person
or not bpy.context.scene.BIMOwnerProperties.user_organisation
):
if file.schema == "IFC2X3":
assert False, "A person and organisation is required in IFC2X3."
return None
return create_owner_history_usecase.Usecase(
file,
{
@@ -175,6 +175,8 @@ class Data:
data_type = "string"
elif "<real>" in data_type:
data_type = "float"
elif "<number>" in data_type:
data_type = "integer"
elif "<integer>" in data_type:
data_type = "integer"
elif "<boolean>" in data_type:
@@ -1,5 +1,6 @@
import bpy
from blenderbim.bim.prop import Attribute
from blenderbim.bim.module.pset.data import Data
import blenderbim.bim.schema
from bpy.types import PropertyGroup
from bpy.props import (
@@ -20,12 +21,16 @@ qtonames = {}
def getPsetNames(self, context):
global psetnames
if "/" in context.active_object.name:
ifc_class = context.active_object.name.split("/")[0]
obj = context.active_object
if "/" in obj.name:
ifc_class = obj.name.split("/")[0]
if ifc_class not in psetnames:
psets = blenderbim.bim.schema.ifc.psetqto.get_applicable(ifc_class, pset_only=True)
psetnames[ifc_class] = [(p.Name, p.Name, "") for p in psets]
return psetnames[ifc_class]
assigned_names = [
Data.psets[p]["Name"] for p in Data.products[obj.BIMObjectProperties.ifc_definition_id]["psets"]
]
return [p for p in psetnames[ifc_class] if p[0] not in assigned_names]
return []
@@ -37,13 +37,21 @@ def draw_psetqto_ui(context, pset_id, pset, props, layout, obj_type):
for prop in pset["Properties"]:
draw_psetqto_editable_ui(box, props, prop)
else:
has_props_displayed = False
for prop in pset["Properties"]:
if prop["value"] is None or prop["value"] == "":
if context.preferences.addons["blenderbim"].preferences.should_hide_empty_props and (
prop["value"] is None or prop["value"] == ""
):
continue
has_props_displayed = True
row = box.row(align=True)
row.scale_y = 0.8
row.label(text=prop["Name"])
row.label(text=str(prop["value"]))
if not has_props_displayed:
row = box.row()
row.scale_y = 0.8
row.label(text="No Properties Set")
def draw_psetqto_editable_ui(box, props, prop):
@@ -167,11 +175,13 @@ class BIM_PT_material_psets(Panel):
def poll(cls, context):
if not context.active_object:
return False
if not context.active_object.active_material:
return False
props = context.active_object.active_material.BIMObjectProperties
if not props.ifc_definition_id:
return False
if IfcStore.get_file().schema == "IFC2X3":
return False # We don't support material psets in IFC2X3 because they suck
return False # We don't support material psets in IFC2X3 because they suck
if props.ifc_definition_id not in Data.products:
Data.load(props.ifc_definition_id)
if not Data.products[props.ifc_definition_id]:
@@ -2,14 +2,19 @@ import bpy
from . import ui, prop, operator
classes = (
operator.AddPropertySetTemplate,
operator.RemovePropertySetTemplate,
operator.EditPropertySetTemplate,
operator.SavePropertySetTemplate,
operator.AddPropertyTemplate,
operator.RemovePropertyTemplate,
prop.PropertySetTemplate,
prop.PropertyTemplate,
operator.SavePsetTemplateFile,
operator.AddPsetTemplate,
operator.EditPsetTemplate,
operator.RemovePsetTemplate,
operator.AddPropTemplate,
operator.EditPropTemplate,
operator.RemovePropTemplate,
operator.EnableEditingPsetTemplate,
operator.DisableEditingPsetTemplate,
operator.EnableEditingPropTemplate,
operator.DisableEditingPropTemplate,
prop.PsetTemplate,
prop.PropTemplate,
prop.BIMPsetTemplateProperties,
ui.BIM_PT_pset_template,
)
@@ -0,0 +1,22 @@
import ifcopenshell
class Usecase:
def __init__(self, file, settings={}):
self.file = file
self.settings = {"pset_template": None}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
prop_template = self.file.create_entity("IfcSimplePropertyTemplate", **{
"GlobalId": ifcopenshell.guid.new(),
"Name": "NewProperty",
"PrimaryMeasureType": "IfcLabel",
"TemplateType": "P_SINGLEVALUE",
"AccessState": "READWRITE"
})
has_property_templates = list(self.settings["pset_template"].HasPropertyTemplates or [])
has_property_templates.append(prop_template)
self.settings["pset_template"].HasPropertyTemplates = has_property_templates
return prop_template
@@ -0,0 +1,17 @@
import ifcopenshell
class Usecase:
def __init__(self, file, settings={}):
self.file = file
self.settings = {}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
self.file.create_entity("IfcPropertySetTemplate", **{
"GlobalId": ifcopenshell.guid.new(),
"Name": "New_Pset",
"TemplateType": "PSET_TYPEDRIVENONLY",
"ApplicableEntity": "IfcTypeObject"
})
@@ -0,0 +1,37 @@
from blenderbim.bim.ifc import IfcStore
class Data:
is_loaded = False
pset_templates = {}
prop_templates = {}
@classmethod
def purge(cls):
cls.is_loaded = False
cls.pset_templates = {}
cls.prop_templates = {}
@classmethod
def load(cls):
cls._file = IfcStore.pset_template_file
cls.pset_templates = {}
cls.prop_templates = {}
for pset_template in cls._file.by_type("IfcPropertySetTemplate"):
data = pset_template.get_info()
data["HasPropertyTemplates"] = [e.id() for e in pset_template.HasPropertyTemplates or []]
if "OwnerHistory" in data:
del data["OwnerHistory"]
cls.pset_templates[pset_template.id()] = data
for prop_template in cls._file.by_type("IfcSimplePropertyTemplate"):
data = prop_template.get_info()
cls.prop_templates[prop_template.id()] = {
"GlobalId": data["GlobalId"],
"Name": data["Name"],
"Description": data["Description"],
"PrimaryMeasureType": data["PrimaryMeasureType"]
}
cls.is_loaded = True
@@ -0,0 +1,10 @@
class Usecase:
def __init__(self, file, settings=None):
self.file = file
self.settings = {"prop_template": None, "attributes": {}}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
for name, value in self.settings["attributes"].items():
setattr(self.settings["prop_template"], name, value)
@@ -0,0 +1,10 @@
class Usecase:
def __init__(self, file, settings=None):
self.file = file
self.settings = {"pset_template": None, "attributes": {}}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
for name, value in self.settings["attributes"].items():
setattr(self.settings["pset_template"], name, value)
@@ -1,127 +1,167 @@
import bpy
import ifcopenshell
from blenderbim.bim.module.pset_template.prop import refreshPropertySetTemplates
import blenderbim.bim.module.pset_template.add_pset_template as add_pset_template
import blenderbim.bim.module.pset_template.edit_pset_template as edit_pset_template
import blenderbim.bim.module.pset_template.remove_pset_template as remove_pset_template
import blenderbim.bim.module.pset_template.add_prop_template as add_prop_template
import blenderbim.bim.module.pset_template.edit_prop_template as edit_prop_template
import blenderbim.bim.module.pset_template.remove_prop_template as remove_prop_template
from blenderbim.bim.module.pset_template.prop import updatePsetTemplateFiles, updatePsetTemplates
from blenderbim.bim.module.pset_template.data import Data
from blenderbim.bim.ifc import IfcStore
class AddPropertySetTemplate(bpy.types.Operator):
bl_idname = "bim.add_property_set_template"
bl_label = "Add Property Set Template"
class AddPsetTemplate(bpy.types.Operator):
bl_idname = "bim.add_pset_template"
bl_label = "Add Pset Template"
def execute(self, context):
props = context.scene.BIMPsetTemplateProperties
props.active_property_set_template.global_id = ""
props.active_property_set_template.name = "New_Pset"
props.active_property_set_template.description = ""
props.active_property_set_template.template_type = "PSET_TYPEDRIVENONLY"
props.active_property_set_template.applicable_entity = "IfcTypeObject"
while len(props.property_templates) > 0:
props.property_templates.remove(0)
add_pset_template.Usecase(IfcStore.pset_template_file).execute()
Data.load()
updatePsetTemplates(self, context)
return {"FINISHED"}
class RemovePropertySetTemplate(bpy.types.Operator):
bl_idname = "bim.remove_property_set_template"
bl_label = "Remove Property Set Template"
class RemovePsetTemplate(bpy.types.Operator):
bl_idname = "bim.remove_pset_template"
bl_label = "Remove Pset Template"
def execute(self, context):
props = context.scene.BIMPsetTemplateProperties
template = IfcStore.pset_template_file.by_guid(props.property_set_templates)
IfcStore.pset_template_file.remove(template)
if props.active_pset_template_id == int(props.pset_templates):
bpy.ops.bim.disable_editing_pset_template()
remove_pset_template.Usecase(IfcStore.pset_template_file, {
"pset_template": IfcStore.pset_template_file.by_id(int(props.pset_templates))
}).execute()
Data.load()
updatePsetTemplates(self, context)
return {"FINISHED"}
class EnableEditingPsetTemplate(bpy.types.Operator):
bl_idname = "bim.enable_editing_pset_template"
bl_label = "Enable Editing Pset Template"
def execute(self, context):
props = context.scene.BIMPsetTemplateProperties
props.active_pset_template_id = int(props.pset_templates)
template = Data.pset_templates[props.active_pset_template_id]
props.active_pset_template.global_id = template["GlobalId"]
props.active_pset_template.name = template["Name"] or ""
props.active_pset_template.description = template["Description"] or ""
props.active_pset_template.template_type = template["TemplateType"]
props.active_pset_template.applicable_entity = template["ApplicableEntity"] or ""
return {"FINISHED"}
class DisableEditingPsetTemplate(bpy.types.Operator):
bl_idname = "bim.disable_editing_pset_template"
bl_label = "Disable Editing Pset Template"
def execute(self, context):
props = context.scene.BIMPsetTemplateProperties
props.active_pset_template_id = 0
return {"FINISHED"}
class EnableEditingPropTemplate(bpy.types.Operator):
bl_idname = "bim.enable_editing_prop_template"
bl_label = "Enable Editing Prop Template"
prop_template: bpy.props.IntProperty()
def execute(self, context):
props = context.scene.BIMPsetTemplateProperties
props.active_prop_template_id = self.prop_template
template = Data.prop_templates[props.active_prop_template_id]
props.active_prop_template.name = template["Name"] or ""
props.active_prop_template.description = template["Description"] or ""
props.active_prop_template.primary_measure_type = template["PrimaryMeasureType"]
return {"FINISHED"}
class DisableEditingPropTemplate(bpy.types.Operator):
bl_idname = "bim.disable_editing_prop_template"
bl_label = "Disable Editing Prop Template"
def execute(self, context):
props = context.scene.BIMPsetTemplateProperties
props.active_prop_template_id = 0
return {"FINISHED"}
class EditPsetTemplate(bpy.types.Operator):
bl_idname = "bim.edit_pset_template"
bl_label = "Edit Pset Template"
def execute(self, context):
props = context.scene.BIMPsetTemplateProperties
edit_pset_template.Usecase(IfcStore.pset_template_file, {
"pset_template": IfcStore.pset_template_file.by_id(props.active_pset_template_id),
"attributes": {
"Name": props.active_pset_template.name,
"Description": props.active_pset_template.description,
"TemplateType": props.active_pset_template.template_type,
"ApplicableEntity": props.active_pset_template.applicable_entity,
}
}).execute()
Data.load()
updatePsetTemplates(self, context)
bpy.ops.bim.disable_editing_pset_template()
return {"FINISHED"}
class SavePsetTemplateFile(bpy.types.Operator):
bl_idname = "bim.save_pset_template_file"
bl_label = "Save Pset Template File"
def execute(self, context):
IfcStore.pset_template_file.write(IfcStore.pset_template_path)
refreshPropertySetTemplates(self, context)
return {"FINISHED"}
class EditPropertySetTemplate(bpy.types.Operator):
bl_idname = "bim.edit_property_set_template"
bl_label = "Edit Property Set Template"
class AddPropTemplate(bpy.types.Operator):
bl_idname = "bim.add_prop_template"
bl_label = "Add Prop Template"
def execute(self, context):
props = context.scene.BIMPsetTemplateProperties
template = IfcStore.pset_template_file.by_guid(props.property_set_templates)
props.active_property_set_template.global_id = template.GlobalId
props.active_property_set_template.name = template.Name
props.active_property_set_template.description = template.Description
props.active_property_set_template.template_type = template.TemplateType
props.active_property_set_template.applicable_entity = template.ApplicableEntity
while len(props.property_templates) > 0:
props.property_templates.remove(0)
if template.HasPropertyTemplates:
for property_template in template.HasPropertyTemplates:
if not property_template.is_a("IfcSimplePropertyTemplate"):
continue
new = props.property_templates.add()
new.global_id = property_template.GlobalId
new.name = property_template.Name
new.description = property_template.Description
new.primary_measure_type = property_template.PrimaryMeasureType
pset_template_id = props.active_pset_template_id or int(props.pset_templates)
add_prop_template.Usecase(IfcStore.pset_template_file, {
"pset_template": IfcStore.pset_template_file.by_id(pset_template_id)
}).execute()
Data.load()
return {"FINISHED"}
class SavePropertySetTemplate(bpy.types.Operator):
bl_idname = "bim.save_property_set_template"
bl_label = "Save Property Set Template"
class RemovePropTemplate(bpy.types.Operator):
bl_idname = "bim.remove_prop_template"
bl_label = "Remove Prop Template"
prop_template: bpy.props.IntProperty()
def execute(self, context):
props = context.scene.BIMPsetTemplateProperties
blender_property_set_template = props.active_property_set_template
if blender_property_set_template.global_id:
template = IfcStore.pset_template_file.by_guid(blender_property_set_template.global_id)
else:
template = IfcStore.pset_template_file.createIfcPropertySetTemplate()
template.GlobalId = ifcopenshell.guid.new()
template.Name = blender_property_set_template.name
template.Description = blender_property_set_template.description
template.TemplateType = blender_property_set_template.template_type
template.ApplicableEntity = blender_property_set_template.applicable_entity
saved_global_ids = []
for blender_property_template in props.property_templates:
if blender_property_template.global_id:
property_template = IfcStore.pset_template_file.by_guid(blender_property_template.global_id)
else:
property_template = IfcStore.pset_template_file.createIfcSimplePropertyTemplate()
property_template.GlobalId = ifcopenshell.guid.new()
if template.HasPropertyTemplates:
has_property_templates = list(template.HasPropertyTemplates)
else:
has_property_templates = []
has_property_templates.append(property_template)
template.HasPropertyTemplates = has_property_templates
property_template.Name = blender_property_template.name
property_template.Description = blender_property_template.description
property_template.PrimaryMeasureType = blender_property_template.primary_measure_type
property_template.TemplateType = "P_SINGLEVALUE"
property_template.AccessState = "READWRITE"
saved_global_ids.append(property_template.GlobalId)
for element in template.HasPropertyTemplates:
if element.GlobalId not in saved_global_ids:
IfcStore.pset_template_file.remove(element)
IfcStore.pset_template_file.write(IfcStore.pset_template_path)
refreshPropertySetTemplates(self, context)
remove_prop_template.Usecase(IfcStore.pset_template_file, {
"prop_template": IfcStore.pset_template_file.by_id(self.prop_template)
}).execute()
Data.load()
return {"FINISHED"}
class AddPropertyTemplate(bpy.types.Operator):
bl_idname = "bim.add_property_template"
bl_label = "Add Property Template"
class EditPropTemplate(bpy.types.Operator):
bl_idname = "bim.edit_prop_template"
bl_label = "Edit Prop Template"
def execute(self, context):
context.scene.BIMPsetTemplateProperties.property_templates.add()
return {"FINISHED"}
class RemovePropertyTemplate(bpy.types.Operator):
bl_idname = "bim.remove_property_template"
bl_label = "Remove Property Template"
index: bpy.props.IntProperty()
def execute(self, context):
bpy.context.scene.BIMPsetTemplateProperties.property_templates.remove(self.index)
props = context.scene.BIMPsetTemplateProperties
edit_prop_template.Usecase(IfcStore.pset_template_file, {
"prop_template": IfcStore.pset_template_file.by_id(props.active_prop_template_id),
"attributes": {
"Name": props.active_prop_template.name,
"Description": props.active_prop_template.description,
"PrimaryMeasureType": props.active_prop_template.primary_measure_type,
}
}).execute()
Data.load()
bpy.ops.bim.disable_editing_prop_template()
return {"FINISHED"}
@@ -14,16 +14,22 @@ from bpy.props import (
FloatVectorProperty,
CollectionProperty,
)
from blenderbim.bim.module.pset_template.data import Data
psettemplatefiles_enum = []
propertysettemplates_enum = []
psettemplates_enum = []
def refreshPropertySetTemplates(self, context):
global propertysettemplates_enum
propertysettemplates_enum.clear()
getPropertySetTemplates(self, context)
def updatePsetTemplateFiles(self, context):
global psettemplates_enum
IfcStore.pset_template_path = os.path.join(
context.scene.BIMProperties.data_dir,
"pset",
context.scene.BIMPsetTemplateProperties.pset_template_files + ".ifc",
)
IfcStore.pset_template_file = ifcopenshell.open(IfcStore.pset_template_path)
updatePsetTemplates(self, context)
def getPsetTemplateFiles(self, context):
@@ -34,19 +40,29 @@ def getPsetTemplateFiles(self, context):
return psettemplatefiles_enum
def getPropertySetTemplates(self, context):
global propertysettemplates_enum
if len(propertysettemplates_enum) < 1:
IfcStore.pset_template_path = os.path.join(
context.scene.BIMProperties.data_dir, "pset", context.scene.BIMPsetTemplateProperties.pset_template_files + ".ifc"
)
IfcStore.pset_template_file = ifcopenshell.open(IfcStore.pset_template_path)
def updatePsetTemplates(self, context):
global psettemplates_enum
psettemplates_enum.clear()
getPsetTemplates(self, context)
def getPsetTemplates(self, context):
global psettemplates_enum
if len(psettemplates_enum) < 1:
if not IfcStore.pset_template_file:
IfcStore.pset_template_path = os.path.join(
context.scene.BIMProperties.data_dir,
"pset",
context.scene.BIMPsetTemplateProperties.pset_template_files + ".ifc",
)
IfcStore.pset_template_file = ifcopenshell.open(IfcStore.pset_template_path)
templates = IfcStore.pset_template_file.by_type("IfcPropertySetTemplate")
propertysettemplates_enum.extend([(t.GlobalId, t.Name, "") for t in templates])
return propertysettemplates_enum
psettemplates_enum.extend([(str(t.id()), t.Name, "") for t in templates])
Data.load()
return psettemplates_enum
class PropertySetTemplate(PropertyGroup):
class PsetTemplate(PropertyGroup):
global_id: StringProperty(name="Global ID")
name: StringProperty(name="Name")
description: StringProperty(name="Description")
@@ -98,7 +114,7 @@ class PropertySetTemplate(PropertyGroup):
applicable_entity: StringProperty(name="Applicable Entity")
class PropertyTemplate(PropertyGroup):
class PropTemplate(PropertyGroup):
global_id: StringProperty(name="Global ID")
name: StringProperty(name="Name")
description: StringProperty(name="Description")
@@ -222,8 +238,10 @@ class PropertyTemplate(PropertyGroup):
class BIMPsetTemplateProperties(PropertyGroup):
pset_template_files: EnumProperty(
items=getPsetTemplateFiles, name="Pset Template Files", update=refreshPropertySetTemplates
items=getPsetTemplateFiles, name="Pset Template Files", update=updatePsetTemplateFiles
)
property_set_templates: EnumProperty(items=getPropertySetTemplates, name="Pset Template Files")
active_property_set_template: PointerProperty(type=PropertySetTemplate)
property_templates: CollectionProperty(name="Property Templates", type=PropertyTemplate)
pset_templates: EnumProperty(items=getPsetTemplates, name="Pset Template Files")
active_pset_template_id: IntProperty(name="Active Pset Template Id")
active_prop_template_id: IntProperty(name="Active Prop Template Id")
active_pset_template: PointerProperty(type=PsetTemplate)
active_prop_template: PointerProperty(type=PropTemplate)

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