diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml new file mode 100644 index 0000000000..8de307ad0e --- /dev/null +++ b/.github/workflows/cd.yml @@ -0,0 +1,107 @@ +name: CD + +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: Install dependencies + run: | + sudo apt update + sudo apt-get install --no-install-recommends \ + git cmake gcc g++ libboost1.67-all-dev python3-all-dev swig libpcre3-dev libxml2-dev \ + liboce-foundation-dev liboce-modeling-dev liboce-ocaf-dev liboce-visualization-dev liboce-ocaf-lite-dev + + - + name: ccache + uses: hendrikmuhs/ccache-action@v1 + + - + name: Build ifcopenshell + run: | + mkdir build && cd build + cmake \ + -DCMAKE_C_COMPILER_LAUNCHER=ccache \ + -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \ + -DCMAKE_INSTALL_PREFIX=$PWD/install/ \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_PREFIX_PATH=/usr \ + -DCMAKE_SYSTEM_PREFIX_PATH=/usr \ + -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 -j $(nproc) + 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 Dockerhub + uses: docker/login-action@v1 + with: + username: aecgeeks + password: ${{ secrets.DOCKER_HUB_TOKEN }} + - + name: Build container image + uses: docker/build-push-action@v2 + with: + context: artifacts + repository: aecgeeks/ifcopenshell + tags: aecgeeks/ifcopenshell:latest + file: ./Dockerfile + push: true diff --git a/.github/workflows/ci-bcf.yml b/.github/workflows/ci-bcf.yml new file mode 100644 index 0000000000..8d689cc2fd --- /dev/null +++ b/.github/workflows/ci-bcf.yml @@ -0,0 +1,38 @@ +name: ci-bcf + +on: + push: + +jobs: + activate: + runs-on: ubuntu-latest + if: | + github.repository == 'IfcOpenShell/IfcOpenShell' && + contains(github.event.head_commit.message, '[bcf release]') + steps: + - run: echo ok go + upload: + needs: activate + name: Upload BCF package to Pypi + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v2 + with: + python-version: '3.x' + - name: Build package + run: | + cd src/bcf + pip install build + python -m build + - name: Publish package + uses: pypa/gh-action-pypi-publish@release/v1 + with: + user: __token__ + password: ${{ secrets.PYPI_TOKEN }} + packages_dir: src/bcf/dist + + + + + diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 77b114df5a..bfa9f4d897 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,119 +1,92 @@ -name: CI +name: ci -on: +on: push: - + paths: + - 'src/**' + - 'test/**' + - 'conda/**' + - 'cmake/**' + - '.github/workflows/ci.yml' + pull_request: 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') + !contains(github.event.head_commit.message, 'skip ci') steps: - - run: echo ok go + - run: echo ok go build: runs-on: ubuntu-20.04 needs: activate steps: - - uses: actions/checkout@v2 - with: - submodules: recursive + - uses: actions/checkout@v2 + with: + submodules: recursive + - name: Install C++ dependencies + run: | + sudo apt update + sudo apt-get install --no-install-recommends \ + git cmake gcc g++ \ + libboost-date-time-dev \ + libboost-filesystem-dev \ + libboost-iostreams-dev \ + libboost-program-options-dev \ + libboost-regex-dev \ + libboost-system-dev \ + libboost-thread-dev \ + python3-all-dev python3-pip \ + swig libpcre3-dev libxml2-dev \ + libtbb-dev nlohmann-json3-dev \ + liboce-foundation-dev liboce-modeling-dev liboce-ocaf-dev liboce-visualization-dev liboce-ocaf-lite-dev \ + libhdf5-dev libcgal-dev + + - name: ccache + uses: hendrikmuhs/ccache-action@v1 - - name: Install dependencies - run: | - sudo apt update - sudo apt-get install --no-install-recommends \ - git cmake gcc g++ libboost-all-dev python3-all-dev swig libpcre3-dev libxml2-dev \ - liboce-foundation-dev liboce-modeling-dev liboce-ocaf-dev liboce-visualization-dev liboce-ocaf-lite-dev \ - libhdf5-dev libcgal-dev + - name: Build ifcopenshell + run: | + mkdir build && cd build + cmake \ + -DCMAKE_C_COMPILER_LAUNCHER=ccache \ + -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \ + -DCMAKE_INSTALL_PREFIX=$PWD/install/ \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_PREFIX_PATH=/usr \ + -DCMAKE_SYSTEM_PREFIX_PATH=/usr \ + -DOCC_INCLUDE_DIR=/usr/include/oce \ + -DOCC_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 \ + "-DSCHEMA_VERSIONS=2x3;4" \ + -DGLTF_SUPPORT=On \ + -DJSON_INCLUDE_DIR=/usr/include \ + -DCGAL_INCLUDE_DIR=/usr/include \ + -DGMP_INCLUDE_DIR=/usr/include \ + -DMPFR_INCLUDE_DIR=/usr/include \ + -DGMP_LIBRARY_DIR=/usr/lib/x86_64-linux-gnu \ + -DMPFR_LIBRARY_DIR=/usr/lib/x86_64-linux-gnu \ + -DHDF5_INCLUDE_DIR=/usr/include/hdf5/serial \ + ../cmake + sudo make -j $(nproc) + sudo make install - - - name: ccache - uses: hendrikmuhs/ccache-action@v1 + - name: Install Python dependencies + run: | + sudo /usr/bin/python -m pip install -U pip + sudo /usr/bin/python -m pip install xmlschema numpy lxml + sudo /usr/bin/python -m pip install src/bcf + sudo /usr/bin/python -m pip install pytest - - - name: Build ifcopenshell - run: | - mkdir build && cd build - cmake \ - -DCMAKE_C_COMPILER_LAUNCHER=ccache \ - -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \ - -DCMAKE_INSTALL_PREFIX=$PWD/install/ \ - -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_PREFIX_PATH=/usr \ - -DCMAKE_SYSTEM_PREFIX_PATH=/usr \ - -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 \ - \ - -DCGAL_INCLUDE_DIR=/usr/include \ - -DGMP_INCLUDE_DIR=/usr/include \ - -DMPFR_INCLUDE_DIR=/usr/include \ - -DGMP_LIBRARY_DIR=/usr/lib/x86_64-linux-gnu \ - -DMPFR_LIBRARY_DIR=/usr/lib/x86_64-linux-gnu \ - \ - "-DSCHEMA_VERSIONS=2x3;4" \ - -DHDF5_INCLUDE_DIR=/usr/include/hdf5/serial \ - ../cmake - make -j $(nproc) - 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 Dockerhub - uses: docker/login-action@v1 - with: - username: aecgeeks - password: ${{ secrets.DOCKER_HUB_TOKEN }} - - - name: Build container image - uses: docker/build-push-action@v2 - with: - context: artifacts - repository: aecgeeks/ifcopenshell - tags: aecgeeks/ifcopenshell:latest - file: ./Dockerfile - push: true + - name: Test + run: | + cd test + sudo /usr/bin/python tests.py + cd ../src/ifcopenshell-python + mv ifcopenshell ifcopenshell-local # Force testing on installed module + make test diff --git a/.gitignore b/.gitignore index 9485fddea2..c49cfab341 100644 --- a/.gitignore +++ b/.gitignore @@ -28,8 +28,10 @@ Pipfile.lock # gettext binary translation files *.mo + # Vim *.swp +*.swo # Flask instance/* @@ -49,3 +51,9 @@ Pipfile.lock # Database *.db +# PyTest +htmlcov +.coverage + +# Blender +*.blend1 diff --git a/src/bcf/MANIFEST.in b/src/bcf/MANIFEST.in new file mode 100644 index 0000000000..30feaf47bc --- /dev/null +++ b/src/bcf/MANIFEST.in @@ -0,0 +1,2 @@ +include src/bcf/v2/xsd/*.xsd +include src/bcf/v3/xsd/*.xsd \ No newline at end of file diff --git a/src/bcf/pyproject.toml b/src/bcf/pyproject.toml new file mode 100644 index 0000000000..dadda3ed02 --- /dev/null +++ b/src/bcf/pyproject.toml @@ -0,0 +1,12 @@ +[build-system] +requires = [ + "setuptools>=42", + "wheel" +] +build-backend = "setuptools.build_meta" + +[tool.black] +line-length = 120 + +[tool.isort] +profile = "black" \ No newline at end of file diff --git a/src/bcf/setup.cfg b/src/bcf/setup.cfg new file mode 100644 index 0000000000..3eba915750 --- /dev/null +++ b/src/bcf/setup.cfg @@ -0,0 +1,37 @@ +[metadata] +name=bcf-client +version=0.0.1 +author = Ifcopenshell +description = A simple Python implementation of BCF +url = https://github.com/IfcOpenShell/IfcOpenShell +project_urls = + Code=https://github.com/IfcOpenShell/IfcOpenShell + Issues=https://github.com/IfcOpenShell/IfcOpenShell/issues +long_description = file: README.md +long_description_content_type = text/markdown +classifiers = + License :: OSI Approved :: GNU General Public License v3 (GPLv3) + Operating System :: OS Independent + Programming Language :: Python :: 3 + Topic :: Scientific/Engineering + Topic :: Utilities +keywords = + Python + file formats + engineering + +[options] +package_dir = + = src +packages = find: +python_requires = >=3 +install_requires= + xmlschema +include_package_data = True + +[options.packages.find] +where = src + +[flake8] +max-line-length = 120 +ignore = E24, E121, E123, E126, E203, E226, E704, E741, W503, W504 diff --git a/src/bcf/src/bcf/__init__.py b/src/bcf/src/bcf/__init__.py new file mode 100644 index 0000000000..a91684cdf7 --- /dev/null +++ b/src/bcf/src/bcf/__init__.py @@ -0,0 +1,17 @@ +# BCF - BCF Python library +# Copyright (C) 2021 Prabhat Singh +# +# This file is part of BCF. +# +# BCF is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BCF is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with BCF. If not, see . diff --git a/src/bcf/bcf/bcfxml.py b/src/bcf/src/bcf/bcfxml.py similarity index 62% rename from src/bcf/bcf/bcfxml.py rename to src/bcf/src/bcf/bcfxml.py index 513c64cdf2..e9bcde349f 100644 --- a/src/bcf/bcf/bcfxml.py +++ b/src/bcf/src/bcf/bcfxml.py @@ -1,3 +1,22 @@ +# BCF - BCF Python library +# Copyright (C) 2021 Prabhat Singh +# +# This file is part of BCF. +# +# BCF is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BCF is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with BCF. If not, see . + + import os.path import zipfile import tempfile diff --git a/src/bcf/src/bcf/v2/__init__.py b/src/bcf/src/bcf/v2/__init__.py new file mode 100644 index 0000000000..adb79b4034 --- /dev/null +++ b/src/bcf/src/bcf/v2/__init__.py @@ -0,0 +1,19 @@ + +# BCF - BCF Python library +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BCF. +# +# BCF is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BCF is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with BCF. If not, see . + diff --git a/src/bcf/bcf/v2/bcfxml.py b/src/bcf/src/bcf/v2/bcfxml.py similarity index 97% rename from src/bcf/bcf/v2/bcfxml.py rename to src/bcf/src/bcf/v2/bcfxml.py index 3b4dd722b0..76c094d69e 100644 --- a/src/bcf/bcf/v2/bcfxml.py +++ b/src/bcf/src/bcf/v2/bcfxml.py @@ -1,3 +1,22 @@ + +# BCF - BCF Python library +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BCF. +# +# BCF is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BCF is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with BCF. If not, see . + import os import uuid import shutil diff --git a/src/bcf/bcf/v2/data.py b/src/bcf/src/bcf/v2/data.py similarity index 84% rename from src/bcf/bcf/v2/data.py rename to src/bcf/src/bcf/v2/data.py index 534aacaffc..e79352dbd0 100644 --- a/src/bcf/bcf/v2/data.py +++ b/src/bcf/src/bcf/v2/data.py @@ -1,3 +1,22 @@ + +# BCF - BCF Python library +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BCF. +# +# BCF is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BCF is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with BCF. If not, see . + class Project: def __init__(self): self.project_id = "" diff --git a/src/bcf/bcf/v2/xsd/markup.xsd b/src/bcf/src/bcf/v2/xsd/markup.xsd similarity index 100% rename from src/bcf/bcf/v2/xsd/markup.xsd rename to src/bcf/src/bcf/v2/xsd/markup.xsd diff --git a/src/bcf/bcf/v2/xsd/project.xsd b/src/bcf/src/bcf/v2/xsd/project.xsd similarity index 100% rename from src/bcf/bcf/v2/xsd/project.xsd rename to src/bcf/src/bcf/v2/xsd/project.xsd diff --git a/src/bcf/bcf/v2/xsd/version.xsd b/src/bcf/src/bcf/v2/xsd/version.xsd similarity index 100% rename from src/bcf/bcf/v2/xsd/version.xsd rename to src/bcf/src/bcf/v2/xsd/version.xsd diff --git a/src/bcf/bcf/v2/xsd/visinfo.xsd b/src/bcf/src/bcf/v2/xsd/visinfo.xsd similarity index 100% rename from src/bcf/bcf/v2/xsd/visinfo.xsd rename to src/bcf/src/bcf/v2/xsd/visinfo.xsd diff --git a/src/bcf/src/bcf/v3/__init__.py b/src/bcf/src/bcf/v3/__init__.py new file mode 100644 index 0000000000..5828265521 --- /dev/null +++ b/src/bcf/src/bcf/v3/__init__.py @@ -0,0 +1,19 @@ + +# BCF - BCF Python library +# Copyright (C) 2021 Prabhat Singh +# +# This file is part of BCF. +# +# BCF is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BCF is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with BCF. If not, see . + diff --git a/src/bcf/bcf/v3/bcfapi.py b/src/bcf/src/bcf/v3/bcfapi.py similarity index 95% rename from src/bcf/bcf/v3/bcfapi.py rename to src/bcf/src/bcf/v3/bcfapi.py index 0ec2cf262f..308f323eb8 100644 --- a/src/bcf/bcf/v3/bcfapi.py +++ b/src/bcf/src/bcf/v3/bcfapi.py @@ -1,3 +1,22 @@ + +# BCF - BCF Python library +# Copyright (C) 2021 Prabhat Singh +# +# This file is part of BCF. +# +# BCF is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BCF is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with BCF. If not, see . + import uuid import time import json @@ -6,9 +25,8 @@ import requests import webbrowser import http.server import base64 - -from werkzeug.datastructures import HeaderSet - +import tempfile +import os client_id, client_secret = "", "" @@ -135,6 +153,7 @@ class BcfClient: self.foundation_client = foundation_client self.version_id = None self.baseurl = None + self.filepath = tempfile.mkdtemp() def set_version(self, version): self.version_id = version["version_id"] @@ -282,7 +301,7 @@ class BcfClient: headers=headers, ) # TODO: write to tmpdir - with open(f"{project_id}_{topic_id}_snippet.txt", "w") as f: + with open(os.path.join(self.filepath, f"{project_id}_{topic_id}_snippet.txt"), "wb") as f: f.write(response.content.decode("utf-8")) return response.status_code, response.content @@ -544,7 +563,7 @@ class BcfClient: f"{self.baseurl}/projects/{project_id}/topics/documents/{document_id}", headers=headers, ) - with open(f"{project_id}_{topic_id}_{document_id}_document.txt", "w") as f: + with open(os.path.join(self.filepath, f"{project_id}_{topic_id}_{document_id}_document.txt"), "wb") as f: f.write(response.content.decode("utf-8")) return response.status_code, response.content diff --git a/src/bcf/bcf/v3/bcfxml.py b/src/bcf/src/bcf/v3/bcfxml.py similarity index 97% rename from src/bcf/bcf/v3/bcfxml.py rename to src/bcf/src/bcf/v3/bcfxml.py index 6298260d3d..9d9578e576 100644 --- a/src/bcf/bcf/v3/bcfxml.py +++ b/src/bcf/src/bcf/v3/bcfxml.py @@ -1,3 +1,22 @@ + +# BCF - BCF Python library +# Copyright (C) 2021 Prabhat Singh +# +# This file is part of BCF. +# +# BCF is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BCF is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with BCF. If not, see . + import os import uuid import shutil diff --git a/src/bcf/bcf/v3/data.py b/src/bcf/src/bcf/v3/data.py similarity index 84% rename from src/bcf/bcf/v3/data.py rename to src/bcf/src/bcf/v3/data.py index b6f241e148..a166a3b1b2 100644 --- a/src/bcf/bcf/v3/data.py +++ b/src/bcf/src/bcf/v3/data.py @@ -1,3 +1,22 @@ + +# BCF - BCF Python library +# Copyright (C) 2021 Prabhat Singh +# +# This file is part of BCF. +# +# BCF is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BCF is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with BCF. If not, see . + class Project: def __init__(self): self.project_id = "" diff --git a/src/bcf/bcf/v3/xsd/documents.xsd b/src/bcf/src/bcf/v3/xsd/documents.xsd similarity index 100% rename from src/bcf/bcf/v3/xsd/documents.xsd rename to src/bcf/src/bcf/v3/xsd/documents.xsd diff --git a/src/bcf/bcf/v3/xsd/extensions.xsd b/src/bcf/src/bcf/v3/xsd/extensions.xsd similarity index 100% rename from src/bcf/bcf/v3/xsd/extensions.xsd rename to src/bcf/src/bcf/v3/xsd/extensions.xsd diff --git a/src/bcf/bcf/v3/xsd/markup.xsd b/src/bcf/src/bcf/v3/xsd/markup.xsd similarity index 100% rename from src/bcf/bcf/v3/xsd/markup.xsd rename to src/bcf/src/bcf/v3/xsd/markup.xsd diff --git a/src/bcf/bcf/v3/xsd/project.xsd b/src/bcf/src/bcf/v3/xsd/project.xsd similarity index 100% rename from src/bcf/bcf/v3/xsd/project.xsd rename to src/bcf/src/bcf/v3/xsd/project.xsd diff --git a/src/bcf/bcf/v3/xsd/shared-types.xsd b/src/bcf/src/bcf/v3/xsd/shared-types.xsd similarity index 100% rename from src/bcf/bcf/v3/xsd/shared-types.xsd rename to src/bcf/src/bcf/v3/xsd/shared-types.xsd diff --git a/src/bcf/bcf/v3/xsd/version.xsd b/src/bcf/src/bcf/v3/xsd/version.xsd similarity index 100% rename from src/bcf/bcf/v3/xsd/version.xsd rename to src/bcf/src/bcf/v3/xsd/version.xsd diff --git a/src/bcf/bcf/v3/xsd/visinfo.xsd b/src/bcf/src/bcf/v3/xsd/visinfo.xsd similarity index 100% rename from src/bcf/bcf/v3/xsd/visinfo.xsd rename to src/bcf/src/bcf/v3/xsd/visinfo.xsd diff --git a/src/blenderbim/Makefile b/src/blenderbim/Makefile index 39d0b78b7a..f331aec6d0 100644 --- a/src/blenderbim/Makefile +++ b/src/blenderbim/Makefile @@ -1,3 +1,21 @@ +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + VERSION:=`date '+%y%m%d'` PYVERSION:=py37 @@ -15,11 +33,13 @@ ifeq ($(PYVERSION), py37) HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.7.5/download/linux-64/hpp-fcl-1.7.5-py37h5f1835d_0.tar.bz2 EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.5/download/linux-64/eigenpy-2.6.5-py37h95e2c48_0.tar.bz2 BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/linux-64/boost-1.74.0-py37h0379df6_3.tar.bz2 +LXML_URL:=https://files.pythonhosted.org/packages/30/c0/d0526314971fc661b083ab135747dc68446a3022686da8c16d25fcf6ef07/lxml-4.6.3-cp37-cp37m-manylinux2014_x86_64.whl endif ifeq ($(PYVERSION), py39) HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.7.5/download/linux-64/hpp-fcl-1.7.5-py39hbcdfc36_0.tar.bz2 EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.5/download/linux-64/eigenpy-2.6.5-py39h5aed9d1_0.tar.bz2 BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/linux-64/boost-1.74.0-py39h5472131_3.tar.bz2 +LXML_URL:=https://files.pythonhosted.org/packages/19/d9/a69c6aff5673554df48120565a14a50eaa41d29ae03b02faa0b023666318/lxml-4.6.3-cp39-cp39-manylinux2014_x86_64.whl endif ASSIMP_URL:=https://anaconda.org/conda-forge/assimp/5.0.1/download/linux-64/assimp-5.0.1-hedfc422_6.tar.bz2 OCTOMAP_URL:=https://anaconda.org/conda-forge/octomap/1.9.7/download/linux-64/octomap-1.9.7-h4bd325d_0.tar.bz2 @@ -31,11 +51,13 @@ ifeq ($(PYVERSION), py37) HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.7.5/download/osx-64/hpp-fcl-1.7.5-py37h2d7f23a_0.tar.bz2 EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.5/download/osx-64/eigenpy-2.6.5-py37h0695097_0.tar.bz2 BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/osx-64/boost-1.74.0-py37hd79e0ac_3.tar.bz2 +LXML_URL:=https://files.pythonhosted.org/packages/1e/3e/f0abc15d5dac50939bccc589aae336d5ead4c72e7ad1039a2e0f3630ea92/lxml-4.6.3-cp37-cp37m-macosx_10_9_x86_64.whl endif ifeq ($(PYVERSION), py39) HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.7.5/download/osx-64/hpp-fcl-1.7.5-py39h1e32b98_0.tar.bz2 EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.5/download/osx-64/eigenpy-2.6.5-py39h5405915_0.tar.bz2 BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/osx-64/boost-1.74.0-py39ha641261_3.tar.bz2 +LXML_URL:=https://files.pythonhosted.org/packages/b8/74/a71f7ad72e8db54ce899efab84507b801660750cbbfa6a39e6717557d36a/lxml-4.6.3-cp39-cp39-macosx_10_9_x86_64.whl endif ASSIMP_URL:=https://anaconda.org/conda-forge/assimp/5.0.1/download/osx-64/assimp-5.0.1-h1224e73_6.tar.bz2 OCTOMAP_URL:=https://anaconda.org/conda-forge/octomap/1.9.7/download/osx-64/octomap-1.9.7-h940c156_0.tar.bz2 @@ -47,11 +69,13 @@ ifeq ($(PYVERSION), py37) HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.7.5/download/win-64/hpp-fcl-1.7.5-py37h839d6b1_0.tar.bz2 EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.5/download/win-64/eigenpy-2.6.5-py37h2c32e34_0.tar.bz2 BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/win-64/boost-1.74.0-py37h3b38789_3.tar.bz2 +LXML_URL:=https://files.pythonhosted.org/packages/9e/5e/171ee9d40a600f565fe691ec5bf7596247ec62cfb2edc00c91afe8ea837b/lxml-4.6.3-cp37-cp37m-win_amd64.whl endif ifeq ($(PYVERSION), py39) HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.7.5/download/win-64/hpp-fcl-1.7.5-py39h2e7c763_0.tar.bz2 EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.5/download/win-64/eigenpy-2.6.5-py39h3ce40e6_0.tar.bz2 BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/win-64/boost-1.74.0-py39hefe7e4c_3.tar.bz2 +LXML_URL:=https://files.pythonhosted.org/packages/72/d4/426ecb8849c47c3e370c87aa0ac05d85768df917ffea27fcd6686a5e6495/lxml-4.6.3-cp39-cp39-win_amd64.whl endif ASSIMP_URL:=https://anaconda.org/conda-forge/assimp/5.0.1/download/win-64/assimp-5.0.1-hc2aa0de_6.tar.bz2 OCTOMAP_URL:=https://anaconda.org/conda-forge/octomap/1.9.7/download/win-64/octomap-1.9.7-h5362a0b_0.tar.bz2 @@ -69,7 +93,7 @@ endif cp -r blenderbim/* dist/blenderbim/ # Provides IfcOpenShell Python functionality - cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcblender-python-$(PYNUMBER)-v0.6.0-2f3c79a-$(PLATFORM)64.zip + cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcblender-python-$(PYNUMBER)-v0.6.0-721fe47-$(PLATFORM)64.zip cd dist/working && unzip ifcblender* cp -r dist/working/io_import_scene_ifc/ifcopenshell dist/blenderbim/libs/site/packages/ @@ -99,7 +123,7 @@ endif cp dist/working/IfcOpenShell-0.6.0/src/ifcopenshell-python/ifcopenshell/entity_instance.py dist/blenderbim/libs/site/packages/ifcopenshell/ cp dist/working/IfcOpenShell-0.6.0/src/ifcopenshell-python/ifcopenshell/file.py dist/blenderbim/libs/site/packages/ifcopenshell/ # Provides bcf functionality - cp -r dist/working/IfcOpenShell-0.6.0/src/bcf/bcf dist/blenderbim/libs/site/packages/ + cp -r dist/working/IfcOpenShell-0.6.0/src/bcf/src/bcf dist/blenderbim/libs/site/packages/ # Provides IFCClash functionality cp -r dist/working/IfcOpenShell-0.6.0/src/ifcclash/ifcclash dist/blenderbim/libs/site/packages/ # Provides BIMTester functionality @@ -332,6 +356,13 @@ endif cd dist/working/ && patch ../blenderbim/libs/site/packages/behave/runner_util.py < runner_util.patch rm -rf dist/working + # Required by ids + mkdir dist/working + cd dist/working && wget $(LXML_URL) + cd dist/working && cp *.whl lxml.zip && unzip lxml.zip + cp -r dist/working/lxml dist/blenderbim/libs/site/packages/ + rm -rf dist/working + # Required by behave mkdir dist/working cd dist/working && wget https://files.pythonhosted.org/packages/f4/65/220bb4075fddb09d5b3ea2c1c1fa66c1c72be9361ec187aab50fa161e576/parse-1.15.0.tar.gz @@ -413,6 +444,39 @@ endif cd dist && zip -r blenderbim-$(VERSION)-$(PYVERSION)-$(PLATFORM).zip ./* rm -rf dist/blenderbim +.PHONY: test +test: + make test-core + make test-bim + +.PHONY: test-core +test-core: + pytest -p no:pytest-blender test/core + +.PHONY: test-bim +test-bim: + pytest test/bim + +.PHONY: qa +qa: + black . + pylint ./* --output-format=colorized --disable all --enable E --disable import-error + +.PHONY: coverage +coverage: + coverage run --source blenderbim.core -m pytest -p no:pytest-blender test/core + coverage html + xdg-open htmlcov/index.html + +.PHONY: license +license: + copyright-header --license GPL3 --copyright-holder "Dion Moult " --copyright-year "2021" --copyright-software "BlenderBIM Add-on" --copyright-software-description "OpenBIM Blender Add-on" -a ./ -o ./ + .PHONY: clean clean: rm -rf dist + rm -rf htmlcov + +.PHONY: dev +dev: + blender -p setup_pytest.py diff --git a/src/blenderbim/blenderbim/__init__.py b/src/blenderbim/blenderbim/__init__.py index 1fd0f93247..728b563351 100644 --- a/src/blenderbim/blenderbim/__init__.py +++ b/src/blenderbim/blenderbim/__init__.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -17,10 +16,14 @@ # You should have received a copy of the GNU General Public License # along with BlenderBIM Add-on. If not, see . +import os +import sys +import site + bl_info = { "name": "BlenderBIM", - "description": "Author, import, and export files in the " "Industry Foundation Classes (.ifc) file format", - "author": "Dion Moult, IfcOpenShell", + "description": "Author, import, and export data using the Industry Foundation Classes schema", + "author": "IfcOpenShell Contributors", "blender": (2, 80, 0), "version": (0, 0, 999999), "location": "File > Export, File > Import, Scene / Object / Material / Mesh Properties", @@ -28,25 +31,15 @@ bl_info = { "category": "Import-Export", } -import os -import site +if sys.modules.get("bpy", None): + # Process *.pth in /libs/site/packages to setup globally importable modules + # This is 3 levels deep as required by the static RPATH of ../../ from dependencies taken from Anaconda + site.addsitedir(os.path.join(os.path.dirname(os.path.realpath(__file__)), "libs", "site", "packages")) + import blenderbim.bim -# process *.pth in /libs/site/packages to setup globally importable modules -# 3 levels deep required by occ static ../../ path -# TODO: 3 levels deep is no longer required as we no longer bundle OCC -cwd = os.path.dirname(os.path.realpath(__file__)) -site.addsitedir(os.path.join(cwd, "libs", "site", "packages")) + def register(): + blenderbim.bim.register() - -# main import -from .bim import * - - -# Explicitely expose bim.xx when imported with from blenderbim import * -# Other bim still are importable using explicit from blenderbim.bim import xxx -__all__ = ["export_ifc", "import_ifc"] - - -if __name__ == "__main__": - register() + def unregister(): + blenderbim.bim.unregister() diff --git a/src/blenderbim/blenderbim/bim/__init__.py b/src/blenderbim/blenderbim/bim/__init__.py index 5df49888d9..8ecb59a8ee 100644 --- a/src/blenderbim/blenderbim/bim/__init__.py +++ b/src/blenderbim/blenderbim/bim/__init__.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -17,157 +16,152 @@ # You should have received a copy of the GNU General Public License # along with BlenderBIM Add-on. If not, see . -# Check if we are running in Blender before loading, to allow for multiprocessing -import sys +import bpy +import importlib +from . import handler, ui, prop, operator -bpy = sys.modules.get("bpy") +modules = { + "project": None, + "search": None, + "bcf": None, + "root": None, + "unit": None, + "model": None, + "georeference": None, + "context": None, + "drawing": None, + "attribute": None, + "type": None, + "spatial": None, + "void": None, + "aggregate": None, + "geometry": None, + "cobie": None, + "resource": None, + "cost": None, + "sequence": None, + "group": None, + "system": None, + "structural": None, + "boundary": None, + "profile": None, + "material": None, + "style": None, + "layer": None, + "owner": None, + "pset": None, + "qto": None, + "classification": None, + "constraint": None, + "document": None, + "pset_template": None, + "clash": None, + "lca": None, + "csv": None, + "bimtester": None, + "diff": None, + "patch": None, + "covetool": None, + "augin": None, + "debug": None, +} -if bpy is not None: - import bpy - import importlib - from . import handler, ui, prop, operator +for name in modules.keys(): + modules[name] = importlib.import_module(f"blenderbim.bim.module.{name}") - modules = { - "project": None, - "search": None, - "bcf": None, - "root": None, - "unit": None, - "model": None, - "georeference": None, - "context": None, - "drawing": None, - "attribute": None, - "type": None, - "spatial": None, - "void": None, - "aggregate": None, - "geometry": None, - "cobie": None, - "resource": None, - "cost": None, - "sequence": None, - "group": None, - "system": None, - "structural": None, - "boundary": None, - "profile": None, - "material": None, - "style": None, - "layer": None, - "owner": None, - "pset": None, - "qto": None, - "classification": None, - "constraint": None, - "document": None, - "pset_template": None, - "clash": None, - "lca": None, - "csv": None, - "bimtester": None, - "diff": None, - "patch": None, - "covetool": None, - "augin": None, - "debug": None, - } +classes = [ + operator.OpenUri, + operator.SelectDataDir, + operator.SelectSchemaDir, + operator.SelectIfcFile, + operator.ExportIFC, + operator.ImportIFC, + operator.OpenUpstream, + operator.AddSectionPlane, + operator.RemoveSectionPlane, + operator.ReloadIfcFile, + operator.AddIfcFile, + operator.RemoveIfcFile, + operator.SetOverrideColour, + operator.SetViewportShadowFromSun, + operator.SnapSpacesTogether, + operator.OverrideDelete, + prop.StrProperty, + prop.Attribute, + prop.BIMProperties, + prop.IfcParameter, + prop.PsetQto, + prop.GlobalId, + prop.BIMObjectProperties, + prop.BIMMaterialProperties, + prop.BIMMeshProperties, + ui.BIM_PT_section_plane, + ui.BIM_UL_generic, + ui.BIM_UL_topics, + ui.BIM_ADDON_preferences, +] - for name in modules.keys(): - modules[name] = importlib.import_module(f"blenderbim.bim.module.{name}") +for mod in modules.values(): + classes.extend(mod.classes) - classes = [ - operator.OpenUri, - operator.SelectDataDir, - operator.SelectSchemaDir, - operator.SelectIfcFile, - operator.ExportIFC, - operator.ImportIFC, - operator.OpenUpstream, - operator.AddSectionPlane, - operator.RemoveSectionPlane, - operator.ReloadIfcFile, - operator.AddIfcFile, - operator.RemoveIfcFile, - operator.SetOverrideColour, - operator.SetViewportShadowFromSun, - operator.LinkIfc, - operator.SnapSpacesTogether, - prop.StrProperty, - prop.Attribute, - prop.BIMProperties, - prop.IfcParameter, - prop.PsetQto, - prop.GlobalId, - prop.BIMObjectProperties, - prop.BIMMaterialProperties, - prop.BIMMeshProperties, - ui.BIM_PT_section_plane, - ui.BIM_UL_generic, - ui.BIM_UL_topics, - ui.BIM_ADDON_preferences, - ] - for module in modules.values(): - classes.extend(module.classes) +def menu_func_export(self, context): + self.layout.operator(operator.ExportIFC.bl_idname, text="Industry Foundation Classes (.ifc/.ifczip/.ifcjson)") - def menu_func_export(self, context): - self.layout.operator(operator.ExportIFC.bl_idname, text="Industry Foundation Classes (.ifc/.ifczip/.ifcjson)") - def menu_func_import(self, context): - self.layout.operator(operator.ImportIFC.bl_idname, text="Industry Foundation Classes (.ifc/.ifczip/.ifcxml)") +def menu_func_import(self, context): + self.layout.operator(operator.ImportIFC.bl_idname, text="Industry Foundation Classes (.ifc/.ifczip/.ifcxml)") - def on_register(scene): - handler.setDefaultProperties(scene) - bpy.app.handlers.depsgraph_update_post.remove(on_register) - def register(): - for cls in classes: - bpy.utils.register_class(cls) - bpy.app.handlers.depsgraph_update_post.append(on_register) - bpy.app.handlers.undo_pre.append(handler.undo_pre) - bpy.app.handlers.undo_post.append(handler.undo_post) - bpy.app.handlers.redo_pre.append(handler.redo_pre) - bpy.app.handlers.redo_post.append(handler.redo_post) - 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.types.TOPBAR_MT_file_export.append(menu_func_export) - bpy.types.TOPBAR_MT_file_import.append(menu_func_import) - bpy.types.Scene.BIMProperties = bpy.props.PointerProperty(type=prop.BIMProperties) - bpy.types.Object.BIMObjectProperties = bpy.props.PointerProperty(type=prop.BIMObjectProperties) - bpy.types.Material.BIMObjectProperties = bpy.props.PointerProperty(type=prop.BIMObjectProperties) - 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.Curve.BIMMeshProperties = bpy.props.PointerProperty(type=prop.BIMMeshProperties) - bpy.types.Camera.BIMMeshProperties = bpy.props.PointerProperty(type=prop.BIMMeshProperties) - bpy.types.PointLight.BIMMeshProperties = bpy.props.PointerProperty(type=prop.BIMMeshProperties) - bpy.types.SCENE_PT_unit.append(ui.ifc_units) +def on_register(scene): + handler.setDefaultProperties(scene) + bpy.app.handlers.depsgraph_update_post.remove(on_register) - for module in modules.values(): - module.register() - def unregister(): - for cls in reversed(classes): - bpy.utils.unregister_class(cls) - bpy.app.handlers.load_post.remove(handler.setDefaultProperties) - bpy.app.handlers.load_post.remove(handler.loadIfcStore) - 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 - del bpy.types.Object.BIMObjectProperties - del bpy.types.Material.BIMObjectProperties - del bpy.types.Collection.BIMObjectProperties # Check if we need this - del bpy.types.Material.BIMMaterialProperties - del bpy.types.Mesh.BIMMeshProperties - del bpy.types.Curve.BIMMeshProperties - del bpy.types.Camera.BIMMeshProperties - del bpy.types.PointLight.BIMMeshProperties - bpy.types.SCENE_PT_unit.remove(ui.ifc_units) +def register(): + for cls in classes: + bpy.utils.register_class(cls) + bpy.app.handlers.depsgraph_update_post.append(on_register) + bpy.app.handlers.undo_pre.append(handler.undo_pre) + bpy.app.handlers.undo_post.append(handler.undo_post) + bpy.app.handlers.redo_pre.append(handler.redo_pre) + bpy.app.handlers.redo_post.append(handler.redo_post) + 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.types.TOPBAR_MT_file_export.append(menu_func_export) + bpy.types.TOPBAR_MT_file_import.append(menu_func_import) + bpy.types.Scene.BIMProperties = bpy.props.PointerProperty(type=prop.BIMProperties) + bpy.types.Object.BIMObjectProperties = bpy.props.PointerProperty(type=prop.BIMObjectProperties) + bpy.types.Material.BIMObjectProperties = bpy.props.PointerProperty(type=prop.BIMObjectProperties) + bpy.types.Material.BIMMaterialProperties = bpy.props.PointerProperty(type=prop.BIMMaterialProperties) + bpy.types.Mesh.BIMMeshProperties = bpy.props.PointerProperty(type=prop.BIMMeshProperties) + bpy.types.Curve.BIMMeshProperties = bpy.props.PointerProperty(type=prop.BIMMeshProperties) + bpy.types.Camera.BIMMeshProperties = bpy.props.PointerProperty(type=prop.BIMMeshProperties) + bpy.types.PointLight.BIMMeshProperties = bpy.props.PointerProperty(type=prop.BIMMeshProperties) + bpy.types.SCENE_PT_unit.append(ui.ifc_units) - for module in reversed(list(modules.values())): - module.unregister() + for mod in modules.values(): + mod.register() + + +def unregister(): + for cls in reversed(classes): + bpy.utils.unregister_class(cls) + bpy.app.handlers.load_post.remove(handler.setDefaultProperties) + bpy.app.handlers.load_post.remove(handler.loadIfcStore) + 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 + del bpy.types.Object.BIMObjectProperties + del bpy.types.Material.BIMObjectProperties + del bpy.types.Material.BIMMaterialProperties + del bpy.types.Mesh.BIMMeshProperties + del bpy.types.Curve.BIMMeshProperties + del bpy.types.Camera.BIMMeshProperties + del bpy.types.PointLight.BIMMeshProperties + bpy.types.SCENE_PT_unit.remove(ui.ifc_units) + + for mod in reversed(list(modules.values())): + mod.unregister() diff --git a/src/blenderbim/blenderbim/bim/data/pset/EPset_Productivity.ifc b/src/blenderbim/blenderbim/bim/data/pset/EPset_Productivity.ifc new file mode 100644 index 0000000000..283946eb80 --- /dev/null +++ b/src/blenderbim/blenderbim/bim/data/pset/EPset_Productivity.ifc @@ -0,0 +1,13 @@ +ISO-10303-21; +HEADER; +FILE_DESCRIPTION((),'2;1'); +FILE_NAME('EPset_Productivity.ifc','2020-01-01T00:00:00',(),(),'EPset_Productivity','EPset_Productivity',$); +FILE_SCHEMA(('IFC4')); +ENDSEC; +DATA; +#1=IFCPROPERTYSETTEMPLATE('24XxTA2ED4o83uiZBG59D_',$,'EPset_Productivity','',.PSET_TYPEDRIVENOVERRIDE.,'IfcConstructionEquipmentResource,IfcLaborResource',(#2,#3,#4)); +#2=IFCSIMPLEPROPERTYTEMPLATE('3mL27FQ4X4MQn5fiGqIt8x',$,'BaseQuantityConsumed','',.P_SINGLEVALUE.,'IfcDuration',$,$,$,$,$,.READWRITE.); +#3=IFCSIMPLEPROPERTYTEMPLATE('1DdJt5_Ar2mR4UDpO$98Ws',$,'BaseQuantityProducedName','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.); +#4=IFCSIMPLEPROPERTYTEMPLATE('2OZPZ$Onb2gOvW20q7gacH',$,'BaseQuantityProducedValue','',.P_SINGLEVALUE.,'IfcReal',$,$,$,$,$,.READWRITE.); +ENDSEC; +END-ISO-10303-21; diff --git a/src/blenderbim/blenderbim/bim/export_ifc.py b/src/blenderbim/blenderbim/bim/export_ifc.py index 5a2113fa08..7a115d0ea9 100644 --- a/src/blenderbim/blenderbim/bim/export_ifc.py +++ b/src/blenderbim/blenderbim/bim/export_ifc.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -114,7 +113,7 @@ class IfcExporter: IfcStore.edited_objs.clear() def sync_object_placement(self, obj): - blender_matrix = np.matrix(obj.matrix_world) + blender_matrix = np.array(obj.matrix_world) element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id) if not hasattr(element, "ObjectPlacement"): return @@ -158,7 +157,6 @@ class IfcExporter: else: parent_collection = obj.users_collection[0] - parent_obj = bpy.data.objects.get(parent_collection.name) if not parent_obj or not parent_obj.BIMObjectProperties.ifc_definition_id: return @@ -174,6 +172,7 @@ class IfcExporter: try: # This will throw an exception if the Blender object no longer exists foo = obj.name + foo return False except: return True @@ -201,7 +200,6 @@ class IfcExportSettings: @staticmethod def factory(context, output_file, logger): - scene_bim = context.scene.BIMProperties settings = IfcExportSettings() settings.output_file = output_file settings.logger = logger diff --git a/src/blenderbim/blenderbim/bim/handler.py b/src/blenderbim/blenderbim/bim/handler.py index ac0d0206ca..cc249506fd 100644 --- a/src/blenderbim/blenderbim/bim/handler.py +++ b/src/blenderbim/blenderbim/bim/handler.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/helper.py b/src/blenderbim/blenderbim/bim/helper.py index 8b7dfc6c1d..e6eea8757d 100644 --- a/src/blenderbim/blenderbim/bim/helper.py +++ b/src/blenderbim/blenderbim/bim/helper.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/ifc.py b/src/blenderbim/blenderbim/bim/ifc.py index e779befc27..b5879fce39 100644 --- a/src/blenderbim/blenderbim/bim/ifc.py +++ b/src/blenderbim/blenderbim/bim/ifc.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -19,8 +18,11 @@ import bpy import uuid +import zipfile +import tempfile import ifcopenshell import blenderbim.bim.handler +from pathlib import Path class IfcStore: @@ -183,7 +185,7 @@ class IfcStore: def commit_link_element(data): obj = bpy.data.objects.get(data["obj"]) IfcStore.id_map[data["id"]] = obj - if data["guid"]: + if "guid" in data: IfcStore.guid_map[data["guid"]] = obj blenderbim.bim.handler.subscribe_to(obj, "mode", blenderbim.bim.handler.mode_callback) blenderbim.bim.handler.subscribe_to(obj, "name", blenderbim.bim.handler.name_callback) diff --git a/src/blenderbim/blenderbim/bim/import_ifc.py b/src/blenderbim/blenderbim/bim/import_ifc.py index 0b174f0c77..d829c6f184 100644 --- a/src/blenderbim/blenderbim/bim/import_ifc.py +++ b/src/blenderbim/blenderbim/bim/import_ifc.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -17,31 +16,23 @@ # You should have received a copy of the GNU General Public License # along with BlenderBIM Add-on. If not, see . -import ifcopenshell -import ifcopenshell.geom -import ifcopenshell.util.geolocation -import ifcopenshell.util.selector -import ifcopenshell.util.element -import ifcopenshell.util.unit -import bpy -import bmesh -import os import re +import bpy +import time +import bmesh import shutil import threading -import json -import time import mathutils -import math -import multiprocessing -import zipfile -import tempfile import numpy as np -from blenderbim.bim.module.drawing.prop import getDiagramScales -from pathlib import Path -from itertools import cycle -from datetime import datetime +import multiprocessing +import ifcopenshell +import ifcopenshell.geom +import ifcopenshell.util.unit +import ifcopenshell.util.element +import ifcopenshell.util.selector +import ifcopenshell.util.geolocation from blenderbim.bim.ifc import IfcStore +from blenderbim.bim.module.drawing.prop import get_diagram_scales class FileCopy(threading.Thread): @@ -172,12 +163,9 @@ 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.filter_mode = None - self.include_elements = set() - self.exclude_elements = set() self.project = None - self.spatial_structure_elements = {} - self.elements = [] + self.collections = {} + self.elements = set() self.type_collection = None self.type_products = {} self.openings = {} @@ -188,7 +176,6 @@ class IfcImporter: self.added_data = {} self.native_elements = set() self.native_data = {} - self.aggregates = {} self.material_creator = MaterialCreator(ifc_import_settings, self) @@ -208,10 +195,6 @@ class IfcImporter: bpy.context.window_manager.progress_begin(0, 100) self.progress = 0 self.profile_code("Starting import process") - self.load_diff() - self.profile_code("Load diff") - self.purge_diff() - self.profile_code("Purge diffs") self.load_file() self.profile_code("Loading file") self.calculate_unit_scale() @@ -222,47 +205,41 @@ class IfcImporter: self.profile_code("Set units") self.create_project() self.profile_code("Create project") - self.create_spatial_hierarchy() - self.profile_code("Create spatial hierarchy") self.process_element_filter() self.profile_code("Process element filter") - self.create_aggregates() - self.profile_code("Create aggregates") - self.create_aggregate_tree() - self.profile_code("Create aggregate tree") + self.create_collections() + self.profile_code("Create collections") self.create_openings_collection() self.profile_code("Create opening collection") self.create_materials() self.profile_code("Create materials") self.create_styles() self.profile_code("Create styles") - self.parse_native_elements() - self.profile_code("Parsing native elements") - self.create_grids() - self.profile_code("Create grids") - self.create_native_products() - self.profile_code("Create native products") - self.create_products() - self.profile_code("Create products") - self.create_empty_and_2d_elements() - self.profile_code("Create empty products") - self.create_type_products() - self.profile_code("Create type products") self.create_annotation() self.profile_code("Create annotation") - self.create_structural_elements() - self.profile_code("Create structural elements") - self.place_objects_in_spatial_tree() - self.profile_code("Placing objects in spatial tree") + self.parse_native_elements() + self.profile_code("Parsing native elements") + self.create_native_elements() + self.profile_code("Create native elements") + self.create_elements() + self.profile_code("Create elements") + self.create_grids() + self.profile_code("Create grids") + self.create_spatial_elements() + self.profile_code("Create spatial elements") + self.create_structural_items() + self.profile_code("Create structural items") + self.create_type_products() + self.profile_code("Create type products") + self.place_objects_in_collections() + self.profile_code("Place objects in collections") if self.ifc_import_settings.should_merge_by_class: self.merge_by_class() self.profile_code("Merging by class") elif self.ifc_import_settings.should_merge_by_material: self.merge_by_material() self.profile_code("Merging by material") - if self.ifc_import_settings.should_merge_materials_by_colour or ( - self.ifc_import_settings.should_auto_set_workarounds and len(self.material_creator.materials) > 300 - ): + if self.ifc_import_settings.should_merge_materials_by_colour or len(self.material_creator.materials) > 300: self.merge_materials_by_colour() self.profile_code("Merging by colour") self.add_project_to_scene() @@ -290,32 +267,32 @@ class IfcImporter: return abs(coords[0]) > limit or abs(coords[1]) > limit or abs(coords[2]) > limit def process_element_filter(self): - if self.ifc_import_settings.ifc_import_filter == "NONE" or not self.ifc_import_settings.ifc_selector: - self.elements = self.file.by_type("IfcElement") - return + if self.ifc_import_settings.has_filter: + self.elements = set(self.ifc_import_settings.elements) + self.spatial_elements = self.get_spatial_elements_filtered_by_elements(self.elements) + else: + self.elements = set(self.file.by_type("IfcElement")) + if self.file.schema == "IFC2X3": + self.spatial_elements = set(self.file.by_type("IfcSpatialStructureElement")) + else: + self.spatial_elements = set(self.file.by_type("IfcSpatialElement")) - selector = ifcopenshell.util.selector.Selector() - elements = selector.parse(self.file, self.ifc_import_settings.ifc_selector) - if self.ifc_import_settings.ifc_import_filter == "WHITELIST": - self.filter_mode = "WHITELIST" - self.include_elements = set(elements) - self.elements = self.include_elements - elif self.ifc_import_settings.ifc_import_filter == "BLACKLIST": - self.filter_mode = "BLACKLIST" - self.exclude_elements = set(elements) - self.elements = [e for e in self.file.by_type("IfcElement") if e not in self.exclude_elements] + def get_spatial_elements_filtered_by_elements(self, elements): + leaf_spatial_elements = set([ifcopenshell.util.element.get_container(e) for e in elements]) + results = set() + for spatial_element in leaf_spatial_elements: + while True: + results.add(spatial_element) + spatial_element = ifcopenshell.util.element.get_aggregate(spatial_element) + if not spatial_element or spatial_element.is_a("IfcContext"): + break + return results def parse_native_elements(self): - if self.filter_mode == "WHITELIST": - for element in self.include_elements: - if self.is_native(element): - self.native_elements[element.GlobalId] = element - self.include_elements -= self.native_elements - elif self.filter_mode == "BLACKLIST": - for element in set(self.file.by_type("IfcElement")) - self.exclude_elements: - if self.is_native(element): - self.native_elements.add(element) - self.exclude_elements |= self.native_elements + for element in self.elements: + if self.is_native(element): + self.native_elements.add(element) + self.elements -= self.native_elements def is_native(self, element): if ( @@ -408,7 +385,6 @@ class IfcImporter: props.has_blender_offset = True def get_offset_point(self): - offset_point = None elements_checked = 0 # If more than these points aren't far away, the file probably isn't absolutely positioned element_checking_threshold = 100 @@ -444,31 +420,33 @@ class IfcImporter: if props.has_blender_offset: if self.is_point_far_away((matrix[0, 3], matrix[1, 3], matrix[2, 3])): obj.BIMObjectProperties.blender_offset_type = "OBJECT_PLACEMENT" - return 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() + 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), ) else: obj.BIMObjectProperties.blender_offset_type = "CARTESIAN_POINT" + + if self.ifc_import_settings.should_offset_model: + matrix[0, 3] += self.ifc_import_settings.model_offset_coordinates[0] + matrix[1, 3] += self.ifc_import_settings.model_offset_coordinates[1] + matrix[2, 3] += self.ifc_import_settings.model_offset_coordinates[2] + return mathutils.Matrix(matrix.tolist()) def find_decomposed_ifc_class(self, element, ifc_class): - results = [] + if element.is_a(ifc_class): + return element rel_aggregates = element.IsDecomposedBy - if not rel_aggregates: - return results for rel_aggregate in rel_aggregates: for part in rel_aggregate.RelatedObjects: - if part.is_a(ifc_class): - results.append(part) - results.extend(self.find_decomposed_ifc_class(part, ifc_class)) - return results + result = self.find_decomposed_ifc_class(part, ifc_class) + if result: + return result def create_grids(self): grids = self.file.by_type("IfcGrid") @@ -503,15 +481,8 @@ class IfcImporter: grid_collection.objects.link(obj) def create_type_products(self): - for collection in self.project["blender"].children: - if collection.name == "Types": - self.type_collection = collection - break - if not self.type_collection: - self.type_collection = bpy.data.collections.new("Types") - self.project["blender"].children.link(self.type_collection) - - if self.filter_mode in ["WHITELIST", "BLACKLIST"]: + # TODO allow filtering of spatial elements too + if self.ifc_import_settings.has_filter: type_products = set([ifcopenshell.util.element.get_type(e) for e in self.elements]) else: type_products = self.file.by_type("IfcTypeProduct") @@ -553,7 +524,7 @@ class IfcImporter: ): return representation_map - def create_native_products(self): + def create_native_elements(self): total = 0 checkpoint = time.time() bm = bmesh.new() @@ -586,40 +557,51 @@ class IfcImporter: bm.to_mesh(mesh) bm.clear() bm.free() - print("Done creating geometry") - def create_products(self): + def create_spatial_elements(self): + products = self.create_products(self.spatial_elements) + self.spatial_elements -= products + products = self.create_curve_products(self.spatial_elements) + self.spatial_elements -= products + for element in self.spatial_elements: + self.create_product(element) + + def create_elements(self): + products = self.create_products(self.elements) + self.elements -= products + products = self.create_curve_products(self.elements) + self.elements -= products + for element in self.elements: + self.create_product(element) + + def create_products(self, products): + results = set() + if not products: + return results if self.ifc_import_settings.should_use_cpu_multiprocessing: iterator = ifcopenshell.geom.iterator( - self.settings, - self.file, - multiprocessing.cpu_count(), - include=self.include_elements or None, - exclude=self.exclude_elements or None, + self.settings, self.file, multiprocessing.cpu_count(), include=products ) else: - iterator = ifcopenshell.geom.iterator( - self.settings, self.file, include=self.include_elements or None, exclude=self.exclude_elements or None - ) + iterator = ifcopenshell.geom.iterator(self.settings, self.file, include=products) valid_file = iterator.initialize() if not valid_file: - return False + return results checkpoint = time.time() - total_created = 0 - approx_total_products = len(self.include_elements) or len(self.file.by_type("IfcElement")) + total = 0 start_progress = self.progress progress_range = 85 - start_progress while True: - if total_created % 250 == 0: + total += 1 + if total % 250 == 0: print( - "{} / ~{} elements processed in {:.2f}s ...".format( - total_created, approx_total_products, time.time() - checkpoint + "{} ({}%) elements processed in {:.2f}s ...".format( + total, iterator.progress(), time.time() - checkpoint ) ) checkpoint = time.time() - if approx_total_products: - self.update_progress(((total_created / approx_total_products) * progress_range) + start_progress) + #self.update_progress(((total / approx_total_products) * progress_range) + start_progress) shape = iterator.get() if shape: product = self.file.by_id(shape.guid) @@ -628,35 +610,18 @@ class IfcImporter: if shape.context not in ["Body", "Facetation"] and IfcStore.get_element(shape.guid): # We only load a single context, and we prioritise the Body context. See #1290. pass - elif product.is_a("IfcAnnotation") and product.ObjectType == "DRAWING": - # We have already processed this during the create_annotation step - pass else: self.create_product(product, shape) - total_created += 1 + results.add(product) if not iterator.next(): break print("Done creating geometry") - - def create_empty_and_2d_elements(self): - curve_products = [] - - unadded_element_ids = set([e.id() for e in self.elements]) - set(self.added_data.keys()) - for element_id in unadded_element_ids: - element = self.file.by_id(element_id) - if element.is_a("IfcPort"): - continue - if not element.Representation: - self.create_product(element) - else: - curve_products.append(element) - if curve_products: - self.create_curve_products(curve_products) + return results def create_annotation(self): self.create_curve_products(self.file.by_type("IfcAnnotation")) - def create_structural_elements(self): + def create_structural_items(self): # Create structural collections self.structural_member_collection = bpy.data.collections.new("Members") self.structural_connection_collection = bpy.data.collections.new("Connections") @@ -695,6 +660,9 @@ class IfcImporter: self.link_element(product, obj) def create_curve_products(self, products): + results = set() + if not products: + return results if self.ifc_import_settings.should_use_cpu_multiprocessing: iterator = ifcopenshell.geom.iterator( self.settings_2d, self.file, multiprocessing.cpu_count(), include=products @@ -703,7 +671,7 @@ class IfcImporter: iterator = ifcopenshell.geom.iterator(self.settings_2d, self.file, include=products) valid_file = iterator.initialize() if not valid_file: - return False + return results checkpoint = time.time() total = 0 while True: @@ -713,10 +681,13 @@ class IfcImporter: checkpoint = time.time() shape = iterator.get() if shape: + product = self.file.by_id(shape.guid) self.create_product(self.file.by_id(shape.guid), shape) + results.add(product) if not iterator.next(): break print("Done creating geometry") + return results def create_product(self, element, shape=None, mesh=None): if element is None: @@ -747,7 +718,7 @@ class IfcImporter: if shape: m = shape.transformation.matrix.data # We use numpy here because Blender mathutils.Matrix is not accurate enough - mat = np.matrix( + mat = np.array( ([m[0], m[3], m[6], m[9]], [m[1], m[4], m[7], m[10]], [m[2], m[5], m[8], m[11]], [0, 0, 0, 1]) ) obj.matrix_world = self.apply_blender_offset_to_matrix_world(obj, mat) @@ -767,7 +738,7 @@ class IfcImporter: def get_representation_item_material_name(self, item): if not item.StyledByItem: return - style_ids = [e.id() for e in self.ifc_importer.file.traverse(item.StyledByItem[0]) if e.is_a("IfcSurfaceStyle")] + style_ids = [e.id() for e in self.file.traverse(item.StyledByItem[0]) if e.is_a("IfcSurfaceStyle")] return style_ids[0] if style_ids else None def create_native_faceted_brep(self, element, mesh_name): @@ -960,15 +931,10 @@ class IfcImporter: return self.openings[element.GlobalId] = obj - def load_diff(self): - if not self.ifc_import_settings.diff_file: - return - with open(self.ifc_import_settings.diff_file, "r") as file: - self.diff = json.load(file) - def load_file(self): self.ifc_import_settings.logger.info("loading file %s", self.ifc_import_settings.input_file) - bpy.context.scene.BIMProperties.ifc_file = self.ifc_import_settings.input_file + if not bpy.context.scene.BIMProperties.ifc_file: + bpy.context.scene.BIMProperties.ifc_file = self.ifc_import_settings.input_file self.file = IfcStore.get_file() def calculate_unit_scale(self): @@ -1004,10 +970,7 @@ class IfcImporter: ) def create_project(self): - if self.file.schema == "IFC2X3": - self.project = {"ifc": self.file.by_type("IfcProject")[0]} - else: - self.project = {"ifc": self.file.by_type("IfcContext")[0]} + self.project = {"ifc": self.file.by_type("IfcProject")[0]} self.project["blender"] = bpy.data.collections.new( "{}/{}".format(self.project["ifc"].is_a(), self.project["ifc"].Name) ) @@ -1015,66 +978,69 @@ class IfcImporter: if obj: self.project["blender"].objects.link(obj) - def create_spatial_hierarchy(self): - if self.project["ifc"].IsDecomposedBy: - for rel_aggregate in self.project["ifc"].IsDecomposedBy: - self.add_related_objects(self.project["blender"], rel_aggregate.RelatedObjects) + def create_collections(self): + if self.ifc_import_settings.collection_mode == "DECOMPOSITION": + self.create_decomposition_collections() + elif self.ifc_import_settings.collection_mode == "SPATIAL_DECOMPOSITION": + self.create_spatial_decomposition_collections() - def add_related_objects(self, parent, related_objects): + def create_decomposition_collections(self): + self.create_spatial_decomposition_collections() + self.create_aggregate_collections() + + def create_spatial_decomposition_collections(self): + for rel_aggregate in self.project["ifc"].IsDecomposedBy or []: + self.create_spatial_decomposition_collection(self.project["blender"], rel_aggregate.RelatedObjects) + self.create_type_collection() + + def create_type_collection(self): + for collection in self.project["blender"].children: + if collection.name == "Types": + self.type_collection = collection + break + if not self.type_collection: + self.type_collection = bpy.data.collections.new("Types") + self.project["blender"].children.link(self.type_collection) + + def create_spatial_decomposition_collection(self, parent, related_objects): for element in related_objects: + if element not in self.spatial_elements: + continue global_id = element.GlobalId collection = bpy.data.collections.new(self.get_name(element)) - self.spatial_structure_elements[global_id] = {"blender": collection} + self.collections[global_id] = 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) + self.create_spatial_decomposition_collection(collection, rel_aggregate.RelatedObjects) - def create_aggregates(self): - if self.filter_mode in ["WHITELIST", "BLACKLIST"]: - rel_aggregates = [e.IsDecomposedBy[0].RelatingObject for e in self.elements if e.IsDecomposedBy] + def create_aggregate_collections(self): + if self.ifc_import_settings.has_filter: + rel_aggregates = [e.IsDecomposedBy[0] for e in self.elements if e.IsDecomposedBy] else: rel_aggregates = [a for a in self.file.by_type("IfcRelAggregates") if a.RelatingObject.is_a("IfcElement")] + if len(rel_aggregates) > 10000: # More than 10,000 collections makes Blender unhappy - print("Falling back to SPATIAL_DECOMPOSITION collection mode") + print("Skipping aggregate collections for performance.") self.ifc_import_settings.collection_mode = "SPATIAL_DECOMPOSITION" - else: - for rel_aggregate in rel_aggregates: - self.create_aggregate(rel_aggregate) + return - def create_aggregate_tree(self): - for aggregate in self.aggregates.values(): - if aggregate["container"].is_a("IfcSpatialStructureElement"): - self.spatial_structure_elements[aggregate["container"].GlobalId]["blender"].children.link( - aggregate["blender"] - ) - else: - self.aggregates[aggregate["container"].GlobalId]["blender"].children.link(aggregate["blender"]) + aggregates = {} + for rel_aggregate in rel_aggregates: + element = rel_aggregate.RelatingObject + collection = bpy.data.collections.new(self.get_name(element)) + aggregates[element.GlobalId] = {"element": element, "collection": collection} + self.collections[element.GlobalId] = collection - def create_aggregate(self, rel_aggregate): - element = rel_aggregate.RelatingObject - obj = bpy.data.objects.new("{}/{}".format(element.is_a(), element.Name), None) - obj.matrix_world = self.apply_blender_offset_to_matrix_world(obj, self.get_element_matrix(element)) - self.link_element(element, obj) - collection = bpy.data.collections.new(obj.name) - collection.objects.link(obj) - self.aggregates[element.GlobalId] = { - "blender": collection, - "blender_obj": obj, - "container": self.get_aggregate_container(element), - } - - def get_aggregate_container(self, element): - if hasattr(element, "ContainedInStructure") and element.ContainedInStructure: - container = element.ContainedInStructure[0].RelatingStructure - elif hasattr(element, "Decomposes") and element.Decomposes: - container = element.Decomposes[0].RelatingObject - return container + for global_id, aggregate in aggregates.items(): + parent = ifcopenshell.util.element.get_aggregate(aggregate["element"]) + if parent: + self.collections[parent.GlobalId].children.link(aggregate["collection"]) + continue + parent = ifcopenshell.util.element.get_container(aggregate["element"]) + if parent: + self.collections[parent.GlobalId].children.link(aggregate["collection"]) def create_openings_collection(self): self.opening_collection = bpy.data.collections.new("IfcOpeningElements") @@ -1135,86 +1101,41 @@ class IfcImporter: def get_name(self, element): return "{}/{}".format(element.is_a(), element.Name) - def purge_diff(self): - if not self.diff: - return - objects_to_purge = [] - for obj in bpy.data.objects: - if "GlobalId" not in obj.BIMObjectProperties.attributes: - continue - global_id = obj.BIMObjectProperties.attributes["GlobalId"].string_value - if global_id in self.diff["deleted"] or global_id in self.diff["changed"].keys(): - objects_to_purge.append(obj) - bpy.ops.object.delete({"selected_objects": objects_to_purge}) - - def place_objects_in_spatial_tree(self): + def place_objects_in_collections(self): for ifc_definition_id, obj in self.added_data.items(): if isinstance(obj, bpy.types.Object): - self.place_object_in_spatial_tree(self.file.by_id(ifc_definition_id), obj) + self.place_object_in_collection(self.file.by_id(ifc_definition_id), obj) - def place_object_in_spatial_tree(self, element, obj): - if element.is_a() in ["IfcProject", "IfcProjectLibrary"]: + def place_object_in_collection(self, element, obj): + if self.ifc_import_settings.collection_mode == "DECOMPOSITION": + self.place_object_in_decomposition_collection(element, obj) + elif self.ifc_import_settings.collection_mode == "SPATIAL_DECOMPOSITION": + self.place_object_in_spatial_decomposition_collection(element, obj) + + def place_object_in_decomposition_collection(self, element, obj): + if element.is_a("IfcProject"): return + elif element.GlobalId in self.collections: + return self.collections[element.GlobalId].objects.link(obj) + elif getattr(element, "Decomposes", None): + aggregate = ifcopenshell.util.element.get_aggregate(element) + return self.collections[aggregate.GlobalId].objects.link(obj) + else: + return self.place_object_in_spatial_decomposition_collection(element, obj) + + def place_object_in_spatial_decomposition_collection(self, element, obj): + if element.is_a("IfcProject"): + return + elif element.GlobalId in self.collections: + return self.collections[element.GlobalId].objects.link(obj) elif element.is_a("IfcTypeObject"): - self.type_collection.objects.link(obj) - elif element.GlobalId in self.aggregates: - return - elif element.GlobalId in self.spatial_structure_elements: - if not obj.data: - return - # Since spatial structure elements are generated as empties, we'll replace it with the representation - spatial_obj = self.spatial_structure_elements[element.GlobalId]["blender_obj"] - spatial_collection = self.spatial_structure_elements[element.GlobalId]["blender"] - spatial_name = spatial_obj.name - spatial_collection.objects.link(obj) - bpy.data.objects.remove(spatial_obj) - obj.name = spatial_name - elif ( - hasattr(element, "ContainedInStructure") - and element.ContainedInStructure - and element.ContainedInStructure[0].RelatingStructure - ): - container = element.ContainedInStructure[0].RelatingStructure - if element.is_a("IfcGrid"): - grid_collection = bpy.data.collections.get(obj.name) - if grid_collection: # Just in case we ran into invalid grids from Revit - self.spatial_structure_elements[container.GlobalId]["blender"].children.link(grid_collection) - grid_collection.objects.link(obj) - else: - self.spatial_structure_elements[container.GlobalId]["blender"].objects.link(obj) - elif hasattr(element, "Decomposes") and element.Decomposes: - collection = None - if element.Decomposes[0].RelatingObject.is_a("IfcProject"): - collection = self.project["blender"] - elif element.Decomposes[0].RelatingObject.is_a("IfcSpatialStructureElement"): - if element.is_a("IfcSpatialStructureElement"): - global_id = element.GlobalId - if global_id in self.spatial_structure_elements: - if ( - element.is_a("IfcSpatialStructureElement") - and "blender_obj" in self.spatial_structure_elements[global_id] - ): - bpy.data.objects.remove(self.spatial_structure_elements[global_id]["blender_obj"]) - collection = self.spatial_structure_elements[global_id]["blender"] - elif self.ifc_import_settings.collection_mode == "SPATIAL_DECOMPOSITION": - # TODO: refactor this to a more holistic collection mode feature - return self.place_object_in_spatial_tree(element.Decomposes[0].RelatingObject, obj) - else: - aggregate = element.Decomposes[0].RelatingObject - aggregate_data = self.aggregates.get(aggregate.GlobalId) - if not aggregate_data: - return self.place_object_in_spatial_tree(aggregate, obj) - collection = aggregate_data["blender"] - if collection: - collection.objects.link(obj) - else: - self.ifc_import_settings.logger.error("An element could not be placed in the spatial tree %s", element) + return self.type_collection.objects.link(obj) elif element.is_a("IfcOpeningElement"): - self.opening_collection.objects.link(obj) + return self.opening_collection.objects.link(obj) elif element.is_a("IfcStructuralMember"): - self.structural_member_collection.objects.link(obj) + return self.structural_member_collection.objects.link(obj) elif element.is_a("IfcStructuralConnection"): - self.structural_connection_collection.objects.link(obj) + return self.structural_connection_collection.objects.link(obj) elif element.is_a("IfcAnnotation") and element.ObjectType == "DRAWING": view_collection = bpy.data.collections.get("Views") if not view_collection: @@ -1224,6 +1145,18 @@ class IfcImporter: drawing_collection = bpy.data.collections.new("IfcGroup/" + group.Name) view_collection.children.link(drawing_collection) drawing_collection.objects.link(obj) + return + + container = ifcopenshell.util.element.get_container(element) + if container: + if element.is_a("IfcGrid"): # TODO: refactor into a more holistic collection mode feature + grid_collection = bpy.data.collections.get(obj.name) + if grid_collection: # Just in case we run into invalid grids from Revit + self.collections[container.GlobalId].children.link(grid_collection) + grid_collection.objects.link(obj) + else: + self.collections[container.GlobalId].objects.link(obj) + else: self.ifc_import_settings.logger.warning("Warning: this object is outside the spatial hierarchy %s", element) bpy.context.scene.collection.objects.link(obj) @@ -1344,7 +1277,9 @@ class IfcImporter: if "TargetView" in pset: camera.BIMCameraProperties.target_view = pset["TargetView"] if "Scale" in pset: - valid_scales = [i[0] for i in getDiagramScales(None, None) if pset["Scale"] == i[0].split("|")[-1]] + valid_scales = [ + i[0] for i in get_diagram_scales(None, bpy.context) if pset["Scale"] == i[0].split("|")[-1] + ] if valid_scales: camera.BIMCameraProperties.diagram_scale = valid_scales[0] else: @@ -1391,13 +1326,7 @@ class IfcImporter: num_vertex_indices = len(geometry.faces) mesh.vertices.add(num_vertices) - if self.ifc_import_settings.should_offset_model: - # Potentially, there is a smarter way to do this. See #1047 - v_index = cycle((0, 1, 2)) - verts = [v + self.ifc_import_settings.model_offset_coordinates[next(v_index)] for v in verts] - mesh.vertices.foreach_set("co", verts) - else: - mesh.vertices.foreach_set("co", verts) + mesh.vertices.foreach_set("co", verts) mesh.loops.add(num_vertex_indices) mesh.loops.foreach_set("vertex_index", geometry.faces) mesh.polygons.add(num_loops) @@ -1472,7 +1401,6 @@ class IfcImportSettings: self.logger = None self.input_file = None self.diff_file = None - self.should_auto_set_workarounds = True self.should_use_cpu_multiprocessing = True self.should_merge_by_class = False self.should_merge_by_material = False @@ -1482,13 +1410,12 @@ class IfcImportSettings: self.angular_tolerance = 0.5 self.should_offset_model = False self.model_offset_coordinates = (0, 0, 0) - self.ifc_import_filter = "NONE" - self.ifc_selector = "" + self.has_filter = None + self.elements = "" self.collection_mode = "DECOMPOSITION" @staticmethod def factory(context, input_file, logger): - scene_bim = context.scene.BIMProperties scene_diff = context.scene.DiffProperties settings = IfcImportSettings() settings.input_file = input_file diff --git a/src/blenderbim/blenderbim/bim/module/__init__.py b/src/blenderbim/blenderbim/bim/module/__init__.py new file mode 100644 index 0000000000..1498d0279b --- /dev/null +++ b/src/blenderbim/blenderbim/bim/module/__init__.py @@ -0,0 +1,17 @@ +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2020, 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . diff --git a/src/blenderbim/blenderbim/bim/module/aggregate/__init__.py b/src/blenderbim/blenderbim/bim/module/aggregate/__init__.py index a97637b771..bd7f6946ea 100644 --- a/src/blenderbim/blenderbim/bim/module/aggregate/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/aggregate/__init__.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/module/aggregate/operator.py b/src/blenderbim/blenderbim/bim/module/aggregate/operator.py index d5b972791a..b9b830df3b 100644 --- a/src/blenderbim/blenderbim/bim/module/aggregate/operator.py +++ b/src/blenderbim/blenderbim/bim/module/aggregate/operator.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/module/aggregate/ui.py b/src/blenderbim/blenderbim/bim/module/aggregate/ui.py index 7f9c033b12..3ce391dc8e 100644 --- a/src/blenderbim/blenderbim/bim/module/aggregate/ui.py +++ b/src/blenderbim/blenderbim/bim/module/aggregate/ui.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -56,7 +55,9 @@ class BIM_PT_aggregate(Panel): row = self.layout.row(align=True) row.prop(props, "relating_object", text="") if props.relating_object: - row.operator("bim.assign_object", icon="CHECKMARK", text="").relating_object = props.relating_object.name + row.operator( + "bim.assign_object", icon="CHECKMARK", text="" + ).relating_object = props.relating_object.name row.operator("bim.disable_editing_aggregate", icon="CANCEL", text="") else: row = self.layout.row(align=True) diff --git a/src/blenderbim/blenderbim/bim/module/attribute/__init__.py b/src/blenderbim/blenderbim/bim/module/attribute/__init__.py index afa3ad0e18..d91a5ad409 100644 --- a/src/blenderbim/blenderbim/bim/module/attribute/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/attribute/__init__.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/module/attribute/operator.py b/src/blenderbim/blenderbim/bim/module/attribute/operator.py index ef5798a5d9..e29bc69628 100644 --- a/src/blenderbim/blenderbim/bim/module/attribute/operator.py +++ b/src/blenderbim/blenderbim/bim/module/attribute/operator.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -119,9 +118,7 @@ class EditAttributes(bpy.types.Operator): elif attribute["type"] == "enum": attributes[attribute["name"]] = blender_attribute.enum_value product = self.file.by_id(oprops.ifc_definition_id) - ifcopenshell.api.run( - "attribute.edit_attributes", self.file, **{"product": product, "attributes": attributes} - ) + ifcopenshell.api.run("attribute.edit_attributes", self.file, **{"product": product, "attributes": attributes}) Data.load(IfcStore.get_file(), oprops.ifc_definition_id) bpy.ops.bim.disable_editing_attributes(obj=obj.name, obj_type=self.obj_type) return {"FINISHED"} diff --git a/src/blenderbim/blenderbim/bim/module/attribute/prop.py b/src/blenderbim/blenderbim/bim/module/attribute/prop.py index d402546092..35391fc321 100644 --- a/src/blenderbim/blenderbim/bim/module/attribute/prop.py +++ b/src/blenderbim/blenderbim/bim/module/attribute/prop.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -32,6 +31,7 @@ from bpy.props import ( CollectionProperty, ) + class BIMAttributeProperties(PropertyGroup): attributes: CollectionProperty(name="Attributes", type=Attribute) is_editing_attributes: BoolProperty(name="Is Editing Attributes") diff --git a/src/blenderbim/blenderbim/bim/module/attribute/ui.py b/src/blenderbim/blenderbim/bim/module/attribute/ui.py index 5bdb184576..e6d5c5fa1b 100644 --- a/src/blenderbim/blenderbim/bim/module/attribute/ui.py +++ b/src/blenderbim/blenderbim/bim/module/attribute/ui.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/module/augin/__init__.py b/src/blenderbim/blenderbim/bim/module/augin/__init__.py index 9f659c7088..d34dd212ef 100644 --- a/src/blenderbim/blenderbim/bim/module/augin/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/augin/__init__.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/module/augin/operator.py b/src/blenderbim/blenderbim/bim/module/augin/operator.py index d4b5b0d316..62f5c3b09d 100644 --- a/src/blenderbim/blenderbim/bim/module/augin/operator.py +++ b/src/blenderbim/blenderbim/bim/module/augin/operator.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -68,6 +67,7 @@ class AuginCreateNewModel(bpy.types.Operator): def execute(self, context): import boto3 from botocore.config import Config + props = context.scene.AuginProperties # Create project @@ -135,7 +135,6 @@ class AuginCreateNewModel(bpy.types.Operator): client.upload_file(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 = { diff --git a/src/blenderbim/blenderbim/bim/module/augin/prop.py b/src/blenderbim/blenderbim/bim/module/augin/prop.py index d111ab0263..3424ac09b8 100644 --- a/src/blenderbim/blenderbim/bim/module/augin/prop.py +++ b/src/blenderbim/blenderbim/bim/module/augin/prop.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/module/augin/ui.py b/src/blenderbim/blenderbim/bim/module/augin/ui.py index 30f593846b..8349cd8c23 100644 --- a/src/blenderbim/blenderbim/bim/module/augin/ui.py +++ b/src/blenderbim/blenderbim/bim/module/augin/ui.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/module/bcf/__init__.py b/src/blenderbim/blenderbim/bim/module/bcf/__init__.py index c64a01ba66..a6cde8cf30 100644 --- a/src/blenderbim/blenderbim/bim/module/bcf/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/bcf/__init__.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/module/bcf/bcfstore.py b/src/blenderbim/blenderbim/bim/module/bcf/bcfstore.py index 88ec005b3e..d6b829339c 100644 --- a/src/blenderbim/blenderbim/bim/module/bcf/bcfstore.py +++ b/src/blenderbim/blenderbim/bim/module/bcf/bcfstore.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -20,6 +19,7 @@ import bcf import bcf.v2.bcfxml + class BcfStore: bcfxml = None diff --git a/src/blenderbim/blenderbim/bim/module/bcf/operator.py b/src/blenderbim/blenderbim/bim/module/bcf/operator.py index 08276a0a33..1ce425c590 100644 --- a/src/blenderbim/blenderbim/bim/module/bcf/operator.py +++ b/src/blenderbim/blenderbim/bim/module/bcf/operator.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -31,6 +30,7 @@ from blenderbim.bim.ifc import IfcStore from math import radians, degrees, atan, tan, cos, sin from mathutils import Vector, Matrix, Euler, geometry + class NewBcfProject(bpy.types.Operator): bl_idname = "bim.new_bcf_project" bl_label = "New BCF Project" @@ -79,7 +79,7 @@ class LoadBcfTopics(bpy.types.Operator): context.scene.BCFProperties.topics.clear() for index, topic_guid in enumerate(bcfxml.topics.keys()): new = context.scene.BCFProperties.topics.add() - bpy.ops.bim.load_bcf_topic(topic_guid = topic_guid, topic_index = index) + bpy.ops.bim.load_bcf_topic(topic_guid=topic_guid, topic_index=index) return {"FINISHED"} @@ -107,7 +107,7 @@ class LoadBcfTopic(bpy.types.Operator): "modified_author": topic.modified_author, "assigned_to": topic.assigned_to, "due_date": topic.due_date, - "description": topic.description + "description": topic.description, } for key, value in data_map.items(): if value is not None: @@ -128,7 +128,7 @@ class LoadBcfTopic(bpy.types.Operator): "type": topic.bim_snippet.snippet_type, "is_external": topic.bim_snippet.is_external, "reference": topic.bim_snippet.reference, - "schema": topic.bim_snippet.reference_schema + "schema": topic.bim_snippet.reference_schema, } for key, value in data_map.items(): if value is not None: @@ -141,7 +141,7 @@ class LoadBcfTopic(bpy.types.Operator): "reference": doc.referenced_document, "description": doc.description, "guid": doc.guid, - "is_external": doc.is_external + "is_external": doc.is_external, } for key, value in data_map.items(): if value is not None: @@ -152,7 +152,7 @@ class LoadBcfTopic(bpy.types.Operator): new_related_topic = new.related_topics.add() new_related_topic.name = related_topic.guid - bpy.ops.bim.load_bcf_comments(topic_guid = topic.guid) + bpy.ops.bim.load_bcf_comments(topic_guid=topic.guid) return {"FINISHED"} @@ -287,11 +287,12 @@ class AddBcfBimSnippet(bpy.types.Operator): @classmethod def poll(cls, context): - return all((getattr(context.scene.BCFProperties, attr, False) for attr in ( - "bim_snippet_reference", - "bim_snippet_schema", - "bim_snippet_type" - ))) + return all( + ( + getattr(context.scene.BCFProperties, attr, False) + for attr in ("bim_snippet_reference", "bim_snippet_schema", "bim_snippet_type") + ) + ) def execute(self, context): bcfxml = bcfstore.BcfStore.get_bcfxml() @@ -303,7 +304,7 @@ class AddBcfBimSnippet(bpy.types.Operator): bim_snippet.reference_schema = props.bim_snippet_schema bim_snippet.snippet_type = props.bim_snippet_type bcfxml.add_bim_snippet(topic, bim_snippet) - bpy.ops.bim.load_bcf_topic(topic_guid = topic.guid, topic_index = props.active_topic_index) + bpy.ops.bim.load_bcf_topic(topic_guid=topic.guid, topic_index=props.active_topic_index) bim_snippet.reference = "" bim_snippet.reference_schema = "" bim_snippet.snippet_type = "" @@ -316,7 +317,7 @@ class AddBcfRelatedTopic(bpy.types.Operator): bl_options = {"REGISTER", "UNDO"} @classmethod - def poll(cls, context): + def poll(cls, context): bcfxml = bcfstore.BcfStore.get_bcfxml() props = context.scene.BCFProperties blender_topic = props.active_topic @@ -347,7 +348,7 @@ class AddBcfRelatedTopic(bpy.types.Operator): topic = bcfxml.topics[blender_topic.name] topic.related_topics.append(related_topic) bcfxml.edit_topic(topic) - bpy.ops.bim.load_bcf_topic(topic_guid = topic.guid, topic_index = props.active_topic_index) + bpy.ops.bim.load_bcf_topic(topic_guid=topic.guid, topic_index=props.active_topic_index) props.related_topic = "" return {"FINISHED"} @@ -516,7 +517,7 @@ class AddBcfReferenceLink(bpy.types.Operator): topic = bcfxml.topics[blender_topic.name] topic.reference_links.append(props.reference_link) bcfxml.edit_topic(topic) - bpy.ops.bim.load_bcf_topic(topic_guid = topic.guid, topic_index = props.active_topic_index) + bpy.ops.bim.load_bcf_topic(topic_guid=topic.guid, topic_index=props.active_topic_index) props.reference_link = "" return {"FINISHED"} @@ -539,7 +540,7 @@ class AddBcfDocumentReference(bpy.types.Operator): document_reference.referenced_document = props.document_reference document_reference.description = props.document_reference_description or None bcfxml.add_document_reference(topic, document_reference) - bpy.ops.bim.load_bcf_topic(topic_guid = topic.guid, topic_index = props.active_topic_index) + bpy.ops.bim.load_bcf_topic(topic_guid=topic.guid, topic_index=props.active_topic_index) props.document_reference = "" props.document_reference_description = "" return {"FINISHED"} @@ -668,7 +669,7 @@ class RemoveBcfDocumentReference(bpy.types.Operator): blender_topic = props.active_topic topic = bcfxml.topics[blender_topic.name] bcfxml.delete_document_reference(topic, self.index) - bpy.ops.bim.load_bcf_topic(topic_guid = topic.guid, topic_index = props.active_topic_index) + bpy.ops.bim.load_bcf_topic(topic_guid=topic.guid, topic_index=props.active_topic_index) return {"FINISHED"} @@ -685,7 +686,7 @@ class RemoveBcfRelatedTopic(bpy.types.Operator): topic = bcfxml.topics[blender_topic.name] del topic.related_topics[self.index] bcfxml.edit_topic(topic) - bpy.ops.bim.load_bcf_topic(topic_guid = topic.guid, topic_index = props.active_topic_index) + bpy.ops.bim.load_bcf_topic(topic_guid=topic.guid, topic_index=props.active_topic_index) return {"FINISHED"} @@ -701,7 +702,7 @@ class RemoveBcfComment(bpy.types.Operator): blender_topic = props.active_topic topic = bcfxml.topics[blender_topic.name] bcfxml.delete_comment(self.comment_guid, topic) - bpy.ops.bim.load_bcf_comments(topic_guid = topic.guid) + bpy.ops.bim.load_bcf_comments(topic_guid=topic.guid) return {"FINISHED"} @@ -720,7 +721,7 @@ class EditBcfComment(bpy.types.Operator): comment = topic.comments[self.comment_guid] comment.comment = blender_comment.comment bcfxml.edit_comment(comment, topic) - bpy.ops.bim.load_bcf_comments(topic_guid = topic.guid) + bpy.ops.bim.load_bcf_comments(topic_guid=topic.guid) return {"FINISHED"} @@ -750,7 +751,7 @@ class AddBcfComment(bpy.types.Operator): comment.viewpoint = bcf.v2.data.Viewpoint() comment.viewpoint.guid = blender_topic.viewpoints bcfxml.add_comment(topic, comment) - bpy.ops.bim.load_bcf_comments(topic_guid = topic.guid) + bpy.ops.bim.load_bcf_comments(topic_guid=topic.guid) props.comment = "" props.has_related_viewpoint = False return {"FINISHED"} @@ -787,14 +788,12 @@ class ActivateBcfViewpoint(bpy.types.Operator): cam_width = context.scene.render.resolution_x cam_height = context.scene.render.resolution_y cam_aspect = cam_width / cam_height - + if viewpoint.snapshot: obj.data.show_background_images = True obj.data.background_images.clear() background = obj.data.background_images.new() - background.image = bpy.data.images.load( - os.path.join(bcfxml.filepath, topic.guid, viewpoint.snapshot) - ) + background.image = bpy.data.images.load(os.path.join(bcfxml.filepath, topic.guid, viewpoint.snapshot)) src_width = background.image.size[0] src_height = background.image.size[1] src_aspect = src_width / src_height @@ -839,19 +838,25 @@ class ActivateBcfViewpoint(bpy.types.Operator): obj.data.angle = radians(camera.field_of_view) else: # https://blender.stackexchange.com/questions/23431/how-to-set-camera-horizontal-and-vertical-fov - obj.data.angle = 2 * atan((0.5 * cam_height) / (0.5 * cam_width / tan(radians(camera.field_of_view) / 2))) + obj.data.angle = 2 * atan( + (0.5 * cam_height) / (0.5 * cam_width / tan(radians(camera.field_of_view) / 2)) + ) - z_axis = Vector((-camera.camera_direction.x, -camera.camera_direction.y, -camera.camera_direction.z)).normalized() + z_axis = Vector( + (-camera.camera_direction.x, -camera.camera_direction.y, -camera.camera_direction.z) + ).normalized() y_axis = Vector((camera.camera_up_vector.x, camera.camera_up_vector.y, camera.camera_up_vector.z)).normalized() x_axis = y_axis.cross(z_axis).normalized() rotation = Matrix((x_axis, y_axis, z_axis)) rotation.invert() - matrix = np.matrix(( - [x_axis[0], y_axis[0], z_axis[0], camera.camera_view_point.x], - [x_axis[1], y_axis[1], z_axis[1], camera.camera_view_point.y], - [x_axis[2], y_axis[2], z_axis[2], camera.camera_view_point.z], - [0, 0, 0, 1], - )) + matrix = np.matrix( + ( + [x_axis[0], y_axis[0], z_axis[0], camera.camera_view_point.x], + [x_axis[1], y_axis[1], z_axis[1], camera.camera_view_point.y], + [x_axis[2], y_axis[2], z_axis[2], camera.camera_view_point.z], + [0, 0, 0, 1], + ) + ) props = context.scene.BIMGeoreferenceProperties if props.has_blender_offset: unit_scale = ifcopenshell.util.unit.calculate_unit_scale(self.file) @@ -961,7 +966,9 @@ class ActivateBcfViewpoint(bpy.types.Operator): stroke.points.add(len(viewpoint.lines) * 2) coords = [] for l in viewpoint.lines: - coords.extend([l.start_point.x, l.start_point.y, l.start_point.z, l.end_point.x, l.end_point.y, l.end_point.z]) + coords.extend( + [l.start_point.x, l.start_point.y, l.start_point.z, l.end_point.x, l.end_point.y, l.end_point.z] + ) stroke.points.foreach_set("co", coords) def create_clipping_planes(self, viewpoint): @@ -1025,6 +1032,7 @@ class ActivateBcfViewpoint(bpy.types.Operator): t = tuple(int(value[i : i + lv // 3], 16) for i in range(0, lv, lv // 3)) return [t[0] / 255.0, t[1] / 255.0, t[2] / 255.0, 1] + class OpenBcfReferenceLink(bpy.types.Operator): bl_idname = "bim.open_bcf_reference_link" bl_label = "Open BCF Reference Link" diff --git a/src/blenderbim/blenderbim/bim/module/bcf/prop.py b/src/blenderbim/blenderbim/bim/module/bcf/prop.py index 8f20938e64..e24f5adba0 100644 --- a/src/blenderbim/blenderbim/bim/module/bcf/prop.py +++ b/src/blenderbim/blenderbim/bim/module/bcf/prop.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -73,7 +72,7 @@ def updateBcfTopicIsEditable(self, context): def updateBcfCommentIsEditable(self, context): if context.scene.BCFProperties.is_loaded and not self.is_editable: - bpy.ops.bim.edit_bcf_comment(comment_guid = self.name) + bpy.ops.bim.edit_bcf_comment(comment_guid=self.name) def refreshBcfTopic(self, context): @@ -91,7 +90,7 @@ class BcfLabel(PropertyGroup): def getBcfViewpoints(self, context, force_update=False): global bcfviewpoints_enum - if bcfviewpoints_enum is None or force_update: # Retrieving Viewpoints is slow. Make sure we only do when needed + if bcfviewpoints_enum is None or force_update: # Retrieving Viewpoints is slow. Make sure we only do when needed bcfviewpoints_enum = [] props = context.scene.BCFProperties bcfxml = bcfstore.BcfStore.get_bcfxml() diff --git a/src/blenderbim/blenderbim/bim/module/bcf/ui.py b/src/blenderbim/blenderbim/bim/module/bcf/ui.py index dfa8de1aa6..4b1fa8d339 100644 --- a/src/blenderbim/blenderbim/bim/module/bcf/ui.py +++ b/src/blenderbim/blenderbim/bim/module/bcf/ui.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -22,6 +21,7 @@ import bpy from . import bcfstore from bpy.types import Panel + class BIM_PT_bcf(Panel): bl_label = "BCF Project" bl_idname = "BIM_PT_bcf" @@ -231,12 +231,12 @@ class BIM_PT_bcf_metadata(Panel): row.operator("bim.add_bcf_document_reference") layout.label(text="Related Topics:") - for index, related_topic in enumerate(topic.related_topics): + for index, related_topic in enumerate(topic.related_topics): try: row = layout.row(align=True) op = row.operator( - "bim.view_bcf_topic", - text=f"Select {bcfxml.topics[related_topic.name.lower()].title}") + "bim.view_bcf_topic", text=f"Select {bcfxml.topics[related_topic.name.lower()].title}" + ) op.topic_guid = related_topic.name row.operator("bim.remove_bcf_related_topic", icon="X", text="").index = index except KeyError: diff --git a/src/blenderbim/blenderbim/bim/module/bimtester/__init__.py b/src/blenderbim/blenderbim/bim/module/bimtester/__init__.py index a9793f5c5d..0018a98abe 100644 --- a/src/blenderbim/blenderbim/bim/module/bimtester/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/bimtester/__init__.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/module/bimtester/operator.py b/src/blenderbim/blenderbim/bim/module/bimtester/operator.py index 7bf9905313..10dc74f20c 100644 --- a/src/blenderbim/blenderbim/bim/module/bimtester/operator.py +++ b/src/blenderbim/blenderbim/bim/module/bimtester/operator.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -21,7 +20,6 @@ import os import bpy import tempfile import webbrowser -import ifcopenshell try: import bimtester @@ -31,7 +29,6 @@ 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 @@ -42,7 +39,7 @@ class ExecuteBIMTester(bpy.types.Operator): @classmethod def poll(cls, context): props = context.scene.BimTesterProperties - return props.ifc_file and props.feature + return (props.ifc_file or props.should_load_from_memory) and props.feature def execute(self, context): props = context.scene.BimTesterProperties @@ -75,10 +72,6 @@ class BIMTesterPurge(bpy.types.Operator): bl_label = "Purge Tests" def execute(self, context): - filename = os.path.join( - context.scene.BimTesterProperties.features_dir, - context.scene.BimTesterProperties.features_file + ".feature", - ) cwd = os.getcwd() os.chdir(context.scene.BimTesterProperties.features_dir) bimtester.clean.TestPurger().purge() @@ -243,19 +236,3 @@ class QAHelper: is_in_scenario = False destination.write(source_line) os.remove(filename + "~") - - -colour_list = [ - (0.651, 0.81, 0.892, 1), - (0.121, 0.471, 0.706, 1), - (0.699, 0.876, 0.54, 1), - (0.199, 0.629, 0.174, 1), - (0.983, 0.605, 0.602, 1), - (0.89, 0.101, 0.112, 1), - (0.989, 0.751, 0.427, 1), - (0.986, 0.497, 0.1, 1), - (0.792, 0.699, 0.839, 1), - (0.414, 0.239, 0.603, 1), - (0.993, 0.999, 0.6, 1), - (0.693, 0.349, 0.157, 1), -] diff --git a/src/blenderbim/blenderbim/bim/module/bimtester/prop.py b/src/blenderbim/blenderbim/bim/module/bimtester/prop.py index 1518ea234e..e3b438423b 100644 --- a/src/blenderbim/blenderbim/bim/module/bimtester/prop.py +++ b/src/blenderbim/blenderbim/bim/module/bimtester/prop.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/module/bimtester/ui.py b/src/blenderbim/blenderbim/bim/module/bimtester/ui.py index af557c77fc..74e6e853fd 100644 --- a/src/blenderbim/blenderbim/bim/module/bimtester/ui.py +++ b/src/blenderbim/blenderbim/bim/module/bimtester/ui.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/module/boundary/__init__.py b/src/blenderbim/blenderbim/bim/module/boundary/__init__.py index 32c66b3b15..1f65558621 100644 --- a/src/blenderbim/blenderbim/bim/module/boundary/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/boundary/__init__.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -20,9 +19,7 @@ import bpy from . import ui -classes = ( - ui.BIM_PT_boundary, -) +classes = (ui.BIM_PT_boundary,) def register(): diff --git a/src/blenderbim/blenderbim/bim/module/boundary/ui.py b/src/blenderbim/blenderbim/bim/module/boundary/ui.py index f401fbdc0b..405f171125 100644 --- a/src/blenderbim/blenderbim/bim/module/boundary/ui.py +++ b/src/blenderbim/blenderbim/bim/module/boundary/ui.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/module/clash/__init__.py b/src/blenderbim/blenderbim/bim/module/clash/__init__.py index 8dcca175c7..7fc23861f1 100644 --- a/src/blenderbim/blenderbim/bim/module/clash/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/clash/__init__.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/module/clash/operator.py b/src/blenderbim/blenderbim/bim/module/clash/operator.py index 90223e46d4..2b2718ee7a 100644 --- a/src/blenderbim/blenderbim/bim/module/clash/operator.py +++ b/src/blenderbim/blenderbim/bim/module/clash/operator.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -232,9 +231,7 @@ class ExecuteIfcClash(bpy.types.Operator): context.scene.render.resolution_x = 480 context.scene.render.resolution_y = 270 context.scene.render.image_settings.file_format = "PNG" - context.scene.render.filepath = os.path.join( - context.scene.BIMProperties.data_dir, "snapshot.png" - ) + context.scene.render.filepath = os.path.join(context.scene.BIMProperties.data_dir, "snapshot.png") bpy.ops.render.opengl(write_still=True) return context.scene.render.filepath diff --git a/src/blenderbim/blenderbim/bim/module/clash/prop.py b/src/blenderbim/blenderbim/bim/module/clash/prop.py index e721db86ce..f057dcd70c 100644 --- a/src/blenderbim/blenderbim/bim/module/clash/prop.py +++ b/src/blenderbim/blenderbim/bim/module/clash/prop.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/module/clash/ui.py b/src/blenderbim/blenderbim/bim/module/clash/ui.py index 9b6d021a42..bc2e7994c1 100644 --- a/src/blenderbim/blenderbim/bim/module/clash/ui.py +++ b/src/blenderbim/blenderbim/bim/module/clash/ui.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/module/classification/__init__.py b/src/blenderbim/blenderbim/bim/module/classification/__init__.py index f03523e884..64d2a29a5a 100644 --- a/src/blenderbim/blenderbim/bim/module/classification/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/classification/__init__.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/module/classification/operator.py b/src/blenderbim/blenderbim/bim/module/classification/operator.py index b1dd53b8fd..1db448c9c7 100644 --- a/src/blenderbim/blenderbim/bim/module/classification/operator.py +++ b/src/blenderbim/blenderbim/bim/module/classification/operator.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -76,6 +75,7 @@ class EnableEditingClassification(bpy.types.Operator): new.name = attribute.name() new.is_null = classification_data[attribute.name()] is None new.is_optional = attribute.optional() + new.data_type = "string" if attribute.name() == "ReferenceTokens": new.string_value = "" if new.is_null else json.dumps(classification_data[attribute.name()]) else: @@ -162,6 +162,7 @@ class EnableEditingClassificationReference(bpy.types.Operator): new.name = attribute.name() new.is_null = reference_data[attribute.name()] is None new.is_optional = attribute.optional() + new.data_type = "string" new.string_value = "" if new.is_null else reference_data[attribute.name()] props.active_reference_id = self.reference return {"FINISHED"} diff --git a/src/blenderbim/blenderbim/bim/module/classification/prop.py b/src/blenderbim/blenderbim/bim/module/classification/prop.py index e760b5eb47..4b5c091763 100644 --- a/src/blenderbim/blenderbim/bim/module/classification/prop.py +++ b/src/blenderbim/blenderbim/bim/module/classification/prop.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/module/classification/ui.py b/src/blenderbim/blenderbim/bim/module/classification/ui.py index cdc6357589..1a03ebf3a5 100644 --- a/src/blenderbim/blenderbim/bim/module/classification/ui.py +++ b/src/blenderbim/blenderbim/bim/module/classification/ui.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/module/cobie/__init__.py b/src/blenderbim/blenderbim/bim/module/cobie/__init__.py index 55ce5b8358..a1ad500ae2 100644 --- a/src/blenderbim/blenderbim/bim/module/cobie/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/cobie/__init__.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/module/cobie/operator.py b/src/blenderbim/blenderbim/bim/module/cobie/operator.py index 58022975a8..3f1a512116 100644 --- a/src/blenderbim/blenderbim/bim/module/cobie/operator.py +++ b/src/blenderbim/blenderbim/bim/module/cobie/operator.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -69,13 +68,14 @@ class ExecuteIfcCobie(bpy.types.Operator): def execute(self, context): from cobie import IfcCobieParser + props = context.scene.COBieProperties if props.should_load_from_memory: output_dir = tempfile.gettempdir() else: - output_dir = os.path.dirname(props.cobie_ifc_file) - + output_dir = os.path.dirname(props.cobie_ifc_file) + output = os.path.join(output_dir, "output") logger = logging.getLogger("IFCtoCOBie") fh = logging.FileHandler(os.path.join(output_dir, "cobie.log")) @@ -92,10 +92,10 @@ class ExecuteIfcCobie(bpy.types.Operator): parser = IfcCobieParser(logger, selector) ifc_file = IfcStore.get_file() - + if not (ifc_file and props.should_load_from_memory): ifc_file = props.cobie_ifc_file - + parser.parse( ifc_file, props.cobie_types, @@ -122,4 +122,3 @@ class ExecuteIfcCobie(bpy.types.Operator): webbrowser.open("file://" + output_dir) webbrowser.open("file://" + output_dir + "/cobie.log") return {"FINISHED"} - diff --git a/src/blenderbim/blenderbim/bim/module/cobie/prop.py b/src/blenderbim/blenderbim/bim/module/cobie/prop.py index 0eb6788f79..567a0f9960 100644 --- a/src/blenderbim/blenderbim/bim/module/cobie/prop.py +++ b/src/blenderbim/blenderbim/bim/module/cobie/prop.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -37,4 +36,3 @@ class COBieProperties(PropertyGroup): cobie_components: StringProperty(default=".COBie", name="COBie Components") cobie_json_file: StringProperty(default="", name="COBie JSON File") should_load_from_memory: BoolProperty(default=False, name="Load from Memory") - diff --git a/src/blenderbim/blenderbim/bim/module/cobie/ui.py b/src/blenderbim/blenderbim/bim/module/cobie/ui.py index c311e58e26..2ae0179a53 100644 --- a/src/blenderbim/blenderbim/bim/module/cobie/ui.py +++ b/src/blenderbim/blenderbim/bim/module/cobie/ui.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -36,11 +35,10 @@ class BIM_PT_cobie(Panel): scene = context.scene props = scene.COBieProperties - - if IfcStore.get_file(): + if IfcStore.get_file(): row = layout.row() row.prop(props, "should_load_from_memory") - + if not IfcStore.get_file() or not props.should_load_from_memory: row = layout.row(align=True) row.prop(props, "cobie_ifc_file") diff --git a/src/blenderbim/blenderbim/bim/module/constraint/__init__.py b/src/blenderbim/blenderbim/bim/module/constraint/__init__.py index 8469bd48bf..97c7dc78c6 100644 --- a/src/blenderbim/blenderbim/bim/module/constraint/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/constraint/__init__.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/module/constraint/operator.py b/src/blenderbim/blenderbim/bim/module/constraint/operator.py index d7b409fa93..e7accd3340 100644 --- a/src/blenderbim/blenderbim/bim/module/constraint/operator.py +++ b/src/blenderbim/blenderbim/bim/module/constraint/operator.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -187,17 +186,21 @@ class AssignConstraint(bpy.types.Operator): return IfcStore.execute_ifc_operator(self, context) def _execute(self, context): - obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object self.file = IfcStore.get_file() - ifcopenshell.api.run( - "constraint.assign_constraint", - self.file, - **{ - "product": self.file.by_id(obj.BIMObjectProperties.ifc_definition_id), - "constraint": self.file.by_id(self.constraint), - } - ) - Data.load(IfcStore.get_file(), obj.BIMObjectProperties.ifc_definition_id) + objs = [bpy.data.objects.get(self.obj)] if self.obj else context.selected_objects + for obj in objs: + obj_id = obj.BIMObjectProperties.ifc_definition_id + if not obj_id: + continue + ifcopenshell.api.run( + "constraint.assign_constraint", + self.file, + **{ + "product": self.file.by_id(obj_id), + "constraint": self.file.by_id(self.constraint), + } + ) + Data.load(self.file, obj_id) return {"FINISHED"} @@ -212,15 +215,19 @@ class UnassignConstraint(bpy.types.Operator): return IfcStore.execute_ifc_operator(self, context) def _execute(self, context): - obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object self.file = IfcStore.get_file() - ifcopenshell.api.run( - "constraint.unassign_constraint", - self.file, - **{ - "product": self.file.by_id(obj.BIMObjectProperties.ifc_definition_id), - "constraint": self.file.by_id(self.constraint), - } - ) - Data.load(IfcStore.get_file(), obj.BIMObjectProperties.ifc_definition_id) + objs = [bpy.data.objects.get(self.obj)] if self.obj else context.selected_objects + for obj in objs: + obj_id = obj.BIMObjectProperties.ifc_definition_id + if not obj_id: + continue + ifcopenshell.api.run( + "constraint.unassign_constraint", + self.file, + **{ + "product": self.file.by_id(obj_id), + "constraint": self.file.by_id(self.constraint), + } + ) + Data.load(self.file, obj_id) return {"FINISHED"} diff --git a/src/blenderbim/blenderbim/bim/module/constraint/prop.py b/src/blenderbim/blenderbim/bim/module/constraint/prop.py index 3bd87458ec..aea987c4a7 100644 --- a/src/blenderbim/blenderbim/bim/module/constraint/prop.py +++ b/src/blenderbim/blenderbim/bim/module/constraint/prop.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/module/constraint/ui.py b/src/blenderbim/blenderbim/bim/module/constraint/ui.py index 5264196e75..27aced601b 100644 --- a/src/blenderbim/blenderbim/bim/module/constraint/ui.py +++ b/src/blenderbim/blenderbim/bim/module/constraint/ui.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -105,7 +104,7 @@ class BIM_PT_object_constraints(Panel): constraint = Data.objectives[constraint_id] icon = "LIGHT" except: - pass # Metric not implemented + pass # Metric not implemented row = self.layout.row(align=True) row.label(text=constraint.get("Name") or "Unnamed") row.operator("bim.unassign_constraint", text="", icon="X").constraint = constraint_id diff --git a/src/blenderbim/blenderbim/bim/module/context/__init__.py b/src/blenderbim/blenderbim/bim/module/context/__init__.py index f4fbdab85e..7577e1d78d 100644 --- a/src/blenderbim/blenderbim/bim/module/context/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/context/__init__.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/module/context/operator.py b/src/blenderbim/blenderbim/bim/module/context/operator.py index 263967f774..9417865eb8 100644 --- a/src/blenderbim/blenderbim/bim/module/context/operator.py +++ b/src/blenderbim/blenderbim/bim/module/context/operator.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/module/context/ui.py b/src/blenderbim/blenderbim/bim/module/context/ui.py index 614795bb8d..3cdc87be29 100644 --- a/src/blenderbim/blenderbim/bim/module/context/ui.py +++ b/src/blenderbim/blenderbim/bim/module/context/ui.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/module/cost/__init__.py b/src/blenderbim/blenderbim/bim/module/cost/__init__.py index c768025826..c431d481b1 100644 --- a/src/blenderbim/blenderbim/bim/module/cost/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/cost/__init__.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -26,7 +25,8 @@ classes = ( operator.EditCostSchedule, operator.EditCostItem, operator.EditCostItemQuantity, - operator.EditCostValue, + operator.EditCostItemValue, + operator.EditCostItemValueFormula, operator.EnableEditingCostSchedule, operator.EnableEditingCostItems, operator.EnableEditingCostItem, @@ -34,6 +34,7 @@ classes = ( operator.EnableEditingCostItemQuantity, operator.EnableEditingCostItemValues, operator.EnableEditingCostItemValue, + operator.EnableEditingCostItemValueFormula, operator.DisableEditingCostItem, operator.DisableEditingCostSchedule, operator.DisableEditingCostItemQuantity, @@ -63,6 +64,7 @@ classes = ( operator.LoadScheduleOfRates, operator.ExpandCostItemRate, operator.ContractCostItemRate, + operator.CalculateCostItemResourceValue, prop.CostItem, prop.CostItemQuantity, prop.CostItemType, diff --git a/src/blenderbim/blenderbim/bim/module/cost/operator.py b/src/blenderbim/blenderbim/bim/module/cost/operator.py index 96bd5cd595..4d08f98010 100644 --- a/src/blenderbim/blenderbim/bim/module/cost/operator.py +++ b/src/blenderbim/blenderbim/bim/module/cost/operator.py @@ -27,6 +27,7 @@ from blenderbim.bim.ifc import IfcStore from bpy_extras.io_utils import ImportHelper from ifcopenshell.api.cost.data import Data from ifcopenshell.api.unit.data import Data as UnitData +from ifcopenshell.api.resource.data import Data as ResourceData class AddCostSchedule(bpy.types.Operator): @@ -590,20 +591,27 @@ class AddCostValue(bpy.types.Operator): def _execute(self, context): self.file = IfcStore.get_file() + parent = self.file.by_id(self.parent) if self.cost_type == "FIXED": category = None + attributes = {"AppliedValue": 0.0} elif self.cost_type == "SUM": category = "*" + attributes = {"Category": category} elif self.cost_type == "CATEGORY": category = self.cost_category - value = ifcopenshell.api.run("cost.add_cost_value", self.file, parent=self.file.by_id(self.parent)) - ifcopenshell.api.run("cost.edit_cost_value", self.file, cost_value=value, attributes={"Category": category}) - Data.load(self.file) + attributes = {"Category": category} + value = ifcopenshell.api.run("cost.add_cost_value", self.file, parent=parent) + ifcopenshell.api.run("cost.edit_cost_value", self.file, cost_value=value, attributes=attributes) + if parent.is_a("IfcConstructionResource"): + ResourceData.load(self.file) + else: + Data.load(self.file) return {"FINISHED"} class RemoveCostItemValue(bpy.types.Operator): - bl_idname = "bim.remove_cost_item_value" + bl_idname = "bim.remove_cost_value" bl_label = "Add Cost Item Value" bl_options = {"REGISTER", "UNDO"} parent: bpy.props.IntProperty() @@ -614,13 +622,17 @@ class RemoveCostItemValue(bpy.types.Operator): def _execute(self, context): self.file = IfcStore.get_file() + parent = self.file.by_id(self.parent) ifcopenshell.api.run( - "cost.remove_cost_item_value", + "cost.remove_cost_value", self.file, - parent=self.file.by_id(self.parent), + parent=parent, cost_value=self.file.by_id(self.cost_value), ) - Data.load(self.file) + if parent.is_a("IfcConstructionResource"): + ResourceData.load(self.file) + else: + Data.load(self.file) return {"FINISHED"} @@ -633,21 +645,25 @@ class EnableEditingCostItemValue(bpy.types.Operator): def execute(self, context): self.props = context.scene.BIMCostProperties self.props.cost_value_attributes.clear() - self.props.active_cost_item_value_id = self.cost_value + self.props.active_cost_value_id = self.cost_value + self.props.cost_value_editing_type = "ATTRIBUTES" data = Data.cost_values[self.cost_value] blenderbim.bim.helper.import_attributes( - data["type"], - self.props.cost_value_attributes, - data, - lambda name, prop, data: self.import_attributes(name, prop, data, context) + data["type"], + self.props.cost_value_attributes, + data, + lambda name, prop, data: self.import_attributes(name, prop, data, context), ) return {"FINISHED"} def import_attributes(self, name, prop, data, context): if name == "AppliedValue": - # TODO: for now, only support simple values + # TODO: for now, only support simple IfcValues (which are effectively IfcMonetaryMeasure) + prop = self.props.cost_value_attributes.add() prop.data_type = "float" + prop.name = "AppliedValue" + prop.is_optional = True prop.float_value = 0.0 if prop.is_null else data[name] return True if ( @@ -699,13 +715,51 @@ class DisableEditingCostItemValue(bpy.types.Operator): def execute(self, context): props = context.scene.BIMCostProperties - props.active_cost_item_value_id = 0 + props.active_cost_value_id = 0 + props.cost_value_editing_type = "" return {"FINISHED"} -class EditCostValue(bpy.types.Operator): +class EnableEditingCostItemValueFormula(bpy.types.Operator): + bl_idname = "bim.enable_editing_cost_item_value_formula" + bl_label = "Enable Editing Cost Item Value Formula" + bl_options = {"REGISTER", "UNDO"} + cost_value: bpy.props.IntProperty() + + def execute(self, context): + self.props = context.scene.BIMCostProperties + self.props.cost_value_attributes.clear() + self.props.active_cost_value_id = self.cost_value + self.props.cost_value_editing_type = "FORMULA" + self.props.cost_value_formula = Data.cost_values[self.cost_value]["Formula"] + return {"FINISHED"} + + +class EditCostItemValueFormula(bpy.types.Operator): + bl_idname = "bim.edit_cost_value_formula" + bl_label = "Edit Cost Value Formula" + bl_options = {"REGISTER", "UNDO"} + cost_value: bpy.props.IntProperty() + + def execute(self, context): + return IfcStore.execute_ifc_operator(self, context) + + def _execute(self, context): + props = context.scene.BIMCostProperties + self.file = IfcStore.get_file() + ifcopenshell.api.run( + "cost.edit_cost_value_formula", + self.file, + **{"cost_value": self.file.by_id(self.cost_value), "formula": props.cost_value_formula}, + ) + Data.load(IfcStore.get_file()) + bpy.ops.bim.disable_editing_cost_item_value() + return {"FINISHED"} + + +class EditCostItemValue(bpy.types.Operator): bl_idname = "bim.edit_cost_value" - bl_label = "Edit Cost Item Value" + bl_label = "Edit Cost Value" bl_options = {"REGISTER", "UNDO"} cost_value: bpy.props.IntProperty() @@ -715,8 +769,8 @@ class EditCostValue(bpy.types.Operator): def _execute(self, context): props = context.scene.BIMCostProperties attributes = blenderbim.bim.helper.export_attributes( - props.cost_value_attributes, - lambda attributes, prop: self.export_attributes(attributes, prop, context)) + props.cost_value_attributes, lambda attributes, prop: self.export_attributes(attributes, prop, context) + ) self.file = IfcStore.get_file() ifcopenshell.api.run( "cost.edit_cost_value", @@ -1000,3 +1054,21 @@ class ContractCostItemRate(bpy.types.Operator): props.contracted_cost_item_rates = json.dumps(contracted_cost_item_rates) bpy.ops.bim.load_schedule_of_rates(cost_schedule=int(props.schedule_of_rates)) return {"FINISHED"} + + +class CalculateCostItemResourceValue(bpy.types.Operator): + bl_idname = "bim.calculate_cost_item_resource_value" + bl_label = "Calculate Cost Item Resource Value" + bl_options = {"REGISTER", "UNDO"} + cost_item: bpy.props.IntProperty() + + def execute(self, context): + return IfcStore.execute_ifc_operator(self, context) + + def _execute(self, context): + self.file = IfcStore.get_file() + ifcopenshell.api.run( + "cost.calculate_cost_item_resource_value", self.file, cost_item=self.file.by_id(self.cost_item) + ) + Data.load(self.file) + return {"FINISHED"} diff --git a/src/blenderbim/blenderbim/bim/module/cost/prop.py b/src/blenderbim/blenderbim/bim/module/cost/prop.py index 94d05993b2..69ac4f825f 100644 --- a/src/blenderbim/blenderbim/bim/module/cost/prop.py +++ b/src/blenderbim/blenderbim/bim/module/cost/prop.py @@ -79,7 +79,7 @@ def update_schedule_of_rates(self, context): bpy.ops.bim.load_schedule_of_rates(cost_schedule=int(self.schedule_of_rates)) -def getQuantityTypes(self, context): +def get_quantity_types(self, context): global quantitytypes_enum if len(quantitytypes_enum) == 0 and IfcStore.get_schema(): quantitytypes_enum.extend( @@ -91,7 +91,7 @@ def getQuantityTypes(self, context): return quantitytypes_enum -def getProductQuantityNames(self, context): +def get_product_quantity_names(self, context): global productquantitynames_enum global productquantitynames_count ifc_file = IfcStore.get_file() @@ -115,7 +115,7 @@ def getProductQuantityNames(self, context): return productquantitynames_enum -def getProcessQuantityNames(self, context): +def get_process_quantity_names(self, context): global processquantitynames_enum global processquantitynames_id ifc_file = IfcStore.get_file() @@ -137,7 +137,7 @@ def getProcessQuantityNames(self, context): return processquantitynames_enum -def getResourceQuantityNames(self, context): +def get_resource_quantity_names(self, context): global resourcequantitynames_enum global resourcequantitynames_id ifc_file = IfcStore.get_file() @@ -166,7 +166,7 @@ def update_active_cost_item_index(self, context): bpy.ops.bim.load_cost_item_quantities() -def updateCostItemIdentification(self, context): +def update_cost_item_identification(self, context): props = context.scene.BIMCostProperties if not props.is_cost_update_enabled or self.identification == "XXX": return @@ -182,7 +182,7 @@ def updateCostItemIdentification(self, context): attribute.string_value = self.identification -def updateCostItemName(self, context): +def update_cost_item_name(self, context): props = context.scene.BIMCostProperties if not props.is_cost_update_enabled or self.name == "Unnamed": return @@ -199,8 +199,8 @@ def updateCostItemName(self, context): class CostItem(PropertyGroup): - name: StringProperty(name="Name", update=updateCostItemName) - identification: StringProperty(name="Identification", update=updateCostItemIdentification) + name: StringProperty(name="Name", update=update_cost_item_name) + identification: StringProperty(name="Identification", update=update_cost_item_identification) ifc_definition_id: IntProperty(name="IFC Definition ID") has_children: BoolProperty(name="Has Children") is_expanded: BoolProperty(name="Is Expanded") @@ -229,10 +229,10 @@ class BIMCostProperties(PropertyGroup): active_cost_item_index: IntProperty(name="Active Cost Item Index", update=update_active_cost_item_index) cost_item_attributes: CollectionProperty(name="Task Attributes", type=Attribute) contracted_cost_items: StringProperty(name="Contracted Cost Items", default="[]") - quantity_types: EnumProperty(items=getQuantityTypes, name="Quantity Types") - product_quantity_names: EnumProperty(items=getProductQuantityNames, name="Product Quantity Names") - process_quantity_names: EnumProperty(items=getProcessQuantityNames, name="Process Quantity Names") - resource_quantity_names: EnumProperty(items=getResourceQuantityNames, name="Resource Quantity Names") + quantity_types: EnumProperty(items=get_quantity_types, name="Quantity Types") + product_quantity_names: EnumProperty(items=get_product_quantity_names, name="Product Quantity Names") + process_quantity_names: EnumProperty(items=get_process_quantity_names, name="Process Quantity Names") + resource_quantity_names: EnumProperty(items=get_resource_quantity_names, name="Resource Quantity Names") active_cost_item_quantity_id: IntProperty(name="Active Cost Item Quantity Id") quantity_attributes: CollectionProperty(name="Quantity Attributes", type=Attribute) cost_types: EnumProperty( @@ -244,8 +244,10 @@ class BIMCostProperties(PropertyGroup): name="Cost Types", ) cost_category: StringProperty(name="Cost Category") - active_cost_item_value_id: IntProperty(name="Active Cost Item Value Id") + active_cost_value_id: IntProperty(name="Active Cost Item Value Id") + cost_value_editing_type: StringProperty(name="Cost Value Editing Type") cost_value_attributes: CollectionProperty(name="Cost Value Attributes", type=Attribute) + cost_value_formula: StringProperty(name="Cost Value Formula") cost_column: StringProperty(name="Cost Column") should_show_column_ui: BoolProperty(name="Should Show Column UI", default=False) columns: CollectionProperty(name="Columns", type=StrProperty) diff --git a/src/blenderbim/blenderbim/bim/module/cost/ui.py b/src/blenderbim/blenderbim/bim/module/cost/ui.py index 12d5deaecc..71f060ccf4 100644 --- a/src/blenderbim/blenderbim/bim/module/cost/ui.py +++ b/src/blenderbim/blenderbim/bim/module/cost/ui.py @@ -16,10 +16,10 @@ # You should have received a copy of the GNU General Public License # along with BlenderBIM Add-on. If not, see . +import blenderbim.bim.helper import blenderbim.bim.module.cost.prop as CostProp from bpy.types import Panel, UIList from blenderbim.bim.ifc import IfcStore -from blenderbim.bim.helper import draw_attributes from ifcopenshell.api.cost.data import Data from ifcopenshell.api.unit.data import Data as UnitData @@ -94,7 +94,7 @@ class BIM_PT_cost_schedules(Panel): self.layout.template_list("BIM_UL_cost_columns", "", self.props, "columns", self.props, "active_column_index") def draw_editable_cost_schedule_ui(self): - draw_attributes(self.props.cost_schedule_attributes, self.layout) + blenderbim.bim.helper.draw_attributes(self.props.cost_schedule_attributes, self.layout) def draw_editable_cost_item_ui(self, cost_schedule_id): row = self.layout.row(align=True) @@ -140,7 +140,7 @@ class BIM_PT_cost_schedules(Panel): self.draw_editable_cost_item_values_ui() def draw_editable_cost_item_attributes_ui(self): - draw_attributes(self.props.cost_item_attributes, self.layout) + blenderbim.bim.helper.draw_attributes(self.props.cost_item_attributes, self.layout) def draw_editable_cost_item_quantities_ui(self): row = self.layout.row(align=True) @@ -175,7 +175,7 @@ class BIM_PT_cost_schedules(Panel): self.draw_editable_cost_item_quantity_ui(box) def draw_editable_cost_item_quantity_ui(self, layout): - draw_attributes(self.props.quantity_attributes, self.layout) + blenderbim.bim.helper.draw_attributes(self.props.quantity_attributes, self.layout) def draw_editable_cost_item_values_ui(self): row = self.layout.row(align=True) @@ -192,68 +192,46 @@ class BIM_PT_cost_schedules(Panel): row = self.layout.row(align=True) self.draw_readonly_cost_value_ui(row, cost_value_id) - if self.props.active_cost_item_value_id: + if self.props.cost_value_editing_type == "ATTRIBUTES": box = self.layout.box() - self.draw_editable_cost_value_ui(box, Data.cost_values[self.props.active_cost_item_value_id]) + self.draw_editable_cost_value_ui(box, Data.cost_values[self.props.active_cost_value_id]) def draw_readonly_cost_value_ui(self, layout, cost_value_id): - # This UI is really poor. Delete and start again. cost_value = Data.cost_values[cost_value_id] - cost_value_label = "{0:.2f}".format(cost_value["AppliedValue"]) - if cost_value["Category"]: - cost_value_label += " ({})".format(cost_value["Category"]) - layout.label(text="", icon="DISC") + + if self.props.active_cost_value_id == cost_value_id and self.props.cost_value_editing_type == "FORMULA": + layout.prop(self.props, "cost_value_formula", text="") + else: + cost_value_label = "{0:.2f}".format(cost_value["AppliedValue"]) + cost_value_label += " = " + cost_value["Formula"] + layout.label(text=cost_value_label, icon="DISC") + self.draw_cost_value_operator_ui(layout, cost_value_id, self.props.active_cost_item_id) - layout.label(text=cost_value_label) - - for component_id in cost_value["Components"] or []: - self.draw_readonly_component_cost_value_ui(layout, component_id, cost_value["id"]) - - def draw_readonly_component_cost_value_ui(self, layout, cost_value_id, parent_id, level=1): - self.draw_cost_value_operator_ui(layout, cost_value_id, parent_id) - cost_value = Data.cost_values[cost_value_id] - cost_value_label = ">" * level - cost_value_label += "{0:.2f}".format(cost_value["AppliedValue"]) - if cost_value["Category"]: - cost_value_label += " ({})".format(cost_value["Category"]) - layout.label(text=cost_value_label) - - for component_id in cost_value["Components"] or []: - self.draw_readonly_component_cost_value_ui(layout, component_id, cost_value["id"], level + 1) def draw_cost_value_operator_ui(self, layout, cost_value_id, parent_id): - if self.props.active_cost_item_value_id and self.props.active_cost_item_value_id == cost_value_id: - op = layout.operator("bim.edit_cost_value", text="", icon="CHECKMARK") - op.cost_value = cost_value_id - op = layout.operator("bim.add_cost_value", text="", icon="ADD") - op.parent = cost_value_id - op.cost_type = self.props.cost_types - if self.props.cost_types == "CATEGORY": - op.cost_category = self.props.cost_category + if self.props.active_cost_value_id and self.props.active_cost_value_id == cost_value_id: + if self.props.cost_value_editing_type == "ATTRIBUTES": + op = layout.operator("bim.edit_cost_value", text="", icon="CHECKMARK") + op.cost_value = cost_value_id + elif self.props.cost_value_editing_type == "FORMULA": + op = layout.operator("bim.edit_cost_value_formula", text="", icon="CHECKMARK") + op.cost_value = cost_value_id layout.operator("bim.disable_editing_cost_item_value", text="", icon="CANCEL") - elif self.props.active_cost_item_value_id: - op = layout.operator("bim.add_cost_value", text="", icon="ADD") - op.parent = cost_value_id - op.cost_type = self.props.cost_types - if self.props.cost_types == "CATEGORY": - op.cost_category = self.props.cost_category - op = layout.operator("bim.remove_cost_item_value", text="", icon="X") + elif self.props.active_cost_value_id: + op = layout.operator("bim.remove_cost_value", text="", icon="X") op.parent = parent_id op.cost_value = cost_value_id else: + op = layout.operator("bim.enable_editing_cost_item_value_formula", text="", icon="CON_TRANSLIKE") + op.cost_value = cost_value_id op = layout.operator("bim.enable_editing_cost_item_value", text="", icon="GREASEPENCIL") op.cost_value = cost_value_id - op = layout.operator("bim.add_cost_value", text="", icon="ADD") - op.parent = cost_value_id - op.cost_type = self.props.cost_types - if self.props.cost_types == "CATEGORY": - op.cost_category = self.props.cost_category - op = layout.operator("bim.remove_cost_item_value", text="", icon="X") + op = layout.operator("bim.remove_cost_value", text="", icon="X") op.parent = parent_id op.cost_value = cost_value_id def draw_editable_cost_value_ui(self, layout, cost_value): - draw_attributes(self.props.cost_value_attributes, layout) + blenderbim.bim.helper.draw_attributes(self.props.cost_value_attributes, layout) class BIM_PT_cost_item_types(Panel): @@ -373,11 +351,29 @@ class BIM_PT_cost_item_quantities(Panel): # Column1 col = grid.column() + has_quantity_names = CostProp.get_product_quantity_names(self, context) + row2 = col.row(align=True) row2.label(text="Elements") op = row2.operator("bim.select_cost_item_products", icon="RESTRICT_SELECT_OFF", text="") op.cost_item = cost_item.ifc_definition_id + if context.selected_objects: + if has_quantity_names: + op = row2.operator("bim.assign_cost_item_quantity", text="", icon="PROPERTIES") + op.related_object_type = "PRODUCT" + op.cost_item = cost_item.ifc_definition_id + op.prop_name = self.props.product_quantity_names + + op = row2.operator("bim.assign_cost_item_quantity", text="", icon="ADD") + op.related_object_type = "PRODUCT" + op.cost_item = cost_item.ifc_definition_id + op.prop_name = "" + + op = row2.operator("bim.unassign_cost_item_quantity", text="", icon="REMOVE") + op.cost_item = cost_item.ifc_definition_id + op.related_object = 0 + row2 = col.row() row2.template_list( "BIM_UL_cost_item_quantities", @@ -388,23 +384,32 @@ class BIM_PT_cost_item_quantities(Panel): "active_cost_item_product_index", ) - row2 = col.row(align=True) - row2.prop(self.props, "product_quantity_names", text="") - op = row2.operator("bim.unassign_cost_item_quantity", text="", icon="REMOVE") - op.cost_item = cost_item.ifc_definition_id - op.related_object = 0 - if CostProp.productquantitynames_enum: - op = row2.operator("bim.assign_cost_item_quantity", text="", icon="ADD") - op.related_object_type = "PRODUCT" - op.cost_item = cost_item.ifc_definition_id - op.prop_name = self.props.product_quantity_names + if has_quantity_names: + row2 = col.row() + row2.prop(self.props, "product_quantity_names", text="") # Column2 col = grid.column() + has_quantity_names = CostProp.get_process_quantity_names(self, context) + row2 = col.row(align=True) row2.label(text="Tasks") + tprops = context.scene.BIMTaskTreeProperties + wprops = context.scene.BIMWorkScheduleProperties + if tprops.tasks and wprops.active_task_index < len(tprops.tasks): + if has_quantity_names: + op = row2.operator("bim.assign_cost_item_quantity", text="", icon="PROPERTIES") + op.related_object_type = "PROCESS" + op.cost_item = cost_item.ifc_definition_id + op.prop_name = self.props.process_quantity_names + + op = row2.operator("bim.assign_cost_item_quantity", text="", icon="ADD") + op.related_object_type = "PROCESS" + op.cost_item = cost_item.ifc_definition_id + op.prop_name = "" + row2 = col.row() row2.template_list( "BIM_UL_cost_item_quantities", @@ -415,20 +420,35 @@ class BIM_PT_cost_item_quantities(Panel): "active_cost_item_process_index", ) - row2 = col.row(align=True) - row2.prop(self.props, "process_quantity_names", text="") - if CostProp.processquantitynames_enum: - op = row2.operator("bim.assign_cost_item_quantity", text="", icon="ADD") - op.related_object_type = "PROCESS" - op.cost_item = cost_item.ifc_definition_id - op.prop_name = self.props.process_quantity_names + if has_quantity_names: + row2 = col.row() + row2.prop(self.props, "process_quantity_names", text="") # Column3 col = grid.column() + has_quantity_names = CostProp.get_resource_quantity_names(self, context) + row2 = col.row(align=True) row2.label(text="Resources") + op = row2.operator("bim.calculate_cost_item_resource_value", text="", icon="DISC") + op.cost_item = cost_item.ifc_definition_id + + rtprops = context.scene.BIMResourceTreeProperties + rprops = context.scene.BIMResourceProperties + if rtprops.resources and rprops.active_resource_index < len(rtprops.resources): + if has_quantity_names: + op = row2.operator("bim.assign_cost_item_quantity", text="", icon="PROPERTIES") + op.related_object_type = "RESOURCE" + op.cost_item = cost_item.ifc_definition_id + op.prop_name = self.props.resource_quantity_names + + op = row2.operator("bim.assign_cost_item_quantity", text="", icon="ADD") + op.related_object_type = "RESOURCE" + op.cost_item = cost_item.ifc_definition_id + op.prop_name = "" + row2 = col.row() row2.template_list( "BIM_UL_cost_item_quantities", @@ -439,13 +459,9 @@ class BIM_PT_cost_item_quantities(Panel): "active_cost_item_resource_index", ) - row2 = col.row(align=True) - row2.prop(self.props, "resource_quantity_names", text="") - if CostProp.resourcequantitynames_enum: - op = row2.operator("bim.assign_cost_item_quantity", text="", icon="ADD") - op.related_object_type = "RESOURCE" - op.cost_item = cost_item.ifc_definition_id - op.prop_name = self.props.resource_quantity_names + if has_quantity_names: + row2 = col.row() + row2.prop(self.props, "resource_quantity_names", text="") class BIM_PT_cost_item_rates(Panel): diff --git a/src/blenderbim/blenderbim/bim/module/covetool/__init__.py b/src/blenderbim/blenderbim/bim/module/covetool/__init__.py index 837f9b8382..5a51292ed5 100644 --- a/src/blenderbim/blenderbim/bim/module/covetool/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/covetool/__init__.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/module/covetool/api.py b/src/blenderbim/blenderbim/bim/module/covetool/api.py index d52e332b09..cff6937d26 100644 --- a/src/blenderbim/blenderbim/bim/module/covetool/api.py +++ b/src/blenderbim/blenderbim/bim/module/covetool/api.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/module/covetool/operator.py b/src/blenderbim/blenderbim/bim/module/covetool/operator.py index 10820227c6..e035063b9e 100644 --- a/src/blenderbim/blenderbim/bim/module/covetool/operator.py +++ b/src/blenderbim/blenderbim/bim/module/covetool/operator.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/module/covetool/prop.py b/src/blenderbim/blenderbim/bim/module/covetool/prop.py index 018cac6566..68ad2188a0 100644 --- a/src/blenderbim/blenderbim/bim/module/covetool/prop.py +++ b/src/blenderbim/blenderbim/bim/module/covetool/prop.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/module/covetool/ui.py b/src/blenderbim/blenderbim/bim/module/covetool/ui.py index d5a91d578c..e67e654caf 100644 --- a/src/blenderbim/blenderbim/bim/module/covetool/ui.py +++ b/src/blenderbim/blenderbim/bim/module/covetool/ui.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/module/csv/__init__.py b/src/blenderbim/blenderbim/bim/module/csv/__init__.py index 24722c9420..238b4698cb 100644 --- a/src/blenderbim/blenderbim/bim/module/csv/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/csv/__init__.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -32,11 +31,9 @@ classes = ( ) - def register(): bpy.types.Scene.CsvProperties = bpy.props.PointerProperty(type=prop.CsvProperties) def unregister(): del bpy.types.Scene.CsvProperties - diff --git a/src/blenderbim/blenderbim/bim/module/csv/operator.py b/src/blenderbim/blenderbim/bim/module/csv/operator.py index e9f9c0739c..e5f248821c 100644 --- a/src/blenderbim/blenderbim/bim/module/csv/operator.py +++ b/src/blenderbim/blenderbim/bim/module/csv/operator.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/module/csv/prop.py b/src/blenderbim/blenderbim/bim/module/csv/prop.py index f1e0dc93f0..39418d754c 100644 --- a/src/blenderbim/blenderbim/bim/module/csv/prop.py +++ b/src/blenderbim/blenderbim/bim/module/csv/prop.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/module/csv/ui.py b/src/blenderbim/blenderbim/bim/module/csv/ui.py index 347877f616..819401c612 100644 --- a/src/blenderbim/blenderbim/bim/module/csv/ui.py +++ b/src/blenderbim/blenderbim/bim/module/csv/ui.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/module/debug/__init__.py b/src/blenderbim/blenderbim/bim/module/debug/__init__.py index 9406090275..11c6a2d54d 100644 --- a/src/blenderbim/blenderbim/bim/module/debug/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/debug/__init__.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/module/debug/operator.py b/src/blenderbim/blenderbim/bim/module/debug/operator.py index 7ef5b31b99..203371d748 100644 --- a/src/blenderbim/blenderbim/bim/module/debug/operator.py +++ b/src/blenderbim/blenderbim/bim/module/debug/operator.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -18,6 +17,7 @@ # along with BlenderBIM Add-on. If not, see . import bpy +import time import logging import ifcopenshell import ifcopenshell.util.placement @@ -88,10 +88,7 @@ class ProfileImportIFC(bpy.types.Operator): import cProfile import pstats - # For Windows - filepath = context.scene.BIMProperties.ifc_file.replace("\\", "\\\\") - - cProfile.run(f"import bpy; bpy.ops.import_ifc.bim(filepath='{filepath}')", "blender.prof") + cProfile.run("import bpy; bpy.ops.bim.load_project_elements()", "blender.prof") p = pstats.Stats("blender.prof") p.sort_stats("cumulative").print_stats(50) return {"FINISHED"} @@ -116,9 +113,16 @@ class CreateAllShapes(bpy.types.Operator): if element.GlobalId in excludes: continue print(f"{i}/{total}:", element) + start = time.time() try: shape = ifcopenshell.geom.create_shape(settings, element) - print("Success", len(shape.geometry.verts), len(shape.geometry.edges), len(shape.geometry.faces)) + print( + "Success", + time.time() - start, + len(shape.geometry.verts), + len(shape.geometry.edges), + len(shape.geometry.faces), + ) except: failures.append(element) print("***** FAILURE *****") @@ -164,9 +168,11 @@ class SelectHighPolygonMeshes(bpy.types.Operator): bl_options = {"REGISTER", "UNDO"} def execute(self, context): - [o.select_set(True) for o in context.view_layer.objects - if o.type == "MESH" - and len(o.data.polygons) > context.scene.BIMDebugProperties.number_of_polygons] + [ + o.select_set(True) + for o in context.view_layer.objects + if o.type == "MESH" and len(o.data.polygons) > context.scene.BIMDebugProperties.number_of_polygons + ] return {"FINISHED"} diff --git a/src/blenderbim/blenderbim/bim/module/debug/prop.py b/src/blenderbim/blenderbim/bim/module/debug/prop.py index 49369f4ff0..2257fa1e52 100644 --- a/src/blenderbim/blenderbim/bim/module/debug/prop.py +++ b/src/blenderbim/blenderbim/bim/module/debug/prop.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/module/debug/ui.py b/src/blenderbim/blenderbim/bim/module/debug/ui.py index 68ce199db7..ea14629b81 100644 --- a/src/blenderbim/blenderbim/bim/module/debug/ui.py +++ b/src/blenderbim/blenderbim/bim/module/debug/ui.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/module/diff/__init__.py b/src/blenderbim/blenderbim/bim/module/diff/__init__.py index a296d69a9e..02c9fbda20 100644 --- a/src/blenderbim/blenderbim/bim/module/diff/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/diff/__init__.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -31,11 +30,9 @@ classes = ( ) - def register(): bpy.types.Scene.DiffProperties = bpy.props.PointerProperty(type=prop.DiffProperties) def unregister(): del bpy.types.Scene.DiffProperties - diff --git a/src/blenderbim/blenderbim/bim/module/diff/operator.py b/src/blenderbim/blenderbim/bim/module/diff/operator.py index 8befc8e1c2..574c79a1b7 100644 --- a/src/blenderbim/blenderbim/bim/module/diff/operator.py +++ b/src/blenderbim/blenderbim/bim/module/diff/operator.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -24,7 +23,6 @@ import json from blenderbim.bim.ifc import IfcStore - class SelectDiffJsonFile(bpy.types.Operator): bl_idname = "bim.select_diff_json_file" bl_label = "Select Diff JSON File" @@ -46,8 +44,8 @@ class VisualiseDiff(bpy.types.Operator): bl_options = {"REGISTER", "UNDO"} def execute(self, context): - #ifc_file = IfcStore.get_file() # In case we get from Store - ifc_file = ifcopenshell.open(context.scene.DiffProperties.diff_new_file) # for Now refer to the new file + # ifc_file = IfcStore.get_file() # In case we get from Store + ifc_file = ifcopenshell.open(context.scene.DiffProperties.diff_new_file) # for Now refer to the new file with open(context.scene.DiffProperties.diff_json_file, "r") as file: diff = json.load(file) for obj in context.visible_objects: diff --git a/src/blenderbim/blenderbim/bim/module/diff/prop.py b/src/blenderbim/blenderbim/bim/module/diff/prop.py index 6233a02a9a..1ae467c960 100644 --- a/src/blenderbim/blenderbim/bim/module/diff/prop.py +++ b/src/blenderbim/blenderbim/bim/module/diff/prop.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/module/diff/ui.py b/src/blenderbim/blenderbim/bim/module/diff/ui.py index 9cfbd3efde..525455329c 100644 --- a/src/blenderbim/blenderbim/bim/module/diff/ui.py +++ b/src/blenderbim/blenderbim/bim/module/diff/ui.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/module/document/__init__.py b/src/blenderbim/blenderbim/bim/module/document/__init__.py index 6d1a6ab13e..edfe7a60ef 100644 --- a/src/blenderbim/blenderbim/bim/module/document/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/document/__init__.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/module/document/operator.py b/src/blenderbim/blenderbim/bim/module/document/operator.py index 1d9ed2929d..a81a053524 100644 --- a/src/blenderbim/blenderbim/bim/module/document/operator.py +++ b/src/blenderbim/blenderbim/bim/module/document/operator.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -276,17 +275,21 @@ class AssignDocument(bpy.types.Operator): return IfcStore.execute_ifc_operator(self, context) def _execute(self, context): - obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object self.file = IfcStore.get_file() - ifcopenshell.api.run( - "document.assign_document", - self.file, - **{ - "product": self.file.by_id(obj.BIMObjectProperties.ifc_definition_id), - "document": self.file.by_id(self.document), - } - ) - Data.load(IfcStore.get_file(), obj.BIMObjectProperties.ifc_definition_id) + objs = [bpy.data.objects.get(self.obj)] if self.obj else context.selected_objects + for obj in objs: + obj_id = obj.BIMObjectProperties.ifc_definition_id + if not obj_id: + continue + ifcopenshell.api.run( + "document.assign_document", + self.file, + **{ + "product": self.file.by_id(obj_id), + "document": self.file.by_id(self.document), + } + ) + Data.load(self.file, obj_id) return {"FINISHED"} @@ -301,15 +304,19 @@ class UnassignDocument(bpy.types.Operator): return IfcStore.execute_ifc_operator(self, context) def _execute(self, context): - obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object self.file = IfcStore.get_file() - ifcopenshell.api.run( - "document.unassign_document", - self.file, - **{ - "product": self.file.by_id(obj.BIMObjectProperties.ifc_definition_id), - "document": self.file.by_id(self.document), - } - ) - Data.load(IfcStore.get_file(), obj.BIMObjectProperties.ifc_definition_id) + objs = [bpy.data.objects.get(self.obj)] if self.obj else context.selected_objects + for obj in objs: + obj_id = obj.BIMObjectProperties.ifc_definition_id + if not obj_id: + continue + ifcopenshell.api.run( + "document.unassign_document", + self.file, + **{ + "product": self.file.by_id(obj_id), + "document": self.file.by_id(self.document), + } + ) + Data.load(self.file, obj_id) return {"FINISHED"} diff --git a/src/blenderbim/blenderbim/bim/module/document/prop.py b/src/blenderbim/blenderbim/bim/module/document/prop.py index e93fc291d9..a470e715ae 100644 --- a/src/blenderbim/blenderbim/bim/module/document/prop.py +++ b/src/blenderbim/blenderbim/bim/module/document/prop.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/module/document/ui.py b/src/blenderbim/blenderbim/bim/module/document/ui.py index e13e2fcb95..2eb5e8b482 100644 --- a/src/blenderbim/blenderbim/bim/module/document/ui.py +++ b/src/blenderbim/blenderbim/bim/module/document/ui.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/module/drawing/__init__.py b/src/blenderbim/blenderbim/bim/module/drawing/__init__.py index a100f5fc1a..b24d93f953 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/__init__.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/module/drawing/annotation.py b/src/blenderbim/blenderbim/bim/module/drawing/annotation.py index 8eaffe09f8..085be95aa1 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/annotation.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/annotation.py @@ -1,6 +1,5 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on -# Copyright (C) 2020, 2021 Dion Moult +# Copyright (C) 2020, 2021 Maxim Vasilyev # # This file is part of BlenderBIM Add-on. # @@ -171,16 +170,14 @@ class Annotator: camera = context.scene.camera z_offset = camera.matrix_world.to_quaternion() @ Vector((0, 0, -1)) if context.scene.render.resolution_x > context.scene.render.resolution_y: - y = ( - camera.data.ortho_scale - * (context.scene.render.resolution_y / context.scene.render.resolution_x) - / 4 - ) + y = camera.data.ortho_scale * (context.scene.render.resolution_y / context.scene.render.resolution_x) / 4 else: y = camera.data.ortho_scale / 4 y_offset = camera.matrix_world.to_quaternion() @ Vector((0, y, 0)) - x_offset = camera.matrix_world.to_quaternion() @ Vector((y/2, 0, 0)) - return (camera.location + z_offset, - camera.location + z_offset + y_offset, - camera.location + z_offset + x_offset, - camera.location + z_offset + x_offset + y_offset) + x_offset = camera.matrix_world.to_quaternion() @ Vector((y / 2, 0, 0)) + return ( + camera.location + z_offset, + camera.location + z_offset + y_offset, + camera.location + z_offset + x_offset, + camera.location + z_offset + x_offset + y_offset, + ) diff --git a/src/blenderbim/blenderbim/bim/module/drawing/decoration.py b/src/blenderbim/blenderbim/bim/module/drawing/decoration.py index 219d52c3fb..1e17b5e9f9 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/decoration.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/decoration.py @@ -1,6 +1,5 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on -# Copyright (C) 2020, 2021 Dion Moult +# Copyright (C) 2020, 2021 Maxim Vasilyev # # This file is part of BlenderBIM Add-on. # @@ -35,7 +34,7 @@ from gpu_extras.batch import batch_for_shader import blenderbim.bim.module.drawing.helper as helper -class BaseDecorator(): +class BaseDecorator: # base name of objects to decorate basename = "IfcAnnotation/Something" @@ -128,10 +127,12 @@ class BaseDecorator(): def __init__(self): # NB: libcode param doesn't work - self.shader = GPUShader(vertexcode=self.VERT_GLSL, - fragcode=self.FRAG_GLSL, - geocode=self.LIB_GLSL + self.GEOM_GLSL, - defines=self.DEF_GLSL) + self.shader = GPUShader( + vertexcode=self.VERT_GLSL, + fragcode=self.FRAG_GLSL, + geocode=self.LIB_GLSL + self.GEOM_GLSL, + defines=self.DEF_GLSL, + ) def get_objects(self, collection): """find relevant objects @@ -172,7 +173,7 @@ class BaseDecorator(): topology.append(1) topology.extend([0] * max(0, cnt - 2)) topology.append(2) - indices.extend((idx+i, idx+i+1) for i in range(cnt-1)) + indices.extend((idx + i, idx + i + 1) for i in range(cnt - 1)) idx += cnt return vertices, indices, topology @@ -192,8 +193,7 @@ class BaseDecorator(): return vertices, indices def get_editmesh_geom(self, obj): - """Parses editmode mesh geometry into line segments - """ + """Parses editmode mesh geometry into line segments""" mesh = bmesh.from_edit_mesh(obj.data) vertices = [] indices = [] @@ -201,7 +201,7 @@ class BaseDecorator(): for edge in mesh.edges: vertices.extend(edge.verts) - indices.append((idx, idx+1)) + indices.append((idx, idx + 1)) idx += 2 vertices = [obj.matrix_world @ v.co for v in vertices] @@ -217,18 +217,18 @@ class BaseDecorator(): color = context.scene.DocProperties.decorations_colour fmt = GPUVertFormat() - fmt.attr_add(id="pos", comp_type='F32', len=3, fetch_mode='FLOAT') + fmt.attr_add(id="pos", comp_type="F32", len=3, fetch_mode="FLOAT") if topology: - fmt.attr_add(id="topo", comp_type='U8', len=1, fetch_mode='INT') + fmt.attr_add(id="topo", comp_type="U8", len=1, fetch_mode="INT") vbo = GPUVertBuf(len=len(vertices), format=fmt) vbo.attr_fill(id="pos", data=vertices) if topology: vbo.attr_fill(id="topo", data=topology) - ibo = GPUIndexBuf(type='LINES', seq=indices) + ibo = GPUIndexBuf(type="LINES", seq=indices) - batch = GPUBatch(type='LINES', buf=vbo, elem=ibo) + batch = GPUBatch(type="LINES", buf=vbo, elem=ibo) bgl.glEnable(bgl.GL_LINE_SMOOTH) bgl.glHint(bgl.GL_LINE_SMOOTH_HINT, bgl.GL_NICEST) @@ -289,7 +289,7 @@ class BaseDecorator(): def format_value(self, context, value): unit_system = context.scene.unit_settings.system - if unit_system == 'IMPERIAL': + if unit_system == "IMPERIAL": precision = context.scene.BIMProperties.imperial_precision if precision == "NONE": precision = 256 @@ -297,12 +297,12 @@ class BaseDecorator(): precision = 1 elif "/" in precision: precision = int(precision.split("/")[1]) - elif unit_system == 'METRIC': + elif unit_system == "METRIC": precision = 3 else: return - return bpy.utils.units.to_string(unit_system, 'LENGTH', value, precision, split_unit=True) + return bpy.utils.units.to_string(unit_system, "LENGTH", value, precision, split_unit=True) class DimensionDecorator(BaseDecorator): @@ -310,12 +310,16 @@ class DimensionDecorator(BaseDecorator): - each edge of a segment with arrow - puts metric text next to each segment """ + basename = "IfcAnnotation/Dimension" - DEF_GLSL = BaseDecorator.DEF_GLSL + """ + DEF_GLSL = ( + BaseDecorator.DEF_GLSL + + """ #define ARROW_ANGLE PI / 12.0 #define ARROW_SIZE 16.0 """ + ) GEOM_GLSL = """ uniform vec2 winsize; @@ -392,7 +396,7 @@ class DimensionDecorator(BaseDecorator): continue length = (v1 - v0).length text = self.format_value(context, length) - self.draw_label(context, text, p0 + (dir) * .5, dir) + self.draw_label(context, text, p0 + (dir) * 0.5, dir) class EqualityDecorator(DimensionDecorator): @@ -401,6 +405,7 @@ class EqualityDecorator(DimensionDecorator): - augments with arrows on both sides - puts 'EQ' label """ + basename = "IfcAnnotation/Equal" def draw_labels(self, context, obj, vertices, indices): @@ -414,7 +419,7 @@ class EqualityDecorator(DimensionDecorator): dir = p1 - p0 if dir.length < 1: continue - self.draw_label(context, "EQ", p0 + (dir) * .5, dir) + self.draw_label(context, "EQ", p0 + (dir) * 0.5, dir) class LeaderDecorator(BaseDecorator): @@ -422,12 +427,16 @@ class LeaderDecorator(BaseDecorator): - head point with arrow - middle points w/out decorations """ + basename = "IfcAnnotation/Leader" - DEF_GLSL = BaseDecorator.DEF_GLSL + """ + DEF_GLSL = ( + BaseDecorator.DEF_GLSL + + """ #define ARROW_ANGLE PI / 12.0 #define ARROW_SIZE 16.0 """ + ) GEOM_GLSL = """ uniform vec2 winsize; @@ -490,13 +499,17 @@ class StairDecorator(BaseDecorator): - tail point with circle - middle points w/out decorations """ + basename = "IfcAnnotation/Stair" - DEF_GLSL = BaseDecorator.DEF_GLSL + """ + DEF_GLSL = ( + BaseDecorator.DEF_GLSL + + """ #define CIRCLE_SIZE 8.0 #define ARROW_ANGLE PI / 3.0 #define ARROW_SIZE 24.0 """ + ) GEOM_GLSL = """ uniform vec2 winsize; @@ -572,10 +585,13 @@ class StairDecorator(BaseDecorator): class HiddenDecorator(BaseDecorator): basename = "IfcAnnotation/Hidden" - DEF_GLSL = BaseDecorator.DEF_GLSL + """ + DEF_GLSL = ( + BaseDecorator.DEF_GLSL + + """ #define DASH_SIZE 16.0 #define DASH_PATTERN 0x0000FFFFU """ + ) GEOM_GLSL = """ uniform vec2 winsize; @@ -639,7 +655,6 @@ class MiscDecorator(HiddenDecorator): class LevelDecorator(BaseDecorator): - def get_splines(self, obj): """Iterates through splines Args: @@ -664,10 +679,13 @@ class LevelDecorator(BaseDecorator): class PlanLevelDecorator(LevelDecorator): basename = "IfcAnnotation/Plan Level" - DEF_GLSL = BaseDecorator.DEF_GLSL + """ + DEF_GLSL = ( + BaseDecorator.DEF_GLSL + + """ #define CIRCLE_SIZE 8.0 #define CROSS_SIZE 16.0 """ + ) GEOM_GLSL = """ uniform vec2 winsize; @@ -746,10 +764,13 @@ class PlanLevelDecorator(LevelDecorator): class SectionLevelDecorator(LevelDecorator): basename = "IfcAnnotation/Section Level" - DEF_GLSL = BaseDecorator.DEF_GLSL + """ + DEF_GLSL = ( + BaseDecorator.DEF_GLSL + + """ #define CALLOUT_GAP 8.0 #define CALLOUT_SIZE 64.0 """ + ) GEOM_GLSL = """ uniform vec2 winsize; @@ -828,12 +849,16 @@ class BreakDecorator(BaseDecorator): Uses first two vertices in verts list. """ + basename = "IfcAnnotation/Break" - DEF_GLSL = BaseDecorator.DEF_GLSL + """ + DEF_GLSL = ( + BaseDecorator.DEF_GLSL + + """ #define BREAK_LENGTH 32.0 #define BREAK_WIDTH 16.0 """ + ) GEOM_GLSL = """ uniform vec2 winsize; @@ -905,11 +930,14 @@ class BreakDecorator(BaseDecorator): class GridDecorator(BaseDecorator): basename = "IfcGridAxis/" - DEF_GLSL = BaseDecorator.DEF_GLSL + """ + DEF_GLSL = ( + BaseDecorator.DEF_GLSL + + """ #define CIRCLE_SIZE 16.0 #define DASH_SIZE 48.0 #define DASH_PATTERN 0x03C0FFFFU """ + ) GEOM_GLSL = """ uniform vec2 winsize; @@ -1019,11 +1047,14 @@ class GridDecorator(BaseDecorator): class SectionViewDecorator(LevelDecorator): basename = "IfcAnnotation/Section" - DEF_GLSL = BaseDecorator.DEF_GLSL + """ + DEF_GLSL = ( + BaseDecorator.DEF_GLSL + + """ #define CIRCLE_SIZE 8.0 #define TRIANGLE_L 32.0 #define TRIANGLE_W 16.0 """ + ) GEOM_GLSL = """ uniform vec2 winsize; @@ -1118,7 +1149,7 @@ class SectionViewDecorator(LevelDecorator): self.draw_lines(context, obj, verts, [(0, 1)]) -class DecorationsHandler(): +class DecorationsHandler: decorators_classes = [ DimensionDecorator, EqualityDecorator, @@ -1130,7 +1161,7 @@ class DecorationsHandler(): SectionLevelDecorator, StairDecorator, BreakDecorator, - SectionViewDecorator + SectionViewDecorator, ] installed = None @@ -1140,12 +1171,12 @@ class DecorationsHandler(): if cls.installed: cls.uninstall() handler = cls() - cls.installed = SpaceView3D.draw_handler_add(handler, (context,), 'WINDOW', 'POST_PIXEL') + cls.installed = SpaceView3D.draw_handler_add(handler, (context,), "WINDOW", "POST_PIXEL") @classmethod def uninstall(cls): try: - SpaceView3D.draw_handler_remove(cls.installed, 'WINDOW') + SpaceView3D.draw_handler_remove(cls.installed, "WINDOW") except ValueError: pass cls.installed = None diff --git a/src/blenderbim/blenderbim/bim/module/drawing/gizmos.py b/src/blenderbim/blenderbim/bim/module/drawing/gizmos.py index 3f8965fe14..e939713dae 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/gizmos.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/gizmos.py @@ -1,6 +1,5 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on -# Copyright (C) 2020, 2021 Dion Moult +# Copyright (C) 2020, 2021 Maxim Vasilyev # # This file is part of BlenderBIM Add-on. # @@ -26,6 +25,7 @@ from mathutils import Vector, Matrix from mathutils import geometry from bpy_extras import view3d_utils from blenderbim.bim.module.drawing.shaders import DotsGizmoShader, ExtrusionGuidesShader, BaseLinesShader +from ifcopenshell.util.unit import si_conversions """Gizmos under the hood @@ -470,8 +470,6 @@ class ExtrusionWidget(types.GizmoGroup): 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 @@ -487,6 +485,7 @@ class ExtrusionWidget(types.GizmoGroup): basis = target.matrix_world.normalized() theme = ctx.preferences.themes[0].user_interface + scale_value = self.get_scale_value(ctx.scene.unit_settings.system, ctx.scene.unit_settings.length_unit) gz = self.handle = self.gizmos.new("BIM_GT_uglydot_3d") gz.matrix_basis = basis @@ -496,7 +495,7 @@ class ExtrusionWidget(types.GizmoGroup): gz.alpha_highlight = 1.0 gz.use_draw_modal = True gz.target_set_prop("offset", prop, "value") - gz.scale_value = 1000 + gz.scale_value = scale_value gz = self.guides = self.gizmos.new("BIM_GT_extrusion_guides") gz.matrix_basis = basis @@ -504,7 +503,7 @@ class ExtrusionWidget(types.GizmoGroup): gz.alpha = gz.alpha_highlight = 0.5 gz.use_draw_modal = True gz.target_set_prop("depth", prop, "value") - gz.scale_value = 1000 + gz.scale_value = scale_value # gz = self.label = self.gizmos.new('GIZMO_GT_dimension_label') # gz.matrix_basis = basis @@ -523,10 +522,30 @@ class ExtrusionWidget(types.GizmoGroup): 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") + + @staticmethod + def get_scale_value(system, length_unit): + scale_value = 1 + if system == "METRIC": + if length_unit == "KILOMETERS": + scale_value /= 1000 + elif length_unit == "CENTIMETERS": + scale_value *= 100 + elif length_unit == "MILLIMETERS": + scale_value *= 1000 + elif length_unit == "MICROMETERS": + scale_value *= 1000000 + elif system == "IMPERIAL": + if length_unit == "MILES": + scale_value /= si_conversions["mile"] + elif length_unit == "FEET": + scale_value /= si_conversions["foot"] + elif length_unit == "INCHES": + scale_value /= si_conversions["inch"] + elif length_unit == "THOU": + scale_value /= si_conversions["thou"] + return scale_value diff --git a/src/blenderbim/blenderbim/bim/module/drawing/handler.py b/src/blenderbim/blenderbim/bim/module/drawing/handler.py index 1c6fff0512..27ea862264 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/handler.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/handler.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/module/drawing/helper.py b/src/blenderbim/blenderbim/bim/module/drawing/helper.py index e7a1c9ad19..e00ef1a5f6 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/helper.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/helper.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/module/drawing/operator.py b/src/blenderbim/blenderbim/bim/module/drawing/operator.py index d78db7b340..ee0eec8ac1 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/operator.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/operator.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -125,15 +124,19 @@ class CreateDrawing(bpy.types.Operator): - IFC file is created - Camera is in Orthographic mode """ + bl_idname = "bim.create_drawing" bl_label = "Create Drawing" @classmethod def poll(cls, context): camera = context.scene.camera - return IfcStore.get_file() \ - and camera.type == "CAMERA" and camera.data.type == "ORTHO" \ + return ( + IfcStore.get_file() + and camera.type == "CAMERA" + and camera.data.type == "ORTHO" and camera.BIMObjectProperties.ifc_definition_id + ) def execute(self, context): self.camera = context.scene.camera @@ -568,9 +571,7 @@ class OpenSheet(bpy.types.Operator): props = context.scene.DocProperties open_with_user_command( context.preferences.addons["blenderbim"].preferences.svg_command, - os.path.join( - context.scene.BIMProperties.data_dir, "sheets", props.active_sheet.name + ".svg" - ), + os.path.join(context.scene.BIMProperties.data_dir, "sheets", props.active_sheet.name + ".svg"), ) return {"FINISHED"} @@ -581,16 +582,16 @@ class AddDrawingToSheet(bpy.types.Operator): bl_options = {"REGISTER", "UNDO"} # TODO: check undo redo + @classmethod + def poll(cls, context): + props = context.scene.DocProperties + return props.drawings and props.sheets and context.scene.BIMProperties.data_dir + def execute(self, context): props = context.scene.DocProperties sheet_builder = sheeter.SheetBuilder() sheet_builder.data_dir = context.scene.BIMProperties.data_dir - try: - sheet_builder.add_drawing( - props.drawings.active_drawing.name, props.active_sheet.name - ) - except FileNotFoundError: - self.report({"ERROR"}, "Drawings need to be created before being added to a sheet") + sheet_builder.add_drawing(props.active_drawing.name, props.active_sheet.name) return {"FINISHED"} @@ -599,6 +600,10 @@ class CreateSheets(bpy.types.Operator): bl_label = "Create Sheets" # TODO: check undo redo + @classmethod + def poll(cls, context): + return context.scene.DocProperties.sheets and context.scene.BIMProperties.data_dir + def execute(self, context): scene = context.scene props = scene.DocProperties @@ -657,6 +662,7 @@ class OpenView(bpy.types.Operator): class OpenViewCamera(bpy.types.Operator): """Select this drawing's camera object and expand its drawing properties""" + bl_idname = "bim.open_view_camera" bl_label = "Open View Camera" bl_options = {"REGISTER", "UNDO"} @@ -664,7 +670,7 @@ class OpenViewCamera(bpy.types.Operator): @classmethod def poll(cls, context): - return bpy.context.object.mode == "OBJECT" + return context.mode == "OBJECT" def execute(self, context): doc_props = context.scene.DocProperties @@ -943,9 +949,7 @@ class ActivateDrawingStyle(bpy.types.Operator): def execute(self, context): scene = context.scene - if scene.camera.data.BIMCameraProperties.active_drawing_style_index < len( - scene.DocProperties.drawing_styles - ): + if scene.camera.data.BIMCameraProperties.active_drawing_style_index < len(scene.DocProperties.drawing_styles): self.drawing_style = scene.DocProperties.drawing_styles[ scene.camera.data.BIMCameraProperties.active_drawing_style_index ] @@ -1103,13 +1107,16 @@ class AddScheduleToSheet(bpy.types.Operator): bl_options = {"REGISTER", "UNDO"} # TODO: check undo redo + @classmethod + def poll(cls, context): + props = context.scene.DocProperties + return props.schedules and props.sheets and context.scene.BIMProperties.data_dir + def execute(self, context): props = context.scene.DocProperties sheet_builder = sheeter.SheetBuilder() sheet_builder.data_dir = context.scene.BIMProperties.data_dir - sheet_builder.add_schedule( - props.active_schedule.name, props.active_sheet.name - ) + sheet_builder.add_schedule(props.active_schedule.name, props.active_sheet.name) return {"FINISHED"} @@ -1258,7 +1265,7 @@ class AddSectionsAnnotations(bpy.types.Operator): bl_options = {"REGISTER", "UNDO"} @classmethod - def poll(cls, context): + def poll(cls, context): camera = helper.get_active_drawing(context.scene)[1] return camera and camera.data.type == "ORTHO" diff --git a/src/blenderbim/blenderbim/bim/module/drawing/prop.py b/src/blenderbim/blenderbim/bim/module/drawing/prop.py index 150d800e32..f87e4e6ee1 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/prop.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/prop.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -54,7 +53,7 @@ def purge(): vector_styles_enum = [] -def getDiagramScales(self, context): +def get_diagram_scales(self, context): global diagram_scales_enum if ( len(diagram_scales_enum) < 1 @@ -271,16 +270,17 @@ class DocProperties(PropertyGroup): @property def active_schedule(self): - return self.schedules[self.active_schedule_index] - + return self.schedules[self.active_schedule_index] + @property def active_drawing(self): - return self.drawings[self.active_drawing_index] - + return self.drawings[self.active_drawing_index] + @property def active_sheet(self): return self.sheets[self.active_sheet_index] + class BIMCameraProperties(PropertyGroup): view_name: StringProperty(name="View Name") target_view: EnumProperty( @@ -294,7 +294,7 @@ class BIMCameraProperties(PropertyGroup): name="Target View", default="PLAN_VIEW", ) - diagram_scale: EnumProperty(items=getDiagramScales, name="Drawing Scale") + diagram_scale: EnumProperty(items=get_diagram_scales, name="Drawing Scale") custom_diagram_scale: StringProperty(name="Custom Scale") raster_x: IntProperty(name="Raster X", default=1000) raster_y: IntProperty(name="Raster Y", default=1000) diff --git a/src/blenderbim/blenderbim/bim/module/drawing/scheduler.py b/src/blenderbim/blenderbim/bim/module/drawing/scheduler.py index 902324e6b0..e25e49a835 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/scheduler.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/scheduler.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -27,7 +26,11 @@ from odf.style import Style class Scheduler: def schedule(self, infile, outfile): - self.svg = svgwrite.Drawing(outfile, debug=False, id="root",) + self.svg = svgwrite.Drawing( + outfile, + debug=False, + id="root", + ) self.padding = 1 self.margin = 1 doc = load(infile) diff --git a/src/blenderbim/blenderbim/bim/module/drawing/shaders.py b/src/blenderbim/blenderbim/bim/module/drawing/shaders.py index ff85417bbd..384b8a0917 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/shaders.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/shaders.py @@ -1,6 +1,5 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on -# Copyright (C) 2020, 2021 Dion Moult +# Copyright (C) 2020, 2021 Maxim Vasilyev # # This file is part of BlenderBIM Add-on. # @@ -24,7 +23,7 @@ from gpu_extras.batch import batch_for_shader class BaseShader: - """Wrapepr for GPUShader + """Wrapper for GPUShader To use for viewport decorations with geometry generated on GPU side. The Geometry shader works in clipping coords (aftre projecting before division and window scaling). diff --git a/src/blenderbim/blenderbim/bim/module/drawing/sheeter.py b/src/blenderbim/blenderbim/bim/module/drawing/sheeter.py index 901771ddea..9d705c195b 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/sheeter.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/sheeter.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/module/drawing/svgwriter.py b/src/blenderbim/blenderbim/bim/module/drawing/svgwriter.py index e00dc0c84f..c048c40800 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/svgwriter.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/svgwriter.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -45,7 +44,7 @@ class External(svgwrite.container.Group): self.xml = xml # Remove namespace - ns = u"{http://www.w3.org/2000/svg}" + ns = "{http://www.w3.org/2000/svg}" nsl = len(ns) for elem in self.xml.iter(): if elem.tag.startswith(ns): @@ -123,12 +122,12 @@ class SvgWriter: self.svg.add( self.svg.image( os.path.join("..", "diagrams", os.path.basename(self.background_image)), - **{"width": self.width, "height": self.height} + **{"width": self.width, "height": self.height}, ) ) def draw_background_elements(self): - return # TODO purge? + return # TODO purge? for element in self.ifc_cutter.background_elements: if element["type"] == "polygon": self.draw_polygon(element, "background") @@ -180,7 +179,7 @@ class SvgWriter: "text-anchor": "middle", "alignment-baseline": "middle", "dominant-baseline": "middle", - } + }, ) ) self.svg.add( @@ -193,7 +192,7 @@ class SvgWriter: "text-anchor": "middle", "alignment-baseline": "middle", "dominant-baseline": "middle", - } + }, ) ) @@ -252,7 +251,7 @@ class SvgWriter: "text-anchor": text_anchor, "alignment-baseline": "baseline", "dominant-baseline": "baseline", - } + }, ) ) @@ -294,7 +293,7 @@ class SvgWriter: "text-anchor": "start", "alignment-baseline": "baseline", "dominant-baseline": "baseline", - } + }, ) ) @@ -325,7 +324,7 @@ class SvgWriter: "text-anchor": "middle", "alignment-baseline": "middle", "dominant-baseline": "middle", - } + }, ) ) self.draw_text_annotations() @@ -473,7 +472,9 @@ class SvgWriter: ) transform = "rotate({}, {}, {})".format( - angle, (text_position * self.scale)[0], (text_position * self.scale)[1], + angle, + (text_position * self.scale)[0], + (text_position * self.scale)[1], ) if text_obj.data.BIMTextProperties.symbol != "None": @@ -516,7 +517,7 @@ class SvgWriter: "alignment-baseline": alignment_baseline, "dominant-baseline": alignment_baseline, "transform": transform, - } + }, ) ) @@ -607,7 +608,7 @@ class SvgWriter: "font-size": annotation.Annotator.get_svg_text_size(2.5), "font-family": "OpenGost Type B TT", "text-anchor": "middle", - } + }, ) ) @@ -623,7 +624,7 @@ class SvgWriter: return spline.bezier_points if spline.bezier_points else spline.points def draw_cut_polygons(self): - return # deprecate? + return # deprecate? for polygon in self.ifc_cutter.cut_polygons: self.draw_polygon(polygon, "cut") diff --git a/src/blenderbim/blenderbim/bim/module/drawing/ui.py b/src/blenderbim/blenderbim/bim/module/drawing/ui.py index 2662e885ec..f45a1b3775 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/ui.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/ui.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -17,10 +16,8 @@ # You should have received a copy of the GNU General Public License # along with BlenderBIM Add-on. If not, see . -import os import bpy from bpy.types import Panel -from bpy.props import StringProperty, BoolProperty class BIM_PT_camera(Panel): @@ -32,7 +29,6 @@ class BIM_PT_camera(Panel): @classmethod def poll(cls, context): - engine = context.engine return context.camera and hasattr(context.active_object.data, "BIMCameraProperties") def draw(self, context): @@ -105,7 +101,6 @@ class BIM_PT_drawing_underlay(Panel): @classmethod def poll(cls, context): - engine = context.engine return context.camera and hasattr(context.active_object.data, "BIMCameraProperties") def draw(self, context): @@ -150,7 +145,6 @@ class BIM_PT_drawing_underlay(Panel): row.operator("bim.activate_drawing_style") - class BIM_PT_drawings(Panel): bl_label = "SVG Drawings" bl_idname = "BIM_PT_drawings" @@ -289,8 +283,6 @@ class BIM_PT_annotation_utilities(Panel): row = layout.row(align=True) row.operator("bim.clean_wireframes") row = layout.row(align=True) - row.operator("bim.link_ifc") - row = layout.row(align=True) row.operator("bim.add_grid") row = layout.row(align=True) row.operator("bim.add_sections_annotations") diff --git a/src/blenderbim/blenderbim/bim/module/geometry/__init__.py b/src/blenderbim/blenderbim/bim/module/geometry/__init__.py index 61cf9906d3..70a06345b6 100644 --- a/src/blenderbim/blenderbim/bim/module/geometry/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/geometry/__init__.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/module/geometry/helper.py b/src/blenderbim/blenderbim/bim/module/geometry/helper.py index d27582d8b6..1fd3ab338c 100644 --- a/src/blenderbim/blenderbim/bim/module/geometry/helper.py +++ b/src/blenderbim/blenderbim/bim/module/geometry/helper.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/module/geometry/operator.py b/src/blenderbim/blenderbim/bim/module/geometry/operator.py index 4af285fa94..565dce4095 100644 --- a/src/blenderbim/blenderbim/bim/module/geometry/operator.py +++ b/src/blenderbim/blenderbim/bim/module/geometry/operator.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -154,12 +153,11 @@ class AddRepresentation(bpy.types.Operator): "geometry.assign_representation", self.file, **{"product": product, "representation": result} ) - existing_mesh = obj.data mesh = obj.data.copy() mesh.name = "{}/{}".format(context_id, result.id()) mesh.BIMMeshProperties.ifc_definition_id = int(result.id()) obj.data = mesh - Data.load(IfcStore.get_file(), obj.BIMObjectProperties.ifc_definition_id) + Data.load(self.file, obj.BIMObjectProperties.ifc_definition_id) if product.is_a("IfcTypeProduct"): if self.file.schema == "IFC2X3": @@ -168,7 +166,7 @@ class AddRepresentation(bpy.types.Operator): types = product.Types if types: for element in types[0].RelatedObjects: - Data.load(IfcStore.get_file(), element.id()) + Data.load(self.file, element.id()) return {"FINISHED"} @@ -239,7 +237,7 @@ class SwitchRepresentation(bpy.types.Operator): if self.disable_opening_subtractions and self.context_of_items.ContextIdentifier == "Body": if self.oprops.ifc_definition_id not in VoidData.products: - VoidData.load(IfcStore.get_file(), self.oprops.ifc_definition_id) + VoidData.load(self.file, self.oprops.ifc_definition_id) for opening_id in VoidData.products[self.oprops.ifc_definition_id]: opening = IfcStore.get_element(opening_id) if not opening: @@ -291,7 +289,7 @@ class RemoveRepresentation(bpy.types.Operator): "geometry.unassign_representation", self.file, **{"product": product, "representation": representation} ) ifcopenshell.api.run("geometry.remove_representation", self.file, **{"representation": representation}) - Data.load(IfcStore.get_file(), product.id()) + Data.load(self.file, product.id()) return {"FINISHED"} @@ -302,6 +300,10 @@ class UpdateRepresentation(bpy.types.Operator): obj: bpy.props.StringProperty() ifc_representation_class: bpy.props.StringProperty() + @classmethod + def poll(cls, context): + return context.active_object.mode == "OBJECT" + def execute(self, context): return IfcStore.execute_ifc_operator(self, context) @@ -381,7 +383,9 @@ class UpdateRepresentation(bpy.types.Operator): obj.data.BIMMeshProperties.ifc_definition_id = int(new_representation.id()) obj.data.name = f"{old_representation.ContextOfItems.id()}/{new_representation.id()}" bpy.ops.bim.remove_representation(representation_id=old_representation.id(), obj=obj.name) - Data.load(IfcStore.get_file(), obj.BIMObjectProperties.ifc_definition_id) + Data.load(self.file, obj.BIMObjectProperties.ifc_definition_id) + if obj.data.BIMMeshProperties.ifc_parameters: + bpy.ops.bim.get_representation_ifc_parameters() class UpdateParametricRepresentation(bpy.types.Operator): @@ -390,15 +394,22 @@ class UpdateParametricRepresentation(bpy.types.Operator): bl_options = {"REGISTER", "UNDO"} index: bpy.props.IntProperty() + @classmethod + def poll(cls, context): + return context.active_object.mode == "OBJECT" + def execute(self, context): self.file = IfcStore.get_file() obj = context.active_object props = obj.data.BIMMeshProperties parameter = props.ifc_parameters[self.index] - element = IfcStore.get_file().by_id(parameter.step_id)[parameter.index] = parameter.value + self.file.by_id(parameter.step_id)[parameter.index] = parameter.value + show_representation_parameters = bool(props.ifc_parameters) bpy.ops.bim.switch_representation( ifc_definition_id=props.ifc_definition_id, should_reload=True, should_switch_all_meshes=True ) + if show_representation_parameters: + bpy.ops.bim.get_representation_ifc_parameters() return {"FINISHED"} @@ -411,7 +422,8 @@ class GetRepresentationIfcParameters(bpy.types.Operator): self.file = IfcStore.get_file() obj = context.active_object props = obj.data.BIMMeshProperties - elements = IfcStore.get_file().traverse(IfcStore.get_file().by_id(props.ifc_definition_id)) + elements = self.file.traverse(self.file.by_id(props.ifc_definition_id)) + props.ifc_parameters.clear() for element in elements: if element.is_a("IfcRepresentationItem") or element.is_a("IfcParameterizedProfileDef"): for i in range(0, len(element)): diff --git a/src/blenderbim/blenderbim/bim/module/geometry/prop.py b/src/blenderbim/blenderbim/bim/module/geometry/prop.py index f120f57282..289a81a6ea 100644 --- a/src/blenderbim/blenderbim/bim/module/geometry/prop.py +++ b/src/blenderbim/blenderbim/bim/module/geometry/prop.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/module/geometry/ui.py b/src/blenderbim/blenderbim/bim/module/geometry/ui.py index b05acc8d03..0888ef27a5 100644 --- a/src/blenderbim/blenderbim/bim/module/geometry/ui.py +++ b/src/blenderbim/blenderbim/bim/module/geometry/ui.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -91,12 +90,12 @@ class BIM_PT_mesh(Panel): row = layout.row(align=True) op = row.operator("bim.switch_representation", text="Bake Voids", icon="SELECT_SUBTRACT") - op.should_switch_all_meshes=True + op.should_switch_all_meshes = True op.should_reload = True op.ifc_definition_id = props.ifc_definition_id op.disable_opening_subtractions = False op = row.operator("bim.switch_representation", text="Dynamic Voids", icon="SELECT_INTERSECT") - op.should_switch_all_meshes=True + op.should_switch_all_meshes = True op.should_reload = True op.ifc_definition_id = props.ifc_definition_id op.disable_opening_subtractions = True diff --git a/src/blenderbim/blenderbim/bim/module/georeference/__init__.py b/src/blenderbim/blenderbim/bim/module/georeference/__init__.py index babbbc870d..7d644a30df 100644 --- a/src/blenderbim/blenderbim/bim/module/georeference/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/georeference/__init__.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/module/georeference/operator.py b/src/blenderbim/blenderbim/bim/module/georeference/operator.py index 2026de99e4..5b3e09037d 100644 --- a/src/blenderbim/blenderbim/bim/module/georeference/operator.py +++ b/src/blenderbim/blenderbim/bim/module/georeference/operator.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -26,6 +25,7 @@ import ifcopenshell.api import blenderbim.bim.helper from blenderbim.bim.ifc import IfcStore from ifcopenshell.api.georeference.data import Data +from ifcopenshell.api.unit.data import Data as UnitData from math import radians, degrees, atan, tan, cos, sin @@ -37,50 +37,18 @@ class EnableEditingGeoreferencing(bpy.types.Operator): def execute(self, context): self.file = IfcStore.get_file() props = context.scene.BIMGeoreferenceProperties + self.props = props props.projected_crs.clear() - for attribute in IfcStore.get_schema().declaration_by_name("IfcProjectedCRS").all_attributes(): - data_type = ifcopenshell.util.attribute.get_primitive_type(attribute) - if data_type == "entity": - continue - new = props.projected_crs.add() - new.name = attribute.name() - new.is_null = Data.projected_crs[attribute.name()] is None - new.is_optional = attribute.optional() - new.data_type = data_type - if data_type == "string": - new.string_value = "" if new.is_null else Data.projected_crs[attribute.name()] - elif data_type == "float": - new.float_value = 0.0 if new.is_null else Data.projected_crs[attribute.name()] - elif data_type == "integer": - new.int_value = 0 if new.is_null else Data.projected_crs[attribute.name()] - elif data_type == "boolean": - new.bool_value = False if new.is_null else Data.projected_crs[attribute.name()] - - props.is_map_unit_null = Data.projected_crs["MapUnit"] is None - if not props.is_map_unit_null: - props.map_unit_type = Data.projected_crs["MapUnit"]["type"] - if props.map_unit_type == "IfcSIUnit": - prefix = ifcopenshell.util.unit.get_prefix(Data.projected_crs["MapUnit"]["Prefix"]) or "" - name = ifcopenshell.util.unit.get_unit_name(Data.projected_crs["MapUnit"]["Name"]) - props.map_unit_si = prefix + name - elif props.map_unit_type == "IfcConversionBasedUnit": - props.map_unit_imperial = Data.projected_crs["MapUnit"]["Name"] + blenderbim.bim.helper.import_attributes( + "IfcProjectedCRS", props.projected_crs, Data.projected_crs, self.import_projected_crs_attributes + ) props.map_conversion.clear() - - for attribute in IfcStore.get_schema().declaration_by_name("IfcMapConversion").all_attributes(): - data_type = ifcopenshell.util.attribute.get_primitive_type(attribute) - if data_type == "entity" or data_type == "select": - continue - new = props.map_conversion.add() - new.name = attribute.name() - new.is_null = Data.map_conversion[attribute.name()] is None - new.is_optional = attribute.optional() - # Enforce a string data type to prevent data loss in single-precision Blender props - new.data_type = "string" - new.string_value = "" if new.is_null else str(Data.map_conversion[attribute.name()]) + blenderbim.bim.helper.import_attributes( + "IfcMapConversion", props.map_conversion, Data.map_conversion, self.import_map_conversion_attributes + ) props.has_true_north = bool(Data.true_north) if Data.true_north: @@ -90,6 +58,27 @@ class EnableEditingGeoreferencing(bpy.types.Operator): props.is_editing = True return {"FINISHED"} + def import_projected_crs_attributes(self, name, prop, data): + if name == "MapUnit": + new = self.props.projected_crs.add() + new.name = name + new.data_type = "enum" + new.is_null = data[name] is None + new.is_optional = True + new.enum_items = json.dumps( + {u["id"]: u["Name"] for u in UnitData.units.values() if u["UnitType"] == "LENGTHUNIT"} + ) + if data["MapUnit"]: + new.enum_value = str(data["MapUnit"]["id"]) + return True + + def import_map_conversion_attributes(self, name, prop, data): + if name not in ["SourceCRS", "TargetCRS"]: + # Enforce a string data type to prevent data loss in single-precision Blender props + prop.data_type = "string" + prop.string_value = "" if prop.is_null else str(data[name]) + return True + class DisableEditingGeoreferencing(bpy.types.Operator): bl_idname = "bim.disable_editing_georeferencing" @@ -114,20 +103,15 @@ class EditGeoreferencing(bpy.types.Operator): self.file = IfcStore.get_file() props = context.scene.BIMGeoreferenceProperties - projected_crs = blenderbim.bim.helper.export_attributes(props.projected_crs) - - map_unit = "" - if not props.is_map_unit_null: - map_unit = props.map_unit_si if props.map_unit_type == "IfcSIUnit" else props.map_unit_imperial - - map_conversion = blenderbim.bim.helper.export_attributes(props.map_conversion, self.export_attributes) + projected_crs = blenderbim.bim.helper.export_attributes(props.projected_crs, self.export_crs_attributes) + map_conversion = blenderbim.bim.helper.export_attributes(props.map_conversion, self.export_map_attributes) true_north = None if props.has_true_north: try: true_north = [float(props.true_north_abscissa), float(props.true_north_ordinate)] - except: - pass + except ValueError: + self.report({"ERROR"}, "True North Abscissa and Ordinate expect a number") ifcopenshell.api.run( "georeference.edit_georeferencing", @@ -135,20 +119,24 @@ class EditGeoreferencing(bpy.types.Operator): **{ "map_conversion": map_conversion, "projected_crs": projected_crs, - "map_unit": map_unit, "true_north": true_north, } ) - Data.load(IfcStore.get_file()) + Data.load(self.file) bpy.ops.bim.disable_editing_georeferencing() return {"FINISHED"} - def export_attributes(self, attributes, prop): + def export_map_attributes(self, attributes, prop): if not prop.is_null and prop.data_type == "string": # We store our floats as string to prevent single precision data loss attributes[prop.name] = float(prop.string_value) return True + def export_crs_attributes(self, attributes, prop): + if not prop.is_null and prop.name == "MapUnit": + attributes[prop.name] = self.file.by_id(int(prop.enum_value)) + return True + class SetBlenderGridNorth(bpy.types.Operator): bl_idname = "bim.set_blender_grid_north" @@ -294,8 +282,7 @@ class ConvertGlobalToLocal(bpy.types.Operator): def poll(cls, context): file = IfcStore.get_file() props = context.scene.BIMGeoreferenceProperties - return file and file.by_type("IfcUnitAssignment") \ - and props.coordinate_input.count(",") == 2 + return file and file.by_type("IfcUnitAssignment") and props.coordinate_input.count(",") == 2 def execute(self, context): if not Data.is_loaded: diff --git a/src/blenderbim/blenderbim/bim/module/georeference/prop.py b/src/blenderbim/blenderbim/bim/module/georeference/prop.py index 900a1bdaaa..b85cb01275 100644 --- a/src/blenderbim/blenderbim/bim/module/georeference/prop.py +++ b/src/blenderbim/blenderbim/bim/module/georeference/prop.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -36,24 +35,8 @@ class BIMGeoreferenceProperties(PropertyGroup): is_editing: BoolProperty(name="Is Editing") map_conversion: CollectionProperty(name="Map Conversion", type=Attribute) projected_crs: CollectionProperty(name="Projected CRS", type=Attribute) - map_unit_type: EnumProperty( - items=[(n, n, "") for n in ["IfcSIUnit", "IfcConversionBasedUnit"]], - name="Map Unit Type", - default="IfcSIUnit", - ) - map_unit_si: EnumProperty( - items=[(n, n.lower().capitalize(), "") for n in ["MILLIMETRE", "CENTIMETRE", "METRE", "KILOMETRE"]], - name="Map Unit SI", - default="METRE", - ) - map_unit_imperial: EnumProperty( - items=[(n, n.lower().capitalize(), "") for n in ["inch", "foot", "yard", "mile"]], - name="Map Unit SI", - default="foot", - ) - is_map_unit_null: BoolProperty(name="Is Map Unit Null") - coordinate_input: StringProperty(name="Coordinate Input", description="Formatted \"x,y,z\" (without quotes)") - coordinate_output: StringProperty(name="Coordinate Output", description="Formatted \"x,y,z\" (without quotes)") + coordinate_input: StringProperty(name="Coordinate Input", description='Formatted "x,y,z" (without quotes)') + coordinate_output: StringProperty(name="Coordinate Output", description='Formatted "x,y,z" (without quotes)') has_blender_offset: BoolProperty(name="Has Blender Offset") blender_eastings: StringProperty(name="Blender Eastings", default="0") blender_northings: StringProperty(name="Blender Northings", default="0") diff --git a/src/blenderbim/blenderbim/bim/module/georeference/ui.py b/src/blenderbim/blenderbim/bim/module/georeference/ui.py index cf5a9e8481..19b1a8b728 100644 --- a/src/blenderbim/blenderbim/bim/module/georeference/ui.py +++ b/src/blenderbim/blenderbim/bim/module/georeference/ui.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -56,14 +55,6 @@ class BIM_PT_gis(Panel): draw_attributes(props.projected_crs, self.layout) - row = self.layout.row(align=True) - row.prop(props, "map_unit_type", text="MapUnit") - if props.map_unit_type == "IfcSIUnit": - row.prop(props, "map_unit_si", text="") - elif props.map_unit_type == "IfcConversionBasedUnit": - row.prop(props, "map_unit_imperial", text="") - row.prop(props, "is_map_unit_null", icon="RADIOBUT_OFF" if props.is_map_unit_null else "RADIOBUT_ON", text="") - row = self.layout.row() row.label(text="Map Conversion", icon="GRID") diff --git a/src/blenderbim/blenderbim/bim/module/group/__init__.py b/src/blenderbim/blenderbim/bim/module/group/__init__.py index 4cf5131a2f..ddfc4ff48c 100644 --- a/src/blenderbim/blenderbim/bim/module/group/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/group/__init__.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -26,6 +25,7 @@ classes = ( operator.AddGroup, operator.EditGroup, operator.RemoveGroup, + operator.ToggleAssigningGroup, operator.AssignGroup, operator.UnassignGroup, operator.EnableEditingGroup, @@ -34,7 +34,9 @@ classes = ( prop.Group, prop.BIMGroupProperties, ui.BIM_PT_groups, + ui.BIM_PT_object_groups, ui.BIM_UL_groups, + ui.BIM_UL_object_groups, ) diff --git a/src/blenderbim/blenderbim/bim/module/group/operator.py b/src/blenderbim/blenderbim/bim/module/group/operator.py index 0cd773791f..fdf7548826 100644 --- a/src/blenderbim/blenderbim/bim/module/group/operator.py +++ b/src/blenderbim/blenderbim/bim/module/group/operator.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -20,6 +19,7 @@ import bpy import ifcopenshell.util.attribute import ifcopenshell.api +import blenderbim.bim.helper from blenderbim.bim.ifc import IfcStore from ifcopenshell.api.group.data import Data @@ -48,6 +48,7 @@ class DisableGroupEditingUI(bpy.types.Operator): def execute(self, context): context.scene.BIMGroupProperties.is_editing = False + context.scene.BIMGroupProperties.active_group_id = 0 return {"FINISHED"} @@ -120,17 +121,8 @@ class EnableEditingGroup(bpy.types.Operator): props = context.scene.BIMGroupProperties props.group_attributes.clear() - data = Data.groups[self.group] + blenderbim.bim.helper.import_attributes("IfcGroup", props.group_attributes, Data.groups[self.group]) - for attribute in IfcStore.get_schema().declaration_by_name("IfcGroup").all_attributes(): - data_type = ifcopenshell.util.attribute.get_primitive_type(attribute) - if data_type == "entity": - 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"} @@ -145,6 +137,16 @@ class DisableEditingGroup(bpy.types.Operator): return {"FINISHED"} +class ToggleAssigningGroup(bpy.types.Operator): + bl_idname = "bim.toggle_assigning_group" + bl_label = "Toggle Assigning Group" + bl_options = {"REGISTER", "UNDO"} + + def execute(self, context): + context.scene.BIMGroupProperties.is_adding = not context.scene.BIMGroupProperties.is_adding + return {"FINISHED"} + + class AssignGroup(bpy.types.Operator): bl_idname = "bim.assign_group" bl_label = "Assign Group" @@ -156,17 +158,20 @@ class AssignGroup(bpy.types.Operator): return IfcStore.execute_ifc_operator(self, context) def _execute(self, context): - product = bpy.data.objects.get(self.product) if self.product else context.active_object self.file = IfcStore.get_file() - ifcopenshell.api.run( - "group.assign_group", - self.file, - **{ - "product": self.file.by_id(product.BIMObjectProperties.ifc_definition_id), - "group": self.file.by_id(self.group), - } - ) - Data.load(IfcStore.get_file()) + products = [bpy.data.objects.get(self.product)] if self.product else context.selected_objects + for product in products: + if not product.BIMObjectProperties.ifc_definition_id: + continue + ifcopenshell.api.run( + "group.assign_group", + self.file, + **{ + "product": self.file.by_id(product.BIMObjectProperties.ifc_definition_id), + "group": self.file.by_id(self.group), + } + ) + Data.load(self.file) return {"FINISHED"} @@ -181,16 +186,19 @@ class UnassignGroup(bpy.types.Operator): return IfcStore.execute_ifc_operator(self, context) def _execute(self, context): - product = bpy.data.objects.get(self.product) if self.product else context.active_object self.file = IfcStore.get_file() - ifcopenshell.api.run( - "group.unassign_group", - self.file, - **{ - "product": self.file.by_id(product.BIMObjectProperties.ifc_definition_id), - "group": self.file.by_id(self.group), - } - ) + products = [bpy.data.objects.get(self.product)] if self.product else context.selected_objects + for product in products: + if not product.BIMObjectProperties.ifc_definition_id: + continue + ifcopenshell.api.run( + "group.unassign_group", + self.file, + **{ + "product": self.file.by_id(product.BIMObjectProperties.ifc_definition_id), + "group": self.file.by_id(self.group), + } + ) Data.load(IfcStore.get_file()) return {"FINISHED"} diff --git a/src/blenderbim/blenderbim/bim/module/group/prop.py b/src/blenderbim/blenderbim/bim/module/group/prop.py index 05decfb78d..03011af513 100644 --- a/src/blenderbim/blenderbim/bim/module/group/prop.py +++ b/src/blenderbim/blenderbim/bim/module/group/prop.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -40,6 +39,7 @@ class Group(PropertyGroup): class BIMGroupProperties(PropertyGroup): group_attributes: CollectionProperty(name="Group Attributes", type=Attribute) is_editing: BoolProperty(name="Is Editing", default=False) + is_adding: BoolProperty(name="Is Adding", default=False) groups: CollectionProperty(name="Groups", type=Group) active_group_index: IntProperty(name="Active Group Index") active_group_id: IntProperty(name="Active Group Id") diff --git a/src/blenderbim/blenderbim/bim/module/group/ui.py b/src/blenderbim/blenderbim/bim/module/group/ui.py index 479a4c787a..a806564db5 100644 --- a/src/blenderbim/blenderbim/bim/module/group/ui.py +++ b/src/blenderbim/blenderbim/bim/module/group/ui.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -68,37 +67,78 @@ class BIM_PT_groups(Panel): row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="") +class BIM_PT_object_groups(Panel): + bl_label = "IFC Groups" + bl_idname = "BIM_PT_object_groups" + bl_options = {"DEFAULT_CLOSED"} + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "object" + + @classmethod + def poll(cls, context): + return IfcStore.get_file() and context.active_object.BIMObjectProperties.ifc_definition_id + + def draw(self, context): + if not Data.is_loaded: + Data.load(IfcStore.get_file()) + self.props = context.scene.BIMGroupProperties + row = self.layout.row(align=True) + if self.props.is_adding: + row.label(text="Adding Groups", icon="OUTLINER") + row.operator("bim.toggle_assigning_group", text="", icon="CANCEL") + self.layout.template_list( + "BIM_UL_object_groups", + "", + self.props, + "groups", + self.props, + "active_group_index", + ) + else: + row.label(text=f"{len(Data.groups)} Groups in IFC Project", icon="OUTLINER") + row.operator("bim.toggle_assigning_group", text="", icon="ADD") + + groups_object = Data.products.get(context.active_object.BIMObjectProperties.ifc_definition_id, []) + for group_id in groups_object: + row = self.layout.row(align=True) + row.label(text=Data.groups[group_id].get("Name", "Unnamed")) + op = row.operator("bim.unassign_group", text="", icon="X") + op.group = group_id + + if not groups_object: + self.layout.label(text="No Group associated with Active Object") + 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: + group_id = item.ifc_definition_id + if context.scene.BIMGroupProperties.active_group_id == group_id: op = row.operator("bim.select_group_products", text="", icon="RESTRICT_SELECT_OFF") - op.group = item.ifc_definition_id + op.group = group_id row.operator("bim.edit_group", text="", icon="CHECKMARK") row.operator("bim.disable_editing_group", text="", icon="CANCEL") elif context.scene.BIMGroupProperties.active_group_id: op = row.operator("bim.select_group_products", text="", icon="RESTRICT_SELECT_OFF") - op.group = item.ifc_definition_id - row.operator("bim.remove_group", text="", icon="X").group = item.ifc_definition_id + op.group = group_id + op = row.operator("bim.remove_group", text="", icon="X") + op.group = group_id else: op = row.operator("bim.select_group_products", text="", icon="RESTRICT_SELECT_OFF") - op.group = item.ifc_definition_id + op.group = group_id 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 + op.group = group_id + op = row.operator("bim.remove_group", text="", icon="X") + op.group = group_id + + +class BIM_UL_object_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) + op = row.operator("bim.assign_group", text="", icon="ADD") + op.group = item.ifc_definition_id diff --git a/src/blenderbim/blenderbim/bim/module/layer/__init__.py b/src/blenderbim/blenderbim/bim/module/layer/__init__.py index 136e70fa88..80cedcd07c 100644 --- a/src/blenderbim/blenderbim/bim/module/layer/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/layer/__init__.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/module/layer/operator.py b/src/blenderbim/blenderbim/bim/module/layer/operator.py index 2ebd391809..2c20351d20 100644 --- a/src/blenderbim/blenderbim/bim/module/layer/operator.py +++ b/src/blenderbim/blenderbim/bim/module/layer/operator.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -21,6 +20,7 @@ import bpy import json import ifcopenshell.util.attribute import ifcopenshell.api +import blenderbim.bim.helper from blenderbim.bim.ifc import IfcStore from ifcopenshell.api.layer.data import Data @@ -63,17 +63,10 @@ class EnableEditingLayer(bpy.types.Operator): props = context.scene.BIMLayerProperties props.layer_attributes.clear() - data = Data.layers[self.layer] + blenderbim.bim.helper.import_attributes( + "IfcPresentationLayerAssignment", props.layer_attributes, Data.layers[self.layer] + ) - for attribute in IfcStore.get_schema().declaration_by_name("IfcPresentationLayerAssignment").all_attributes(): - data_type = ifcopenshell.util.attribute.get_primitive_type(attribute) - if data_type == "entity" or data_type == "select": - continue - new = props.layer_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_layer_id = self.layer return {"FINISHED"} diff --git a/src/blenderbim/blenderbim/bim/module/layer/prop.py b/src/blenderbim/blenderbim/bim/module/layer/prop.py index 61c310bdb2..c9c0c1fd28 100644 --- a/src/blenderbim/blenderbim/bim/module/layer/prop.py +++ b/src/blenderbim/blenderbim/bim/module/layer/prop.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/module/layer/ui.py b/src/blenderbim/blenderbim/bim/module/layer/ui.py index 542556adca..1508f370d4 100644 --- a/src/blenderbim/blenderbim/bim/module/layer/ui.py +++ b/src/blenderbim/blenderbim/bim/module/layer/ui.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/module/lca/prop.py b/src/blenderbim/blenderbim/bim/module/lca/prop.py index 90c3b77f9d..d612b3c666 100644 --- a/src/blenderbim/blenderbim/bim/module/lca/prop.py +++ b/src/blenderbim/blenderbim/bim/module/lca/prop.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -45,9 +44,7 @@ def get_product_systems(self, context): if not len(productsystems_enum): client = olca.Client(context.preferences.addons["blenderbim"].preferences.openlca_port) try: - productsystems_enum = [ - (ps.name, ps.name, "") for ps in client.get_all(olca.ProductSystem) - ] + productsystems_enum = [(ps.name, ps.name, "") for ps in client.get_all(olca.ProductSystem)] except: pass return productsystems_enum diff --git a/src/blenderbim/blenderbim/bim/module/material/__init__.py b/src/blenderbim/blenderbim/bim/module/material/__init__.py index ea1ff8cd0d..aac236d890 100644 --- a/src/blenderbim/blenderbim/bim/module/material/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/material/__init__.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/module/material/operator.py b/src/blenderbim/blenderbim/bim/module/material/operator.py index e735461ef8..d72b86bbe8 100644 --- a/src/blenderbim/blenderbim/bim/module/material/operator.py +++ b/src/blenderbim/blenderbim/bim/module/material/operator.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -365,6 +364,7 @@ class RemoveListItem(bpy.types.Operator): obj: bpy.props.StringProperty() list_item_set: bpy.props.IntProperty() list_item: bpy.props.IntProperty() + list_item_index: bpy.props.IntProperty() def execute(self, context): return IfcStore.execute_ifc_operator(self, context) @@ -377,7 +377,7 @@ class RemoveListItem(bpy.types.Operator): self.file, **{ "material_list": self.file.by_id(self.list_item_set), - "material": self.file.by_id(self.list_item), + "material_index": self.list_item_index, }, ) Data.load_lists() @@ -429,14 +429,7 @@ class EnableEditingAssignedMaterial(bpy.types.Operator): props.material_set_attributes.clear() - for attribute in IfcStore.get_schema().declaration_by_name(material_set_class).all_attributes(): - if "" not in str(attribute.type_of_attribute): - continue - if attribute.name() in material_set_data: - new = props.material_set_attributes.add() - new.name = attribute.name() - new.is_null = material_set_data[attribute.name()] is None - new.string_value = "" if new.is_null else material_set_data[attribute.name()] + blenderbim.bim.helper.import_attributes(material_set_class, props.material_set_attributes, material_set_data) return {"FINISHED"} def import_attributes(self, name, prop, data): @@ -508,10 +501,7 @@ class EditAssignedMaterial(bpy.types.Operator): return {"FINISHED"} material_set = self.file.by_id(self.material_set) - - attributes = {} - for attribute in props.material_set_attributes: - attributes[attribute.name] = None if attribute.is_null else attribute.string_value + attributes = blenderbim.bim.helper.export_attributes(props.material_set_attributes) ifcopenshell.api.run( "material.edit_assigned_material", self.file, diff --git a/src/blenderbim/blenderbim/bim/module/material/prop.py b/src/blenderbim/blenderbim/bim/module/material/prop.py index 9412dfe35a..660303d471 100644 --- a/src/blenderbim/blenderbim/bim/module/material/prop.py +++ b/src/blenderbim/blenderbim/bim/module/material/prop.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -69,10 +68,12 @@ def getParameterizedProfileClasses(self, context): for t in IfcStore.get_schema().declaration_by_name("IfcParameterizedProfileDef").subtypes() ] for ifc_class in parameterizedprofileclasses_enum: - parameterizedprofileclasses_enum.extend([ - (t.name(), t.name(), "") - for t in IfcStore.get_schema().declaration_by_name(ifc_class[0]).subtypes() or [] - ]) + parameterizedprofileclasses_enum.extend( + [ + (t.name(), t.name(), "") + for t in IfcStore.get_schema().declaration_by_name(ifc_class[0]).subtypes() or [] + ] + ) return parameterizedprofileclasses_enum @@ -111,7 +112,9 @@ class BIMObjectMaterialProperties(PropertyGroup): material_set_attributes: CollectionProperty(name="Material Set Attributes", type=Attribute) active_material_set_item_id: IntProperty(name="Active Material Set ID") material_set_item_attributes: CollectionProperty(name="Material Set Item Attributes", type=Attribute) - material_set_item_profile_attributes: CollectionProperty(name="Material Set Item Profile Attributes", type=Attribute) + material_set_item_profile_attributes: CollectionProperty( + name="Material Set Item Profile Attributes", type=Attribute + ) material_set_item_material: EnumProperty(items=getMaterials, name="Material") profile_classes: EnumProperty(items=getProfileClasses, name="Profile Classes") parameterized_profile_classes: EnumProperty( diff --git a/src/blenderbim/blenderbim/bim/module/material/ui.py b/src/blenderbim/blenderbim/bim/module/material/ui.py index f5630e2aec..d89f48a2ac 100644 --- a/src/blenderbim/blenderbim/bim/module/material/ui.py +++ b/src/blenderbim/blenderbim/bim/module/material/ui.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -258,6 +257,8 @@ class BIM_PT_object_material(Panel): if self.product_data["type"] == "IfcMaterialList": setattr(op, "list_item_set", self.material_set_id) setattr(op, self.set_item_name, item["id"]) + if hasattr(op, f"{self.set_item_name}_index"): + setattr(op, f"{self.set_item_name}_index", index) def draw_read_only_set_ui(self): if ( diff --git a/src/blenderbim/blenderbim/bim/module/model/__init__.py b/src/blenderbim/blenderbim/bim/module/model/__init__.py index f1698528bb..00f3ab557c 100644 --- a/src/blenderbim/blenderbim/bim/module/model/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/model/__init__.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/module/model/door.py b/src/blenderbim/blenderbim/bim/module/model/door.py index 197f1e6400..fb89e134e6 100644 --- a/src/blenderbim/blenderbim/bim/module/model/door.py +++ b/src/blenderbim/blenderbim/bim/module/model/door.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -30,7 +29,7 @@ def add_object(self, context): guid = ifcopenshell.guid.new() leaf_width = self.overall_width - 0.045 - 0.045 # TODO reimplement 2D. See #1222. - #verts = [ + # verts = [ # # Left lining # Vector((0, 0, 0)), # Vector((0, self.depth, 0)), @@ -50,8 +49,8 @@ def add_object(self, context): # Vector((0.045, self.depth + leaf_width, 0)), # Vector((0.080, self.depth + leaf_width, 0)), # Vector((0.080, self.depth, 0)), - #] - #edges = [ + # ] + # edges = [ # [0, 1], # [1, 2], # [2, 3], @@ -66,9 +65,9 @@ def add_object(self, context): # [13, 14], # [14, 15], # [15, 12], # Door panel - #] + # ] ## Door swing - #for i in range(0, 9): + # for i in range(0, 9): # verts.append( # Vector( # ( @@ -79,11 +78,11 @@ def add_object(self, context): # ) # ) # edges.append([16 + i, 17 + i]) - #edges.pop() - #faces = [] - #mesh = bpy.data.meshes.new(name="Plan/Annotation/PLAN_VIEW/" + guid) - #mesh.use_fake_user = True - #mesh.from_pydata(verts, edges, faces) + # edges.pop() + # faces = [] + # mesh = bpy.data.meshes.new(name="Plan/Annotation/PLAN_VIEW/" + guid) + # mesh.use_fake_user = True + # mesh.from_pydata(verts, edges, faces) # Door lining profile verts = [ @@ -191,19 +190,19 @@ def add_object(self, context): 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 + # obj2.data.name = "Model/Body/MODEL_VIEW/" + guid + # obj2.data.use_fake_user = True # TODO: reimplement. See #1222. - #rep = obj2.BIMObjectProperties.representation_contexts.add() - #rep.context = "Model" - #rep.name = "Body" - #rep.target_view = "MODEL_VIEW" + # rep = obj2.BIMObjectProperties.representation_contexts.add() + # rep.context = "Model" + # rep.name = "Body" + # rep.target_view = "MODEL_VIEW" - #rep = obj2.BIMObjectProperties.representation_contexts.add() - #rep.context = "Plan" - #rep.name = "Annotation" - #rep.target_view = "PLAN_VIEW" + # rep = obj2.BIMObjectProperties.representation_contexts.add() + # rep.context = "Plan" + # rep.name = "Annotation" + # rep.target_view = "PLAN_VIEW" class BIM_OT_add_object(Operator): diff --git a/src/blenderbim/blenderbim/bim/module/model/grid.py b/src/blenderbim/blenderbim/bim/module/model/grid.py index f1b0353f9f..08b424bb6b 100644 --- a/src/blenderbim/blenderbim/bim/module/model/grid.py +++ b/src/blenderbim/blenderbim/bim/module/model/grid.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/module/model/handler.py b/src/blenderbim/blenderbim/bim/module/model/handler.py index 16b1c16b02..10abf5b2a4 100644 --- a/src/blenderbim/blenderbim/bim/module/model/handler.py +++ b/src/blenderbim/blenderbim/bim/module/model/handler.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/module/model/opening.py b/src/blenderbim/blenderbim/bim/module/model/opening.py index 58f0bfd137..c075a8046f 100644 --- a/src/blenderbim/blenderbim/bim/module/model/opening.py +++ b/src/blenderbim/blenderbim/bim/module/model/opening.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -151,32 +150,32 @@ class AddElementOpening(bpy.types.Operator): Vector((-0.1, 0, z)), Vector((-0.1, y, 0)), Vector((-0.1, y, z)), - Vector((dimension+0.1, 0, 0)), - Vector((dimension+0.1, 0, z)), - Vector((dimension+0.1, y, 0)), - Vector((dimension+0.1, y, z)), + Vector((dimension + 0.1, 0, 0)), + Vector((dimension + 0.1, 0, z)), + Vector((dimension + 0.1, y, 0)), + Vector((dimension + 0.1, y, z)), ] elif dimension == voided_obj.dimensions[1]: verts = [ Vector((0, -0.1, 0)), Vector((0, -0.1, z)), - Vector((0, dimension+0.1, 0)), - Vector((0, dimension+0.1, z)), + Vector((0, dimension + 0.1, 0)), + Vector((0, dimension + 0.1, z)), Vector((x, -0.1, 0)), Vector((x, -0.1, z)), - Vector((x, dimension+0.1, 0)), - Vector((x, dimension+0.1, z)), + Vector((x, dimension + 0.1, 0)), + Vector((x, dimension + 0.1, z)), ] elif dimension == voided_obj.dimensions[2]: verts = [ Vector((0, 0, -0.1)), - Vector((0, 0, dimension+0.1)), + Vector((0, 0, dimension + 0.1)), Vector((0, y, -0.1)), - Vector((0, y, dimension+0.1)), + Vector((0, y, dimension + 0.1)), Vector((x, 0, -0.1)), - Vector((x, 0, dimension+0.1)), + Vector((x, 0, dimension + 0.1)), Vector((x, y, -0.1)), - Vector((x, y, dimension+0.1)), + Vector((x, y, dimension + 0.1)), ] edges = [] faces = [ diff --git a/src/blenderbim/blenderbim/bim/module/model/pie.py b/src/blenderbim/blenderbim/bim/module/model/pie.py index 5c5480f07f..2d4195eb1c 100644 --- a/src/blenderbim/blenderbim/bim/module/model/pie.py +++ b/src/blenderbim/blenderbim/bim/module/model/pie.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -20,6 +19,7 @@ import bpy from blenderbim.bim.ifc import IfcStore + class OpenPieClass(bpy.types.Operator): bl_idname = "bim.open_pie_class" bl_label = "Open Pie Class" diff --git a/src/blenderbim/blenderbim/bim/module/model/product.py b/src/blenderbim/blenderbim/bim/module/model/product.py index c03062d007..fc7ed03907 100644 --- a/src/blenderbim/blenderbim/bim/module/model/product.py +++ b/src/blenderbim/blenderbim/bim/module/model/product.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -121,16 +120,18 @@ class AddTypeInstance(bpy.types.Operator): bpy.ops.bim.assign_type(relating_type=int(tprops.relating_type), related_object=obj.name) if building_obj: - if instance_class == "IfcWindow": - # TODO For now we are hardcoding windows as a prototype + if instance_class in ["IfcWindow", "IfcDoor"]: + # TODO For now we are hardcoding windows and doors as a prototype bpy.ops.bim.add_element_opening( voided_building_element=building_obj.name, filling_building_element=obj.name ) + if instance_class == "IfcDoor": + obj.location[2] = building_obj.location[2] - min([v[2] for v in obj.bound_box]) else: if collection_obj and collection_obj.BIMObjectProperties.ifc_definition_id: obj.location[2] = collection_obj.location[2] - min([v[2] for v in obj.bound_box]) - bpy.ops.object.select_all(action='DESELECT') + bpy.ops.object.select_all(action="DESELECT") obj.select_set(True) context.view_layer.objects.active = obj return {"FINISHED"} diff --git a/src/blenderbim/blenderbim/bim/module/model/profile.py b/src/blenderbim/blenderbim/bim/module/model/profile.py index 6c9c82f139..cfa7ed47ce 100644 --- a/src/blenderbim/blenderbim/bim/module/model/profile.py +++ b/src/blenderbim/blenderbim/bim/module/model/profile.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/module/model/prop.py b/src/blenderbim/blenderbim/bim/module/model/prop.py index 55389ded4c..1d5f985fb8 100644 --- a/src/blenderbim/blenderbim/bim/module/model/prop.py +++ b/src/blenderbim/blenderbim/bim/module/model/prop.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -40,6 +39,7 @@ def purge(): global relating_types_enum relating_types_enum = [] + def getRelatingTypes(self, context): global relating_types_enum if len(relating_types_enum) < 1: @@ -47,5 +47,6 @@ def getRelatingTypes(self, context): relating_types_enum.extend((str(e.id()), e.Name, "") for e in elements) return relating_types_enum + class BIMModelProperties(PropertyGroup): relating_type: EnumProperty(items=getRelatingTypes, name="Relating Type") diff --git a/src/blenderbim/blenderbim/bim/module/model/root.py b/src/blenderbim/blenderbim/bim/module/model/root.py index 4870d1094c..65c72152f1 100644 --- a/src/blenderbim/blenderbim/bim/module/model/root.py +++ b/src/blenderbim/blenderbim/bim/module/model/root.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/module/model/slab.py b/src/blenderbim/blenderbim/bim/module/model/slab.py index c99e30f79a..c9847d1a21 100644 --- a/src/blenderbim/blenderbim/bim/module/model/slab.py +++ b/src/blenderbim/blenderbim/bim/module/model/slab.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/module/model/stair.py b/src/blenderbim/blenderbim/bim/module/model/stair.py index aacb1b40ad..3b06c52334 100644 --- a/src/blenderbim/blenderbim/bim/module/model/stair.py +++ b/src/blenderbim/blenderbim/bim/module/model/stair.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/module/model/task.py b/src/blenderbim/blenderbim/bim/module/model/task.py index a7d75b0318..3bc4ed748c 100644 --- a/src/blenderbim/blenderbim/bim/module/model/task.py +++ b/src/blenderbim/blenderbim/bim/module/model/task.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/module/model/ui.py b/src/blenderbim/blenderbim/bim/module/model/ui.py index 00a34969bf..68891e71e6 100644 --- a/src/blenderbim/blenderbim/bim/module/model/ui.py +++ b/src/blenderbim/blenderbim/bim/module/model/ui.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -37,7 +36,7 @@ class BIM_PT_authoring(Panel): tprops = context.scene.BIMTypeProperties col = self.layout.column(align=True) enabled = True - + if type_prop.getIfcTypes(tprops, context): col.prop(tprops, "ifc_class", text="", icon="FILE_VOLUME") else: diff --git a/src/blenderbim/blenderbim/bim/module/model/wall.py b/src/blenderbim/blenderbim/bim/module/model/wall.py index 3678422016..9e3dc5c82d 100644 --- a/src/blenderbim/blenderbim/bim/module/model/wall.py +++ b/src/blenderbim/blenderbim/bim/module/model/wall.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/module/model/window.py b/src/blenderbim/blenderbim/bim/module/model/window.py index 8890051ac3..fd081ab0a8 100644 --- a/src/blenderbim/blenderbim/bim/module/model/window.py +++ b/src/blenderbim/blenderbim/bim/module/model/window.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -29,7 +28,7 @@ from blenderbim.bim.ifc import IfcStore def add_object(self, context): guid = ifcopenshell.guid.new() leaf_width = self.overall_width - 0.045 - 0.045 - #verts = [ + # verts = [ # # Left lining # Vector((0, 0, 0)), # Vector((0, self.depth, 0)), @@ -50,8 +49,8 @@ def add_object(self, context): # Vector((0.04, (self.depth / 2) - 0.005, 0)), # Vector((self.overall_width - 0.04, (self.depth / 2) - 0.005, 0)), # Vector((self.overall_width - 0.04, (self.depth / 2) + 0.005, 0)), - #] - #edges = [ + # ] + # edges = [ # [0, 1], # [1, 2], # [2, 3], @@ -66,11 +65,11 @@ def add_object(self, context): # [13, 14], # [14, 15], # [15, 12], # Window panel - #] - #faces = [] - #mesh = bpy.data.meshes.new(name="Plan/Annotation/PLAN_VIEW/" + guid) - #mesh.use_fake_user = True - #mesh.from_pydata(verts, edges, faces) + # ] + # faces = [] + # mesh = bpy.data.meshes.new(name="Plan/Annotation/PLAN_VIEW/" + guid) + # mesh.use_fake_user = True + # mesh.from_pydata(verts, edges, faces) # Window lining profile verts = [ @@ -174,18 +173,18 @@ def add_object(self, context): 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 + # obj2.data.name = "Model/Body/MODEL_VIEW/" + guid + # obj2.data.use_fake_user = True - #rep = obj2.BIMObjectProperties.representation_contexts.add() - #rep.context = "Model" - #rep.name = "Body" - #rep.target_view = "MODEL_VIEW" + # rep = obj2.BIMObjectProperties.representation_contexts.add() + # rep.context = "Model" + # rep.name = "Body" + # rep.target_view = "MODEL_VIEW" - #rep = obj2.BIMObjectProperties.representation_contexts.add() - #rep.context = "Plan" - #rep.name = "Annotation" - #rep.target_view = "PLAN_VIEW" + # rep = obj2.BIMObjectProperties.representation_contexts.add() + # rep.context = "Plan" + # rep.name = "Annotation" + # rep.target_view = "PLAN_VIEW" class BIM_OT_add_object(Operator): diff --git a/src/blenderbim/blenderbim/bim/module/model/workspace.py b/src/blenderbim/blenderbim/bim/module/model/workspace.py index 7215a1bb2d..4d271a6f57 100644 --- a/src/blenderbim/blenderbim/bim/module/model/workspace.py +++ b/src/blenderbim/blenderbim/bim/module/model/workspace.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -67,7 +66,6 @@ class BimTool(WorkSpaceTool): else: row.label(text="No Relating Type") - row.label(text="", icon="BLANK1") row = layout.row(align=True) diff --git a/src/blenderbim/blenderbim/bim/module/owner/__init__.py b/src/blenderbim/blenderbim/bim/module/owner/__init__.py index b60dc2c370..e33cb27af8 100644 --- a/src/blenderbim/blenderbim/bim/module/owner/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/owner/__init__.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/module/owner/operator.py b/src/blenderbim/blenderbim/bim/module/owner/operator.py index 50cb544dd3..9c5eb5c3b1 100644 --- a/src/blenderbim/blenderbim/bim/module/owner/operator.py +++ b/src/blenderbim/blenderbim/bim/module/owner/operator.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -40,15 +39,12 @@ class AddOrRemoveElementFromCollection(bpy.types.Operator): bl_idname = "bim.add_or_remove_element_from_collection" bl_label = "Add or Remove Element From Collection" bl_options = {"REGISTER", "UNDO"} - operation : bpy.props.EnumProperty( - items=( - ("+", 'Add', "Add item to collection"), - ("-", 'Remove', "Remove item from collection") - ), + operation: bpy.props.EnumProperty( + items=(("+", "Add", "Add item to collection"), ("-", "Remove", "Remove item from collection")), default="+", ) - collection_path : bpy.props.StringProperty() - selected_item_idx : bpy.props.IntProperty(default=-1) + collection_path: bpy.props.StringProperty() + selected_item_idx: bpy.props.IntProperty(default=-1) def execute(self, context): # Ugly but I hate using eval() @@ -281,7 +277,7 @@ class EnableEditingAddress(bpy.types.Operator): address.description = data["Description"] or "" address.user_defined_purpose = data["UserDefinedPurpose"] or "" - if data["type"] == "IfcTelecomAddress": + if data["type"] == "IfcTelecomAddress": populate_collection(address.telephone_numbers, data.get("TelephoneNumbers", None)) populate_collection(address.facsimile_numbers, data.get("FacsimileNumbers", None)) address.pager_number = data["PagerNumber"] or "" diff --git a/src/blenderbim/blenderbim/bim/module/owner/prop.py b/src/blenderbim/blenderbim/bim/module/owner/prop.py index 9629575be1..867df5e021 100644 --- a/src/blenderbim/blenderbim/bim/module/owner/prop.py +++ b/src/blenderbim/blenderbim/bim/module/owner/prop.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -36,12 +35,14 @@ from bpy.props import ( _persons_enum = [] _organisations_enum = [] + def purge(): global _persons_enum global _organisations_enum _persons_enum.clear() _organisations_enum.clear() + def getPersons(self, context): global _persons_enum if not Data.is_loaded: diff --git a/src/blenderbim/blenderbim/bim/module/owner/ui.py b/src/blenderbim/blenderbim/bim/module/owner/ui.py index e451c3af2c..42565c302e 100644 --- a/src/blenderbim/blenderbim/bim/module/owner/ui.py +++ b/src/blenderbim/blenderbim/bim/module/owner/ui.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -30,11 +29,8 @@ def draw_string_collection(layout, owner, collection_name): for i in range(len(collection)): if i == 0: row = draw_prop_on_new_row( - column, - collection[i], - "name", - align=True, - text=f"{owner.bl_rna.properties[collection_name].name}") + column, collection[i], "name", align=True, text=f"{owner.bl_rna.properties[collection_name].name}" + ) add_op = row.operator(AddOrRemoveElementFromCollection.bl_idname, icon="ADD", text="") add_op.operation = "+" add_op.collection_path = collection.path_from_id() @@ -46,7 +42,7 @@ def draw_string_collection(layout, owner, collection_name): rem_op.selected_item_idx = i -def draw_prop_on_new_row(layout, owner, attribute, align=False, **kwargs): +def draw_prop_on_new_row(layout, owner, attribute, align=False, **kwargs): row = layout.row(align=align) row.prop(owner, attribute, **kwargs) return row @@ -103,7 +99,7 @@ def draw_addresses_ui(box, assigned_object_id, addresses, file, context): draw_prop_on_new_row(box2, blender_address, "pager_number") draw_string_collection(box2, blender_address, "electronic_mail_addresses") draw_prop_on_new_row(box2, blender_address, "www_home_page_url") - if file.schema != "IFC2X3": + if file.schema != "IFC2X3": draw_string_collection(box2, blender_address, "messaging_ids") elif address["type"] == "IfcPostalAddress": draw_prop_on_new_row(box2, blender_address, "internal_location") @@ -151,7 +147,7 @@ class BIM_PT_people(Panel): row = draw_prop_on_new_row(box, blender_person, "name", align=True, icon="USER", text="") row.operator("bim.edit_person", icon="CHECKMARK", text="") row.operator("bim.disable_editing_person", icon="CANCEL", text="") - draw_prop_on_new_row(box, blender_person, "family_name") + draw_prop_on_new_row(box, blender_person, "family_name") draw_prop_on_new_row(box, blender_person, "given_name") draw_string_collection(box, blender_person, "middle_names") draw_string_collection(box, blender_person, "prefix_titles") @@ -204,8 +200,8 @@ class BIM_PT_organisations(Panel): row = box.row(align=True) row.prop(blender_organisation, "name", icon="USER", text="") row.operator("bim.edit_organisation", icon="CHECKMARK", text="") - row.operator("bim.disable_editing_organisation", icon="CANCEL", text="") - draw_prop_on_new_row(box, blender_organisation, "identification") + row.operator("bim.disable_editing_organisation", icon="CANCEL", text="") + draw_prop_on_new_row(box, blender_organisation, "identification") draw_prop_on_new_row(box, blender_organisation, "description") draw_roles_ui(box, organisation_id, organisation["Roles"], context) @@ -215,7 +211,9 @@ class BIM_PT_organisations(Panel): row.label(text=organisation["Name"]) if organisation["Roles"]: row.label(text=", ".join([Data.roles[r]["Role"] for r in organisation["Roles"]])) - row.operator("bim.enable_editing_organisation", icon="GREASEPENCIL", text="").organisation_id = organisation_id + row.operator( + "bim.enable_editing_organisation", icon="GREASEPENCIL", text="" + ).organisation_id = organisation_id if not organisation["is_engaged"]: row.operator("bim.remove_organisation", icon="X", text="").organisation_id = organisation_id diff --git a/src/blenderbim/blenderbim/bim/module/patch/__init__.py b/src/blenderbim/blenderbim/bim/module/patch/__init__.py index 12aec24743..b5c7ef84c6 100644 --- a/src/blenderbim/blenderbim/bim/module/patch/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/patch/__init__.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/module/patch/operator.py b/src/blenderbim/blenderbim/bim/module/patch/operator.py index ea76cd51c3..e8c4d1b89c 100644 --- a/src/blenderbim/blenderbim/bim/module/patch/operator.py +++ b/src/blenderbim/blenderbim/bim/module/patch/operator.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -68,7 +67,7 @@ class ExecuteIfcPatch(bpy.types.Operator): @classmethod def poll(cls, context): input_file = context.scene.BIMPatchProperties.ifc_patch_input - return os.path.isfile(input_file) and "ifc" in os.path.splitext(input_file)[1] + return os.path.isfile(input_file) and "ifc" in os.path.splitext(input_file)[1].lower() def execute(self, context): props = context.scene.BIMPatchProperties @@ -100,7 +99,7 @@ class UpdateIfcPatchArguments(bpy.types.Operator): return {"FINISHED"} patch_args = context.scene.BIMPatchProperties.ifc_patch_args_attr patch_args.clear() - docs = ifcpatch.extract_docs(self.recipe, "Patcher", "__init__", ("src", "file", "logger", "args")) + docs = ifcpatch.extract_docs(self.recipe, "Patcher", "__init__", ("src", "file", "logger", "args")) if docs and "inputs" in docs: inputs = docs["inputs"] for arg_name in inputs: diff --git a/src/blenderbim/blenderbim/bim/module/patch/prop.py b/src/blenderbim/blenderbim/bim/module/patch/prop.py index ac9e09faa9..27c0e44ab5 100644 --- a/src/blenderbim/blenderbim/bim/module/patch/prop.py +++ b/src/blenderbim/blenderbim/bim/module/patch/prop.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -43,7 +42,7 @@ def purge(): ifcpatchrecipes_enum = [] -def getIfcPatchRecipes(self, context): +def get_ifcpatch_recipes(self, context): global ifcpatchrecipes_enum if len(ifcpatchrecipes_enum) < 1: ifcpatchrecipes_enum.clear() @@ -53,15 +52,16 @@ def getIfcPatchRecipes(self, context): if f == "__init__": continue docs = ifcpatch.extract_docs(f, "Patcher", "__init__", ("src", "file", "logger", "args")) - ifcpatchrecipes_enum.append((f, f, docs.get("description","") if docs else "")) + ifcpatchrecipes_enum.append((f, f, docs.get("description", "") if docs else "")) return ifcpatchrecipes_enum + def update_ifc_patch_recipe(self, context): - bpy.ops.bim.update_ifc_patch_arguments(recipe = self.ifc_patch_recipes) + bpy.ops.bim.update_ifc_patch_arguments(recipe=self.ifc_patch_recipes) class BIMPatchProperties(PropertyGroup): - ifc_patch_recipes: EnumProperty(items=getIfcPatchRecipes, name="Recipes", update=update_ifc_patch_recipe) + ifc_patch_recipes: EnumProperty(items=get_ifcpatch_recipes, name="Recipes", update=update_ifc_patch_recipe) ifc_patch_input: StringProperty(default="", name="IFC Patch Input IFC") ifc_patch_output: StringProperty(default="", name="IFC Patch Output IFC") ifc_patch_args: StringProperty(default="", name="Arguments") diff --git a/src/blenderbim/blenderbim/bim/module/patch/ui.py b/src/blenderbim/blenderbim/bim/module/patch/ui.py index 1617adbb27..ec699ab3e0 100644 --- a/src/blenderbim/blenderbim/bim/module/patch/ui.py +++ b/src/blenderbim/blenderbim/bim/module/patch/ui.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -37,8 +36,7 @@ class BIM_PT_patch(bpy.types.Panel): scene = context.scene props = scene.BIMPatchProperties row = layout.row() - row.prop(props, "ifc_patch_recipes") - + row.prop(props, "ifc_patch_recipes") row = layout.row(align=True) row.prop(props, "ifc_patch_input") diff --git a/src/blenderbim/blenderbim/bim/module/profile/__init__.py b/src/blenderbim/blenderbim/bim/module/profile/__init__.py index 60e3baa2c2..18c05e7619 100644 --- a/src/blenderbim/blenderbim/bim/module/profile/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/profile/__init__.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/module/profile/operator.py b/src/blenderbim/blenderbim/bim/module/profile/operator.py index f94f13bfb0..76ea8fa98a 100644 --- a/src/blenderbim/blenderbim/bim/module/profile/operator.py +++ b/src/blenderbim/blenderbim/bim/module/profile/operator.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/module/profile/prop.py b/src/blenderbim/blenderbim/bim/module/profile/prop.py index 868093f64a..1f8f06ca33 100644 --- a/src/blenderbim/blenderbim/bim/module/profile/prop.py +++ b/src/blenderbim/blenderbim/bim/module/profile/prop.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -35,6 +34,7 @@ from bpy.props import ( CollectionProperty, ) + class Profile(PropertyGroup): name: StringProperty(name="Name") ifc_definition_id: IntProperty(name="IFC Definition ID") diff --git a/src/blenderbim/blenderbim/bim/module/profile/ui.py b/src/blenderbim/blenderbim/bim/module/profile/ui.py index 3d81fce607..8044de9f54 100644 --- a/src/blenderbim/blenderbim/bim/module/profile/ui.py +++ b/src/blenderbim/blenderbim/bim/module/profile/ui.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/module/project/__init__.py b/src/blenderbim/blenderbim/bim/module/project/__init__.py index e05b1c8294..a486c8d554 100644 --- a/src/blenderbim/blenderbim/bim/module/project/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/project/__init__.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -22,7 +21,13 @@ from . import ui, prop, operator classes = ( operator.CreateProject, - operator.CreateProjectLibrary, + operator.LoadProject, + operator.UnloadProject, + operator.LoadProjectElements, + operator.LinkIfc, + operator.UnlinkIfc, + operator.UnloadLink, + operator.LoadLink, operator.SelectLibraryFile, operator.ChangeLibraryElement, operator.RefreshLibrary, @@ -35,10 +40,15 @@ classes = ( operator.DisableEditingHeader, operator.EditHeader, prop.LibraryElement, + prop.FilterCategory, + prop.Link, prop.BIMProjectProperties, ui.BIM_PT_project, ui.BIM_PT_project_library, + ui.BIM_PT_links, ui.BIM_UL_library, + ui.BIM_UL_filter_categories, + ui.BIM_UL_links, ) diff --git a/src/blenderbim/blenderbim/bim/module/project/operator.py b/src/blenderbim/blenderbim/bim/module/project/operator.py index 600364a524..9f39040977 100644 --- a/src/blenderbim/blenderbim/bim/module/project/operator.py +++ b/src/blenderbim/blenderbim/bim/module/project/operator.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -17,12 +16,15 @@ # You should have received a copy of the GNU General Public License # along with BlenderBIM Add-on. If not, see . +import os import bpy +import time import logging +import tempfile import ifcopenshell import ifcopenshell.api +import ifcopenshell.util.selector import ifcopenshell.util.representation -import bpy import blenderbim.bim.handler from blenderbim.bim.ifc import IfcStore from blenderbim.bim import import_ifc @@ -98,46 +100,6 @@ class CreateProject(bpy.types.Operator): IfcStore.file = data["file"] -class CreateProjectLibrary(bpy.types.Operator): - bl_idname = "bim.create_project_library" - bl_label = "Create Project Library" - bl_options = {"REGISTER", "UNDO"} - - def execute(self, context): - IfcStore.begin_transaction(self) - IfcStore.add_transaction_operation(self, rollback=self.rollback, commit=lambda data: True) - result = self._execute(context) - self.transaction_data = {"file": self.file} - IfcStore.add_transaction_operation(self, rollback=lambda data: True, commit=self.commit) - IfcStore.end_transaction(self) - return result - - def _execute(self, context): - self.file = IfcStore.get_file() - if self.file: - return {"FINISHED"} - - IfcStore.file = ifcopenshell.api.run( - "project.create_file", **{"version": context.scene.BIMProperties.export_schema} - ) - self.file = IfcStore.get_file() - - if self.file.schema == "IFC2X3": - bpy.ops.bim.add_person() - bpy.ops.bim.add_organisation() - - project_library = bpy.data.objects.new("My Project Library", None) - bpy.ops.bim.assign_class(obj=project_library.name, ifc_class="IfcProjectLibrary") - bpy.ops.bim.assign_unit() - return {"FINISHED"} - - def rollback(self, data): - IfcStore.file = None - - def commit(self, data): - IfcStore.file = data["file"] - - class SelectLibraryFile(bpy.types.Operator): bl_idname = "bim.select_library_file" bl_label = "Select Library File" @@ -158,6 +120,7 @@ class SelectLibraryFile(bpy.types.Operator): IfcStore.library_path = self.filepath IfcStore.library_file = ifcopenshell.open(self.filepath) bpy.ops.bim.refresh_library() + context.area.tag_redraw() return {"FINISHED"} def invoke(self, context, event): @@ -205,29 +168,27 @@ class ChangeLibraryElement(bpy.types.Operator): def execute(self, context): self.props = context.scene.BIMProjectProperties + self.file = IfcStore.get_file() + self.library_file = IfcStore.library_file ifc_classes = set() self.props.active_library_element = self.element_name crumb = self.props.library_breadcrumb.add() crumb.name = self.element_name - elements = IfcStore.library_file.by_type(self.element_name) + elements = self.library_file.by_type(self.element_name) [ifc_classes.add(e.is_a()) for e in elements] self.props.library_elements.clear() if len(ifc_classes) == 1 and list(ifc_classes)[0] == self.element_name: for name, ifc_definition_id in sorted([(self.get_name(e), e.id()) for e in elements]): - new = self.props.library_elements.add() - new.name = name - new.ifc_definition_id = ifc_definition_id - element = IfcStore.library_file.by_id(ifc_definition_id) - if IfcStore.library_file.schema == "IFC2X3" or not IfcStore.library_file.by_type("IfcProjectLibrary"): - new.is_declared = False - elif getattr(element, "HasContext", None) and element.HasContext[0].RelatingContext.is_a( - "IfcProjectLibrary" - ): - new.is_declared = True + self.add_library_asset(name, ifc_definition_id) else: for ifc_class in sorted(ifc_classes): + if ifc_class == self.element_name: + continue new = self.props.library_elements.add() new.name = ifc_class + for name, ifc_definition_id, ifc_class in sorted([(self.get_name(e), e.id(), e.is_a()) for e in elements]): + if ifc_class == self.element_name: + self.add_library_asset(name, ifc_definition_id) return {"FINISHED"} def get_name(self, element): @@ -235,6 +196,26 @@ class ChangeLibraryElement(bpy.types.Operator): return element.ProfileName or "Unnamed" return element.Name or "Unnamed" + def add_library_asset(self, name, ifc_definition_id): + new = self.props.library_elements.add() + new.name = name + new.ifc_definition_id = ifc_definition_id + element = self.library_file.by_id(ifc_definition_id) + if self.library_file.schema == "IFC2X3" or not self.library_file.by_type("IfcProjectLibrary"): + new.is_declared = False + elif getattr(element, "HasContext", None) and element.HasContext[0].RelatingContext.is_a("IfcProjectLibrary"): + new.is_declared = True + try: + if element.is_a("IfcMaterial"): + next(e for e in self.file.by_type("IfcMaterial") if e.Name == name) + elif element.is_a("IfcProfileDef"): + next(e for e in self.file.by_type("IfcProfileDef") if e.ProfileName == name) + else: + self.file.by_guid(element.GlobalId) + new.is_appended = True + except (AttributeError, RuntimeError, StopIteration): + new.is_appended = False + class RewindLibrary(bpy.types.Operator): bl_idname = "bim.rewind_library" @@ -340,6 +321,7 @@ class AppendLibraryElement(bpy.types.Operator): bl_label = "Append Library Element" bl_options = {"REGISTER", "UNDO"} definition: bpy.props.IntProperty() + prop_index: bpy.props.IntProperty() @classmethod def poll(cls, context): @@ -362,6 +344,7 @@ class AppendLibraryElement(bpy.types.Operator): self.import_type_from_ifc(element, context) elif element.is_a("IfcMaterial"): self.import_material_from_ifc(element, context) + context.scene.BIMProjectProperties.library_elements[self.prop_index].is_appended = True blenderbim.bim.handler.purge_module_data() return {"FINISHED"} @@ -394,7 +377,7 @@ class AppendLibraryElement(bpy.types.Operator): self.import_type_materials(element, ifc_importer) self.import_type_styles(element, ifc_importer) ifc_importer.create_type_product(element) - ifc_importer.place_objects_in_spatial_tree() + ifc_importer.place_objects_in_collections() def import_type_materials(self, element, ifc_importer): for rel in element.HasAssociations: @@ -522,3 +505,203 @@ class DisableEditingHeader(bpy.types.Operator): def execute(self, context): context.scene.BIMProjectProperties.is_editing = False return {"FINISHED"} + + +class LoadProject(bpy.types.Operator): + bl_idname = "bim.load_project" + bl_label = "Load Project" + bl_options = {"REGISTER", "UNDO"} + filepath: bpy.props.StringProperty(subtype="FILE_PATH") + filter_glob: bpy.props.StringProperty(default="*.ifc;*.ifczip;*.ifcxml", options={"HIDDEN"}) + is_advanced: bpy.props.BoolProperty(name="Enable Advanced Mode", default=False) + + def execute(self, context): + if not os.path.exists(self.filepath) or "ifc" not in os.path.splitext(self.filepath)[1].lower(): + return {"FINISHED"} + context.scene.BIMProperties.ifc_file = self.filepath + context.scene.BIMProjectProperties.is_loading = True + if not self.is_advanced: + bpy.ops.bim.load_project_elements() + return {"FINISHED"} + + def invoke(self, context, event): + context.window_manager.fileselect_add(self) + return {"RUNNING_MODAL"} + + +class UnloadProject(bpy.types.Operator): + bl_idname = "bim.unload_project" + bl_label = "Unload Project" + bl_options = {"REGISTER", "UNDO"} + + def execute(self, context): + IfcStore.purge() + context.scene.BIMProperties.ifc_file = "" + context.scene.BIMProjectProperties.is_loading = False + return {"FINISHED"} + + +class LoadProjectElements(bpy.types.Operator): + bl_idname = "bim.load_project_elements" + bl_label = "Load Project Elements" + bl_options = {"REGISTER", "UNDO"} + + def execute(self, context): + self.props = context.scene.BIMProjectProperties + self.file = IfcStore.get_file() + start = time.time() + logger = logging.getLogger("ImportIFC") + path_log = os.path.join(context.scene.BIMProperties.data_dir, "process.log") + if not os.access(context.scene.BIMProperties.data_dir, os.W_OK): + path_log = os.path.join(tempfile.mkdtemp(), "process.log") + logging.basicConfig( + filename=path_log, + filemode="a", + level=logging.DEBUG, + ) + settings = import_ifc.IfcImportSettings.factory(context, context.scene.BIMProperties.ifc_file, logger) + settings.has_filter = self.props.filter_mode != "NONE" + if self.props.filter_mode == "DECOMPOSITION": + settings.elements = self.get_decomposition_elements() + elif self.props.filter_mode == "IFC_CLASS": + settings.elements = self.get_ifc_class_elements() + elif self.props.filter_mode == "WHITELIST": + settings.elements = self.get_whitelist_elements() + elif self.props.filter_mode == "BLACKLIST": + settings.elements = self.get_blacklist_elements() + settings.collection_mode = self.props.collection_mode + settings.should_use_cpu_multiprocessing = self.props.should_use_cpu_multiprocessing + settings.should_merge_by_class = self.props.should_merge_by_class + settings.should_merge_by_material = self.props.should_merge_by_material + settings.should_merge_materials_by_colour = self.props.should_merge_materials_by_colour + settings.should_clean_mesh = self.props.should_clean_mesh + settings.deflection_tolerance = self.props.deflection_tolerance + settings.angular_tolerance = self.props.angular_tolerance + settings.should_offset_model = self.props.should_offset_model + settings.model_offset_coordinates = ( + [float(o) for o in self.props.model_offset_coordinates.split(",")] + if self.props.model_offset_coordinates + else (0, 0, 0) + ) + settings.logger.info("Starting import") + ifc_importer = import_ifc.IfcImporter(settings) + ifc_importer.execute() + settings.logger.info("Import finished in {:.2f} seconds".format(time.time() - start)) + print("Import finished in {:.2f} seconds".format(time.time() - start)) + context.scene.BIMProjectProperties.is_loading = False + return {"FINISHED"} + + def get_decomposition_elements(self): + containers = set() + for filter_category in self.props.filter_categories: + if not filter_category.is_selected: + continue + container = self.file.by_id(filter_category.ifc_definition_id) + while container: + containers.add(container) + container = ifcopenshell.util.element.get_aggregate(container) + if container.is_a("IfcContext"): + container = None + elements = set() + for container in containers: + for rel in container.ContainsElements: + elements.update(rel.RelatedElements) + self.append_decomposed_elements(elements) + return elements + + def append_decomposed_elements(self, elements): + decomposed_elements = set() + for element in elements: + if element.IsDecomposedBy: + for subelement in element.IsDecomposedBy[0].RelatedObjects: + decomposed_elements.add(subelement) + if decomposed_elements: + self.append_decomposed_elements(decomposed_elements) + elements.update(decomposed_elements) + + def get_ifc_class_elements(self): + elements = set() + for filter_category in self.props.filter_categories: + if not filter_category.is_selected: + continue + elements.update(self.file.by_type(filter_category.name, include_subtypes=False)) + return elements + + def get_whitelist_elements(self): + selector = ifcopenshell.util.selector.Selector() + return set(selector.parse(self.file, self.props.filter_query)) + + def get_blacklist_elements(self): + selector = ifcopenshell.util.selector.Selector() + return set(self.file.by_type("IfcElement")) - set(selector.parse(self.file, self.props.filter_query)) + + +class LinkIfc(bpy.types.Operator): + bl_idname = "bim.link_ifc" + bl_label = "Link IFC" + bl_options = {"REGISTER", "UNDO"} + filepath: bpy.props.StringProperty(subtype="FILE_PATH") + filter_glob: bpy.props.StringProperty(default="*.blend;*.blend1", options={"HIDDEN"}) + + def execute(self, context): + new = context.scene.BIMProjectProperties.links.add() + new.name = self.filepath + bpy.ops.bim.load_link(filepath=self.filepath) + return {"FINISHED"} + + def invoke(self, context, event): + context.window_manager.fileselect_add(self) + return {"RUNNING_MODAL"} + + +class UnlinkIfc(bpy.types.Operator): + bl_idname = "bim.unlink_ifc" + bl_label = "UnLink IFC" + bl_options = {"REGISTER", "UNDO"} + filepath: bpy.props.StringProperty() + + def execute(self, context): + bpy.ops.bim.unload_link(filepath=self.filepath) + index = context.scene.BIMProjectProperties.links.find(self.filepath) + if index != -1: + context.scene.BIMProjectProperties.links.remove(index) + return {"FINISHED"} + + +class UnloadLink(bpy.types.Operator): + bl_idname = "bim.unload_link" + bl_label = "Unload Link" + bl_options = {"REGISTER", "UNDO"} + filepath: bpy.props.StringProperty() + + def execute(self, context): + for collection in context.scene.collection.children: + if collection.library and collection.library.filepath == self.filepath: + context.scene.collection.children.unlink(collection) + for scene in bpy.data.scenes: + if scene.library and scene.library.filepath == self.filepath: + bpy.data.scenes.remove(scene) + link = context.scene.BIMProjectProperties.links.get(self.filepath) + link.is_loaded = False + return {"FINISHED"} + + +class LoadLink(bpy.types.Operator): + bl_idname = "bim.load_link" + bl_label = "Load Link" + bl_options = {"REGISTER", "UNDO"} + filepath: bpy.props.StringProperty() + + def execute(self, context): + with bpy.data.libraries.load(self.filepath, link=True) as (data_from, data_to): + data_to.scenes = data_from.scenes + for scene in bpy.data.scenes: + if not scene.library or scene.library.filepath != self.filepath: + continue + for child in scene.collection.children: + if "IfcProject" not in child.name: + continue + bpy.data.scenes[0].collection.children.link(child) + link = context.scene.BIMProjectProperties.links.get(self.filepath) + link.is_loaded = True + return {"FINISHED"} diff --git a/src/blenderbim/blenderbim/bim/module/project/prop.py b/src/blenderbim/blenderbim/bim/module/project/prop.py index 4a263dc3eb..aaff522274 100644 --- a/src/blenderbim/blenderbim/bim/module/project/prop.py +++ b/src/blenderbim/blenderbim/bim/module/project/prop.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -18,29 +17,63 @@ # along with BlenderBIM Add-on. If not, see . import bpy +from blenderbim.bim.ifc import IfcStore from blenderbim.bim.prop import StrProperty from bpy.types import PropertyGroup from bpy.props import ( - PointerProperty, StringProperty, - EnumProperty, BoolProperty, - IntProperty, FloatProperty, - FloatVectorProperty, + IntProperty, CollectionProperty, ) +def update_filter_mode(self, context): + self.filter_categories.clear() + if self.filter_mode == "NONE": + return + file = IfcStore.get_file() + if self.filter_mode == "DECOMPOSITION": + if file.schema == "IFC2X3": + elements = file.by_type("IfcSpatialStructureElement") + else: + elements = file.by_type("IfcSpatialElement") + for element in elements: + new = self.filter_categories.add() + new.name = "{}/{}".format(element.is_a(), element.Name or "Unnamed") + new.ifc_definition_id = element.id() + new.total_elements = sum([len(r.RelatedElements) for r in element.ContainsElements]) + elif self.filter_mode == "IFC_CLASS": + for ifc_class in sorted(list(set([e.is_a() for e in file.by_type("IfcElement")]))): + new = self.filter_categories.add() + new.name = ifc_class + new.total_elements = len(file.by_type(ifc_class, include_subtypes=False)) + + class LibraryElement(PropertyGroup): name: StringProperty(name="Name") ifc_definition_id: IntProperty(name="IFC Definition ID") is_declared: BoolProperty(name="Is Declared", default=False) + is_appended: BoolProperty(name="Is Appended", default=False) + + +class FilterCategory(PropertyGroup): + name: StringProperty(name="Name") + ifc_definition_id: IntProperty(name="IFC Definition ID") + is_selected: BoolProperty(name="Is Selected", default=False) + total_elements: IntProperty(name="Total Elements") + + +class Link(PropertyGroup): + name: StringProperty(name="Name") + is_loaded: BoolProperty(name="Is Loaded", default=False) class BIMProjectProperties(PropertyGroup): is_authoring: BoolProperty(name="Enable Authoring Mode", default=True) is_editing: BoolProperty(name="Is Editing", default=False) + is_loading: BoolProperty(name="Is Loading", default=False) mvd: StringProperty(name="MVD") author_name: StringProperty(name="Author") author_email: StringProperty(name="Author Email") @@ -51,3 +84,40 @@ class BIMProjectProperties(PropertyGroup): library_breadcrumb: CollectionProperty(name="Library Breadcrumb", type=StrProperty) library_elements: CollectionProperty(name="Library Elements", type=LibraryElement) active_library_element_index: IntProperty(name="Active Library Element Index") + collection_mode: bpy.props.EnumProperty( + items=[ + ("DECOMPOSITION", "Decomposition", "Collections represent aggregates and spatial containers"), + ("SPATIAL_DECOMPOSITION", "Spatial Decomposition", "Collections represent spatial containers"), + ("IFC_CLASS", "IFC Class", "Collections represent IFC class"), + ("NONE", "None", "No collections are created"), + ], + name="Collection Mode", + ) + filter_mode: bpy.props.EnumProperty( + items=[ + ("NONE", "None", "No filtering is performed"), + ("DECOMPOSITION", "Decomposition", "Filter objects by decomposition"), + ("IFC_CLASS", "IFC Class", "Filter objects by class"), + ("WHITELIST", "Whitelist", "Filter objects using a custom whitelist query"), + ("BLACKLIST", "Blacklist", "Filter objects using a custom blacklist query"), + ], + name="Filter Mode", + update=update_filter_mode, + ) + filter_categories: CollectionProperty(name="Filter Categories", type=FilterCategory) + active_filter_category_index: IntProperty(name="Active Filter Category Index") + filter_query: StringProperty(name="Filter Query") + should_use_cpu_multiprocessing: BoolProperty(name="Import with CPU Multiprocessing", default=True) + should_merge_by_class: BoolProperty(name="Import and Merge by Class", default=False) + should_merge_by_material: BoolProperty(name="Import and Merge by Material", default=False) + should_merge_materials_by_colour: BoolProperty(name="Import and Merge Materials by Colour", default=False) + should_clean_mesh: BoolProperty(name="Import and Clean Mesh", default=True) + deflection_tolerance: FloatProperty(name="Import Deflection Tolerance", default=0.001) + angular_tolerance: FloatProperty(name="Import Angular Tolerance", default=0.5) + should_offset_model: BoolProperty(name="Import and Offset Model", default=False) + model_offset_coordinates: StringProperty(name="Model Offset Coordinates", default="0,0,0") + links: CollectionProperty(name="Links", type=Link) + active_link_index: IntProperty(name="Active Link Index") + + def get_library_element_index(self, lib_element): + return next((i for i in range(len(self.library_elements)) if self.library_elements[i] == lib_element)) diff --git a/src/blenderbim/blenderbim/bim/module/project/ui.py b/src/blenderbim/blenderbim/bim/module/project/ui.py index 75198cb957..a594ad5306 100644 --- a/src/blenderbim/blenderbim/bim/module/project/ui.py +++ b/src/blenderbim/blenderbim/bim/module/project/ui.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -33,12 +32,56 @@ class BIM_PT_project(Panel): self.layout.use_property_decorate = False self.layout.use_property_split = True props = context.scene.BIMProperties + pprops = context.scene.BIMProjectProperties self.file = IfcStore.get_file() - if self.file or props.ifc_file: + if pprops.is_loading: + self.draw_load_ui(context) + elif self.file or props.ifc_file: self.draw_project_ui(context) else: self.draw_create_project_ui(context) + def draw_load_ui(self, context): + pprops = context.scene.BIMProjectProperties + row = self.layout.row() + row.prop(pprops, "collection_mode") + row = self.layout.row() + row.prop(pprops, "filter_mode") + if pprops.filter_mode in ["DECOMPOSITION", "IFC_CLASS"]: + self.layout.template_list( + "BIM_UL_filter_categories", + "", + pprops, + "filter_categories", + pprops, + "active_filter_category_index", + ) + elif pprops.filter_mode in ["WHITELIST", "BLACKLIST"]: + row = self.layout.row() + row.prop(pprops, "filter_query") + row = self.layout.row() + row.prop(pprops, "should_use_cpu_multiprocessing") + row = self.layout.row() + row.prop(pprops, "should_merge_by_class") + row = self.layout.row() + row.prop(pprops, "should_merge_by_material") + row = self.layout.row() + row.prop(pprops, "should_merge_materials_by_colour") + row = self.layout.row() + row.prop(pprops, "should_clean_mesh") + row = self.layout.row() + row.prop(pprops, "deflection_tolerance") + row = self.layout.row() + row.prop(pprops, "angular_tolerance") + row = self.layout.row() + row.prop(pprops, "should_offset_model") + row = self.layout.row() + row.prop(pprops, "model_offset_coordinates") + + row = self.layout.row(align=True) + row.operator("bim.load_project_elements") + row.operator("bim.unload_project", text="", icon="CANCEL") + def draw_project_ui(self, context): props = context.scene.BIMProperties pprops = context.scene.BIMProjectProperties @@ -110,11 +153,9 @@ class BIM_PT_project(Panel): row.prop(props, "area_unit", text="Area Unit") row = self.layout.row() row.prop(props, "volume_unit", text="Volume Unit") - row = self.layout.row() + row = self.layout.row(align=True) row.operator("bim.create_project") - if props.export_schema != "IFC2X3": - row = self.layout.row() - row.operator("bim.create_project_library") + row.operator("bim.load_project") class BIM_PT_project_library(Panel): @@ -158,6 +199,29 @@ class BIM_PT_project_library(Panel): ) +class BIM_PT_links(Panel): + bl_label = "IFC Links" + bl_idname = "BIM_PT_links" + bl_options = {"DEFAULT_CLOSED"} + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "scene" + + def draw(self, context): + self.props = context.scene.BIMProjectProperties + row = self.layout.row(align=True) + row.operator("bim.link_ifc") + if self.props.links: + self.layout.template_list( + "BIM_UL_links", + "", + self.props, + "links", + self.props, + "active_link_index", + ) + + class BIM_UL_library(UIList): def draw_item(self, context, layout, data, item, icon, active_data, active_propname): if item: @@ -178,5 +242,38 @@ class BIM_UL_library(UIList): op = row.operator("bim.assign_library_declaration", text="", icon="KEYFRAME", emboss=False) op.definition = item.ifc_definition_id if item.ifc_definition_id: - op = row.operator("bim.append_library_element", text="", icon="APPEND_BLEND") - op.definition = item.ifc_definition_id + if item.is_appended: + row.label(text="", icon="CHECKMARK") + else: + op = row.operator("bim.append_library_element", text="", icon="APPEND_BLEND") + op.definition = item.ifc_definition_id + op.prop_index = data.get_library_element_index(item) + + +class BIM_UL_filter_categories(UIList): + def draw_item(self, context, layout, data, item, icon, active_data, active_propname): + if item: + row = layout.row(align=True) + row.label(text=f"{item.name} ({item.total_elements})") + row.prop( + item, + "is_selected", + icon="CHECKBOX_HLT" if item.is_selected else "CHECKBOX_DEHLT", + text="", + emboss=False, + ) + + +class BIM_UL_links(UIList): + def draw_item(self, context, layout, data, item, icon, active_data, active_propname): + if item: + row = layout.row(align=True) + row.label(text=item.name) + if item.is_loaded: + op = row.operator("bim.unload_link", text="", icon="UNLINKED") + op.filepath = item.name + else: + op = row.operator("bim.load_link", text="", icon="LINKED") + op.filepath = item.name + op = row.operator("bim.unlink_ifc", text="", icon="X") + op.filepath = item.name diff --git a/src/blenderbim/blenderbim/bim/module/pset/__init__.py b/src/blenderbim/blenderbim/bim/module/pset/__init__.py index ace6e1253f..8c360d3aa8 100644 --- a/src/blenderbim/blenderbim/bim/module/pset/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/pset/__init__.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -40,6 +39,7 @@ classes = ( ui.BIM_PT_material_psets, ui.BIM_PT_task_qtos, ui.BIM_PT_resource_qtos, + ui.BIM_PT_resource_psets, ui.BIM_PT_profile_psets, ui.BIM_PT_work_schedule_psets, ) diff --git a/src/blenderbim/blenderbim/bim/module/pset/operator.py b/src/blenderbim/blenderbim/bim/module/pset/operator.py index 3e6a6edf82..1747d047d4 100644 --- a/src/blenderbim/blenderbim/bim/module/pset/operator.py +++ b/src/blenderbim/blenderbim/bim/module/pset/operator.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/module/pset/prop.py b/src/blenderbim/blenderbim/bim/module/pset/prop.py index ee2c1c4f4a..f47cadd87e 100644 --- a/src/blenderbim/blenderbim/bim/module/pset/prop.py +++ b/src/blenderbim/blenderbim/bim/module/pset/prop.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -56,9 +55,7 @@ def getPsetNames(self, context): 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] - assigned_names = [ - Data.psets[p]["Name"] for p in Data.products[obj.BIMObjectProperties.ifc_definition_id]["psets"] - ] + 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] @@ -80,6 +77,17 @@ def getTaskQtoNames(self, context): return qtonames[ifc_class] +def getResourcePsetNames(self, context): + global psetnames + rprops = context.scene.BIMResourceProperties + rtprops = context.scene.BIMResourceTreeProperties + ifc_class = IfcStore.get_file().by_id(rtprops.resources[rprops.active_resource_index].ifc_definition_id).is_a() + 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] + + def getResourceQtoNames(self, context): global qtonames rprops = context.scene.BIMResourceProperties @@ -147,6 +155,7 @@ class ResourcePsetProperties(PropertyGroup): active_pset_id: IntProperty(name="Active Pset ID") active_pset_name: StringProperty(name="Pset Name") properties: CollectionProperty(name="Properties", type=Attribute) + pset_name: EnumProperty(items=getResourcePsetNames, name="Pset Name") qto_name: EnumProperty(items=getResourceQtoNames, name="Qto Name") diff --git a/src/blenderbim/blenderbim/bim/module/pset/qto_calculator.py b/src/blenderbim/blenderbim/bim/module/pset/qto_calculator.py index 4e2469d0f6..8411267ed2 100644 --- a/src/blenderbim/blenderbim/bim/module/pset/qto_calculator.py +++ b/src/blenderbim/blenderbim/bim/module/pset/qto_calculator.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -162,7 +161,11 @@ class QtoCalculator: else: tf_tris = ( (me.vertices[tfv[0]], me.vertices[tfv[1]], me.vertices[tfv[2]]), - (me.vertices[tfv[2]], me.vertices[tfv[3]], me.vertices[tfv[0]],), + ( + me.vertices[tfv[2]], + me.vertices[tfv[3]], + me.vertices[tfv[0]], + ), ) for tf_iter in tf_tris: diff --git a/src/blenderbim/blenderbim/bim/module/pset/ui.py b/src/blenderbim/blenderbim/bim/module/pset/ui.py index 75d087812a..90f5791adf 100644 --- a/src/blenderbim/blenderbim/bim/module/pset/ui.py +++ b/src/blenderbim/blenderbim/bim/module/pset/ui.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -304,6 +303,40 @@ class BIM_PT_resource_qtos(Panel): draw_psetqto_ui(context, qto_id, qto, props, self.layout, "Resource") +class BIM_PT_resource_psets(Panel): + bl_label = "IFC Resource Property Sets" + bl_idname = "BIM_PT_resource_psets" + bl_options = {"DEFAULT_CLOSED"} + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "scene" + bl_parent_id = "BIM_PT_resources" + + @classmethod + def poll(cls, context): + props = context.scene.BIMResourceProperties + total_resources = len(context.scene.BIMResourceTreeProperties.resources) + if total_resources > 0 and props.active_resource_index < total_resources: + return True + return False + + def draw(self, context): + props = context.scene.ResourcePsetProperties + rprops = context.scene.BIMResourceProperties + rtprops = context.scene.BIMResourceTreeProperties + ifc_definition_id = rtprops.resources[rprops.active_resource_index].ifc_definition_id + if ifc_definition_id not in Data.products: + Data.load(IfcStore.get_file(), ifc_definition_id) + row = self.layout.row(align=True) + row.prop(props, "pset_name", text="") + op = row.operator("bim.add_pset", icon="ADD", text="") + op.obj_type = "Resource" + + psets = [(pset_id, Data.psets[pset_id]) for pset_id in Data.products[ifc_definition_id]["psets"]] + for pset_id, pset in sorted(psets, key=lambda v: v[1]["Name"]): + draw_psetqto_ui(context, pset_id, pset, props, self.layout, "Resource") + + class BIM_PT_profile_psets(Panel): bl_label = "IFC Profile Property Sets" bl_idname = "BIM_PT_profile_psets" diff --git a/src/blenderbim/blenderbim/bim/module/pset_template/__init__.py b/src/blenderbim/blenderbim/bim/module/pset_template/__init__.py index 4094532ac0..e4e5591da4 100644 --- a/src/blenderbim/blenderbim/bim/module/pset_template/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/pset_template/__init__.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/module/pset_template/operator.py b/src/blenderbim/blenderbim/bim/module/pset_template/operator.py index 496ab64aac..b9e86ba16a 100644 --- a/src/blenderbim/blenderbim/bim/module/pset_template/operator.py +++ b/src/blenderbim/blenderbim/bim/module/pset_template/operator.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -73,9 +72,11 @@ class RemovePsetTemplate(bpy.types.Operator): props = context.scene.BIMPsetTemplateProperties if props.active_pset_template_id == int(props.pset_templates): bpy.ops.bim.disable_editing_pset_template() - ifcopenshell.api.run("pset_template.remove_pset_template", IfcStore.pset_template_file, **{ - "pset_template": IfcStore.pset_template_file.by_id(int(props.pset_templates)) - }) + ifcopenshell.api.run( + "pset_template.remove_pset_template", + IfcStore.pset_template_file, + **{"pset_template": IfcStore.pset_template_file.by_id(int(props.pset_templates))} + ) Data.load(IfcStore.pset_template_file) updatePsetTemplates(self, context) return {"FINISHED"} @@ -158,15 +159,19 @@ class EditPsetTemplate(bpy.types.Operator): def _execute(self, context): props = context.scene.BIMPsetTemplateProperties - ifcopenshell.api.run("pset_template.edit_pset_template", 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, + ifcopenshell.api.run( + "pset_template.edit_pset_template", + 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, + }, } - }) + ) Data.load(IfcStore.pset_template_file) updatePsetTemplates(self, context) bpy.ops.bim.disable_editing_pset_template() @@ -179,7 +184,6 @@ class EditPsetTemplate(bpy.types.Operator): IfcStore.pset_template_file.redo() - class SavePsetTemplateFile(bpy.types.Operator): bl_idname = "bim.save_pset_template_file" bl_label = "Save Pset Template File" @@ -208,9 +212,11 @@ class AddPropTemplate(bpy.types.Operator): def _execute(self, context): props = context.scene.BIMPsetTemplateProperties pset_template_id = props.active_pset_template_id or int(props.pset_templates) - ifcopenshell.api.run("pset_template.add_prop_template", IfcStore.pset_template_file, **{ - "pset_template": IfcStore.pset_template_file.by_id(pset_template_id) - }) + ifcopenshell.api.run( + "pset_template.add_prop_template", + IfcStore.pset_template_file, + **{"pset_template": IfcStore.pset_template_file.by_id(pset_template_id)} + ) Data.load(IfcStore.pset_template_file) return {"FINISHED"} @@ -238,9 +244,11 @@ class RemovePropTemplate(bpy.types.Operator): def _execute(self, context): props = context.scene.BIMPsetTemplateProperties - ifcopenshell.api.run("pset_template.remove_prop_template", IfcStore.pset_template_file, **{ - "prop_template": IfcStore.pset_template_file.by_id(self.prop_template) - }) + ifcopenshell.api.run( + "pset_template.remove_prop_template", + IfcStore.pset_template_file, + **{"prop_template": IfcStore.pset_template_file.by_id(self.prop_template)} + ) Data.load(IfcStore.pset_template_file) return {"FINISHED"} @@ -267,14 +275,18 @@ class EditPropTemplate(bpy.types.Operator): def _execute(self, context): props = context.scene.BIMPsetTemplateProperties - ifcopenshell.api.run("pset_template.edit_prop_template", 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, + ifcopenshell.api.run( + "pset_template.edit_prop_template", + 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, + }, } - }) + ) Data.load(IfcStore.pset_template_file) bpy.ops.bim.disable_editing_prop_template() return {"FINISHED"} diff --git a/src/blenderbim/blenderbim/bim/module/pset_template/prop.py b/src/blenderbim/blenderbim/bim/module/pset_template/prop.py index dae0f8d77f..6f41a98a23 100644 --- a/src/blenderbim/blenderbim/bim/module/pset_template/prop.py +++ b/src/blenderbim/blenderbim/bim/module/pset_template/prop.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/module/pset_template/ui.py b/src/blenderbim/blenderbim/bim/module/pset_template/ui.py index 0080ebb877..0049612fd5 100644 --- a/src/blenderbim/blenderbim/bim/module/pset_template/ui.py +++ b/src/blenderbim/blenderbim/bim/module/pset_template/ui.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/module/qto/__init__.py b/src/blenderbim/blenderbim/bim/module/qto/__init__.py index c99f657c4e..6583deae6a 100644 --- a/src/blenderbim/blenderbim/bim/module/qto/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/qto/__init__.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/module/qto/helper.py b/src/blenderbim/blenderbim/bim/module/qto/helper.py index 8826a8d5b7..80dd27b565 100644 --- a/src/blenderbim/blenderbim/bim/module/qto/helper.py +++ b/src/blenderbim/blenderbim/bim/module/qto/helper.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -38,25 +37,25 @@ def calculate_volumes(objs, context): def calculate_mesh_quantity(objs: bpy.types.Object, context, operation): - """Get the sum of the target quantity on all passed mesh objects + """Get the sum of the target quantity on all passed mesh objects - :param objs: iterable of mesh object - :param context: current execution context - :param operation: function which takes a single bmesh as an argument, returns a float value - :returns float: - """ - result = 0 - edit_mode = context.active_object.mode == "EDIT" - for obj in objs: - if edit_mode: - bm = bmesh.from_edit_mesh(obj.data) - result += operation(bm) - else: - bm = bmesh.new() - bm.from_mesh(obj.data) - result += operation(bm) - bm.free() - return result + :param objs: iterable of mesh object + :param context: current execution context + :param operation: function which takes a single bmesh as an argument, returns a float value + :returns float: + """ + result = 0 + edit_mode = context.active_object.mode == "EDIT" + for obj in objs: + if edit_mode: + bm = bmesh.from_edit_mesh(obj.data) + result += operation(bm) + else: + bm = bmesh.new() + bm.from_mesh(obj.data) + result += operation(bm) + bm.free() + return result def calculate_formwork_area(objs, context): diff --git a/src/blenderbim/blenderbim/bim/module/qto/operator.py b/src/blenderbim/blenderbim/bim/module/qto/operator.py index de4b968764..0a40caddcc 100644 --- a/src/blenderbim/blenderbim/bim/module/qto/operator.py +++ b/src/blenderbim/blenderbim/bim/module/qto/operator.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -128,11 +127,6 @@ class QuantifyObjects(bpy.types.Operator): product=self.file.by_id(obj.BIMObjectProperties.ifc_definition_id), name=props.qto_name, ) - ifcopenshell.api.run( - "pset.edit_qto", - self.file, - qto=qto, - properties={props.prop_name: result} - ) + ifcopenshell.api.run("pset.edit_qto", self.file, qto=qto, properties={props.prop_name: result}) PsetData.load(self.file, obj.BIMObjectProperties.ifc_definition_id) return {"FINISHED"} diff --git a/src/blenderbim/blenderbim/bim/module/qto/prop.py b/src/blenderbim/blenderbim/bim/module/qto/prop.py index 4fec6fec26..0bc3eb4af9 100644 --- a/src/blenderbim/blenderbim/bim/module/qto/prop.py +++ b/src/blenderbim/blenderbim/bim/module/qto/prop.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -31,6 +30,7 @@ from bpy.props import ( CollectionProperty, ) + class BIMQtoProperties(PropertyGroup): qto_result: StringProperty(default="", name="Qto Result") qto_methods: EnumProperty( diff --git a/src/blenderbim/blenderbim/bim/module/qto/ui.py b/src/blenderbim/blenderbim/bim/module/qto/ui.py index aea862d0ce..0b021e66ff 100644 --- a/src/blenderbim/blenderbim/bim/module/qto/ui.py +++ b/src/blenderbim/blenderbim/bim/module/qto/ui.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/module/resource/__init__.py b/src/blenderbim/blenderbim/bim/module/resource/__init__.py index 8794902494..b2bdd02794 100644 --- a/src/blenderbim/blenderbim/bim/module/resource/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/resource/__init__.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -26,16 +25,29 @@ classes = ( operator.EnableEditingResource, operator.LoadResources, operator.AddResource, + operator.AddResourceQuantity, operator.EditResource, operator.RemoveResource, + operator.RemoveResourceQuantity, operator.LoadResourceProperties, operator.ExpandResource, operator.ContractResource, operator.AssignResource, operator.UnassignResource, operator.EnableEditingResourceTime, + operator.EnableEditingResourceQuantity, + operator.EnableEditingResourceBaseQuantity, + operator.EnableEditingResourceCosts, + operator.EnableEditingResourceCostValueFormula, + operator.EnableEditingResourceCostValue, operator.EditResourceTime, + operator.EditResourceQuantity, + operator.EditResourceCostValue, + operator.EditResourceCostValueFormula, operator.DisableEditingResourceTime, + operator.DisableEditingResourceQuantity, + operator.DisableEditingResourceCostValue, + operator.CalculateResourceWork, prop.Resource, prop.BIMResourceProperties, prop.BIMResourceTreeProperties, @@ -48,6 +60,7 @@ def register(): bpy.types.Scene.BIMResourceProperties = bpy.props.PointerProperty(type=prop.BIMResourceProperties) bpy.types.Scene.BIMResourceTreeProperties = bpy.props.PointerProperty(type=prop.BIMResourceTreeProperties) + def unregister(): del bpy.types.Scene.BIMResourceProperties del bpy.types.Scene.BIMResourceTreeProperties diff --git a/src/blenderbim/blenderbim/bim/module/resource/operator.py b/src/blenderbim/blenderbim/bim/module/resource/operator.py index 34b70a80f4..412101ef71 100644 --- a/src/blenderbim/blenderbim/bim/module/resource/operator.py +++ b/src/blenderbim/blenderbim/bim/module/resource/operator.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -19,14 +18,15 @@ import bpy import json +import time +import isodate import ifcopenshell.api -from blenderbim.bim.ifc import IfcStore -from ifcopenshell.api.resource.data import Data import blenderbim.bim.helper import blenderbim.bim.module.sequence.helper as helper -import time from datetime import datetime -import isodate +from blenderbim.bim.ifc import IfcStore +from ifcopenshell.api.resource.data import Data +from ifcopenshell.api.unit.data import Data as UnitData class LoadResources(bpy.types.Operator): @@ -95,7 +95,6 @@ class EnableEditingResource(bpy.types.Operator): new.enum_value = data[attribute.name()] - class LoadResourceProperties(bpy.types.Operator): bl_idname = "bim.load_resource_properties" bl_label = "Load Resource Properties" @@ -301,7 +300,9 @@ class EnableEditingResourceTime(bpy.types.Operator): data = Data.resource_times[resource_time_id] - blenderbim.bim.helper.import_attributes("IfcResourceTime", props.resource_time_attributes, data, self.import_attributes) + blenderbim.bim.helper.import_attributes( + "IfcResourceTime", props.resource_time_attributes, data, self.import_attributes + ) props.active_resource_time_id = resource_time_id props.active_resource_id = self.resource props.editing_resource_type = "USAGE" @@ -319,7 +320,9 @@ class EnableEditingResourceTime(bpy.types.Operator): return True def add_resource_time(self): - resource_time = ifcopenshell.api.run("resource.add_resource_time", self.file, resource=self.file.by_id(self.resource)) + resource_time = ifcopenshell.api.run( + "resource.add_resource_time", self.file, resource=self.file.by_id(self.resource) + ) Data.load(self.file) return resource_time @@ -345,7 +348,9 @@ class EditResourceTime(bpy.types.Operator): def _execute(self, context): self.props = context.scene.BIMResourceProperties - attributes = blenderbim.bim.helper.export_attributes(self.props.resource_time_attributes, self.export_attributes) + attributes = blenderbim.bim.helper.export_attributes( + self.props.resource_time_attributes, self.export_attributes + ) self.file = IfcStore.get_file() ifcopenshell.api.run( @@ -365,9 +370,310 @@ class EditResourceTime(bpy.types.Operator): return True attributes[prop.name] = helper.parse_datetime(prop.string_value) return True - elif prop.name =="LevelingDelay" or "Work" in prop.name: + elif prop.name == "LevelingDelay" or "Work" in prop.name: if prop.is_null: attributes[prop.name] = None return True attributes[prop.name] = helper.parse_duration(prop.string_value) return True + + +class CalculateResourceWork(bpy.types.Operator): + bl_idname = "bim.calculate_resource_work" + bl_label = "Calculate Resource Work" + bl_options = {"REGISTER", "UNDO"} + resource: bpy.props.IntProperty() + + def execute(self, context): + self.file = IfcStore.get_file() + ifcopenshell.api.run("resource.calculate_resource_work", self.file, resource=self.file.by_id(self.resource)) + Data.load(self.file) + bpy.ops.bim.load_resources() + return {"FINISHED"} + + +class EnableEditingResourceCosts(bpy.types.Operator): + bl_idname = "bim.enable_editing_resource_costs" + bl_label = "Enable Editing Resource Costs" + bl_options = {"REGISTER", "UNDO"} + resource: bpy.props.IntProperty() + + def execute(self, context): + props = context.scene.BIMResourceProperties + props.active_resource_id = self.resource + props.editing_resource_type = "COSTS" + bpy.ops.bim.disable_editing_resource_cost_value() + return {"FINISHED"} + + +class DisableEditingResourceCostValue(bpy.types.Operator): + bl_idname = "bim.disable_editing_resource_cost_value" + bl_label = "Disable Editing Resource Cost Value" + bl_options = {"REGISTER", "UNDO"} + + def execute(self, context): + props = context.scene.BIMResourceProperties + props.active_cost_value_id = 0 + props.cost_value_editing_type = "" + return {"FINISHED"} + + +class DisableEditingResourceCostValue(bpy.types.Operator): + bl_idname = "bim.disable_editing_resource_cost_value" + bl_label = "Disable Editing Resource Cost Value" + bl_options = {"REGISTER", "UNDO"} + + def execute(self, context): + props = context.scene.BIMResourceProperties + props.active_cost_value_id = 0 + props.cost_value_editing_type = "" + return {"FINISHED"} + + +class EnableEditingResourceCostValueFormula(bpy.types.Operator): + bl_idname = "bim.enable_editing_resource_cost_value_formula" + bl_label = "Enable Editing Resource Cost Value Formula" + bl_options = {"REGISTER", "UNDO"} + cost_value: bpy.props.IntProperty() + + def execute(self, context): + self.props = context.scene.BIMResourceProperties + self.props.cost_value_attributes.clear() + self.props.active_cost_value_id = self.cost_value + self.props.cost_value_editing_type = "FORMULA" + self.props.cost_value_formula = Data.cost_values[self.cost_value]["Formula"] + return {"FINISHED"} + + +class EnableEditingResourceCostValue(bpy.types.Operator): + bl_idname = "bim.enable_editing_resource_cost_value" + bl_label = "Enable Editing Resource Cost Value" + bl_options = {"REGISTER", "UNDO"} + cost_value: bpy.props.IntProperty() + + def execute(self, context): + self.props = context.scene.BIMResourceProperties + self.props.cost_value_attributes.clear() + self.props.active_cost_value_id = self.cost_value + self.props.cost_value_editing_type = "ATTRIBUTES" + data = Data.cost_values[self.cost_value] + + blenderbim.bim.helper.import_attributes( + data["type"], + self.props.cost_value_attributes, + data, + lambda name, prop, data: self.import_attributes(name, prop, data, context), + ) + return {"FINISHED"} + + def import_attributes(self, name, prop, data, context): + if name == "AppliedValue": + # TODO: for now, only support simple IfcValues (which are effectively IfcMonetaryMeasure) + prop = self.props.cost_value_attributes.add() + prop.data_type = "float" + prop.name = "AppliedValue" + prop.is_optional = True + prop.float_value = 0.0 if prop.is_null else data[name] + return True + elif name == "UnitBasis": + prop = self.props.cost_value_attributes.add() + prop.name = "UnitBasisValue" + prop.data_type = "float" + prop.is_null = data["UnitBasis"] is None + prop.is_optional = True + if data["UnitBasis"]: + prop.float_value = data["UnitBasis"]["ValueComponent"] or 0 + else: + prop.float_value = 0 + + prop = self.props.cost_value_attributes.add() + prop.name = "UnitBasisUnit" + prop.data_type = "enum" + prop.is_null = prop.is_optional = False + units = {} + for unit_id, unit in UnitData.units.items(): + if unit.get("UnitType", None) in [ + "AREAUNIT", + "LENGTHUNIT", + "TIMEUNIT", + "VOLUMEUNIT", + "MASSUNIT", + "USERDEFINED", + ]: + if unit["type"] == "IfcContextDependentUnit": + units[unit_id] = f"{unit['UnitType']} / {unit['Name']}" + else: + name = unit["Name"] + if unit.get("Prefix", None): + name = f"(unit['Prefix']) {name}" + units[unit_id] = f"{unit['UnitType']} / {name}" + prop.enum_items = json.dumps(units) + if data["UnitBasis"] and data["UnitBasis"]["UnitComponent"]: + prop.enum_value = str(data["UnitBasis"]["UnitComponent"]) + return True + + +class EditResourceCostValueFormula(bpy.types.Operator): + bl_idname = "bim.edit_resource_cost_value_formula" + bl_label = "Edit Resource Cost Value Formula" + bl_options = {"REGISTER", "UNDO"} + cost_value: bpy.props.IntProperty() + + def execute(self, context): + return IfcStore.execute_ifc_operator(self, context) + + def _execute(self, context): + props = context.scene.BIMResourceProperties + self.file = IfcStore.get_file() + ifcopenshell.api.run( + "cost.edit_cost_value_formula", + self.file, + **{"cost_value": self.file.by_id(self.cost_value), "formula": props.cost_value_formula}, + ) + Data.load(IfcStore.get_file()) + bpy.ops.bim.disable_editing_resource_cost_value() + return {"FINISHED"} + + +class EditResourceCostValue(bpy.types.Operator): + bl_idname = "bim.edit_resource_cost_value" + bl_label = "Edit Resource Cost Value" + bl_options = {"REGISTER", "UNDO"} + cost_value: bpy.props.IntProperty() + + def execute(self, context): + return IfcStore.execute_ifc_operator(self, context) + + def _execute(self, context): + props = context.scene.BIMResourceProperties + attributes = blenderbim.bim.helper.export_attributes( + props.cost_value_attributes, lambda attributes, prop: self.export_attributes(attributes, prop, context) + ) + self.file = IfcStore.get_file() + ifcopenshell.api.run( + "cost.edit_cost_value", + self.file, + **{"cost_value": self.file.by_id(self.cost_value), "attributes": attributes}, + ) + Data.load(IfcStore.get_file()) + bpy.ops.bim.disable_editing_resource_cost_value() + return {"FINISHED"} + + def export_attributes(self, attributes, prop, context): + if prop.name == "UnitBasisValue": + if prop.is_null: + attributes["UnitBasis"] = None + return True + attributes["UnitBasis"] = { + "ValueComponent": prop.float_value or 1, + "UnitComponent": IfcStore.get_file().by_id( + int(context.scene.BIMResourceProperties.cost_value_attributes.get("UnitBasisUnit").enum_value) + ), + } + return True + if prop.name == "UnitBasisUnit": + return True + + +class EnableEditingResourceBaseQuantity(bpy.types.Operator): + bl_idname = "bim.enable_editing_resource_base_quantity" + bl_label = "Enable Editing Resource Quantity" + bl_options = {"REGISTER", "UNDO"} + resource: bpy.props.IntProperty() + + def execute(self, context): + props = context.scene.BIMResourceProperties + props.active_resource_id = self.resource + props.editing_resource_type = "QUANTITY" + return {"FINISHED"} + + +class AddResourceQuantity(bpy.types.Operator): + bl_idname = "bim.add_resource_quantity" + bl_label = "Add Resource Quantity" + bl_options = {"REGISTER", "UNDO"} + resource: bpy.props.IntProperty() + ifc_class: bpy.props.StringProperty() + + def execute(self, context): + return IfcStore.execute_ifc_operator(self, context) + + def _execute(self, context): + self.file = IfcStore.get_file() + ifcopenshell.api.run( + "resource.add_resource_quantity", + self.file, + resource=self.file.by_id(self.resource), + ifc_class=self.ifc_class, + ) + Data.load(self.file) + return {"FINISHED"} + + +class RemoveResourceQuantity(bpy.types.Operator): + bl_idname = "bim.remove_resource_quantity" + bl_label = "Remove Resource Quantity" + bl_options = {"REGISTER", "UNDO"} + resource: bpy.props.IntProperty() + + def execute(self, context): + return IfcStore.execute_ifc_operator(self, context) + + def _execute(self, context): + self.file = IfcStore.get_file() + ifcopenshell.api.run( + "resource.remove_resource_quantity", + self.file, + resource=self.file.by_id(self.resource), + ) + Data.load(self.file) + return {"FINISHED"} + + +class EnableEditingResourceQuantity(bpy.types.Operator): + bl_idname = "bim.enable_editing_resource_quantity" + bl_label = "Enable Editing Resource Quantity" + bl_options = {"REGISTER", "UNDO"} + resource: bpy.props.IntProperty() + + def execute(self, context): + self.props = context.scene.BIMResourceProperties + self.props.quantity_attributes.clear() + self.props.is_editing_quantity = True + data = Data.resources[self.resource]["BaseQuantity"] + blenderbim.bim.helper.import_attributes(data["type"], self.props.quantity_attributes, data) + return {"FINISHED"} + + +class DisableEditingResourceQuantity(bpy.types.Operator): + bl_idname = "bim.disable_editing_resource_quantity" + bl_label = "Disable Editing Resource Quantity" + bl_options = {"REGISTER", "UNDO"} + + def execute(self, context): + props = context.scene.BIMResourceProperties + props.is_editing_quantity = False + return {"FINISHED"} + + +class EditResourceQuantity(bpy.types.Operator): + bl_idname = "bim.edit_resource_quantity" + bl_label = "Edit Resource Quantity" + bl_options = {"REGISTER", "UNDO"} + physical_quantity: bpy.props.IntProperty() + + def execute(self, context): + return IfcStore.execute_ifc_operator(self, context) + + def _execute(self, context): + props = context.scene.BIMResourceProperties + attributes = blenderbim.bim.helper.export_attributes(props.quantity_attributes) + self.file = IfcStore.get_file() + ifcopenshell.api.run( + "resource.edit_resource_quantity", + self.file, + **{"physical_quantity": self.file.by_id(self.physical_quantity), "attributes": attributes}, + ) + Data.load(IfcStore.get_file()) + bpy.ops.bim.disable_editing_resource_quantity() + return {"FINISHED"} diff --git a/src/blenderbim/blenderbim/bim/module/resource/prop.py b/src/blenderbim/blenderbim/bim/module/resource/prop.py index 5681deba24..178171cd37 100644 --- a/src/blenderbim/blenderbim/bim/module/resource/prop.py +++ b/src/blenderbim/blenderbim/bim/module/resource/prop.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -35,6 +34,14 @@ from bpy.props import ( ) +quantitytypes_enum = [] + + +def purge(): + global quantitytypes_enum + quantitytypes_enum = [] + + def updateResourceName(self, context): props = context.scene.BIMResourceProperties if not props.is_resource_update_enabled or self.name == "Unnamed": @@ -51,6 +58,18 @@ def updateResourceName(self, context): attribute.string_value = self.name +def get_quantity_types(self, context): + global quantitytypes_enum + if len(quantitytypes_enum) == 0 and IfcStore.get_schema(): + quantitytypes_enum.extend( + [ + (t.name(), t.name(), "") + for t in IfcStore.get_schema().declaration_by_name("IfcPhysicalSimpleQuantity").subtypes() + ] + ) + return quantitytypes_enum + + class Resource(PropertyGroup): name: StringProperty(name="Name", update=updateResourceName) ifc_definition_id: IntProperty(name="IFC Definition ID") @@ -74,3 +93,19 @@ class BIMResourceProperties(PropertyGroup): active_resource_time_id: IntProperty(name="Active Resource Usage Id") resource_time_attributes: CollectionProperty(name="Resource Usage Attributes", type=Attribute) editing_resource_type: StringProperty(name="Editing Resource Type") + cost_types: EnumProperty( + items=[ + ("FIXED", "Fixed", "The cost value is a fixed number"), + ("SUM", "Sum", "The cost value is automatically derived from the sum of all nested cost items"), + ("CATEGORY", "Category", "The cost value represents a single category"), + ], + name="Cost Types", + ) + cost_category: StringProperty(name="Cost Category") + active_cost_value_id: IntProperty(name="Active Resource Cost Value Id") + cost_value_editing_type: StringProperty(name="Cost Value Editing Type") + cost_value_attributes: CollectionProperty(name="Cost Value Attributes", type=Attribute) + cost_value_formula: StringProperty(name="Cost Value Formula") + quantity_types: EnumProperty(items=get_quantity_types, name="Quantity Types") + is_editing_quantity: BoolProperty(name="Is Editing Quantity") + quantity_attributes: CollectionProperty(name="Quantity Attributes", type=Attribute) diff --git a/src/blenderbim/blenderbim/bim/module/resource/ui.py b/src/blenderbim/blenderbim/bim/module/resource/ui.py index 8c63c25cab..5d6bf8950c 100644 --- a/src/blenderbim/blenderbim/bim/module/resource/ui.py +++ b/src/blenderbim/blenderbim/bim/module/resource/ui.py @@ -16,10 +16,10 @@ # You should have received a copy of the GNU General Public License # along with BlenderBIM Add-on. If not, see . +import blenderbim.bim.helper from bpy.types import Panel, UIList from blenderbim.bim.ifc import IfcStore from ifcopenshell.api.resource.data import Data -import blenderbim.bim.helper class BIM_PT_resources(Panel): @@ -52,36 +52,7 @@ class BIM_PT_resources(Panel): if not self.props.is_editing: return - row = self.layout.row(align=True) - op = row.operator("bim.add_resource", text="Add SubContract", icon="TEXT") - op.ifc_class = "IfcSubContractResource" - op.resource = 0 - op = row.operator("bim.add_resource", text="Add Crew", icon="COMMUNITY") - op.ifc_class = "IfcCrewResource" - op.resource = 0 - - icon_map = { - "IfcSubContractResource": "TEXT", - "IfcConstructionEquipmentResource": "TOOL_SETTINGS", - "IfcLaborResource": "OUTLINER_OB_ARMATURE", - "IfcConstructionMaterialResource": "MATERIAL", - "IfcConstructionProductResource": "PACKAGE", - } - - total_resources = len(self.tprops.resources) - if ( - total_resources - and self.props.active_resource_index < total_resources - and Data.resources[self.tprops.resources[self.props.active_resource_index].ifc_definition_id]["type"] - != "IfcSubContractResource" - ): - row = self.layout.row(align=True) - for ifc_class, icon in icon_map.items(): - label = ifc_class.replace("Ifc", "").replace("Construction", "").replace("Resource", "") - op = row.operator("bim.add_resource", text=label, icon=icon) - op.resource = self.tprops.resources[self.props.active_resource_index].ifc_definition_id - op.ifc_class = ifc_class - + self.draw_resource_operators() self.layout.template_list( "BIM_UL_resources", "", @@ -90,18 +61,166 @@ class BIM_PT_resources(Panel): self.props, "active_resource_index", ) + if self.props.active_resource_id and self.props.editing_resource_type == "ATTRIBUTES": self.draw_editable_resource_attributes_ui() - + elif self.props.active_resource_id and self.props.editing_resource_type == "QUANTITY": + self.draw_editable_resource_quantity_ui() + elif self.props.active_resource_id and self.props.editing_resource_type == "COSTS": + self.draw_editable_resource_costs_ui() elif self.props.active_resource_id and self.props.editing_resource_type == "USAGE": self.draw_editable_resource_time_attributes_ui() + def draw_resource_operators(self): + row = self.layout.row(align=True) + op = row.operator("bim.add_resource", text="Add SubContract", icon="TEXT") + op.ifc_class = "IfcSubContractResource" + op.resource = 0 + op = row.operator("bim.add_resource", text="Add Crew", icon="COMMUNITY") + op.ifc_class = "IfcCrewResource" + op.resource = 0 + + total_resources = len(self.tprops.resources) + if not total_resources or self.props.active_resource_index >= total_resources: + return + + ifc_definition_id = self.tprops.resources[self.props.active_resource_index].ifc_definition_id + resource = Data.resources[ifc_definition_id] + + if resource["type"] != "IfcSubContractResource": + icon_map = { + "IfcSubContractResource": "TEXT", + "IfcConstructionEquipmentResource": "TOOL_SETTINGS", + "IfcLaborResource": "OUTLINER_OB_ARMATURE", + "IfcConstructionMaterialResource": "MATERIAL", + "IfcConstructionProductResource": "PACKAGE", + } + row = self.layout.row(align=True) + for ifc_class, icon in icon_map.items(): + label = ifc_class.replace("Ifc", "").replace("Construction", "").replace("Resource", "") + op = row.operator("bim.add_resource", text=label, icon=icon) + op.resource = ifc_definition_id + op.ifc_class = ifc_class + + row = self.layout.row(align=True) + row.alignment = "RIGHT" + + if self.props.active_resource_id == ifc_definition_id and self.props.editing_resource_type == "ATTRIBUTES": + row.operator("bim.edit_resource", text="", icon="CHECKMARK") + row.operator("bim.disable_editing_resource", text="", icon="CANCEL") + elif self.props.active_resource_id == ifc_definition_id and self.props.editing_resource_type == "USAGE": + row.operator("bim.edit_resource_time", text="", icon="CHECKMARK") + row.operator("bim.disable_editing_resource_time", text="", icon="CANCEL") + elif self.props.active_resource_id == ifc_definition_id and self.props.editing_resource_type == "COSTS": + row.operator("bim.disable_editing_resource", text="", icon="CANCEL") + elif self.props.active_resource_id == ifc_definition_id and self.props.editing_resource_type == "QUANTITY": + row.operator("bim.disable_editing_resource", text="", icon="CANCEL") + elif self.props.active_resource_id: + row.operator("bim.add_resource", text="", icon="ADD").resource = ifc_definition_id + row.operator("bim.remove_resource", text="", icon="X").resource = ifc_definition_id + else: + if resource["type"] in ["IfcLaborResource", "IfcConstructionEquipmentResource"]: + op = row.operator("bim.calculate_resource_work", text="", icon="TEMP") + op.resource = ifc_definition_id + row.operator("bim.enable_editing_resource_time", text="", icon="TIME").resource = ifc_definition_id + op = row.operator("bim.enable_editing_resource_base_quantity", text="", icon="PROPERTIES") + op.resource = ifc_definition_id + op = row.operator("bim.enable_editing_resource_costs", text="", icon="DISC") + op.resource = ifc_definition_id + row.operator("bim.enable_editing_resource", text="", icon="GREASEPENCIL").resource = ifc_definition_id + row.operator("bim.remove_resource", text="", icon="X").resource = ifc_definition_id + def draw_editable_resource_attributes_ui(self): blenderbim.bim.helper.draw_attributes(self.props.resource_attributes, self.layout) def draw_editable_resource_time_attributes_ui(self): blenderbim.bim.helper.draw_attributes(self.props.resource_time_attributes, self.layout) + def draw_editable_resource_quantity_ui(self): + resource = Data.resources[self.props.active_resource_id] + + if resource["BaseQuantity"]: + quantity = resource["BaseQuantity"] + value = quantity[[k for k in quantity.keys() if "Value" in k][0]] + row = self.layout.row(align=True) + row.label(text=quantity["Name"]) + row.label(text="{0:.2f}".format(value)) + if self.props.is_editing_quantity: + op = row.operator("bim.edit_resource_quantity", text="", icon="CHECKMARK") + op.physical_quantity = quantity["id"] + row.operator("bim.disable_editing_resource_quantity", text="", icon="CANCEL") + else: + op = row.operator("bim.enable_editing_resource_quantity", text="", icon="GREASEPENCIL") + op.resource = self.props.active_resource_id + op = row.operator("bim.remove_resource_quantity", text="", icon="X") + op.resource = self.props.active_resource_id + + if self.props.is_editing_quantity: + box = self.layout.box() + blenderbim.bim.helper.draw_attributes(self.props.quantity_attributes, box) + else: + row = self.layout.row(align=True) + row.prop(self.props, "quantity_types", text="") + op = row.operator("bim.add_resource_quantity", text="", icon="ADD") + op.resource = self.props.active_resource_id + op.ifc_class = self.props.quantity_types + + def draw_editable_resource_costs_ui(self): + row = self.layout.row(align=True) + row.prop(self.props, "cost_types", text="") + if self.props.cost_types == "CATEGORY": + row.prop(self.props, "cost_category", text="") + op = row.operator("bim.add_cost_value", text="", icon="ADD") + op.parent = self.props.active_resource_id + op.cost_type = self.props.cost_types + if self.props.cost_types == "CATEGORY": + op.cost_category = self.props.cost_category + + for cost_value_id in Data.resources[self.props.active_resource_id]["BaseCosts"] or []: + row = self.layout.row(align=True) + self.draw_readonly_cost_value_ui(row, cost_value_id) + + if self.props.cost_value_editing_type == "ATTRIBUTES": + box = self.layout.box() + self.draw_editable_cost_value_ui(box, Data.cost_values[self.props.active_cost_value_id]) + + def draw_readonly_cost_value_ui(self, layout, cost_value_id): + cost_value = Data.cost_values[cost_value_id] + + if self.props.active_cost_value_id == cost_value_id and self.props.cost_value_editing_type == "FORMULA": + layout.prop(self.props, "cost_value_formula", text="") + else: + cost_value_label = "{0:.2f}".format(cost_value["AppliedValue"]) + cost_value_label += " = " + cost_value["Formula"] + layout.label(text=cost_value_label, icon="DISC") + + self.draw_cost_value_operator_ui(layout, cost_value_id, self.props.active_resource_id) + + def draw_cost_value_operator_ui(self, layout, cost_value_id, parent_id): + if self.props.active_cost_value_id and self.props.active_cost_value_id == cost_value_id: + if self.props.cost_value_editing_type == "ATTRIBUTES": + op = layout.operator("bim.edit_resource_cost_value", text="", icon="CHECKMARK") + op.cost_value = cost_value_id + elif self.props.cost_value_editing_type == "FORMULA": + op = layout.operator("bim.edit_resource_cost_value_formula", text="", icon="CHECKMARK") + op.cost_value = cost_value_id + layout.operator("bim.disable_editing_resource_cost_value", text="", icon="CANCEL") + elif self.props.active_cost_value_id: + op = layout.operator("bim.remove_cost_value", text="", icon="X") + op.parent = parent_id + op.cost_value = cost_value_id + else: + op = layout.operator("bim.enable_editing_resource_cost_value_formula", text="", icon="CON_TRANSLIKE") + op.cost_value = cost_value_id + op = layout.operator("bim.enable_editing_resource_cost_value", text="", icon="GREASEPENCIL") + op.cost_value = cost_value_id + op = layout.operator("bim.remove_cost_value", text="", icon="X") + op.parent = parent_id + op.cost_value = cost_value_id + + def draw_editable_cost_value_ui(self, layout, cost_value): + blenderbim.bim.helper.draw_attributes(self.props.cost_value_attributes, layout) + class BIM_UL_resources(UIList): def draw_item(self, context, layout, data, item, icon, active_data, active_propname): @@ -141,19 +260,3 @@ class BIM_UL_resources(UIList): else: op = row.operator("bim.assign_resource", text="", icon="KEYFRAME", emboss=False) op.resource = item.ifc_definition_id - - if props.active_resource_id == item.ifc_definition_id and props.editing_resource_type == "ATTRIBUTES": - row.operator("bim.edit_resource", text="", icon="CHECKMARK") - row.operator("bim.disable_editing_resource", text="", icon="CANCEL") - elif props.active_resource_id == item.ifc_definition_id and props.editing_resource_type == "USAGE": - row.operator("bim.edit_resource_time", text="", icon="CHECKMARK") - row.operator("bim.disable_editing_resource_time", text="", icon="CANCEL") - elif props.active_resource_id: - row.operator("bim.add_resource", text="", icon="ADD").resource = item.ifc_definition_id - row.operator("bim.remove_resource", text="", icon="X").resource = item.ifc_definition_id - else: - row.operator( - "bim.enable_editing_resource", text="", icon="GREASEPENCIL" - ).resource = item.ifc_definition_id - row.operator("bim.enable_editing_resource_time", text="", icon="TIME").resource = item.ifc_definition_id - row.operator("bim.remove_resource", text="", icon="X").resource = item.ifc_definition_id diff --git a/src/blenderbim/blenderbim/bim/module/root/__init__.py b/src/blenderbim/blenderbim/bim/module/root/__init__.py index 4c7c15696e..b43b044acf 100644 --- a/src/blenderbim/blenderbim/bim/module/root/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/root/__init__.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/module/root/operator.py b/src/blenderbim/blenderbim/bim/module/root/operator.py index 544357b7c4..3470c45cbb 100644 --- a/src/blenderbim/blenderbim/bim/module/root/operator.py +++ b/src/blenderbim/blenderbim/bim/module/root/operator.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -18,12 +17,11 @@ # along with BlenderBIM Add-on. If not, see . import bpy -import numpy as np import ifcopenshell import ifcopenshell.api import ifcopenshell.util.schema import ifcopenshell.util.element -from ifcopenshell.api.geometry.data import Data as GeometryData +import blenderbim.bim.handler from ifcopenshell.api.void.data import Data as VoidData from blenderbim.bim.ifc import IfcStore @@ -117,11 +115,11 @@ class AssignClass(bpy.types.Operator): def _execute(self, context): objects = [bpy.data.objects.get(self.obj)] if self.obj else context.selected_objects self.file = IfcStore.get_file() + if not self.ifc_class: + self.ifc_class = context.scene.BIMRootProperties.ifc_class self.declaration = IfcStore.get_schema().declaration_by_name(self.ifc_class) if self.predefined_type == "USERDEFINED": self.predefined_type = self.userdefined_type - elif self.predefined_type == "": - predefined_type = None for obj in objects: self.assign_class(context, obj) return {"FINISHED"} @@ -134,7 +132,7 @@ class AssignClass(bpy.types.Operator): self.file, **{ "ifc_class": self.ifc_class, - "predefined_type": self.predefined_type, + "predefined_type": self.predefined_type or None, "name": obj.name, }, ) @@ -149,6 +147,7 @@ class AssignClass(bpy.types.Operator): if product.is_a("IfcElementType"): self.place_in_types_collection(obj, context) elif product.is_a("IfcOpeningElement"): + obj.display_type = "WIRE" self.place_in_openings_collection(obj, context) elif ( product.is_a("IfcSpatialElement") @@ -312,11 +311,14 @@ class CopyClass(bpy.types.Operator): else: bpy.ops.bim.add_representation(obj=obj.name) if result.is_a("IfcSpatialElement") or result.is_a("IfcSpatialStructureElement"): - self.place_in_spatial_collection(old_element, obj) + self.place_in_spatial_collection(result, obj) + elif result.is_a("IfcOpeningElement"): + self.add_opening_modifiers(result, obj) + blenderbim.bim.handler.purge_module_data() return {"FINISHED"} - def place_in_spatial_collection(self, old_element, obj): - aggregate = ifcopenshell.util.element.get_aggregate(old_element) + def place_in_spatial_collection(self, element, obj): + aggregate = ifcopenshell.util.element.get_aggregate(element) if not aggregate: return container_obj = IfcStore.get_element(aggregate.id()) @@ -329,3 +331,17 @@ class CopyClass(bpy.types.Operator): new = bpy.data.collections.new(obj.name) new.objects.link(obj) collection.children.link(new) + + def add_opening_modifiers(self, result, obj): + for rel in result.VoidsElements: + building_obj = IfcStore.get_element(rel.RelatingBuildingElement.id()) + try: + modifier = next(m for m in obj.modifiers if m.type == "BOOLEAN" and m.object == obj) + except StopIteration: + modifier = building_obj.modifiers.new("IfcOpeningElement", "BOOLEAN") + modifier.object = obj + finally: + modifier.operation = "DIFFERENCE" + modifier.solver = "EXACT" + modifier.use_self = True + modifier.operand_type = "OBJECT" diff --git a/src/blenderbim/blenderbim/bim/module/root/prop.py b/src/blenderbim/blenderbim/bim/module/root/prop.py index 6a28a40b51..1e73812bf3 100644 --- a/src/blenderbim/blenderbim/bim/module/root/prop.py +++ b/src/blenderbim/blenderbim/bim/module/root/prop.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/module/root/ui.py b/src/blenderbim/blenderbim/bim/module/root/ui.py index aa2ea98ec2..950195a5ab 100644 --- a/src/blenderbim/blenderbim/bim/module/root/ui.py +++ b/src/blenderbim/blenderbim/bim/module/root/ui.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -51,8 +50,8 @@ class BIM_PT_class(Panel): row.operator("bim.reassign_class", icon="CHECKMARK") row.operator("bim.disable_reassign_class", icon="CANCEL", text="") self.draw_class_dropdowns( - context, - root_prop.getIfcPredefinedTypes(context.scene.BIMRootProperties, context)) + context, root_prop.getIfcPredefinedTypes(context.scene.BIMRootProperties, context) + ) else: data = Data.products[props.ifc_definition_id] name = data["type"] diff --git a/src/blenderbim/blenderbim/bim/module/search/__init__.py b/src/blenderbim/blenderbim/bim/module/search/__init__.py index d11e217190..2550dd1c7b 100644 --- a/src/blenderbim/blenderbim/bim/module/search/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/search/__init__.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/module/search/operator.py b/src/blenderbim/blenderbim/bim/module/search/operator.py index af4743877f..566bf5048e 100644 --- a/src/blenderbim/blenderbim/bim/module/search/operator.py +++ b/src/blenderbim/blenderbim/bim/module/search/operator.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -44,11 +43,7 @@ colour_list = [ def does_keyword_exist(pattern, string, context): string = str(string) props = context.scene.BIMSearchProperties - if ( - props.should_use_regex - and props.should_ignorecase - and re.search(pattern, string, flags=re.IGNORECASE) - ): + if props.should_use_regex and props.should_ignorecase and re.search(pattern, string, flags=re.IGNORECASE): return True elif props.should_use_regex and re.search(pattern, string): return True @@ -60,6 +55,7 @@ def does_keyword_exist(pattern, string, context): class SelectGlobalId(bpy.types.Operator): """Click to select the objects that match with the given Global ID""" + bl_idname = "bim.select_global_id" bl_label = "Select GlobalId" bl_options = {"REGISTER", "UNDO"} @@ -81,6 +77,7 @@ class SelectGlobalId(bpy.types.Operator): class SelectIfcClass(bpy.types.Operator): """Click to select all objects that match with the given IFC class""" + bl_idname = "bim.select_ifc_class" bl_label = "Select IFC Class" bl_options = {"REGISTER", "UNDO"} @@ -99,6 +96,7 @@ class SelectIfcClass(bpy.types.Operator): class SelectAttribute(bpy.types.Operator): """Click to select all objects that match with the given Attribute Name and Value""" + bl_idname = "bim.select_attribute" bl_label = "Select Attribute" bl_options = {"REGISTER", "UNDO"} @@ -124,6 +122,7 @@ class SelectAttribute(bpy.types.Operator): class SelectPset(bpy.types.Operator): """Click to select all objects that match with the given Pset Name, Properties Name and Value""" + bl_idname = "bim.select_pset" bl_label = "Select Pset" bl_options = {"REGISTER", "UNDO"} @@ -157,6 +156,7 @@ class SelectPset(bpy.types.Operator): class ColourByAttribute(bpy.types.Operator): """Click to colour different objects according to given Attribute Name""" + bl_idname = "bim.colour_by_attribute" bl_label = "Colour by Attribute" bl_options = {"REGISTER", "UNDO"} @@ -207,6 +207,7 @@ class ColourByAttribute(bpy.types.Operator): class ColourByPset(bpy.types.Operator): """Click to colour different objects according to given Prop Name""" + bl_idname = "bim.colour_by_pset" bl_label = "Colour by Pset" bl_options = {"REGISTER", "UNDO"} @@ -265,6 +266,7 @@ class ColourByPset(bpy.types.Operator): class ColourByClass(bpy.types.Operator): """Click to colour different objects according to their IFC Classes""" + bl_idname = "bim.colour_by_class" bl_label = "Colour by Class" bl_options = {"REGISTER", "UNDO"} @@ -310,6 +312,7 @@ class ColourByClass(bpy.types.Operator): class ResetObjectColours(bpy.types.Operator): """Reset the colour of selected objects""" + bl_idname = "bim.reset_object_colours" bl_label = "Reset Colours" diff --git a/src/blenderbim/blenderbim/bim/module/search/prop.py b/src/blenderbim/blenderbim/bim/module/search/prop.py index 722d051314..78a0949e69 100644 --- a/src/blenderbim/blenderbim/bim/module/search/prop.py +++ b/src/blenderbim/blenderbim/bim/module/search/prop.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -30,6 +29,7 @@ from bpy.props import ( CollectionProperty, ) + class BIMSearchProperties(PropertyGroup): should_use_regex: BoolProperty(name="Search With Regex", default=False) should_ignorecase: BoolProperty(name="Search Ignoring Case", default=True) diff --git a/src/blenderbim/blenderbim/bim/module/search/ui.py b/src/blenderbim/blenderbim/bim/module/search/ui.py index b02a328b7b..405a3623da 100644 --- a/src/blenderbim/blenderbim/bim/module/search/ui.py +++ b/src/blenderbim/blenderbim/bim/module/search/ui.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/module/sequence/__init__.py b/src/blenderbim/blenderbim/bim/module/sequence/__init__.py index 10d973b167..12be9e5872 100644 --- a/src/blenderbim/blenderbim/bim/module/sequence/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/sequence/__init__.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -98,6 +97,7 @@ classes = ( operator.LoadTaskInputs, operator.LoadTaskResources, operator.LoadTaskOutputs, + operator.CalculateTaskDuration, prop.WorkPlan, prop.BIMWorkPlanProperties, prop.Task, @@ -109,6 +109,7 @@ classes = ( prop.RecurrenceComponent, prop.BIMWorkCalendarProperties, prop.DatePickerProperties, + prop.BIMDateTextProperties, ui.BIM_PT_work_plans, ui.BIM_PT_work_schedules, ui.BIM_PT_work_calendars, @@ -132,6 +133,7 @@ def register(): bpy.types.Scene.BIMTaskTreeProperties = bpy.props.PointerProperty(type=prop.BIMTaskTreeProperties) bpy.types.Scene.BIMWorkCalendarProperties = bpy.props.PointerProperty(type=prop.BIMWorkCalendarProperties) bpy.types.Scene.DatePickerProperties = bpy.props.PointerProperty(type=prop.DatePickerProperties) + bpy.types.TextCurve.BIMDateTextProperties = bpy.props.PointerProperty(type=prop.BIMDateTextProperties) bpy.types.TOPBAR_MT_file_import.append(menu_func_import) @@ -141,4 +143,5 @@ def unregister(): del bpy.types.Scene.BIMTaskTreeProperties del bpy.types.Scene.BIMWorkCalendarProperties del bpy.types.Scene.DatePickerProperties + del bpy.types.TextCurve.BIMDateTextProperties bpy.types.TOPBAR_MT_file_import.remove(menu_func_import) diff --git a/src/blenderbim/blenderbim/bim/module/sequence/helper.py b/src/blenderbim/blenderbim/bim/module/sequence/helper.py index 6c5282b693..2e26f69921 100644 --- a/src/blenderbim/blenderbim/bim/module/sequence/helper.py +++ b/src/blenderbim/blenderbim/bim/module/sequence/helper.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -93,4 +92,4 @@ def get_scene_prop(prop_path): def set_scene_prop(prop_path, value): parent = get_scene_prop(prop_path[: prop_path.rfind(".")]) prop = prop_path.split(".")[-1] - parent[prop] = value \ No newline at end of file + parent[prop] = value diff --git a/src/blenderbim/blenderbim/bim/module/sequence/operator.py b/src/blenderbim/blenderbim/bim/module/sequence/operator.py index 0af442fffe..3331d56782 100644 --- a/src/blenderbim/blenderbim/bim/module/sequence/operator.py +++ b/src/blenderbim/blenderbim/bim/module/sequence/operator.py @@ -443,7 +443,7 @@ class AddTask(bpy.types.Operator): def _execute(self, context): props = context.scene.BIMWorkScheduleProperties self.file = IfcStore.get_file() - ifcopenshell.api.run("sequence.add_task", self.file, **{"parent_task": self.file.by_id(self.task)}) + ifcopenshell.api.run("sequence.add_task", self.file, parent_task=self.file.by_id(self.task)) Data.load(self.file) bpy.ops.bim.enable_editing_tasks(work_schedule=props.active_work_schedule_id) return {"FINISHED"} @@ -461,7 +461,7 @@ class AddSummaryTask(bpy.types.Operator): def _execute(self, context): props = context.scene.BIMWorkScheduleProperties self.file = IfcStore.get_file() - ifcopenshell.api.run("sequence.add_task", self.file, **{"work_schedule": self.file.by_id(self.work_schedule)}) + ifcopenshell.api.run("sequence.add_task", self.file, work_schedule=self.file.by_id(self.work_schedule)) Data.load(self.file) bpy.ops.bim.enable_editing_tasks(work_schedule=props.active_work_schedule_id) return {"FINISHED"} @@ -797,6 +797,7 @@ class AssignProduct(bpy.types.Operator): related_object=self.file.by_id(self.task), ) Data.load(self.file) + bpy.ops.bim.load_task_outputs() return {"FINISHED"} @@ -825,6 +826,7 @@ class UnassignProduct(bpy.types.Operator): related_object=self.file.by_id(self.task), ) Data.load(self.file) + bpy.ops.bim.load_task_outputs() return {"FINISHED"} @@ -909,9 +911,7 @@ class UnassignProcess(bpy.types.Operator): def unassign_resource(self): task = self.file.by_id(self.task) resource = self.file.by_id(self.resource) - ifcopenshell.api.run( - "sequence.unassign_process", self.file, related_object=resource, relating_process=task - ) + ifcopenshell.api.run("sequence.unassign_process", self.file, related_object=resource, relating_process=task) ifcopenshell.api.run("resource.remove_resource", self.file, resource=resource) ResourceData.load(self.file) Data.load(self.file) @@ -1742,11 +1742,12 @@ class VisualiseWorkScheduleDateRange(bpy.types.Operator): self.animate_input(obj, product_frame) elif product_frame["relationship"] == "output": self.animate_output(obj, product_frame) + self.add_text_animation_handler() area = next(area for area in context.screen.areas if area.type == "VIEW_3D") area.spaces[0].shading.color_type = "OBJECT" context.scene.frame_start = self.start_frame - context.scene.frame_end = self.start_frame + self.total_frames + context.scene.frame_end = int(self.start_frame + self.total_frames) # with open("/home/dion/animation.json", "w") as json_file: # guid_frames = {} # for k, v in self.product_frames.items(): @@ -1754,6 +1755,35 @@ class VisualiseWorkScheduleDateRange(bpy.types.Operator): # json.dump(guid_frames, json_file) return {"FINISHED"} + def add_text_animation_handler(self): + data = bpy.data.curves.get("Timeline") + if not data: + data = bpy.data.curves.new(type="FONT", name="Timeline") + obj = bpy.data.objects.get("Timeline") + if not obj: + obj = bpy.data.objects.new(name="Timeline", object_data=data) + bpy.context.scene.collection.objects.link(obj) + obj.data.BIMDateTextProperties.start_frame = self.start_frame + obj.data.BIMDateTextProperties.total_frames = int(self.total_frames) + obj.data.BIMDateTextProperties.start = self.props.visualisation_start + obj.data.BIMDateTextProperties.finish = self.props.visualisation_finish + bpy.app.handlers.frame_change_post.append(self.animate_text) + + def remove_text_animation_handler(self): + bpy.app.handlers.frame_change_post.remove(self.animate_text) + + def animate_text(self, scene, context): + data = bpy.data.curves.get("Timeline") + if not data or not bpy.data.objects.get("Timeline"): + self.remove_text_animation_handler() + scene.frame_current + props = data.BIMDateTextProperties + start = parser.parse(props.start, dayfirst=True, fuzzy=True) + finish = parser.parse(props.finish, dayfirst=True, fuzzy=True) + duration = finish - start + frame_date = (((scene.frame_current - props.start_frame) / props.total_frames) * duration) + start + data.body = frame_date.date().isoformat() + def animate_input(self, obj, product_frame): if product_frame["type"] in ["LOGISTIC", "MOVE", "DISPOSAL"]: self.animate_movement_from(obj, product_frame) @@ -1774,24 +1804,34 @@ class VisualiseWorkScheduleDateRange(bpy.types.Operator): def animate_creation(self, obj, product_frame): obj.hide_viewport = True + obj.hide_render = True obj.keyframe_insert(data_path="hide_viewport", frame=self.start_frame) + obj.keyframe_insert(data_path="hide_render", frame=self.start_frame) obj.hide_viewport = False + obj.hide_render = False obj.color = (0.0, 1.0, 0.0, 1) obj.keyframe_insert(data_path="hide_viewport", frame=product_frame["STARTED"]) + obj.keyframe_insert(data_path="hide_render", frame=product_frame["STARTED"]) obj.keyframe_insert(data_path="color", frame=product_frame["STARTED"]) obj.color = (1.0, 1.0, 1.0, 1) obj.keyframe_insert(data_path="color", frame=product_frame["COMPLETED"]) def animate_destruction(self, obj, product_frame): obj.color = (1.0, 1.0, 1.0, 1) + obj.hide_viewport = False + obj.hide_render = False obj.keyframe_insert(data_path="color", frame=self.start_frame) + obj.keyframe_insert(data_path="hide_viewport", frame=self.start_frame) + obj.keyframe_insert(data_path="hide_render", frame=self.start_frame) obj.keyframe_insert(data_path="color", frame=product_frame["STARTED"] - 1) obj.color = (1.0, 0.0, 0.0, 1) obj.keyframe_insert(data_path="color", frame=product_frame["STARTED"]) obj.hide_viewport = True + obj.hide_render = True obj.color = (0.0, 0.0, 0.0, 1) obj.keyframe_insert(data_path="color", frame=product_frame["COMPLETED"]) obj.keyframe_insert(data_path="hide_viewport", frame=product_frame["COMPLETED"]) + obj.keyframe_insert(data_path="hide_render", frame=product_frame["COMPLETED"]) def animate_operation(self, obj, product_frame): obj.color = (1.0, 1.0, 1.0, 1) @@ -1804,10 +1844,14 @@ class VisualiseWorkScheduleDateRange(bpy.types.Operator): def animate_movement_to(self, obj, product_frame): obj.hide_viewport = True + obj.hide_render = True obj.keyframe_insert(data_path="hide_viewport", frame=self.start_frame) + obj.keyframe_insert(data_path="hide_render", frame=self.start_frame) obj.hide_viewport = False + obj.hide_render = False obj.color = (1.0, 1.0, 0.0, 1) obj.keyframe_insert(data_path="hide_viewport", frame=product_frame["STARTED"]) + obj.keyframe_insert(data_path="hide_render", frame=product_frame["STARTED"]) obj.keyframe_insert(data_path="color", frame=product_frame["STARTED"]) obj.color = (1.0, 1.0, 1.0, 1) obj.keyframe_insert(data_path="color", frame=product_frame["COMPLETED"]) @@ -1815,24 +1859,36 @@ class VisualiseWorkScheduleDateRange(bpy.types.Operator): def animate_movement_from(self, obj, product_frame): obj.color = (1.0, 1.0, 1.0, 1) obj.keyframe_insert(data_path="color", frame=self.start_frame) + obj.hide_viewport = False + obj.hide_render = False obj.keyframe_insert(data_path="color", frame=product_frame["STARTED"] - 1) + obj.keyframe_insert(data_path="hide_viewport", frame=product_frame["STARTED"] - 1) + obj.keyframe_insert(data_path="hide_render", frame=product_frame["STARTED"] - 1) obj.color = (1.0, 0.5, 0.0, 1) obj.keyframe_insert(data_path="color", frame=product_frame["STARTED"]) obj.hide_viewport = True + obj.hide_render = True obj.color = (0.0, 0.0, 0.0, 1) obj.keyframe_insert(data_path="color", frame=product_frame["COMPLETED"]) obj.keyframe_insert(data_path="hide_viewport", frame=product_frame["COMPLETED"]) + obj.keyframe_insert(data_path="hide_render", frame=product_frame["COMPLETED"]) def animate_consumption(self, obj, product_frame): obj.color = (1.0, 1.0, 1.0, 1) obj.keyframe_insert(data_path="color", frame=self.start_frame) + obj.hide_viewport = False + obj.hide_render = False obj.keyframe_insert(data_path="color", frame=product_frame["STARTED"] - 1) + obj.keyframe_insert(data_path="hide_viewport", frame=product_frame["STARTED"] - 1) + obj.keyframe_insert(data_path="hide_render", frame=product_frame["STARTED"] - 1) obj.color = (0.0, 1.0, 1.0, 1) obj.keyframe_insert(data_path="color", frame=product_frame["STARTED"]) obj.hide_viewport = True + obj.hide_render = True obj.color = (0.0, 0.0, 0.0, 1) obj.keyframe_insert(data_path="color", frame=product_frame["COMPLETED"]) obj.keyframe_insert(data_path="hide_viewport", frame=product_frame["COMPLETED"]) + obj.keyframe_insert(data_path="hide_render", frame=product_frame["COMPLETED"]) def calculate_total_frames(self, context): if self.props.speed_types == "FRAME_SPEED": @@ -2098,9 +2154,24 @@ class LoadTaskOutputs(bpy.types.Operator): self.tprops = context.scene.BIMTaskTreeProperties ifc_definition_id = self.tprops.tasks[self.props.active_task_index].ifc_definition_id self.props.task_outputs.clear() - for output_id in Data.tasks[ifc_definition_id]["outputs"]: + for output_id in Data.tasks[ifc_definition_id]["Outputs"]: product = self.file.by_id(output_id) new = self.props.task_outputs.add() new.ifc_definition_id = output_id new.name = product.Name or "Unnamed" return {"FINISHED"} + + +class CalculateTaskDuration(bpy.types.Operator): + bl_idname = "bim.calculate_task_duration" + bl_label = "Calculate Task Duration" + bl_options = {"REGISTER", "UNDO"} + task: bpy.props.IntProperty() + + def execute(self, context): + props = context.scene.BIMWorkScheduleProperties + self.file = IfcStore.get_file() + ifcopenshell.api.run("sequence.calculate_task_duration", self.file, task=self.file.by_id(self.task)) + Data.load(self.file) + bpy.ops.bim.enable_editing_tasks(work_schedule=props.active_work_schedule_id) + return {"FINISHED"} diff --git a/src/blenderbim/blenderbim/bim/module/sequence/prop.py b/src/blenderbim/blenderbim/bim/module/sequence/prop.py index 78e7a0b0b3..7f002e8197 100644 --- a/src/blenderbim/blenderbim/bim/module/sequence/prop.py +++ b/src/blenderbim/blenderbim/bim/module/sequence/prop.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -205,15 +204,15 @@ def updateTaskDuration(self, context): bpy.ops.bim.load_task_properties() -def updateVisualisationStart(self, context): - updateVisualisationStartFinish(self, context, "visualisation_start") +def update_visualisation_start(self, context): + update_visualisation_start_finish(self, context, "visualisation_start") -def updateVisualisationFinish(self, context): - updateVisualisationStartFinish(self, context, "visualisation_finish") +def update_visualisation_finish(self, context): + update_visualisation_start_finish(self, context, "visualisation_finish") -def updateVisualisationStartFinish(self, context, startfinish): +def update_visualisation_start_finish(self, context, startfinish): def canonicalise_time(time): if not time: return "-" @@ -317,8 +316,8 @@ class BIMWorkScheduleProperties(PropertyGroup): active_sequence_id: IntProperty(name="Active Sequence Id") sequence_attributes: CollectionProperty(name="Sequence Attributes", type=Attribute) time_lag_attributes: CollectionProperty(name="Time Lag Attributes", type=Attribute) - visualisation_start: StringProperty(name="Visualisation Start", update=updateVisualisationStart) - visualisation_finish: StringProperty(name="Visualisation Finish", update=updateVisualisationFinish) + visualisation_start: StringProperty(name="Visualisation Start", update=update_visualisation_start) + visualisation_finish: StringProperty(name="Visualisation Finish", update=update_visualisation_finish) speed_multiplier: FloatProperty(name="Speed Multiplier", default=10000) speed_animation_duration: StringProperty(name="Speed Animation Duration", default="PT1S") speed_animation_frames: IntProperty(name="Speed Animation Frames", default=24) @@ -389,3 +388,10 @@ class BIMWorkCalendarProperties(PropertyGroup): class DatePickerProperties(PropertyGroup): display_date: StringProperty() selected_date: StringProperty() + + +class BIMDateTextProperties(PropertyGroup): + start_frame: IntProperty(name="Start Frame") + total_frames: IntProperty(name="Total Frames") + start: StringProperty(name="Start") + finish: StringProperty(name="Finish") diff --git a/src/blenderbim/blenderbim/bim/module/sequence/ui.py b/src/blenderbim/blenderbim/bim/module/sequence/ui.py index 7586690242..37c4bd138e 100644 --- a/src/blenderbim/blenderbim/bim/module/sequence/ui.py +++ b/src/blenderbim/blenderbim/bim/module/sequence/ui.py @@ -363,9 +363,7 @@ class BIM_PT_task_icom(Panel): op.related_object = "" row2 = col.row() - row2.template_list( - "BIM_UL_task_inputs", "", self.props, "task_inputs", self.props, "active_task_input_index" - ) + row2.template_list("BIM_UL_task_inputs", "", self.props, "task_inputs", self.props, "active_task_input_index") # Column2 col = grid.column() @@ -373,17 +371,24 @@ class BIM_PT_task_icom(Panel): row2 = col.row(align=True) row2.label(text="Resources") + op = row2.operator("bim.calculate_task_duration", text="", icon="TEMP") + op.task = task.ifc_definition_id + total_resources = len(context.scene.BIMResourceTreeProperties.resources) if total_resources and context.scene.BIMResourceProperties.active_resource_index < total_resources: - resource_id = context.scene.BIMResourceTreeProperties.resources[context.scene.BIMResourceProperties.active_resource_index].ifc_definition_id op = row2.operator("bim.assign_process", icon="ADD", text="") op.task = task.ifc_definition_id op.related_object_type = "RESOURCE" - op.resource = resource_id + op.resource = context.scene.BIMResourceTreeProperties.resources[ + context.scene.BIMResourceProperties.active_resource_index + ].ifc_definition_id + + total_task_resources = len(self.props.task_resources) + if total_task_resources and self.props.active_task_resource_index < total_task_resources: op = row2.operator("bim.unassign_process", icon="REMOVE", text="") op.task = task.ifc_definition_id op.related_object_type = "RESOURCE" - op.resource = resource_id + op.resource = self.props.task_resources[self.props.active_task_resource_index].ifc_definition_id row2 = col.row() row2.template_list( @@ -430,7 +435,7 @@ class BIM_UL_task_inputs(UIList): if item: row = layout.row(align=True) row.prop(item, "name", emboss=False, text="") - #row.operator("bim.remove_task_column", text="", icon="X").name = item.name + # row.operator("bim.remove_task_column", text="", icon="X").name = item.name class BIM_UL_task_resources(UIList): diff --git a/src/blenderbim/blenderbim/bim/module/spatial/__init__.py b/src/blenderbim/blenderbim/bim/module/spatial/__init__.py index 9335ac64d4..1d1b95cc90 100644 --- a/src/blenderbim/blenderbim/bim/module/spatial/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/spatial/__init__.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/module/spatial/operator.py b/src/blenderbim/blenderbim/bim/module/spatial/operator.py index 7f8fe7b3aa..3b02e5651a 100644 --- a/src/blenderbim/blenderbim/bim/module/spatial/operator.py +++ b/src/blenderbim/blenderbim/bim/module/spatial/operator.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -49,6 +48,8 @@ class AssignContainer(bpy.types.Operator): ) for related_element in related_elements: oprops = related_element.BIMObjectProperties + if not oprops.ifc_definition_id: + continue props = related_element.BIMObjectSpatialProperties ifcopenshell.api.run( @@ -134,25 +135,27 @@ class RemoveContainer(bpy.types.Operator): return IfcStore.execute_ifc_operator(self, context) def _execute(self, context): - obj = bpy.data.objects.get(self.obj, context.active_object) - oprops = obj.BIMObjectProperties + active_object = context.active_object self.file = IfcStore.get_file() - ifcopenshell.api.run( - "spatial.remove_container", self.file, **{"product": self.file.by_id(oprops.ifc_definition_id)} - ) - Data.load(IfcStore.get_file(), oprops.ifc_definition_id) + objs = [bpy.data.objects.get(self.obj)] if self.obj else context.selected_objects + for obj in objs: + obj_id = obj.BIMObjectProperties.ifc_definition_id + if not obj_id: + continue + ifcopenshell.api.run("spatial.remove_container", self.file, **{"product": self.file.by_id(obj_id)}) + Data.load(self.file, obj_id) - aggregate_collection = bpy.data.collections.get(obj.name) - if aggregate_collection: - self.remove_collection(context.scene.collection, aggregate_collection) - for collection in bpy.data.collections: - self.remove_collection(collection, spatial_collection) - context.scene.collection.children.link(aggregate_collection) - else: - for collection in obj.users_collection: - collection.objects.unlink(obj) - context.scene.collection.objects.link(obj) - context.view_layer.objects.active = obj + aggregate_collection = bpy.data.collections.get(obj.name) + if aggregate_collection: + self.remove_collection(context.scene.collection, aggregate_collection) + for collection in bpy.data.collections: + self.remove_collection(collection, spatial_collection) + context.scene.collection.children.link(aggregate_collection) + else: + for collection in obj.users_collection: + collection.objects.unlink(obj) + context.scene.collection.objects.link(obj) + context.view_layer.objects.active = active_object return {"FINISHED"} def remove_collection(self, parent, child): @@ -168,6 +171,7 @@ class CopyToContainer(bpy.types.Operator): Check the mark next to a container in the container list to select it Several containers can be selected at a time """ + bl_idname = "bim.copy_to_container" bl_label = "Copy To Container" bl_options = {"REGISTER", "UNDO"} @@ -182,7 +186,9 @@ class CopyToContainer(bpy.types.Operator): sprops = context.scene.BIMSpatialProperties container_ids = [c.ifc_definition_id for c in sprops.spatial_elements if c.is_selected] for obj in objects: - container = ifcopenshell.util.element.get_container(self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)) + container = ifcopenshell.util.element.get_container( + self.file.by_id(obj.BIMObjectProperties.ifc_definition_id) + ) if container: container_obj = IfcStore.get_element(container.id()) local_position = container_obj.matrix_world.inverted() @ obj.matrix_world diff --git a/src/blenderbim/blenderbim/bim/module/spatial/prop.py b/src/blenderbim/blenderbim/bim/module/spatial/prop.py index b9a4538f2a..769479f3e8 100644 --- a/src/blenderbim/blenderbim/bim/module/spatial/prop.py +++ b/src/blenderbim/blenderbim/bim/module/spatial/prop.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/module/spatial/ui.py b/src/blenderbim/blenderbim/bim/module/spatial/ui.py index 8d88132e5f..00a4e8541d 100644 --- a/src/blenderbim/blenderbim/bim/module/spatial/ui.py +++ b/src/blenderbim/blenderbim/bim/module/spatial/ui.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/module/structural/__init__.py b/src/blenderbim/blenderbim/bim/module/structural/__init__.py index edccdc92a5..c37f6a4751 100644 --- a/src/blenderbim/blenderbim/bim/module/structural/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/structural/__init__.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/module/structural/operator.py b/src/blenderbim/blenderbim/bim/module/structural/operator.py index 5bb3e1469b..5d60ef6e40 100644 --- a/src/blenderbim/blenderbim/bim/module/structural/operator.py +++ b/src/blenderbim/blenderbim/bim/module/structural/operator.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -607,7 +606,10 @@ class AssignStructuralLoadCase(bpy.types.Operator): ifcopenshell.api.run( "aggregate.assign_object", self.file, - **{"relating_object": self.file.by_id(self.work_plan), "product": self.file.by_id(self.load_case),}, + **{ + "relating_object": self.file.by_id(self.work_plan), + "product": self.file.by_id(self.load_case), + }, ) Data.load(IfcStore.get_file()) return {"FINISHED"} @@ -624,7 +626,10 @@ class UnassignStructuralLoadCase(bpy.types.Operator): ifcopenshell.api.run( "aggregate.unassign_object", self.file, - **{"relating_object": self.file.by_id(self.work_plan), "product": self.file.by_id(self.load_case),}, + **{ + "relating_object": self.file.by_id(self.work_plan), + "product": self.file.by_id(self.load_case), + }, ) Data.load(IfcStore.get_file()) return {"FINISHED"} @@ -968,7 +973,10 @@ class EditStructuralLoad(bpy.types.Operator): ifcopenshell.api.run( "structural.edit_structural_load", self.file, - **{"structural_load": self.file.by_id(props.active_structural_load_id), "attributes": attributes,}, + **{ + "structural_load": self.file.by_id(props.active_structural_load_id), + "attributes": attributes, + }, ) Data.load(IfcStore.get_file()) bpy.ops.bim.load_structural_loads() diff --git a/src/blenderbim/blenderbim/bim/module/structural/prop.py b/src/blenderbim/blenderbim/bim/module/structural/prop.py index 5da6624f1c..38fb4e571a 100644 --- a/src/blenderbim/blenderbim/bim/module/structural/prop.py +++ b/src/blenderbim/blenderbim/bim/module/structural/prop.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -56,10 +55,12 @@ def getApplicableStructuralLoadTypes(self, context): ) types = [("IfcStructuralLoadTemperature", "IfcStructuralLoadTemperature", "")] if "IfcStructuralPointConnection" in element_classes: - types.extend([ - ("IfcStructuralLoadSingleForce", "IfcStructuralLoadSingleForce", ""), - ("IfcStructuralLoadSingleDisplacement", "IfcStructuralLoadSingleDisplacement", "") - ]) + types.extend( + [ + ("IfcStructuralLoadSingleForce", "IfcStructuralLoadSingleForce", ""), + ("IfcStructuralLoadSingleDisplacement", "IfcStructuralLoadSingleDisplacement", ""), + ] + ) if "IfcStructuralCurveMember" in element_classes: types.append(("IfcStructuralLoadLinearForce", "IfcStructuralLoadLinearForce", "")) if "IfcStructuralSurfaceMember" in element_classes: diff --git a/src/blenderbim/blenderbim/bim/module/structural/ui.py b/src/blenderbim/blenderbim/bim/module/structural/ui.py index 0f04cc71f2..498a651749 100644 --- a/src/blenderbim/blenderbim/bim/module/structural/ui.py +++ b/src/blenderbim/blenderbim/bim/module/structural/ui.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/module/style/__init__.py b/src/blenderbim/blenderbim/bim/module/style/__init__.py index b60aac300f..086450e2ea 100644 --- a/src/blenderbim/blenderbim/bim/module/style/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/style/__init__.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/module/style/operator.py b/src/blenderbim/blenderbim/bim/module/style/operator.py index 598cc9bc3f..c03376c187 100644 --- a/src/blenderbim/blenderbim/bim/module/style/operator.py +++ b/src/blenderbim/blenderbim/bim/module/style/operator.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -53,8 +52,25 @@ class UpdateStyleColours(bpy.types.Operator): self.file = IfcStore.get_file() material = bpy.data.materials.get(self.material) if self.material else context.active_object.active_material settings = get_colour_settings(material) - settings["style"] = self.file.by_id(material.BIMMaterialProperties.ifc_style_id) - ifcopenshell.api.run("style.edit_style_colours", self.file, **settings) + for style in self.file.by_id(material.BIMMaterialProperties.ifc_style_id).Styles: + if style.is_a("IfcSurfaceStyleRendering"): + ifcopenshell.api.run( + "style.edit_surface_style", + self.file, + style=style, + attributes={ + "SurfaceColour": settings["surface_colour"], + "Transparency": settings["transparency"], + "DiffuseColour": settings["diffuse_colour"], + }, + ) + elif style.is_a("IfcSurfaceStyleShading"): + ifcopenshell.api.run( + "style.edit_surface_style", + self.file, + style=style, + attributes={"SurfaceColour": settings["surface_colour"], "Transparency": settings["transparency"]}, + ) return {"FINISHED"} @@ -97,11 +113,15 @@ class AddStyle(bpy.types.Operator): if material.BIMObjectProperties.ifc_definition_id: context = ifcopenshell.util.representation.get_context(self.file, "Model", "Body", "MODEL_VIEW") if context: - ifcopenshell.api.run("style.assign_material_style", self.file, **{ - "material": self.file.by_id(material.BIMObjectProperties.ifc_definition_id), - "style": style, - "context": context, - }) + ifcopenshell.api.run( + "style.assign_material_style", + self.file, + **{ + "material": self.file.by_id(material.BIMObjectProperties.ifc_definition_id), + "style": style, + "context": context, + } + ) return {"FINISHED"} @@ -164,7 +184,7 @@ class EditStyle(bpy.types.Operator): attributes = blenderbim.bim.helper.export_attributes(props.attributes) self.file = IfcStore.get_file() style = self.file.by_id(material.BIMMaterialProperties.ifc_style_id) - ifcopenshell.api.run("style.edit_style", self.file, **{"style": style, "attributes": attributes}) + ifcopenshell.api.run("style.edit_presentation_style", self.file, **{"style": style, "attributes": attributes}) Data.load(IfcStore.get_file(), material.BIMMaterialProperties.ifc_style_id) bpy.ops.bim.disable_editing_style() return {"FINISHED"} diff --git a/src/blenderbim/blenderbim/bim/module/style/prop.py b/src/blenderbim/blenderbim/bim/module/style/prop.py index fe0fcbf6b9..7b56fc86ee 100644 --- a/src/blenderbim/blenderbim/bim/module/style/prop.py +++ b/src/blenderbim/blenderbim/bim/module/style/prop.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -32,6 +31,7 @@ from bpy.props import ( CollectionProperty, ) + class BIMStyleProperties(PropertyGroup): attributes: CollectionProperty(name="Attributes", type=Attribute) is_editing_attributes: BoolProperty(name="Is Editing Attributes") diff --git a/src/blenderbim/blenderbim/bim/module/style/ui.py b/src/blenderbim/blenderbim/bim/module/style/ui.py index a2df554fb5..9da4296975 100644 --- a/src/blenderbim/blenderbim/bim/module/style/ui.py +++ b/src/blenderbim/blenderbim/bim/module/style/ui.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/module/system/__init__.py b/src/blenderbim/blenderbim/bim/module/system/__init__.py index c5652828e0..115d400737 100644 --- a/src/blenderbim/blenderbim/bim/module/system/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/system/__init__.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -26,6 +25,7 @@ classes = ( operator.AddSystem, operator.EditSystem, operator.RemoveSystem, + operator.ToggleAssigningSystem, operator.AssignSystem, operator.UnassignSystem, operator.EnableEditingSystem, @@ -34,7 +34,9 @@ classes = ( prop.System, prop.BIMSystemProperties, ui.BIM_PT_systems, + ui.BIM_PT_object_systems, ui.BIM_UL_systems, + ui.BIM_UL_object_systems, ) diff --git a/src/blenderbim/blenderbim/bim/module/system/operator.py b/src/blenderbim/blenderbim/bim/module/system/operator.py index 51597aa7c0..bbfa9c40eb 100644 --- a/src/blenderbim/blenderbim/bim/module/system/operator.py +++ b/src/blenderbim/blenderbim/bim/module/system/operator.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -20,6 +19,7 @@ import bpy import ifcopenshell.util.attribute import ifcopenshell.api +import blenderbim.bim.helper from blenderbim.bim.ifc import IfcStore from ifcopenshell.api.system.data import Data @@ -48,6 +48,7 @@ class DisableSystemEditingUI(bpy.types.Operator): def execute(self, context): context.scene.BIMSystemProperties.is_editing = False + context.scene.BIMSystemProperties.active_system_id = 0 return {"FINISHED"} @@ -85,7 +86,9 @@ class EditSystem(bpy.types.Operator): attributes[attribute.name] = attribute.string_value self.file = IfcStore.get_file() ifcopenshell.api.run( - "system.edit_system", self.file, **{"system": self.file.by_id(props.active_system_id), "attributes": attributes} + "system.edit_system", + self.file, + **{"system": self.file.by_id(props.active_system_id), "attributes": attributes} ) Data.load(IfcStore.get_file()) bpy.ops.bim.load_systems() @@ -120,17 +123,7 @@ class EnableEditingSystem(bpy.types.Operator): props = context.scene.BIMSystemProperties props.system_attributes.clear() - data = Data.systems[self.system] - - for attribute in IfcStore.get_schema().declaration_by_name("IfcSystem").all_attributes(): - data_type = ifcopenshell.util.attribute.get_primitive_type(attribute) - if data_type == "entity": - continue - new = props.system_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()] + blenderbim.bim.helper.import_attributes("IfcSystem", props.system_attributes, Data.systems[self.system]) props.active_system_id = self.system return {"FINISHED"} @@ -145,6 +138,16 @@ class DisableEditingSystem(bpy.types.Operator): return {"FINISHED"} +class ToggleAssigningSystem(bpy.types.Operator): + bl_idname = "bim.toggle_assigning_system" + bl_label = "Toggle Assigning System" + bl_options = {"REGISTER", "UNDO"} + + def execute(self, context): + context.scene.BIMSystemProperties.is_adding = not context.scene.BIMSystemProperties.is_adding + return {"FINISHED"} + + class AssignSystem(bpy.types.Operator): bl_idname = "bim.assign_system" bl_label = "Assign System" @@ -156,17 +159,20 @@ class AssignSystem(bpy.types.Operator): return IfcStore.execute_ifc_operator(self, context) def _execute(self, context): - product = bpy.data.objects.get(self.product) if self.product else context.active_object self.file = IfcStore.get_file() - ifcopenshell.api.run( - "system.assign_system", - self.file, - **{ - "product": self.file.by_id(product.BIMObjectProperties.ifc_definition_id), - "system": self.file.by_id(self.system), - } - ) - Data.load(IfcStore.get_file()) + products = [bpy.data.objects.get(self.product)] if self.product else context.selected_objects + for product in products: + if not product.BIMObjectProperties.ifc_definition_id: + continue + ifcopenshell.api.run( + "system.assign_system", + self.file, + **{ + "product": self.file.by_id(product.BIMObjectProperties.ifc_definition_id), + "system": self.file.by_id(self.system), + } + ) + Data.load(self.file) return {"FINISHED"} @@ -181,17 +187,23 @@ class UnassignSystem(bpy.types.Operator): return IfcStore.execute_ifc_operator(self, context) def _execute(self, context): - product = bpy.data.objects.get(self.product) if self.product else context.active_object self.file = IfcStore.get_file() - ifcopenshell.api.run( - "system.unassign_system", - self.file, - **{ - "product": self.file.by_id(product.BIMObjectProperties.ifc_definition_id), - "system": self.file.by_id(self.system), - } - ) - Data.load(IfcStore.get_file()) + products = [bpy.data.objects.get(self.product)] if self.product else context.selected_objects + for product in products: + props = product.BIMObjectProperties + if not props.ifc_definition_id: + continue + if not (props.ifc_definition_id in Data.products and self.system in Data.products[props.ifc_definition_id]): + continue + ifcopenshell.api.run( + "system.unassign_system", + self.file, + **{ + "product": self.file.by_id(product.BIMObjectProperties.ifc_definition_id), + "system": self.file.by_id(self.system), + } + ) + Data.load(self.file) return {"FINISHED"} @@ -202,7 +214,6 @@ class SelectSystemProducts(bpy.types.Operator): system: bpy.props.IntProperty() def execute(self, context): - self.file = IfcStore.get_file() for obj in context.visible_objects: obj.select_set(False) if not obj.BIMObjectProperties.ifc_definition_id: diff --git a/src/blenderbim/blenderbim/bim/module/system/prop.py b/src/blenderbim/blenderbim/bim/module/system/prop.py index f792ac8597..d38c867e25 100644 --- a/src/blenderbim/blenderbim/bim/module/system/prop.py +++ b/src/blenderbim/blenderbim/bim/module/system/prop.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -40,6 +39,7 @@ class System(PropertyGroup): class BIMSystemProperties(PropertyGroup): system_attributes: CollectionProperty(name="System Attributes", type=Attribute) is_editing: BoolProperty(name="Is Editing", default=False) + is_adding: BoolProperty(name="Is Adding", default=False) systems: CollectionProperty(name="Systems", type=System) active_system_index: IntProperty(name="Active System Index") active_system_id: IntProperty(name="Active System Id") diff --git a/src/blenderbim/blenderbim/bim/module/system/ui.py b/src/blenderbim/blenderbim/bim/module/system/ui.py index 516e382a90..24ece1eecf 100644 --- a/src/blenderbim/blenderbim/bim/module/system/ui.py +++ b/src/blenderbim/blenderbim/bim/module/system/ui.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -68,37 +67,78 @@ class BIM_PT_systems(Panel): row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="") +class BIM_PT_object_systems(Panel): + bl_label = "IFC Systems" + bl_idname = "BIM_PT_object_systems" + bl_options = {"DEFAULT_CLOSED"} + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "object" + + @classmethod + def poll(cls, context): + return IfcStore.get_file() and context.active_object.BIMObjectProperties.ifc_definition_id + + def draw(self, context): + if not Data.is_loaded: + Data.load(IfcStore.get_file()) + self.props = context.scene.BIMSystemProperties + row = self.layout.row(align=True) + if self.props.is_adding: + row.label(text="Adding Systems", icon="OUTLINER") + row.operator("bim.toggle_assigning_system", text="", icon="CANCEL") + self.layout.template_list( + "BIM_UL_object_systems", + "", + self.props, + "systems", + self.props, + "active_system_index", + ) + else: + row.label(text=f"{len(Data.systems)} Systems in IFC Project", icon="OUTLINER") + row.operator("bim.toggle_assigning_system", text="", icon="ADD") + + systems_object = Data.products.get(context.active_object.BIMObjectProperties.ifc_definition_id, []) + for system_id in systems_object: + row = self.layout.row(align=True) + row.label(text=Data.systems[system_id].get("Name", "Unnamed")) + op = row.operator("bim.unassign_system", text="", icon="X") + op.system = system_id + + if not systems_object: + self.layout.label(text="No System associated with Active Object") + class BIM_UL_systems(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_system", text="", icon="KEYFRAME_HLT", emboss=False) - op.system = item.ifc_definition_id - else: - op = row.operator("bim.assign_system", text="", icon="KEYFRAME", emboss=False) - op.system = item.ifc_definition_id - - if context.scene.BIMSystemProperties.active_system_id == item.ifc_definition_id: + system_id = item.ifc_definition_id + if context.scene.BIMSystemProperties.active_system_id == system_id: op = row.operator("bim.select_system_products", text="", icon="RESTRICT_SELECT_OFF") - op.system = item.ifc_definition_id + op.system = system_id row.operator("bim.edit_system", text="", icon="CHECKMARK") row.operator("bim.disable_editing_system", text="", icon="CANCEL") elif context.scene.BIMSystemProperties.active_system_id: op = row.operator("bim.select_system_products", text="", icon="RESTRICT_SELECT_OFF") - op.system = item.ifc_definition_id - row.operator("bim.remove_system", text="", icon="X").system = item.ifc_definition_id + op.system = system_id + op = row.operator("bim.remove_system", text="", icon="X") + op.system = system_id else: op = row.operator("bim.select_system_products", text="", icon="RESTRICT_SELECT_OFF") - op.system = item.ifc_definition_id + op.system = system_id op = row.operator("bim.enable_editing_system", text="", icon="GREASEPENCIL") - op.system = item.ifc_definition_id - row.operator("bim.remove_system", text="", icon="X").system = item.ifc_definition_id + op.system = system_id + op = row.operator("bim.remove_system", text="", icon="X") + op.system = system_id + + +class BIM_UL_object_systems(UIList): + def draw_item(self, context, layout, data, item, icon, active_data, active_propname): + if item: + row = layout.row(align=True) + row.label(text=item.name) + op = row.operator("bim.assign_system", text="", icon="ADD") + op.system = item.ifc_definition_id diff --git a/src/blenderbim/blenderbim/bim/module/type/__init__.py b/src/blenderbim/blenderbim/bim/module/type/__init__.py index 329a930875..72f1a07edc 100644 --- a/src/blenderbim/blenderbim/bim/module/type/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/type/__init__.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/module/type/operator.py b/src/blenderbim/blenderbim/bim/module/type/operator.py index d1a889b459..4a95206fe9 100644 --- a/src/blenderbim/blenderbim/bim/module/type/operator.py +++ b/src/blenderbim/blenderbim/bim/module/type/operator.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/module/type/prop.py b/src/blenderbim/blenderbim/bim/module/type/prop.py index c5b6c2221d..7088cd20c6 100644 --- a/src/blenderbim/blenderbim/bim/module/type/prop.py +++ b/src/blenderbim/blenderbim/bim/module/type/prop.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/module/type/ui.py b/src/blenderbim/blenderbim/bim/module/type/ui.py index 84deb86a70..67d680e6e4 100644 --- a/src/blenderbim/blenderbim/bim/module/type/ui.py +++ b/src/blenderbim/blenderbim/bim/module/type/ui.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/module/unit/__init__.py b/src/blenderbim/blenderbim/bim/module/unit/__init__.py index a0942261df..4fd0c0c258 100644 --- a/src/blenderbim/blenderbim/bim/module/unit/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/unit/__init__.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/module/unit/operator.py b/src/blenderbim/blenderbim/bim/module/unit/operator.py index d55c0cd3e6..aa0527f690 100644 --- a/src/blenderbim/blenderbim/bim/module/unit/operator.py +++ b/src/blenderbim/blenderbim/bim/module/unit/operator.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -223,7 +222,6 @@ class AddContextDependentUnit(bpy.types.Operator): return {"FINISHED"} - class EnableEditingUnit(bpy.types.Operator): bl_idname = "bim.enable_editing_unit" bl_label = "Enable Editing Unit" @@ -236,14 +234,15 @@ class EnableEditingUnit(bpy.types.Operator): data = Data.units[self.unit] blenderbim.bim.helper.import_attributes( data["type"], - props.unit_attributes, - data, - lambda name, prop, data: self.import_attributes(name, prop, data, context)) + props.unit_attributes, + data, + lambda name, prop, data: self.import_attributes(name, prop, data, context), + ) props.active_unit_id = self.unit return {"FINISHED"} def import_attributes(self, name, prop, data, context): - if name == "Dimensions": + if name == "Dimensions" and data["type"] != "IfcSIUnit": new = context.scene.BIMUnitProperties.unit_attributes.add() new.name = name new.is_null = data[name] is None diff --git a/src/blenderbim/blenderbim/bim/module/unit/prop.py b/src/blenderbim/blenderbim/bim/module/unit/prop.py index d28d093c73..9dcddcbd1a 100644 --- a/src/blenderbim/blenderbim/bim/module/unit/prop.py +++ b/src/blenderbim/blenderbim/bim/module/unit/prop.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/module/unit/ui.py b/src/blenderbim/blenderbim/bim/module/unit/ui.py index 7fb0f9d5c4..790e960fe9 100644 --- a/src/blenderbim/blenderbim/bim/module/unit/ui.py +++ b/src/blenderbim/blenderbim/bim/module/unit/ui.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/module/void/__init__.py b/src/blenderbim/blenderbim/bim/module/void/__init__.py index 95c3503a82..c2afbcbaf6 100644 --- a/src/blenderbim/blenderbim/bim/module/void/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/void/__init__.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/module/void/operator.py b/src/blenderbim/blenderbim/bim/module/void/operator.py index 292aa1466b..e1276e3ea8 100644 --- a/src/blenderbim/blenderbim/bim/module/void/operator.py +++ b/src/blenderbim/blenderbim/bim/module/void/operator.py @@ -21,7 +21,6 @@ import ifcopenshell.api import ifcopenshell.util.representation from blenderbim.bim.ifc import IfcStore from ifcopenshell.api.void.data import Data -from ifcopenshell.api.context.data import Data as ContextData class AddOpening(bpy.types.Operator): @@ -56,21 +55,20 @@ class AddOpening(bpy.types.Operator): "element": self.file.by_id(element_id), }, ) - Data.load(IfcStore.get_file(), element_id) + Data.load(self.file, element_id) - has_modifier = False - - for modifier in obj.modifiers: - if modifier.type == "BOOLEAN" and modifier.object and modifier.object == opening: - has_modifier = True - break - - if not has_modifier: + try: + modifier = next(m for m in obj.modifiers if m.type == "BOOLEAN" and m.object == opening) + except StopIteration: modifier = obj.modifiers.new("IfcOpeningElement", "BOOLEAN") - modifier.operation = "DIFFERENCE" modifier.object = opening + finally: + modifier.operation = "DIFFERENCE" modifier.solver = "EXACT" modifier.use_self = True + modifier.operand_type = "OBJECT" + + context.view_layer.objects.active = obj return {"FINISHED"} @@ -87,17 +85,28 @@ class RemoveOpening(bpy.types.Operator): def _execute(self, context): obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object self.file = IfcStore.get_file() + is_modifier_removed = False for modifier in obj.modifiers: if modifier.type != "BOOLEAN": continue if modifier.object and modifier.object.BIMObjectProperties.ifc_definition_id == self.opening_id: - IfcStore.unlink_element(obj=modifier.object) - if "/" in modifier.object.name and modifier.object.name[0:3] == "Ifc": - modifier.object.name = "/".join(modifier.object.name.split("/")[1:]) + is_modifier_removed = True obj.modifiers.remove(modifier) break + opening = IfcStore.get_element(self.opening_id) + opening.name = "/".join(opening.name.split("/")[1:]) + IfcStore.unlink_element(obj=opening) + ifcopenshell.api.run("void.remove_opening", self.file, **{"opening": self.file.by_id(self.opening_id)}) + + if not is_modifier_removed: + bpy.ops.bim.switch_representation( + ifc_definition_id=obj.data.BIMMeshProperties.ifc_definition_id, + should_reload=True, + should_switch_all_meshes=True, + ) + Data.load(IfcStore.get_file(), obj.BIMObjectProperties.ifc_definition_id) return {"FINISHED"} @@ -119,15 +128,18 @@ class AddFilling(bpy.types.Operator): return {"FINISHED"} self.file = IfcStore.get_file() element_id = obj.BIMObjectProperties.ifc_definition_id + opening_id = opening.BIMObjectProperties.ifc_definition_id + if not element_id or not opening_id: + return {"FINISHED"} ifcopenshell.api.run( "void.add_filling", self.file, **{ - "opening": self.file.by_id(opening.BIMObjectProperties.ifc_definition_id), + "opening": self.file.by_id(opening_id), "element": self.file.by_id(element_id), }, ) - Data.load(IfcStore.get_file(), element_id) + Data.load(self.file, element_id) return {"FINISHED"} diff --git a/src/blenderbim/blenderbim/bim/module/void/prop.py b/src/blenderbim/blenderbim/bim/module/void/prop.py index 08866a8897..faa3813f46 100644 --- a/src/blenderbim/blenderbim/bim/module/void/prop.py +++ b/src/blenderbim/blenderbim/bim/module/void/prop.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -24,4 +23,3 @@ from bpy.props import PointerProperty class VoidProperties(PropertyGroup): desired_opening: PointerProperty(name="Desired Opening To Fill", type=bpy.types.Object) - diff --git a/src/blenderbim/blenderbim/bim/module/void/ui.py b/src/blenderbim/blenderbim/bim/module/void/ui.py index 792867c864..572ad8f71a 100644 --- a/src/blenderbim/blenderbim/bim/module/void/ui.py +++ b/src/blenderbim/blenderbim/bim/module/void/ui.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/blenderbim/bim/operator.py b/src/blenderbim/blenderbim/bim/operator.py index 14cf470a42..1df58dba4f 100644 --- a/src/blenderbim/blenderbim/bim/operator.py +++ b/src/blenderbim/blenderbim/bim/operator.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -27,12 +26,10 @@ import webbrowser import ifcopenshell import blenderbim.bim.handler from . import export_ifc -from . import import_ifc from . import schema from blenderbim.bim.ifc import IfcStore -from bpy_extras.io_utils import ImportHelper -from mathutils import Vector, Matrix, Euler, geometry -from math import radians, degrees, atan, tan, cos, sin +from mathutils import Vector, Matrix, Euler +from math import radians class ExportIFC(bpy.types.Operator): @@ -101,66 +98,13 @@ class ExportIFC(bpy.types.Operator): return {"FINISHED"} -class ImportIFC(bpy.types.Operator, ImportHelper): +class ImportIFC(bpy.types.Operator): bl_idname = "import_ifc.bim" bl_label = "Import IFC" bl_options = {"REGISTER", "UNDO"} - filename_ext = ".ifc" - filter_glob: bpy.props.StringProperty(default="*.ifc;*.ifczip;*.ifcxml", options={"HIDDEN"}) - - should_auto_set_workarounds: bpy.props.BoolProperty(name="Automatically Set Vendor Workarounds", default=True) - should_use_cpu_multiprocessing: bpy.props.BoolProperty(name="Import with CPU Multiprocessing", default=True) - should_merge_by_class: bpy.props.BoolProperty(name="Import and Merge by Class", default=False) - should_merge_by_material: bpy.props.BoolProperty(name="Import and Merge by Material", default=False) - should_merge_materials_by_colour: bpy.props.BoolProperty(name="Import and Merge Materials by Colour", default=False) - should_clean_mesh: bpy.props.BoolProperty(name="Import and Clean Mesh", default=True) - deflection_tolerance: bpy.props.FloatProperty(name="Import Deflection Tolerance", default=0.001) - angular_tolerance: bpy.props.FloatProperty(name="Import Angular Tolerance", default=0.5) - should_offset_model: bpy.props.BoolProperty(name="Import and Offset Model", default=False) - model_offset_coordinates: bpy.props.StringProperty(name="Model Offset Coordinates", default="0,0,0") - ifc_import_filter: bpy.props.EnumProperty( - items=[ - ("NONE", "None", ""), - ("WHITELIST", "Whitelist", ""), - ("BLACKLIST", "Blacklist", ""), - ], - name="Import Filter", - ) - ifc_selector: bpy.props.StringProperty(default="", name="IFC Selector") def execute(self, context): - start = time.time() - logger = logging.getLogger("ImportIFC") - path_log = os.path.join(context.scene.BIMProperties.data_dir, "process.log") - if not os.access(context.scene.BIMProperties.data_dir, os.W_OK): - path_log = os.path.join(tempfile.mkdtemp(), "process.log") - logging.basicConfig( - filename=path_log, - filemode="a", - level=logging.DEBUG, - ) - - settings = import_ifc.IfcImportSettings.factory(context, self.filepath, logger) - settings.should_auto_set_workarounds = self.should_auto_set_workarounds - settings.should_use_cpu_multiprocessing = self.should_use_cpu_multiprocessing - settings.should_merge_by_class = self.should_merge_by_class - settings.should_merge_by_material = self.should_merge_by_material - settings.should_merge_materials_by_colour = self.should_merge_materials_by_colour - settings.should_clean_mesh = self.should_clean_mesh - settings.deflection_tolerance = self.deflection_tolerance - settings.angular_tolerance = self.angular_tolerance - settings.should_offset_model = self.should_offset_model - settings.model_offset_coordinates = ( - [float(o) for o in self.model_offset_coordinates.split(",")] if self.model_offset_coordinates else (0, 0, 0) - ) - settings.ifc_import_filter = self.ifc_import_filter - settings.ifc_selector = self.ifc_selector - - settings.logger.info("Starting import") - ifc_importer = import_ifc.IfcImporter(settings) - ifc_importer.execute() - settings.logger.info("Import finished in {:.2f} seconds".format(time.time() - start)) - print("Import finished in {:.2f} seconds".format(time.time() - start)) + bpy.ops.bim.load_project("INVOKE_DEFAULT") return {"FINISHED"} @@ -529,34 +473,6 @@ class SetViewportShadowFromSun(bpy.types.Operator): return {"FINISHED"} -class LinkIfc(bpy.types.Operator): - bl_idname = "bim.link_ifc" - bl_label = "Link IFC" - bl_options = {"REGISTER", "UNDO"} - filepath: bpy.props.StringProperty(subtype="FILE_PATH") - - def execute(self, context): - # context.active_object.active_material.BIMMaterialProperties.location = self.filepath - # coll_name = "MyCollection" - - with bpy.data.libraries.load(self.filepath, link=True) as (data_from, data_to): - data_to.scenes = data_from.scenes - - for scene in bpy.data.scenes: - if not scene.library or scene.library.filepath != self.filepath: - continue - for child in scene.collection.children: - if "IfcProject" not in child.name: - continue - bpy.data.scenes[0].collection.children.link(child) - - return {"FINISHED"} - - def invoke(self, context, event): - context.window_manager.fileselect_add(self) - return {"RUNNING_MODAL"} - - class SnapSpacesTogether(bpy.types.Operator): bl_idname = "bim.snap_spaces_together" bl_label = "Snap Spaces Together" @@ -726,3 +642,38 @@ class CopyAttributeToSelection(bpy.types.Operator): a.name() for a in self.schema.declaration_by_name(ifc_class).all_attributes() ] return self.applicable_attributes_cache[ifc_class] + + +class OverrideDelete(bpy.types.Operator): + bl_idname = "object.delete" + bl_label = "Delete" + + @classmethod + def poll(cls, context): + return context.active_object is not None + + def execute(self, context): + if IfcStore.get_file(): + return IfcStore.execute_ifc_operator(self, context) + for obj in context.selected_objects: + bpy.data.objects.remove(obj) + return {"FINISHED"} + + def invoke(self, context, event): + return context.window_manager.invoke_confirm(self, event) + + def _execute(self, context): + for obj in context.selected_objects: + if obj.BIMObjectProperties.ifc_definition_id: + element = IfcStore.get_file().by_id(obj.BIMObjectProperties.ifc_definition_id) + if element.is_a("IfcOpeningElement"): + self.delete_opening_element(element) + elif element.HasOpenings: + for rel in element.HasOpenings: + self.delete_opening_element(rel.RelatedOpeningElement) + bpy.data.objects.remove(obj) + return {"FINISHED"} + + def delete_opening_element(self, element): + obj = IfcStore.get_element(element.VoidsElements[0].RelatingBuildingElement.id()) + bpy.ops.bim.remove_opening(opening_id=element.id(), obj=obj.name) diff --git a/src/blenderbim/blenderbim/bim/prop.py b/src/blenderbim/blenderbim/bim/prop.py index 21fb7b6d3a..5c90d32e93 100644 --- a/src/blenderbim/blenderbim/bim/prop.py +++ b/src/blenderbim/blenderbim/bim/prop.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -65,7 +64,7 @@ def updateDataDir(self, context): blenderbim.bim.schema.ifc.data_dir = context.scene.BIMProperties.data_dir -def updateIfcFile(self, context): +def update_ifc_file(self, context): if context.scene.BIMProperties.ifc_file: blenderbim.bim.handler.loadIfcStore(context.scene) @@ -222,7 +221,7 @@ class BIMProperties(PropertyGroup): data_dir: StringProperty( default=os.path.join(cwd, "data") + os.path.sep, name="Data Directory", update=updateDataDir ) - ifc_file: StringProperty(name="IFC File", update=updateIfcFile) + ifc_file: StringProperty(name="IFC File", update=update_ifc_file) export_schema: EnumProperty(items=[("IFC4", "IFC4", ""), ("IFC2X3", "IFC2X3", "")], name="IFC Schema") last_transaction: StringProperty(name="Last Transaction") contexts: EnumProperty(items=getContexts, name="Contexts") diff --git a/src/blenderbim/blenderbim/bim/schema.py b/src/blenderbim/blenderbim/bim/schema.py index 4cdeeb2b7e..90ab1ce2e3 100644 --- a/src/blenderbim/blenderbim/bim/schema.py +++ b/src/blenderbim/blenderbim/bim/schema.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -97,6 +96,7 @@ class IfcSchema: ifc = IfcSchema() + def reload(): global ifc ifc = IfcSchema() diff --git a/src/blenderbim/blenderbim/bim/ui.py b/src/blenderbim/blenderbim/bim/ui.py index b67aeb3ba4..639d8411fa 100644 --- a/src/blenderbim/blenderbim/bim/ui.py +++ b/src/blenderbim/blenderbim/bim/ui.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/bcf/bcf/__init__.py b/src/blenderbim/blenderbim/libs/site/packages/.gitignore similarity index 100% rename from src/bcf/bcf/__init__.py rename to src/blenderbim/blenderbim/libs/site/packages/.gitignore diff --git a/src/blenderbim/blenderbim/libs/site/packages/hppfcl.pth b/src/blenderbim/blenderbim/libs/site/packages/hppfcl.pth deleted file mode 100644 index c0d165217a..0000000000 --- a/src/blenderbim/blenderbim/libs/site/packages/hppfcl.pth +++ /dev/null @@ -1,3 +0,0 @@ -# expose hppfcl as site-package - -hppfcl diff --git a/src/blenderbim/blenderbim/libs/site/packages/ifcopenshell.pth b/src/blenderbim/blenderbim/libs/site/packages/ifcopenshell.pth deleted file mode 100644 index 55ebfa3025..0000000000 --- a/src/blenderbim/blenderbim/libs/site/packages/ifcopenshell.pth +++ /dev/null @@ -1,3 +0,0 @@ -# expose ifcopenshell as site-package - -ifcopenshell diff --git a/src/blenderbim/blenderbim/libs/site/packages/svgwrite.pth b/src/blenderbim/blenderbim/libs/site/packages/svgwrite.pth deleted file mode 100644 index 7cd7e50c2b..0000000000 --- a/src/blenderbim/blenderbim/libs/site/packages/svgwrite.pth +++ /dev/null @@ -1,3 +0,0 @@ -# setup svgwrite - -svgwrite diff --git a/src/blenderbim/demo-library.blend b/src/blenderbim/demo-library.blend index 06132f34b1..58249f435e 100644 Binary files a/src/blenderbim/demo-library.blend and b/src/blenderbim/demo-library.blend differ diff --git a/src/blenderbim/docs/conf.py b/src/blenderbim/docs/conf.py index c441ae8a0a..62b5dea99e 100644 --- a/src/blenderbim/docs/conf.py +++ b/src/blenderbim/docs/conf.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/docs/ifcopenshell-python/api-documentation.rst b/src/blenderbim/docs/ifcopenshell-python/api-documentation.rst index feabcc8345..299250fb90 100644 --- a/src/blenderbim/docs/ifcopenshell-python/api-documentation.rst +++ b/src/blenderbim/docs/ifcopenshell-python/api-documentation.rst @@ -16,3 +16,6 @@ API Documentation .. automodule:: ifcopenshell.validate :members: + +.. automodule:: ifcopenshell.ids + :members: diff --git a/src/blenderbim/dxf2ifc.py b/src/blenderbim/dxf2ifc.py index 62bdfd9659..8a1bc4e71b 100644 --- a/src/blenderbim/dxf2ifc.py +++ b/src/blenderbim/dxf2ifc.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -20,6 +19,7 @@ import ifcopenshell import ezdxf + class Dxf2Ifc: def execute(self): self.create_ifc_file() @@ -36,7 +36,10 @@ class Dxf2Ifc: [ self.file.createIfcFaceOuterBound( self.file.createIfcPolyLoop( - [self.file.createIfcCartesianPoint((face[index].dxf.location)) for index in range(len(face) -1)] + [ + self.file.createIfcCartesianPoint((face[index].dxf.location)) + for index in range(len(face) - 1) + ] ), True, ) diff --git a/src/blenderbim/extract.py b/src/blenderbim/extract.py index fcc1ac0d3a..c97a95f5c1 100644 --- a/src/blenderbim/extract.py +++ b/src/blenderbim/extract.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/gbxml.py b/src/blenderbim/gbxml.py index 50abaaaee9..591a1c3509 100644 --- a/src/blenderbim/gbxml.py +++ b/src/blenderbim/gbxml.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/generate_demo_library.py b/src/blenderbim/generate_demo_library.py index 2911a5fae9..24b6ffa5d9 100644 --- a/src/blenderbim/generate_demo_library.py +++ b/src/blenderbim/generate_demo_library.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -29,13 +28,21 @@ class LibraryGenerator: self.file = ifcopenshell.api.run("project.create_file") self.project = ifcopenshell.api.run( + "root.create_entity", self.file, ifc_class="IfcProject", name="BlenderBIM Demo" + ) + self.library = ifcopenshell.api.run( "root.create_entity", self.file, ifc_class="IfcProjectLibrary", name="BlenderBIM Demo Library" ) + ifcopenshell.api.run( + "project.assign_declaration", self.file, definition=self.library, relating_context=self.project + ) ifcopenshell.api.run("unit.assign_unit", self.file, length={"is_metric": True, "raw": "METERS"}) ifcopenshell.api.run("context.add_context", self.file, context="Model") - self.body = ifcopenshell.api.run( - "context.add_context", self.file, context="Model", subcontext="Body", target_view="MODEL_VIEW" - ) + self.representations = { + "body": ifcopenshell.api.run( + "context.add_context", self.file, context="Model", subcontext="Body", target_view="MODEL_VIEW" + ) + } ifcopenshell.api.run("context.add_context", self.file, context="Plan") self.annotation = ifcopenshell.api.run( "context.add_context", self.file, context="Model", subcontext="Annotation", target_view="PLAN_VIEW" @@ -109,7 +116,9 @@ class LibraryGenerator: ) self.create_profile_type("IfcBeamType", "DEMO2", profile) - self.create_window_type("IfcWindowType", "DEMO1") + self.create_type("IfcWindowType", "DEMO1", {"body": "Window"}) + self.create_type("IfcDoorType", "DEMO1", {"body": "Door"}) + self.create_type("IfcFurnitureType", "BUNNY", {"body": "Bunny"}) self.file.write("blenderbim-demo-library.ifc") @@ -119,7 +128,7 @@ class LibraryGenerator: layer_set = rel.RelatingMaterial layer = ifcopenshell.api.run("material.add_layer", self.file, layer_set=layer_set, material=self.material) layer.LayerThickness = thickness - ifcopenshell.api.run("project.assign_declaration", self.file, definition=element, relating_context=self.project) + ifcopenshell.api.run("project.assign_declaration", self.file, definition=element, relating_context=self.library) return element def create_profile_type(self, ifc_class, name, profile): @@ -130,35 +139,35 @@ class LibraryGenerator: "material.add_profile", self.file, profile_set=profile_set, material=self.material ) ifcopenshell.api.run("material.assign_profile", self.file, material_profile=material_profile, profile=profile) - ifcopenshell.api.run("project.assign_declaration", self.file, definition=element, relating_context=self.project) + ifcopenshell.api.run("project.assign_declaration", self.file, definition=element, relating_context=self.library) - def create_window_type(self, ifc_class, name): + def create_type(self, ifc_class, name, representations): element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class=ifc_class, name=name) - obj = bpy.data.objects.get("Window") - representation = ifcopenshell.api.run( - "geometry.add_representation", - self.file, - context=self.body, - blender_object=obj, - geometry=obj.data, - total_items=max(1, len(obj.material_slots)), - ) - - ifcopenshell.api.run( - "style.assign_representation_styles", - self.file, - **{ - "shape_representation": representation, - "styles": [ - ifcopenshell.api.run("style.add_style", self.file, **self.get_style_settings(s.material)) - for s in obj.material_slots - ], - }, - ) - ifcopenshell.api.run( - "geometry.assign_representation", self.file, product=element, representation=representation - ) - ifcopenshell.api.run("project.assign_declaration", self.file, definition=element, relating_context=self.project) + for rep_name, obj_name in representations.items(): + obj = bpy.data.objects.get(obj_name) + representation = ifcopenshell.api.run( + "geometry.add_representation", + self.file, + context=self.representations[rep_name], + blender_object=obj, + geometry=obj.data, + total_items=max(1, len(obj.material_slots)), + ) + ifcopenshell.api.run( + "style.assign_representation_styles", + self.file, + **{ + "shape_representation": representation, + "styles": [ + ifcopenshell.api.run("style.add_style", self.file, **self.get_style_settings(s.material)) + for s in obj.material_slots + ], + }, + ) + ifcopenshell.api.run( + "geometry.assign_representation", self.file, product=element, representation=representation + ) + ifcopenshell.api.run("project.assign_declaration", self.file, definition=element, relating_context=self.library) def get_style_settings(self, material): transparency = material.diffuse_color[3] diff --git a/src/blenderbim/generate_site_library.py b/src/blenderbim/generate_site_library.py index 21c9f8d829..e25a6334c4 100644 --- a/src/blenderbim/generate_site_library.py +++ b/src/blenderbim/generate_site_library.py @@ -28,8 +28,14 @@ class LibraryGenerator: self.file = ifcopenshell.api.run("project.create_file") self.project = ifcopenshell.api.run( + "root.create_entity", self.file, ifc_class="IfcProject", name="BlenderBIM Demo" + ) + self.library = ifcopenshell.api.run( "root.create_entity", self.file, ifc_class="IfcProjectLibrary", name="BlenderBIM Demo Library" ) + ifcopenshell.api.run( + "project.assign_declaration", self.file, definition=self.library, relating_context=self.library + ) ifcopenshell.api.run("unit.assign_unit", self.file, length={"is_metric": True, "raw": "METERS"}) ifcopenshell.api.run("context.add_context", self.file, context="Model") self.representations = { @@ -81,7 +87,7 @@ class LibraryGenerator: ifcopenshell.api.run( "geometry.assign_representation", self.file, product=element, representation=representation ) - ifcopenshell.api.run("project.assign_declaration", self.file, definition=element, relating_context=self.project) + ifcopenshell.api.run("project.assign_declaration", self.file, definition=element, relating_context=self.library) def get_style_settings(self, material): transparency = material.diffuse_color[3] diff --git a/src/blenderbim/generate_util_type_json.py b/src/blenderbim/generate_util_type_json.py index aa93b0b0f8..c73445673d 100644 --- a/src/blenderbim/generate_util_type_json.py +++ b/src/blenderbim/generate_util_type_json.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/getIfcElements.py b/src/blenderbim/getIfcElements.py index 76ed473c31..a62d91ca4e 100644 --- a/src/blenderbim/getIfcElements.py +++ b/src/blenderbim/getIfcElements.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/get_description.py b/src/blenderbim/get_description.py index ebb673eff8..6e7a58975b 100644 --- a/src/blenderbim/get_description.py +++ b/src/blenderbim/get_description.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/get_volume_by_material.py b/src/blenderbim/get_volume_by_material.py index 0f880dd698..dfb96d700c 100644 --- a/src/blenderbim/get_volume_by_material.py +++ b/src/blenderbim/get_volume_by_material.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/blenderbim/libraries/blenderbim-demo-library.ifc b/src/blenderbim/libraries/blenderbim-demo-library.ifc new file mode 100644 index 0000000000..b9bfd53120 --- /dev/null +++ b/src/blenderbim/libraries/blenderbim-demo-library.ifc @@ -0,0 +1,920 @@ +ISO-10303-21; +HEADER; +FILE_DESCRIPTION(('ViewDefinition[DesignTransferView]'),'2;1'); +FILE_NAME('/dev/null','2021-09-01T08:53:20+10:00',(),(),'IfcOpenShell 0.6.0b0','IfcOpenShell 0.6.0b0','Nobody'); +FILE_SCHEMA(('IFC4')); +ENDSEC; +DATA; +#1=IFCACTORROLE(.USERDEFINED.,'CONTRIBUTOR',$); +#2=IFCTELECOMADDRESS(.USERDEFINED.,'The main webpage of the software collection.','WEBPAGE',$,$,$,$,'https://ifcopenshell.org',$); +#3=IFCTELECOMADDRESS(.USERDEFINED.,'The BlenderBIM Add-on webpage of the software collection.','WEBPAGE',$,$,$,$,'https://blenderbim.org',$); +#4=IFCTELECOMADDRESS(.USERDEFINED.,'The source code repository of the software collection.','REPOSITORY',$,$,$,$,'https://github.com/IfcOpenShell/IfcOpenShell.git',$); +#5=IFCORGANIZATION($,'IfcOpenShell','IfcOpenShell is an open source (LGPL) software library that helps users and software developers to work with the IFC file format.',(#1),(#2,#3,#4)); +#6=IFCAPPLICATION(#5,'0.0.210605','BlenderBIM Add-on','BlenderBIM'); +#7=IFCPROJECT('2FauadAdf9pvM8aE3a1vec',$,'BlenderBIM Demo',$,$,$,$,(#18,#24),#13); +#8=IFCPROJECTLIBRARY('0d1L7oy091iulj2eUTPIZO',$,'BlenderBIM Demo Library',$,$,$,$,$,$); +#9=IFCRELDECLARES('1XnMqZs81BFex6KdRTd93O',$,$,$,#7,(#8)); +#10=IFCSIUNIT(*,.LENGTHUNIT.,$,.METRE.); +#11=IFCSIUNIT(*,.AREAUNIT.,$,.SQUARE_METRE.); +#12=IFCSIUNIT(*,.VOLUMEUNIT.,$,.CUBIC_METRE.); +#13=IFCUNITASSIGNMENT((#12,#10,#11)); +#14=IFCCARTESIANPOINT((0.,0.,0.)); +#15=IFCDIRECTION((0.,0.,1.)); +#16=IFCDIRECTION((1.,0.,0.)); +#17=IFCAXIS2PLACEMENT3D(#14,#15,#16); +#18=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Model',3,1.E-05,#17,$); +#19=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Body','Model',*,*,*,*,#18,$,.MODEL_VIEW.,$); +#20=IFCCARTESIANPOINT((0.,0.,0.)); +#21=IFCDIRECTION((0.,0.,1.)); +#22=IFCDIRECTION((1.,0.,0.)); +#23=IFCAXIS2PLACEMENT3D(#20,#21,#22); +#24=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Plan',2,1.E-05,#23,$); +#25=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Annotation','Model',*,*,*,*,#18,$,.PLAN_VIEW.,$); +#26=IFCMATERIAL('Unknown',$,$); +#27=IFCWALLTYPE('2oPHjJ1mj14fByRdGpdswW',$,'DEMO50',$,$,$,$,$,$,$); +#28=IFCMATERIALLAYERSET((#30),$,$); +#29=IFCRELASSOCIATESMATERIAL('0hH1WZJxvCswA5NJX2q3CL',$,$,$,(#27),#28); +#30=IFCMATERIALLAYER(#26,0.05,$,$,$,$,$); +#31=IFCRELDECLARES('2cIzc_nR53Yf2v_UoC8vm_',$,$,$,#8,(#40,#73,#44,#88,#48,#83,#32,#65,#98,#54,#78,#144,#190,#36,#69,#102,#27,#60,#93)); +#32=IFCWALLTYPE('1W9xakcVrB2fmPvHmunEKZ',$,'DEMO100',$,$,$,$,$,$,$); +#33=IFCMATERIALLAYERSET((#35),$,$); +#34=IFCRELASSOCIATESMATERIAL('0g6Joc9xH01gj8ldoXV7tP',$,$,$,(#32),#33); +#35=IFCMATERIALLAYER(#26,0.1,$,$,$,$,$); +#36=IFCWALLTYPE('3biYbSqev8K8L$LGJ7EMTs',$,'DEMO200',$,$,$,$,$,$,$); +#37=IFCMATERIALLAYERSET((#39),$,$); +#38=IFCRELASSOCIATESMATERIAL('05zC4XF0D8muS2OS5sQz0e',$,$,$,(#36),#37); +#39=IFCMATERIALLAYER(#26,0.2,$,$,$,$,$); +#40=IFCWALLTYPE('3Y9DTm8CjDLvu6DuvmwBE6',$,'DEMO300',$,$,$,$,$,$,$); +#41=IFCMATERIALLAYERSET((#43),$,$); +#42=IFCRELASSOCIATESMATERIAL('2IJv0GKenC$uyrbNMWPmur',$,$,$,(#40),#41); +#43=IFCMATERIALLAYER(#26,0.3,$,$,$,$,$); +#44=IFCCOVERINGTYPE('2zhMYHx2D3IwE8DAMCmh7m',$,'DEMO10',$,$,$,$,$,$,$); +#45=IFCMATERIALLAYERSET((#47),$,$); +#46=IFCRELASSOCIATESMATERIAL('1t0xssglbBdxSmrw0Idc5A',$,$,$,(#44),#45); +#47=IFCMATERIALLAYER(#26,0.01,$,$,$,$,$); +#48=IFCCOVERINGTYPE('3aTtGLBXPBfBQ5AjqT70Bz',$,'DEMO20',$,$,(#52),$,$,$,$); +#49=IFCMATERIALLAYERSET((#51),$,$); +#50=IFCRELASSOCIATESMATERIAL('3MA_Yuye52HPeOj4qOz_Ez',$,$,$,(#48),#49); +#51=IFCMATERIALLAYER(#26,0.02,$,$,$,$,$); +#52=IFCPROPERTYSET('1ng0AJNV50IOrM16_UrOdp',$,'EPset_Parametric',$,(#53)); +#53=IFCPROPERTYSINGLEVALUE('LayerSetDirection',$,IFCLABEL('AXIS2'),$); +#54=IFCCOVERINGTYPE('0MUtWoJwL7pQLKzkL9yhMg',$,'DEMO30',$,$,(#58),$,$,$,$); +#55=IFCMATERIALLAYERSET((#57),$,$); +#56=IFCRELASSOCIATESMATERIAL('0JmGj91ozA9Qqb7k1jfW7c',$,$,$,(#54),#55); +#57=IFCMATERIALLAYER(#26,0.03,$,$,$,$,$); +#58=IFCPROPERTYSET('1SVj8L4bf7Avw9Ttfn42rr',$,'EPset_Parametric',$,(#59)); +#59=IFCPROPERTYSINGLEVALUE('LayerSetDirection',$,IFCLABEL('AXIS3'),$); +#60=IFCRAMPTYPE('10Lc$d81jBlv1$Nw4r1G5p',$,'DEMO200',$,$,$,$,$,$,$); +#61=IFCMATERIALLAYERSET((#63),$,$); +#62=IFCRELASSOCIATESMATERIAL('0QAw8cXJHAaPTGorRer0LE',$,$,$,(#60),#61); +#63=IFCMATERIALLAYER(#26,0.2,$,$,$,$,$); +#64=IFCCIRCLEPROFILEDEF(.AREA.,$,$,0.3); +#65=IFCPILETYPE('0vzTWCLsf82gXaa5XBEjmX',$,'DEMO1',$,$,$,$,$,$,$); +#66=IFCMATERIALPROFILESET($,$,(#68),$); +#67=IFCRELASSOCIATESMATERIAL('2VH9GztyLAKwDiMxorZnCa',$,$,$,(#65),#66); +#68=IFCMATERIALPROFILE($,$,#26,#64,$,$); +#69=IFCSLABTYPE('1Zm7Mw0mDE49d6OAAbiljp',$,'DEMO150',$,$,$,$,$,$,$); +#70=IFCMATERIALLAYERSET((#72),$,$); +#71=IFCRELASSOCIATESMATERIAL('2ubPX6SlL2NADJ8Q6IVqC$',$,$,$,(#69),#70); +#72=IFCMATERIALLAYER(#26,0.2,$,$,$,$,$); +#73=IFCSLABTYPE('2Q$yfvdY9FFwmfMPpGV9rn',$,'DEMO250',$,$,$,$,$,$,$); +#74=IFCMATERIALLAYERSET((#76),$,$); +#75=IFCRELASSOCIATESMATERIAL('1wT9Sxb519AvjgkW436_Ax',$,$,$,(#73),#74); +#76=IFCMATERIALLAYER(#26,0.3,$,$,$,$,$); +#77=IFCRECTANGLEPROFILEDEF(.AREA.,$,$,0.5,0.6); +#78=IFCCOLUMNTYPE('2fKlK0mB9BkBUjD9GBduNj',$,'DEMO1',$,$,$,$,$,$,$); +#79=IFCMATERIALPROFILESET($,$,(#81),$); +#80=IFCRELASSOCIATESMATERIAL('0VIr$rmPr9eOwvMHckyKNq',$,$,$,(#78),#79); +#81=IFCMATERIALPROFILE($,$,#26,#77,$,$); +#82=IFCCIRCLEHOLLOWPROFILEDEF(.AREA.,$,$,0.25,0.005); +#83=IFCCOLUMNTYPE('29H7BcvLr9PeILdG$esLQr',$,'DEMO2',$,$,$,$,$,$,$); +#84=IFCMATERIALPROFILESET($,$,(#86),$); +#85=IFCRELASSOCIATESMATERIAL('1st9fPEOb8d8JDw_krYvoe',$,$,$,(#83),#84); +#86=IFCMATERIALPROFILE($,$,#26,#82,$,$); +#87=IFCRECTANGLEHOLLOWPROFILEDEF(.AREA.,$,$,0.075,0.15,0.005,0.005,0.005); +#88=IFCCOLUMNTYPE('3cS_RzY7L928cugHAHEdO$',$,'DEMO3',$,$,$,$,$,$,$); +#89=IFCMATERIALPROFILESET($,$,(#91),$); +#90=IFCRELASSOCIATESMATERIAL('0rF5dzkMrFrQQW4S71rDpE',$,$,$,(#88),#89); +#91=IFCMATERIALPROFILE($,$,#26,#87,$,$); +#92=IFCISHAPEPROFILEDEF(.AREA.,'DEMO-I',$,0.1,0.2,0.005,0.01,0.005,$,$); +#93=IFCBEAMTYPE('31lTcjHz5DABs7Q1Hyf1zc',$,'DEMO1',$,$,$,$,$,$,$); +#94=IFCMATERIALPROFILESET($,$,(#96),$); +#95=IFCRELASSOCIATESMATERIAL('14YLYhEp93p9EVnoqLrbUt',$,$,$,(#93),#94); +#96=IFCMATERIALPROFILE($,$,#26,#92,$,$); +#97=IFCCSHAPEPROFILEDEF(.AREA.,'DEMO-C',$,0.2,0.1,0.0015,0.03,0.005); +#98=IFCBEAMTYPE('1rc$eFVe16yfx_aL6Lt3ey',$,'DEMO2',$,$,$,$,$,$,$); +#99=IFCMATERIALPROFILESET($,$,(#101),$); +#100=IFCRELASSOCIATESMATERIAL('2fuWKLohT0KPjC4t9jRz_G',$,$,$,(#98),#99); +#101=IFCMATERIALPROFILE($,$,#26,#97,$,$); +#102=IFCWINDOWTYPE('2drJ7e_JH2X9EMzOm_KQzx',$,'DEMO1',$,$,$,(#143),$,$,$,$,$,$); +#103=IFCINDEXEDPOLYGONALFACE((13,17,18,14)); +#104=IFCINDEXEDPOLYGONALFACE((5,6,3,4)); +#105=IFCINDEXEDPOLYGONALFACE((7,8,2,1)); +#106=IFCINDEXEDPOLYGONALFACE((6,7,1,3)); +#107=IFCINDEXEDPOLYGONALFACE((8,5,4,2)); +#108=IFCINDEXEDPOLYGONALFACE((12,11,10,9)); +#109=IFCINDEXEDPOLYGONALFACE((15,19,20,16)); +#110=IFCINDEXEDPOLYGONALFACE((14,18,19,15)); +#111=IFCINDEXEDPOLYGONALFACE((16,20,17,13)); +#112=IFCINDEXEDPOLYGONALFACE((4,17,20,2)); +#113=IFCINDEXEDPOLYGONALFACE((2,20,19,1)); +#114=IFCINDEXEDPOLYGONALFACE((8,16,13,5)); +#115=IFCINDEXEDPOLYGONALFACE((7,15,16,8)); +#116=IFCINDEXEDPOLYGONALFACE((1,19,18,3)); +#117=IFCINDEXEDPOLYGONALFACE((3,18,17,4)); +#118=IFCINDEXEDPOLYGONALFACE((6,14,15,7)); +#119=IFCINDEXEDPOLYGONALFACE((5,13,14,6)); +#120=IFCINDEXEDPOLYGONALFACE((24,21,22,23)); +#121=IFCINDEXEDPOLYGONALFACE((11,23,22,10)); +#122=IFCINDEXEDPOLYGONALFACE((10,22,21,9)); +#123=IFCINDEXEDPOLYGONALFACE((9,21,24,12)); +#124=IFCINDEXEDPOLYGONALFACE((12,24,23,11)); +#125=IFCCARTESIANPOINTLIST3D(((0.899999976158142,0.,1.20000004768372),(0.899999976158142,0.,0.),(0.,0.,1.20000004768372),(0.,0.,0.),(0.0999999940395355,0.,0.0999999940395355),(0.0999999940395355,0.,1.10000002384186),(0.800000011920929,0.,1.10000002384186),(0.800000011920929,0.,0.0999999940395355),(0.0999999940395355,0.0199999995529652,0.0999999940395355),(0.0999999940395355,0.0199999995529652,1.10000002384186),(0.800000011920929,0.0199999995529652,1.10000002384186),(0.800000011920929,0.0199999995529652,0.0999999940395355),(0.0999999940395355,0.0500000007450581,0.0999999940395355),(0.0999999940395355,0.0500000007450581,1.10000002384186),(0.800000011920929,0.0500000007450581,1.10000002384186),(0.800000011920929,0.0500000007450581,0.0999999940395355),(0.,0.0500000007450581,0.),(0.,0.0500000007450581,1.20000004768372),(0.899999976158142,0.0500000007450581,1.20000004768372),(0.899999976158142,0.0500000007450581,0.),(0.0999999940395355,0.0299999993294477,0.0999999940395355),(0.0999999940395355,0.0299999993294477,1.10000002384186),(0.800000011920929,0.0299999993294477,1.10000002384186),(0.800000011920929,0.0299999993294477,0.0999999940395355))); +#126=IFCPOLYGONALFACESET(#125,$,(#103,#104,#105,#106,#107,#109,#110,#111,#112,#113,#114,#115,#116,#117,#118,#119),$); +#127=IFCPOLYGONALFACESET(#125,$,(#108,#120,#121,#122,#123,#124),$); +#128=IFCSHAPEREPRESENTATION(#19,'Body','Tessellation',(#126,#127)); +#129=IFCCOLOURRGB($,0.0429765619337559,0.0429765619337559,0.0429765619337559); +#130=IFCCOLOURRGB($,0.0429765619337559,0.0429765619337559,0.0429765619337559); +#131=IFCSURFACESTYLERENDERING(#129,0.,#130,$,$,$,$,$,.NOTDEFINED.); +#132=IFCSURFACESTYLE('Frame',.BOTH.,(#131)); +#133=IFCCOLOURRGB($,0.800000011920929,1.,1.); +#134=IFCCOLOURRGB($,0.800000011920929,1.,1.); +#135=IFCSURFACESTYLERENDERING(#133,0.799999997019768,#134,$,$,$,$,$,.NOTDEFINED.); +#136=IFCSURFACESTYLE('Glass',.BOTH.,(#135)); +#137=IFCSTYLEDITEM(#126,(#132),'Frame'); +#138=IFCSTYLEDITEM(#127,(#136),'Glass'); +#139=IFCCARTESIANPOINT((0.,0.,0.)); +#140=IFCDIRECTION((1.,0.,0.)); +#141=IFCDIRECTION((0.,0.,1.)); +#142=IFCAXIS2PLACEMENT3D(#139,#141,#140); +#143=IFCREPRESENTATIONMAP(#142,#128); +#144=IFCDOORTYPE('3H2bOe2HX4oOCEQSKFPtgq',$,'DEMO1',$,$,$,(#189),$,$,$,$,$,$); +#145=IFCINDEXEDPOLYGONALFACE((17,14,15,16)); +#146=IFCINDEXEDPOLYGONALFACE((2,28,29,3)); +#147=IFCINDEXEDPOLYGONALFACE((27,31,32,30,29,28)); +#148=IFCINDEXEDPOLYGONALFACE((7,3,5,6)); +#149=IFCINDEXEDPOLYGONALFACE((8,2,3,7)); +#150=IFCINDEXEDPOLYGONALFACE((23,7,6,24)); +#151=IFCINDEXEDPOLYGONALFACE((21,22,23,24,4,20)); +#152=IFCINDEXEDPOLYGONALFACE((12,15,14,13)); +#153=IFCINDEXEDPOLYGONALFACE((16,15,12,19)); +#154=IFCINDEXEDPOLYGONALFACE((11,26,25,10)); +#155=IFCINDEXEDPOLYGONALFACE((25,31,27,1)); +#156=IFCINDEXEDPOLYGONALFACE((24,6,11,4)); +#157=IFCINDEXEDPOLYGONALFACE((20,10,9,21)); +#158=IFCINDEXEDPOLYGONALFACE((9,1,2,8)); +#159=IFCINDEXEDPOLYGONALFACE((10,25,1,9)); +#160=IFCINDEXEDPOLYGONALFACE((22,8,7,23)); +#161=IFCINDEXEDPOLYGONALFACE((4,11,10,20)); +#162=IFCINDEXEDPOLYGONALFACE((19,18,17,16)); +#163=IFCINDEXEDPOLYGONALFACE((19,12,13,18)); +#164=IFCINDEXEDPOLYGONALFACE((21,9,8,22)); +#165=IFCINDEXEDPOLYGONALFACE((6,5,26,11)); +#166=IFCINDEXEDPOLYGONALFACE((18,13,14,17)); +#167=IFCINDEXEDPOLYGONALFACE((5,30,32,26)); +#168=IFCINDEXEDPOLYGONALFACE((1,27,28,2)); +#169=IFCINDEXEDPOLYGONALFACE((26,32,31,25)); +#170=IFCINDEXEDPOLYGONALFACE((3,29,30,5)); +#171=IFCCARTESIANPOINTLIST3D(((0.955000162124634,0.0999999940395355,2.09000015258789),(0.955000162124634,0.0450000055134296,2.09000015258789),(0.970000028610229,0.0450000017881393,2.10500001907349),(0.,0.,0.),(0.970000028610229,0.,2.10500001907349),(0.0399999991059303,0.,2.10500001907349),(0.0399999991059303,0.0450000017881393,2.10500001907349),(0.0549999997019768,0.0450000055134296,2.09000015258789),(0.0549999997019768,0.0999999940395355,2.09000015258789),(0.,0.0999999940395355,2.14500021934509),(0.,-3.72529029846191E-09,2.14500021934509),(0.044999998062849,0.,2.09999990463257),(0.044999998062849,0.0399999991059303,2.09999990463257),(0.965000033378601,0.0399999991059303,2.09999990463257),(0.965000033378601,0.,2.09999990463257),(0.965000033378601,0.,0.),(0.965000033378601,0.0399999991059303,0.),(0.044999998062849,0.0399999991059303,0.),(0.044999998062849,0.,0.),(0.,0.0999999940395355,0.),(0.0549999997019768,0.0999999940395355,0.),(0.0549999997019768,0.0450000017881393,0.),(0.0399999991059303,0.0450000017881393,0.),(0.0399999991059303,0.,0.),(1.01000034809113,0.0999999940395355,2.14500021934509),(1.01000034809113,-3.72529029846191E-09,2.14500021934509),(0.955000162124634,0.0999999940395355,0.),(0.955000162124634,0.0450000055134296,0.),(0.970000028610229,0.0450000017881393,0.),(0.970000028610229,0.,0.),(1.01000034809113,0.0999999940395355,0.),(1.01000034809113,-3.72529029846191E-09,0.))); +#172=IFCPOLYGONALFACESET(#171,$,(#146,#147,#148,#149,#150,#151,#154,#155,#156,#157,#158,#159,#160,#161,#164,#165,#167,#168,#169,#170),$); +#173=IFCPOLYGONALFACESET(#171,$,(#145,#152,#153,#162,#163,#166),$); +#174=IFCSHAPEREPRESENTATION(#19,'Body','Tessellation',(#172,#173)); +#175=IFCCOLOURRGB($,0.0429765619337559,0.0429765619337559,0.0429765619337559); +#176=IFCCOLOURRGB($,0.0429765619337559,0.0429765619337559,0.0429765619337559); +#177=IFCSURFACESTYLERENDERING(#175,0.,#176,$,$,$,$,$,.NOTDEFINED.); +#178=IFCSURFACESTYLE('Frame',.BOTH.,(#177)); +#179=IFCCOLOURRGB($,0.184475064277649,0.184475019574165,0.184475019574165); +#180=IFCCOLOURRGB($,0.184475064277649,0.184475019574165,0.184475019574165); +#181=IFCSURFACESTYLERENDERING(#179,0.,#180,$,$,$,$,$,.NOTDEFINED.); +#182=IFCSURFACESTYLE('Panel',.BOTH.,(#181)); +#183=IFCSTYLEDITEM(#172,(#178),'Frame'); +#184=IFCSTYLEDITEM(#173,(#182),'Panel'); +#185=IFCCARTESIANPOINT((0.,0.,0.)); +#186=IFCDIRECTION((1.,0.,0.)); +#187=IFCDIRECTION((0.,0.,1.)); +#188=IFCAXIS2PLACEMENT3D(#185,#187,#186); +#189=IFCREPRESENTATIONMAP(#188,#174); +#190=IFCFURNITURETYPE('3cTIUwHOfEieRO1GYLMhRr',$,'BUNNY',$,$,$,(#911),$,$,$,$); +#191=IFCINDEXEDPOLYGONALFACE((187,278,44)); +#192=IFCINDEXEDPOLYGONALFACE((21,52,60)); +#193=IFCINDEXEDPOLYGONALFACE((91,100,31)); +#194=IFCINDEXEDPOLYGONALFACE((162,19,191)); +#195=IFCINDEXEDPOLYGONALFACE((288,180,159)); +#196=IFCINDEXEDPOLYGONALFACE((241,219,307)); +#197=IFCINDEXEDPOLYGONALFACE((54,93,173)); +#198=IFCINDEXEDPOLYGONALFACE((60,45,21)); +#199=IFCINDEXEDPOLYGONALFACE((58,110,55)); +#200=IFCINDEXEDPOLYGONALFACE((64,18,70)); +#201=IFCINDEXEDPOLYGONALFACE((2,207,162)); +#202=IFCINDEXEDPOLYGONALFACE((10,176,188)); +#203=IFCINDEXEDPOLYGONALFACE((105,114,113)); +#204=IFCINDEXEDPOLYGONALFACE((220,106,249)); +#205=IFCINDEXEDPOLYGONALFACE((252,321,244)); +#206=IFCINDEXEDPOLYGONALFACE((162,57,19)); +#207=IFCINDEXEDPOLYGONALFACE((224,147,220)); +#208=IFCINDEXEDPOLYGONALFACE((90,373,124)); +#209=IFCINDEXEDPOLYGONALFACE((70,199,64)); +#210=IFCINDEXEDPOLYGONALFACE((256,248,258)); +#211=IFCINDEXEDPOLYGONALFACE((115,212,207)); +#212=IFCINDEXEDPOLYGONALFACE((103,36,7)); +#213=IFCINDEXEDPOLYGONALFACE((71,306,320)); +#214=IFCINDEXEDPOLYGONALFACE((297,267,294)); +#215=IFCINDEXEDPOLYGONALFACE((57,50,19)); +#216=IFCINDEXEDPOLYGONALFACE((117,44,188)); +#217=IFCINDEXEDPOLYGONALFACE((62,56,153)); +#218=IFCINDEXEDPOLYGONALFACE((106,147,120)); +#219=IFCINDEXEDPOLYGONALFACE((254,244,245)); +#220=IFCINDEXEDPOLYGONALFACE((208,207,2)); +#221=IFCINDEXEDPOLYGONALFACE((256,257,250)); +#222=IFCINDEXEDPOLYGONALFACE((203,205,211)); +#223=IFCINDEXEDPOLYGONALFACE((56,278,157)); +#224=IFCINDEXEDPOLYGONALFACE((103,7,9)); +#225=IFCINDEXEDPOLYGONALFACE((63,140,176)); +#226=IFCINDEXEDPOLYGONALFACE((15,109,118)); +#227=IFCINDEXEDPOLYGONALFACE((59,159,180)); +#228=IFCINDEXEDPOLYGONALFACE((158,154,208)); +#229=IFCINDEXEDPOLYGONALFACE((300,241,308)); +#230=IFCINDEXEDPOLYGONALFACE((23,32,42)); +#231=IFCINDEXEDPOLYGONALFACE((44,278,56)); +#232=IFCINDEXEDPOLYGONALFACE((189,259,67)); +#233=IFCINDEXEDPOLYGONALFACE((309,304,333)); +#234=IFCINDEXEDPOLYGONALFACE((136,89,259)); +#235=IFCINDEXEDPOLYGONALFACE((31,191,19)); +#236=IFCINDEXEDPOLYGONALFACE((295,304,294)); +#237=IFCINDEXEDPOLYGONALFACE((50,38,19)); +#238=IFCINDEXEDPOLYGONALFACE((44,62,10)); +#239=IFCINDEXEDPOLYGONALFACE((369,25,227)); +#240=IFCINDEXEDPOLYGONALFACE((136,47,233)); +#241=IFCINDEXEDPOLYGONALFACE((33,54,201)); +#242=IFCINDEXEDPOLYGONALFACE((333,304,329)); +#243=IFCINDEXEDPOLYGONALFACE((281,110,285)); +#244=IFCINDEXEDPOLYGONALFACE((275,80,276)); +#245=IFCINDEXEDPOLYGONALFACE((119,106,120)); +#246=IFCINDEXEDPOLYGONALFACE((276,80,233)); +#247=IFCINDEXEDPOLYGONALFACE((232,318,312)); +#248=IFCINDEXEDPOLYGONALFACE((208,63,115)); +#249=IFCINDEXEDPOLYGONALFACE((150,288,159)); +#250=IFCINDEXEDPOLYGONALFACE((286,287,284)); +#251=IFCINDEXEDPOLYGONALFACE((286,285,287)); +#252=IFCINDEXEDPOLYGONALFACE((285,286,279)); +#253=IFCINDEXEDPOLYGONALFACE((239,171,240)); +#254=IFCINDEXEDPOLYGONALFACE((233,47,276)); +#255=IFCINDEXEDPOLYGONALFACE((124,213,90)); +#256=IFCINDEXEDPOLYGONALFACE((157,278,47)); +#257=IFCINDEXEDPOLYGONALFACE((187,47,157)); +#258=IFCINDEXEDPOLYGONALFACE((268,75,222)); +#259=IFCINDEXEDPOLYGONALFACE((101,269,232)); +#260=IFCINDEXEDPOLYGONALFACE((277,7,13)); +#261=IFCINDEXEDPOLYGONALFACE((140,63,74)); +#262=IFCINDEXEDPOLYGONALFACE((140,74,56)); +#263=IFCINDEXEDPOLYGONALFACE((74,153,56)); +#264=IFCINDEXEDPOLYGONALFACE((57,201,50)); +#265=IFCINDEXEDPOLYGONALFACE((320,236,193)); +#266=IFCINDEXEDPOLYGONALFACE((222,236,268)); +#267=IFCINDEXEDPOLYGONALFACE((173,50,201)); +#268=IFCINDEXEDPOLYGONALFACE((299,267,297)); +#269=IFCINDEXEDPOLYGONALFACE((162,212,57)); +#270=IFCINDEXEDPOLYGONALFACE((208,115,207)); +#271=IFCINDEXEDPOLYGONALFACE((267,292,274)); +#272=IFCINDEXEDPOLYGONALFACE((98,197,277)); +#273=IFCINDEXEDPOLYGONALFACE((295,328,329)); +#274=IFCINDEXEDPOLYGONALFACE((158,208,2)); +#275=IFCINDEXEDPOLYGONALFACE((201,57,33)); +#276=IFCINDEXEDPOLYGONALFACE((187,47,278)); +#277=IFCINDEXEDPOLYGONALFACE((241,307,308)); +#278=IFCINDEXEDPOLYGONALFACE((335,317,245)); +#279=IFCINDEXEDPOLYGONALFACE((328,330,329)); +#280=IFCINDEXEDPOLYGONALFACE((84,128,121)); +#281=IFCINDEXEDPOLYGONALFACE((331,330,328)); +#282=IFCINDEXEDPOLYGONALFACE((300,331,328)); +#283=IFCINDEXEDPOLYGONALFACE((129,19,38)); +#284=IFCINDEXEDPOLYGONALFACE((154,298,66)); +#285=IFCINDEXEDPOLYGONALFACE((317,322,323)); +#286=IFCINDEXEDPOLYGONALFACE((302,297,303)); +#287=IFCINDEXEDPOLYGONALFACE((212,93,167)); +#288=IFCINDEXEDPOLYGONALFACE((94,185,184)); +#289=IFCINDEXEDPOLYGONALFACE((211,121,100)); +#290=IFCINDEXEDPOLYGONALFACE((212,173,93)); +#291=IFCINDEXEDPOLYGONALFACE((317,254,245)); +#292=IFCINDEXEDPOLYGONALFACE((51,15,41)); +#293=IFCINDEXEDPOLYGONALFACE((321,339,244)); +#294=IFCINDEXEDPOLYGONALFACE((244,335,245)); +#295=IFCINDEXEDPOLYGONALFACE((211,204,121)); +#296=IFCINDEXEDPOLYGONALFACE((246,72,358)); +#297=IFCINDEXEDPOLYGONALFACE((300,360,301)); +#298=IFCINDEXEDPOLYGONALFACE((234,177,39)); +#299=IFCINDEXEDPOLYGONALFACE((125,152,177)); +#300=IFCINDEXEDPOLYGONALFACE((338,314,311)); +#301=IFCINDEXEDPOLYGONALFACE((149,94,152)); +#302=IFCINDEXEDPOLYGONALFACE((39,175,137)); +#303=IFCINDEXEDPOLYGONALFACE((334,292,267)); +#304=IFCINDEXEDPOLYGONALFACE((343,338,340,346)); +#305=IFCINDEXEDPOLYGONALFACE((283,286,284)); +#306=IFCINDEXEDPOLYGONALFACE((129,16,53)); +#307=IFCINDEXEDPOLYGONALFACE((102,249,106)); +#308=IFCINDEXEDPOLYGONALFACE((197,12,23)); +#309=IFCINDEXEDPOLYGONALFACE((330,310,178)); +#310=IFCINDEXEDPOLYGONALFACE((307,61,22,308)); +#311=IFCINDEXEDPOLYGONALFACE((300,310,331)); +#312=IFCINDEXEDPOLYGONALFACE((205,190,194)); +#313=IFCINDEXEDPOLYGONALFACE((133,2,31)); +#314=IFCINDEXEDPOLYGONALFACE((85,92,20)); +#315=IFCINDEXEDPOLYGONALFACE((360,39,301)); +#316=IFCINDEXEDPOLYGONALFACE((122,47,136)); +#317=IFCINDEXEDPOLYGONALFACE((281,282,186)); +#318=IFCINDEXEDPOLYGONALFACE((2,191,31)); +#319=IFCINDEXEDPOLYGONALFACE((250,249,247)); +#320=IFCINDEXEDPOLYGONALFACE((58,214,216)); +#321=IFCINDEXEDPOLYGONALFACE((234,138,143)); +#322=IFCINDEXEDPOLYGONALFACE((141,298,154)); +#323=IFCINDEXEDPOLYGONALFACE((27,45,76)); +#324=IFCINDEXEDPOLYGONALFACE((146,181,145)); +#325=IFCINDEXEDPOLYGONALFACE((144,181,180)); +#326=IFCINDEXEDPOLYGONALFACE((195,185,179)); +#327=IFCINDEXEDPOLYGONALFACE((228,223,229)); +#328=IFCINDEXEDPOLYGONALFACE((49,358,72)); +#329=IFCINDEXEDPOLYGONALFACE((74,34,183)); +#330=IFCINDEXEDPOLYGONALFACE((221,218,223)); +#331=IFCINDEXEDPOLYGONALFACE((146,107,108)); +#332=IFCINDEXEDPOLYGONALFACE((194,204,205)); +#333=IFCINDEXEDPOLYGONALFACE((352,359,280,279)); +#334=IFCINDEXEDPOLYGONALFACE((46,64,199)); +#335=IFCINDEXEDPOLYGONALFACE((366,86,251)); +#336=IFCINDEXEDPOLYGONALFACE((48,114,105)); +#337=IFCINDEXEDPOLYGONALFACE((198,95,164)); +#338=IFCINDEXEDPOLYGONALFACE((372,65,167)); +#339=IFCINDEXEDPOLYGONALFACE((74,132,153)); +#340=IFCINDEXEDPOLYGONALFACE((21,12,4)); +#341=IFCINDEXEDPOLYGONALFACE((288,111,113)); +#342=IFCINDEXEDPOLYGONALFACE((75,225,174)); +#343=IFCINDEXEDPOLYGONALFACE((166,262,200)); +#344=IFCINDEXEDPOLYGONALFACE((223,230,229)); +#345=IFCINDEXEDPOLYGONALFACE((26,92,3)); +#346=IFCINDEXEDPOLYGONALFACE((219,88,82)); +#347=IFCINDEXEDPOLYGONALFACE((355,357,365,364)); +#348=IFCINDEXEDPOLYGONALFACE((322,325,324)); +#349=IFCINDEXEDPOLYGONALFACE((257,220,250)); +#350=IFCINDEXEDPOLYGONALFACE((289,104,253)); +#351=IFCINDEXEDPOLYGONALFACE((228,108,221)); +#352=IFCINDEXEDPOLYGONALFACE((119,218,102)); +#353=IFCINDEXEDPOLYGONALFACE((367,124,25)); +#354=IFCINDEXEDPOLYGONALFACE((327,325,326)); +#355=IFCINDEXEDPOLYGONALFACE((40,115,63)); +#356=IFCINDEXEDPOLYGONALFACE((321,248,247)); +#357=IFCINDEXEDPOLYGONALFACE((158,83,141)); +#358=IFCINDEXEDPOLYGONALFACE((13,98,277)); +#359=IFCINDEXEDPOLYGONALFACE((352,345,343,365)); +#360=IFCINDEXEDPOLYGONALFACE((5,374,1)); +#361=IFCINDEXEDPOLYGONALFACE((339,347,348,341)); +#362=IFCINDEXEDPOLYGONALFACE((135,87,22)); +#363=IFCINDEXEDPOLYGONALFACE((156,224,231)); +#364=IFCINDEXEDPOLYGONALFACE((163,63,170)); +#365=IFCINDEXEDPOLYGONALFACE((56,142,140)); +#366=IFCINDEXEDPOLYGONALFACE((362,355,356,363)); +#367=IFCINDEXEDPOLYGONALFACE((88,203,15)); +#368=IFCINDEXEDPOLYGONALFACE((24,163,73)); +#369=IFCINDEXEDPOLYGONALFACE((14,78,68)); +#370=IFCINDEXEDPOLYGONALFACE((248,260,258)); +#371=IFCINDEXEDPOLYGONALFACE((78,26,17)); +#372=IFCINDEXEDPOLYGONALFACE((16,17,53)); +#373=IFCINDEXEDPOLYGONALFACE((161,164,95)); +#374=IFCINDEXEDPOLYGONALFACE((291,287,293)); +#375=IFCINDEXEDPOLYGONALFACE((127,18,32)); +#376=IFCINDEXEDPOLYGONALFACE((182,199,148)); +#377=IFCINDEXEDPOLYGONALFACE((319,71,320)); +#378=IFCINDEXEDPOLYGONALFACE((225,232,312)); +#379=IFCINDEXEDPOLYGONALFACE((302,309,289)); +#380=IFCINDEXEDPOLYGONALFACE((13,36,11)); +#381=IFCINDEXEDPOLYGONALFACE((308,87,310)); +#382=IFCINDEXEDPOLYGONALFACE((353,348,347,246)); +#383=IFCINDEXEDPOLYGONALFACE((262,79,200)); +#384=IFCINDEXEDPOLYGONALFACE((131,73,135)); +#385=IFCINDEXEDPOLYGONALFACE((370,213,243)); +#386=IFCINDEXEDPOLYGONALFACE((92,100,91)); +#387=IFCINDEXEDPOLYGONALFACE((89,233,80)); +#388=IFCINDEXEDPOLYGONALFACE((332,165,174)); +#389=IFCINDEXEDPOLYGONALFACE((1,374,2)); +#390=IFCINDEXEDPOLYGONALFACE((28,368,209)); +#391=IFCINDEXEDPOLYGONALFACE((189,136,259)); +#392=IFCINDEXEDPOLYGONALFACE((326,332,327)); +#393=IFCINDEXEDPOLYGONALFACE((117,122,189)); +#394=IFCINDEXEDPOLYGONALFACE((132,16,40)); +#395=IFCINDEXEDPOLYGONALFACE((263,334,311)); +#396=IFCINDEXEDPOLYGONALFACE((134,183,68)); +#397=IFCINDEXEDPOLYGONALFACE((157,122,142)); +#398=IFCINDEXEDPOLYGONALFACE((239,230,97)); +#399=IFCINDEXEDPOLYGONALFACE((180,96,59)); +#400=IFCINDEXEDPOLYGONALFACE((99,113,111)); +#401=IFCINDEXEDPOLYGONALFACE((22,131,135)); +#402=IFCINDEXEDPOLYGONALFACE((321,249,349)); +#403=IFCINDEXEDPOLYGONALFACE((156,120,147)); +#404=IFCINDEXEDPOLYGONALFACE((148,181,182)); +#405=IFCINDEXEDPOLYGONALFACE((152,126,149)); +#406=IFCINDEXEDPOLYGONALFACE((346,340,337,344)); +#407=IFCINDEXEDPOLYGONALFACE((358,215,353,246)); +#408=IFCINDEXEDPOLYGONALFACE((275,89,80)); +#409=IFCINDEXEDPOLYGONALFACE((240,37,239)); +#410=IFCINDEXEDPOLYGONALFACE((14,183,34)); +#411=IFCINDEXEDPOLYGONALFACE((293,295,274)); +#412=IFCINDEXEDPOLYGONALFACE((350,351,344,342)); +#413=IFCINDEXEDPOLYGONALFACE((148,112,96)); +#414=IFCINDEXEDPOLYGONALFACE((313,325,264)); +#415=IFCINDEXEDPOLYGONALFACE((154,170,208)); +#416=IFCINDEXEDPOLYGONALFACE((226,123,46)); +#417=IFCINDEXEDPOLYGONALFACE((351,364,346,344)); +#418=IFCINDEXEDPOLYGONALFACE((355,362,216,357)); +#419=IFCINDEXEDPOLYGONALFACE((349,339,321)); +#420=IFCINDEXEDPOLYGONALFACE((318,324,327)); +#421=IFCINDEXEDPOLYGONALFACE((338,311,334,340)); +#422=IFCINDEXEDPOLYGONALFACE((326,299,302)); +#423=IFCINDEXEDPOLYGONALFACE((112,59,96)); +#424=IFCINDEXEDPOLYGONALFACE((262,198,242)); +#425=IFCINDEXEDPOLYGONALFACE((272,51,41)); +#426=IFCINDEXEDPOLYGONALFACE((318,261,315)); +#427=IFCINDEXEDPOLYGONALFACE((167,57,212)); +#428=IFCINDEXEDPOLYGONALFACE((271,266,255)); +#429=IFCINDEXEDPOLYGONALFACE((218,246,102)); +#430=IFCINDEXEDPOLYGONALFACE((94,179,185)); +#431=IFCINDEXEDPOLYGONALFACE((343,346,364,365)); +#432=IFCINDEXEDPOLYGONALFACE((40,153,132)); +#433=IFCINDEXEDPOLYGONALFACE((345,314,338,343)); +#434=IFCINDEXEDPOLYGONALFACE((8,121,204)); +#435=IFCINDEXEDPOLYGONALFACE((32,64,123)); +#436=IFCINDEXEDPOLYGONALFACE((88,109,82)); +#437=IFCINDEXEDPOLYGONALFACE((133,128,81)); +#438=IFCINDEXEDPOLYGONALFACE((193,319,320)); +#439=IFCINDEXEDPOLYGONALFACE((370,367,369)); +#440=IFCINDEXEDPOLYGONALFACE((6,9,42)); +#441=IFCINDEXEDPOLYGONALFACE((214,186,282)); +#442=IFCINDEXEDPOLYGONALFACE((200,75,166)); +#443=IFCINDEXEDPOLYGONALFACE((375,79,139)); +#444=IFCINDEXEDPOLYGONALFACE((95,309,333)); +#445=IFCINDEXEDPOLYGONALFACE((221,49,72)); +#446=IFCINDEXEDPOLYGONALFACE((36,273,11)); +#447=IFCINDEXEDPOLYGONALFACE((69,155,251)); +#448=IFCINDEXEDPOLYGONALFACE((316,302,289)); +#449=IFCINDEXEDPOLYGONALFACE((297,304,303)); +#450=IFCINDEXEDPOLYGONALFACE((195,159,196)); +#451=IFCINDEXEDPOLYGONALFACE((110,186,55)); +#452=IFCINDEXEDPOLYGONALFACE((323,324,315)); +#453=IFCINDEXEDPOLYGONALFACE((172,83,242)); +#454=IFCINDEXEDPOLYGONALFACE((61,219,82)); +#455=IFCINDEXEDPOLYGONALFACE((283,291,265)); +#456=IFCINDEXEDPOLYGONALFACE((184,175,177)); +#457=IFCINDEXEDPOLYGONALFACE((349,246,347)); +#458=IFCINDEXEDPOLYGONALFACE((174,166,75)); +#459=IFCINDEXEDPOLYGONALFACE((48,363,361)); +#460=IFCINDEXEDPOLYGONALFACE((199,237,46)); +#461=IFCINDEXEDPOLYGONALFACE((164,242,198)); +#462=IFCINDEXEDPOLYGONALFACE((290,317,335,336)); +#463=IFCINDEXEDPOLYGONALFACE((217,298,160)); +#464=IFCINDEXEDPOLYGONALFACE((193,200,79)); +#465=IFCINDEXEDPOLYGONALFACE((253,166,165)); +#466=IFCINDEXEDPOLYGONALFACE((202,116,51)); +#467=IFCINDEXEDPOLYGONALFACE((236,366,268)); +#468=IFCINDEXEDPOLYGONALFACE((170,73,163)); +#469=IFCINDEXEDPOLYGONALFACE((360,328,296)); +#470=IFCINDEXEDPOLYGONALFACE((354,350,348,353)); +#471=IFCINDEXEDPOLYGONALFACE((359,357,216,214)); +#472=IFCINDEXEDPOLYGONALFACE((143,110,125)); +#473=IFCINDEXEDPOLYGONALFACE((265,314,345,283)); +#474=IFCINDEXEDPOLYGONALFACE((252,261,260)); +#475=IFCINDEXEDPOLYGONALFACE((305,337,340,334)); +#476=IFCINDEXEDPOLYGONALFACE((131,116,24)); +#477=IFCINDEXEDPOLYGONALFACE((104,168,253)); +#478=IFCINDEXEDPOLYGONALFACE((126,99,111)); +#479=IFCINDEXEDPOLYGONALFACE((47,275,276)); +#480=IFCINDEXEDPOLYGONALFACE((230,120,97)); +#481=IFCINDEXEDPOLYGONALFACE((279,283,345,352)); +#482=IFCINDEXEDPOLYGONALFACE((67,89,275)); +#483=IFCINDEXEDPOLYGONALFACE((257,271,255)); +#484=IFCINDEXEDPOLYGONALFACE((257,231,224)); +#485=IFCINDEXEDPOLYGONALFACE((316,253,165)); +#486=IFCINDEXEDPOLYGONALFACE((17,3,53)); +#487=IFCINDEXEDPOLYGONALFACE((273,171,266)); +#488=IFCINDEXEDPOLYGONALFACE((260,270,258)); +#489=IFCINDEXEDPOLYGONALFACE((362,58,216)); +#490=IFCINDEXEDPOLYGONALFACE((48,108,107)); +#491=IFCINDEXEDPOLYGONALFACE((57,65,33)); +#492=IFCINDEXEDPOLYGONALFACE((160,172,164)); +#493=IFCINDEXEDPOLYGONALFACE((190,235,184)); +#494=IFCINDEXEDPOLYGONALFACE((354,353,215,361)); +#495=IFCINDEXEDPOLYGONALFACE((258,271,256)); +#496=IFCINDEXEDPOLYGONALFACE((155,366,251)); +#497=IFCINDEXEDPOLYGONALFACE((365,357,359,352)); +#498=IFCINDEXEDPOLYGONALFACE((169,20,26)); +#499=IFCINDEXEDPOLYGONALFACE((312,174,225)); +#500=IFCINDEXEDPOLYGONALFACE((273,43,11)); +#501=IFCINDEXEDPOLYGONALFACE((264,317,290)); +#502=IFCINDEXEDPOLYGONALFACE((287,296,293)); +#503=IFCINDEXEDPOLYGONALFACE((159,149,150)); +#504=IFCINDEXEDPOLYGONALFACE((267,305,334)); +#505=IFCINDEXEDPOLYGONALFACE((206,211,100)); +#506=IFCINDEXEDPOLYGONALFACE((126,150,149)); +#507=IFCINDEXEDPOLYGONALFACE((288,114,144)); +#508=IFCINDEXEDPOLYGONALFACE((266,101,273)); +#509=IFCINDEXEDPOLYGONALFACE((123,42,32)); +#510=IFCINDEXEDPOLYGONALFACE((255,171,231)); +#511=IFCINDEXEDPOLYGONALFACE((34,116,14)); +#512=IFCINDEXEDPOLYGONALFACE((91,3,92)); +#513=IFCINDEXEDPOLYGONALFACE((287,143,138)); +#514=IFCINDEXEDPOLYGONALFACE((77,12,71)); +#515=IFCINDEXEDPOLYGONALFACE((95,178,161)); +#516=IFCINDEXEDPOLYGONALFACE((285,280,281)); +#517=IFCINDEXEDPOLYGONALFACE((242,139,262)); +#518=IFCINDEXEDPOLYGONALFACE((332,318,327)); +#519=IFCINDEXEDPOLYGONALFACE((226,239,37)); +#520=IFCINDEXEDPOLYGONALFACE((175,219,137)); +#521=IFCINDEXEDPOLYGONALFACE((177,94,184)); +#522=IFCINDEXEDPOLYGONALFACE((103,226,37)); +#523=IFCINDEXEDPOLYGONALFACE((372,371,65)); +#524=IFCINDEXEDPOLYGONALFACE((341,335,244,339)); +#525=IFCINDEXEDPOLYGONALFACE((101,69,43)); +#526=IFCINDEXEDPOLYGONALFACE((146,192,182)); +#527=IFCINDEXEDPOLYGONALFACE((52,77,5)); +#528=IFCINDEXEDPOLYGONALFACE((133,60,52)); +#529=IFCINDEXEDPOLYGONALFACE((28,243,213)); +#530=IFCINDEXEDPOLYGONALFACE((110,126,125)); +#531=IFCINDEXEDPOLYGONALFACE((140,188,176)); +#532=IFCINDEXEDPOLYGONALFACE((341,342,336,335)); +#533=IFCINDEXEDPOLYGONALFACE((82,131,61)); +#534=IFCINDEXEDPOLYGONALFACE((290,336,337,305)); +#535=IFCINDEXEDPOLYGONALFACE((109,51,116)); +#536=IFCINDEXEDPOLYGONALFACE((210,29,90)); +#537=IFCINDEXEDPOLYGONALFACE((45,30,21)); +#538=IFCINDEXEDPOLYGONALFACE((204,196,8)); +#539=IFCINDEXEDPOLYGONALFACE((229,238,237)); +#540=IFCINDEXEDPOLYGONALFACE((161,217,160)); +#541=IFCINDEXEDPOLYGONALFACE((305,264,290)); +#542=IFCINDEXEDPOLYGONALFACE((84,60,81)); +#543=IFCINDEXEDPOLYGONALFACE((185,190,184)); +#544=IFCINDEXEDPOLYGONALFACE((5,133,52)); +#545=IFCINDEXEDPOLYGONALFACE((189,187,117)); +#546=IFCINDEXEDPOLYGONALFACE((226,237,238)); +#547=IFCINDEXEDPOLYGONALFACE((23,277,197)); +#548=IFCINDEXEDPOLYGONALFACE((76,8,27)); +#549=IFCINDEXEDPOLYGONALFACE((294,274,295)); +#550=IFCINDEXEDPOLYGONALFACE((145,114,107)); +#551=IFCINDEXEDPOLYGONALFACE((188,44,10)); +#552=IFCINDEXEDPOLYGONALFACE((41,203,85)); +#553=IFCINDEXEDPOLYGONALFACE((13,43,86)); +#554=IFCINDEXEDPOLYGONALFACE((355,364,351,356)); +#555=IFCINDEXEDPOLYGONALFACE((234,125,177)); +#556=IFCINDEXEDPOLYGONALFACE((40,38,50)); +#557=IFCINDEXEDPOLYGONALFACE((272,85,20)); +#558=IFCINDEXEDPOLYGONALFACE((215,48,361)); +#559=IFCINDEXEDPOLYGONALFACE((39,241,301)); +#560=IFCINDEXEDPOLYGONALFACE((311,292,263)); +#561=IFCINDEXEDPOLYGONALFACE((69,86,43)); +#562=IFCINDEXEDPOLYGONALFACE((310,161,178)); +#563=IFCINDEXEDPOLYGONALFACE((202,169,78)); +#564=IFCINDEXEDPOLYGONALFACE((248,250,247)); +#565=IFCINDEXEDPOLYGONALFACE((296,138,360)); +#566=IFCINDEXEDPOLYGONALFACE((42,9,23)); +#567=IFCINDEXEDPOLYGONALFACE((203,206,85)); +#568=IFCINDEXEDPOLYGONALFACE((202,272,169)); +#569=IFCINDEXEDPOLYGONALFACE((342,344,337,336)); +#570=IFCINDEXEDPOLYGONALFACE((129,35,19)); +#571=IFCINDEXEDPOLYGONALFACE((2,162,191)); +#572=IFCINDEXEDPOLYGONALFACE((366,306,98)); +#573=IFCINDEXEDPOLYGONALFACE((361,363,356,354)); +#574=IFCINDEXEDPOLYGONALFACE((68,17,134)); +#575=IFCINDEXEDPOLYGONALFACE((54,173,201)); +#576=IFCINDEXEDPOLYGONALFACE((210,167,151)); +#577=IFCINDEXEDPOLYGONALFACE((156,171,97)); +#578=IFCINDEXEDPOLYGONALFACE((54,151,93)); +#579=IFCINDEXEDPOLYGONALFACE((59,8,196)); +#580=IFCINDEXEDPOLYGONALFACE((213,210,90)); +#581=IFCINDEXEDPOLYGONALFACE((54,371,373)); +#582=IFCINDEXEDPOLYGONALFACE((130,243,209)); +#583=IFCINDEXEDPOLYGONALFACE((359,214,282,280)); +#584=IFCINDEXEDPOLYGONALFACE((142,117,188)); +#585=IFCINDEXEDPOLYGONALFACE((28,367,368)); +#586=IFCINDEXEDPOLYGONALFACE((237,228,229)); +#587=IFCINDEXEDPOLYGONALFACE((362,105,99)); +#588=IFCINDEXEDPOLYGONALFACE((291,314,265)); +#589=IFCINDEXEDPOLYGONALFACE((45,70,18)); +#590=IFCINDEXEDPOLYGONALFACE((210,372,167)); +#591=IFCINDEXEDPOLYGONALFACE((62,63,176)); +#592=IFCINDEXEDPOLYGONALFACE((91,19,35)); +#593=IFCINDEXEDPOLYGONALFACE((206,203,211)); +#594=IFCINDEXEDPOLYGONALFACE((269,260,261)); +#595=IFCINDEXEDPOLYGONALFACE((53,35,129)); +#596=IFCINDEXEDPOLYGONALFACE((54,29,151)); +#597=IFCINDEXEDPOLYGONALFACE((130,368,370)); +#598=IFCINDEXEDPOLYGONALFACE((67,187,189)); +#599=IFCINDEXEDPOLYGONALFACE((371,25,124)); +#600=IFCINDEXEDPOLYGONALFACE((130,209,368)); +#601=IFCINDEXEDPOLYGONALFACE((243,130,370)); +#602=IFCINDEXEDPOLYGONALFACE((213,227,210)); +#603=IFCINDEXEDPOLYGONALFACE((227,372,210)); +#604=IFCINDEXEDPOLYGONALFACE((167,93,151)); +#605=IFCINDEXEDPOLYGONALFACE((372,227,25)); +#606=IFCINDEXEDPOLYGONALFACE((373,29,54)); +#607=IFCINDEXEDPOLYGONALFACE((213,369,227)); +#608=IFCINDEXEDPOLYGONALFACE((371,124,373)); +#609=IFCINDEXEDPOLYGONALFACE((341,348,350,342)); +#610=IFCINDEXEDPOLYGONALFACE((135,66,217)); +#611=IFCINDEXEDPOLYGONALFACE((65,371,33)); +#612=IFCINDEXEDPOLYGONALFACE((350,354,356,351)); +#613=IFCINDEXEDPOLYGONALFACE((333,330,178)); +#614=IFCINDEXEDPOLYGONALFACE((315,254,323)); +#615=IFCINDEXEDPOLYGONALFACE((127,12,30)); +#616=IFCINDEXEDPOLYGONALFACE((100,128,31)); +#617=IFCINDEXEDPOLYGONALFACE((319,5,77)); +#618=IFCINDEXEDPOLYGONALFACE((374,158,2)); +#619=IFCINDEXEDPOLYGONALFACE((375,83,374)); +#620=IFCINDEXEDPOLYGONALFACE((314,274,311)); +#621=IFCINDEXEDPOLYGONALFACE((21,4,52)); +#622=IFCINDEXEDPOLYGONALFACE((288,144,180)); +#623=IFCINDEXEDPOLYGONALFACE((241,137,219)); +#624=IFCINDEXEDPOLYGONALFACE((60,76,45)); +#625=IFCINDEXEDPOLYGONALFACE((10,62,176)); +#626=IFCINDEXEDPOLYGONALFACE((220,147,106)); +#627=IFCINDEXEDPOLYGONALFACE((90,29,373)); +#628=IFCINDEXEDPOLYGONALFACE((70,148,199)); +#629=IFCINDEXEDPOLYGONALFACE((103,37,36)); +#630=IFCINDEXEDPOLYGONALFACE((71,197,306)); +#631=IFCINDEXEDPOLYGONALFACE((117,187,44)); +#632=IFCINDEXEDPOLYGONALFACE((62,44,56)); +#633=IFCINDEXEDPOLYGONALFACE((254,252,244)); +#634=IFCINDEXEDPOLYGONALFACE((59,196,159)); +#635=IFCINDEXEDPOLYGONALFACE((158,141,154)); +#636=IFCINDEXEDPOLYGONALFACE((300,301,241)); +#637=IFCINDEXEDPOLYGONALFACE((23,127,32)); +#638=IFCINDEXEDPOLYGONALFACE((309,303,304)); +#639=IFCINDEXEDPOLYGONALFACE((295,329,304)); +#640=IFCINDEXEDPOLYGONALFACE((369,367,25)); +#641=IFCINDEXEDPOLYGONALFACE((119,102,106)); +#642=IFCINDEXEDPOLYGONALFACE((232,269,318)); +#643=IFCINDEXEDPOLYGONALFACE((208,170,63)); +#644=IFCINDEXEDPOLYGONALFACE((239,97,171)); +#645=IFCINDEXEDPOLYGONALFACE((124,28,213)); +#646=IFCINDEXEDPOLYGONALFACE((268,155,75)); +#647=IFCINDEXEDPOLYGONALFACE((101,270,269)); +#648=IFCINDEXEDPOLYGONALFACE((277,9,7)); +#649=IFCINDEXEDPOLYGONALFACE((320,306,236)); +#650=IFCINDEXEDPOLYGONALFACE((222,193,236)); +#651=IFCINDEXEDPOLYGONALFACE((173,115,50)); +#652=IFCINDEXEDPOLYGONALFACE((299,313,267)); +#653=IFCINDEXEDPOLYGONALFACE((162,207,212)); +#654=IFCINDEXEDPOLYGONALFACE((98,306,197)); +#655=IFCINDEXEDPOLYGONALFACE((295,296,328)); +#656=IFCINDEXEDPOLYGONALFACE((84,81,128)); +#657=IFCINDEXEDPOLYGONALFACE((302,299,297)); +#658=IFCINDEXEDPOLYGONALFACE((212,115,173)); +#659=IFCINDEXEDPOLYGONALFACE((317,323,254)); +#660=IFCINDEXEDPOLYGONALFACE((211,205,204)); +#661=IFCINDEXEDPOLYGONALFACE((39,177,175)); +#662=IFCINDEXEDPOLYGONALFACE((334,263,292)); +#663=IFCINDEXEDPOLYGONALFACE((283,279,286)); +#664=IFCINDEXEDPOLYGONALFACE((129,38,16)); +#665=IFCINDEXEDPOLYGONALFACE((102,349,249)); +#666=IFCINDEXEDPOLYGONALFACE((197,71,12)); +#667=IFCINDEXEDPOLYGONALFACE((330,331,310)); +#668=IFCINDEXEDPOLYGONALFACE((300,308,310)); +#669=IFCINDEXEDPOLYGONALFACE((205,203,190)); +#670=IFCINDEXEDPOLYGONALFACE((133,1,2)); +#671=IFCINDEXEDPOLYGONALFACE((85,206,92)); +#672=IFCINDEXEDPOLYGONALFACE((360,234,39)); +#673=IFCINDEXEDPOLYGONALFACE((122,157,47)); +#674=IFCINDEXEDPOLYGONALFACE((281,280,282)); +#675=IFCINDEXEDPOLYGONALFACE((250,220,249)); +#676=IFCINDEXEDPOLYGONALFACE((58,55,214)); +#677=IFCINDEXEDPOLYGONALFACE((234,360,138)); +#678=IFCINDEXEDPOLYGONALFACE((141,172,298)); +#679=IFCINDEXEDPOLYGONALFACE((27,112,45)); +#680=IFCINDEXEDPOLYGONALFACE((146,182,181)); +#681=IFCINDEXEDPOLYGONALFACE((144,145,181)); +#682=IFCINDEXEDPOLYGONALFACE((195,194,185)); +#683=IFCINDEXEDPOLYGONALFACE((228,221,223)); +#684=IFCINDEXEDPOLYGONALFACE((49,215,358)); +#685=IFCINDEXEDPOLYGONALFACE((74,163,34)); +#686=IFCINDEXEDPOLYGONALFACE((221,72,218)); +#687=IFCINDEXEDPOLYGONALFACE((146,145,107)); +#688=IFCINDEXEDPOLYGONALFACE((194,195,204)); +#689=IFCINDEXEDPOLYGONALFACE((46,123,64)); +#690=IFCINDEXEDPOLYGONALFACE((366,98,86)); +#691=IFCINDEXEDPOLYGONALFACE((48,107,114)); +#692=IFCINDEXEDPOLYGONALFACE((198,104,95)); +#693=IFCINDEXEDPOLYGONALFACE((74,183,132)); +#694=IFCINDEXEDPOLYGONALFACE((21,30,12)); +#695=IFCINDEXEDPOLYGONALFACE((288,150,111)); +#696=IFCINDEXEDPOLYGONALFACE((75,155,225)); +#697=IFCINDEXEDPOLYGONALFACE((166,168,262)); +#698=IFCINDEXEDPOLYGONALFACE((223,119,230)); +#699=IFCINDEXEDPOLYGONALFACE((26,20,92)); +#700=IFCINDEXEDPOLYGONALFACE((219,235,88)); +#701=IFCINDEXEDPOLYGONALFACE((322,264,325)); +#702=IFCINDEXEDPOLYGONALFACE((257,224,220)); +#703=IFCINDEXEDPOLYGONALFACE((289,309,104)); +#704=IFCINDEXEDPOLYGONALFACE((228,146,108)); +#705=IFCINDEXEDPOLYGONALFACE((119,223,218)); +#706=IFCINDEXEDPOLYGONALFACE((367,28,124)); +#707=IFCINDEXEDPOLYGONALFACE((327,324,325)); +#708=IFCINDEXEDPOLYGONALFACE((40,50,115)); +#709=IFCINDEXEDPOLYGONALFACE((321,252,248)); +#710=IFCINDEXEDPOLYGONALFACE((13,86,98)); +#711=IFCINDEXEDPOLYGONALFACE((5,375,374)); +#712=IFCINDEXEDPOLYGONALFACE((135,217,87)); +#713=IFCINDEXEDPOLYGONALFACE((156,147,224)); +#714=IFCINDEXEDPOLYGONALFACE((163,74,63)); +#715=IFCINDEXEDPOLYGONALFACE((56,157,142)); +#716=IFCINDEXEDPOLYGONALFACE((88,190,203)); +#717=IFCINDEXEDPOLYGONALFACE((24,34,163)); +#718=IFCINDEXEDPOLYGONALFACE((14,202,78)); +#719=IFCINDEXEDPOLYGONALFACE((248,252,260)); +#720=IFCINDEXEDPOLYGONALFACE((78,169,26)); +#721=IFCINDEXEDPOLYGONALFACE((16,134,17)); +#722=IFCINDEXEDPOLYGONALFACE((161,160,164)); +#723=IFCINDEXEDPOLYGONALFACE((291,284,287)); +#724=IFCINDEXEDPOLYGONALFACE((127,30,18)); +#725=IFCINDEXEDPOLYGONALFACE((182,192,199)); +#726=IFCINDEXEDPOLYGONALFACE((319,77,71)); +#727=IFCINDEXEDPOLYGONALFACE((225,69,232)); +#728=IFCINDEXEDPOLYGONALFACE((302,303,309)); +#729=IFCINDEXEDPOLYGONALFACE((13,7,36)); +#730=IFCINDEXEDPOLYGONALFACE((308,22,87)); +#731=IFCINDEXEDPOLYGONALFACE((262,139,79)); +#732=IFCINDEXEDPOLYGONALFACE((131,24,73)); +#733=IFCINDEXEDPOLYGONALFACE((370,369,213)); +#734=IFCINDEXEDPOLYGONALFACE((92,206,100)); +#735=IFCINDEXEDPOLYGONALFACE((89,136,233)); +#736=IFCINDEXEDPOLYGONALFACE((332,316,165)); +#737=IFCINDEXEDPOLYGONALFACE((189,122,136)); +#738=IFCINDEXEDPOLYGONALFACE((326,316,332)); +#739=IFCINDEXEDPOLYGONALFACE((117,142,122)); +#740=IFCINDEXEDPOLYGONALFACE((132,134,16)); +#741=IFCINDEXEDPOLYGONALFACE((134,132,183)); +#742=IFCINDEXEDPOLYGONALFACE((239,238,230)); +#743=IFCINDEXEDPOLYGONALFACE((180,181,96)); +#744=IFCINDEXEDPOLYGONALFACE((99,105,113)); +#745=IFCINDEXEDPOLYGONALFACE((22,61,131)); +#746=IFCINDEXEDPOLYGONALFACE((321,247,249)); +#747=IFCINDEXEDPOLYGONALFACE((156,97,120)); +#748=IFCINDEXEDPOLYGONALFACE((148,96,181)); +#749=IFCINDEXEDPOLYGONALFACE((152,125,126)); +#750=IFCINDEXEDPOLYGONALFACE((240,36,37)); +#751=IFCINDEXEDPOLYGONALFACE((14,68,183)); +#752=IFCINDEXEDPOLYGONALFACE((293,296,295)); +#753=IFCINDEXEDPOLYGONALFACE((148,70,112)); +#754=IFCINDEXEDPOLYGONALFACE((313,299,325)); +#755=IFCINDEXEDPOLYGONALFACE((154,66,170)); +#756=IFCINDEXEDPOLYGONALFACE((226,6,123)); +#757=IFCINDEXEDPOLYGONALFACE((349,347,339)); +#758=IFCINDEXEDPOLYGONALFACE((318,315,324)); +#759=IFCINDEXEDPOLYGONALFACE((326,325,299)); +#760=IFCINDEXEDPOLYGONALFACE((112,27,59)); +#761=IFCINDEXEDPOLYGONALFACE((262,168,198)); +#762=IFCINDEXEDPOLYGONALFACE((272,202,51)); +#763=IFCINDEXEDPOLYGONALFACE((318,269,261)); +#764=IFCINDEXEDPOLYGONALFACE((167,65,57)); +#765=IFCINDEXEDPOLYGONALFACE((271,270,266)); +#766=IFCINDEXEDPOLYGONALFACE((218,72,246)); +#767=IFCINDEXEDPOLYGONALFACE((94,149,179)); +#768=IFCINDEXEDPOLYGONALFACE((40,62,153)); +#769=IFCINDEXEDPOLYGONALFACE((8,84,121)); +#770=IFCINDEXEDPOLYGONALFACE((32,18,64)); +#771=IFCINDEXEDPOLYGONALFACE((88,15,109)); +#772=IFCINDEXEDPOLYGONALFACE((133,31,128)); +#773=IFCINDEXEDPOLYGONALFACE((193,79,319)); +#774=IFCINDEXEDPOLYGONALFACE((370,368,367)); +#775=IFCINDEXEDPOLYGONALFACE((6,103,9)); +#776=IFCINDEXEDPOLYGONALFACE((214,55,186)); +#777=IFCINDEXEDPOLYGONALFACE((200,222,75)); +#778=IFCINDEXEDPOLYGONALFACE((375,319,79)); +#779=IFCINDEXEDPOLYGONALFACE((95,104,309)); +#780=IFCINDEXEDPOLYGONALFACE((221,108,49)); +#781=IFCINDEXEDPOLYGONALFACE((36,240,273)); +#782=IFCINDEXEDPOLYGONALFACE((69,225,155)); +#783=IFCINDEXEDPOLYGONALFACE((316,326,302)); +#784=IFCINDEXEDPOLYGONALFACE((297,294,304)); +#785=IFCINDEXEDPOLYGONALFACE((195,179,159)); +#786=IFCINDEXEDPOLYGONALFACE((110,281,186)); +#787=IFCINDEXEDPOLYGONALFACE((323,322,324)); +#788=IFCINDEXEDPOLYGONALFACE((172,141,83)); +#789=IFCINDEXEDPOLYGONALFACE((61,307,219)); +#790=IFCINDEXEDPOLYGONALFACE((283,284,291)); +#791=IFCINDEXEDPOLYGONALFACE((184,235,175)); +#792=IFCINDEXEDPOLYGONALFACE((349,102,246)); +#793=IFCINDEXEDPOLYGONALFACE((174,165,166)); +#794=IFCINDEXEDPOLYGONALFACE((48,105,363)); +#795=IFCINDEXEDPOLYGONALFACE((199,192,237)); +#796=IFCINDEXEDPOLYGONALFACE((164,172,242)); +#797=IFCINDEXEDPOLYGONALFACE((217,66,298)); +#798=IFCINDEXEDPOLYGONALFACE((193,222,200)); +#799=IFCINDEXEDPOLYGONALFACE((253,168,166)); +#800=IFCINDEXEDPOLYGONALFACE((202,14,116)); +#801=IFCINDEXEDPOLYGONALFACE((236,306,366)); +#802=IFCINDEXEDPOLYGONALFACE((170,66,73)); +#803=IFCINDEXEDPOLYGONALFACE((360,300,328)); +#804=IFCINDEXEDPOLYGONALFACE((143,285,110)); +#805=IFCINDEXEDPOLYGONALFACE((252,254,261)); +#806=IFCINDEXEDPOLYGONALFACE((131,109,116)); +#807=IFCINDEXEDPOLYGONALFACE((104,198,168)); +#808=IFCINDEXEDPOLYGONALFACE((126,58,99)); +#809=IFCINDEXEDPOLYGONALFACE((47,67,275)); +#810=IFCINDEXEDPOLYGONALFACE((230,119,120)); +#811=IFCINDEXEDPOLYGONALFACE((67,259,89)); +#812=IFCINDEXEDPOLYGONALFACE((257,256,271)); +#813=IFCINDEXEDPOLYGONALFACE((257,255,231)); +#814=IFCINDEXEDPOLYGONALFACE((316,289,253)); +#815=IFCINDEXEDPOLYGONALFACE((17,26,3)); +#816=IFCINDEXEDPOLYGONALFACE((273,240,171)); +#817=IFCINDEXEDPOLYGONALFACE((362,99,58)); +#818=IFCINDEXEDPOLYGONALFACE((48,49,108)); +#819=IFCINDEXEDPOLYGONALFACE((160,298,172)); +#820=IFCINDEXEDPOLYGONALFACE((190,88,235)); +#821=IFCINDEXEDPOLYGONALFACE((258,270,271)); +#822=IFCINDEXEDPOLYGONALFACE((155,268,366)); +#823=IFCINDEXEDPOLYGONALFACE((169,272,20)); +#824=IFCINDEXEDPOLYGONALFACE((312,332,174)); +#825=IFCINDEXEDPOLYGONALFACE((273,101,43)); +#826=IFCINDEXEDPOLYGONALFACE((264,322,317)); +#827=IFCINDEXEDPOLYGONALFACE((287,138,296)); +#828=IFCINDEXEDPOLYGONALFACE((159,179,149)); +#829=IFCINDEXEDPOLYGONALFACE((267,313,305)); +#830=IFCINDEXEDPOLYGONALFACE((126,111,150)); +#831=IFCINDEXEDPOLYGONALFACE((288,113,114)); +#832=IFCINDEXEDPOLYGONALFACE((266,270,101)); +#833=IFCINDEXEDPOLYGONALFACE((123,6,42)); +#834=IFCINDEXEDPOLYGONALFACE((255,266,171)); +#835=IFCINDEXEDPOLYGONALFACE((34,24,116)); +#836=IFCINDEXEDPOLYGONALFACE((91,35,3)); +#837=IFCINDEXEDPOLYGONALFACE((287,285,143)); +#838=IFCINDEXEDPOLYGONALFACE((77,4,12)); +#839=IFCINDEXEDPOLYGONALFACE((95,333,178)); +#840=IFCINDEXEDPOLYGONALFACE((285,279,280)); +#841=IFCINDEXEDPOLYGONALFACE((242,83,139)); +#842=IFCINDEXEDPOLYGONALFACE((332,312,318)); +#843=IFCINDEXEDPOLYGONALFACE((226,238,239)); +#844=IFCINDEXEDPOLYGONALFACE((175,235,219)); +#845=IFCINDEXEDPOLYGONALFACE((177,152,94)); +#846=IFCINDEXEDPOLYGONALFACE((103,6,226)); +#847=IFCINDEXEDPOLYGONALFACE((372,25,371)); +#848=IFCINDEXEDPOLYGONALFACE((101,232,69)); +#849=IFCINDEXEDPOLYGONALFACE((146,228,192)); +#850=IFCINDEXEDPOLYGONALFACE((52,4,77)); +#851=IFCINDEXEDPOLYGONALFACE((133,81,60)); +#852=IFCINDEXEDPOLYGONALFACE((28,209,243)); +#853=IFCINDEXEDPOLYGONALFACE((110,58,126)); +#854=IFCINDEXEDPOLYGONALFACE((140,142,188)); +#855=IFCINDEXEDPOLYGONALFACE((82,109,131)); +#856=IFCINDEXEDPOLYGONALFACE((109,15,51)); +#857=IFCINDEXEDPOLYGONALFACE((210,151,29)); +#858=IFCINDEXEDPOLYGONALFACE((45,18,30)); +#859=IFCINDEXEDPOLYGONALFACE((204,195,196)); +#860=IFCINDEXEDPOLYGONALFACE((229,230,238)); +#861=IFCINDEXEDPOLYGONALFACE((161,87,217)); +#862=IFCINDEXEDPOLYGONALFACE((305,313,264)); +#863=IFCINDEXEDPOLYGONALFACE((84,76,60)); +#864=IFCINDEXEDPOLYGONALFACE((185,194,190)); +#865=IFCINDEXEDPOLYGONALFACE((5,1,133)); +#866=IFCINDEXEDPOLYGONALFACE((226,46,237)); +#867=IFCINDEXEDPOLYGONALFACE((23,9,277)); +#868=IFCINDEXEDPOLYGONALFACE((76,84,8)); +#869=IFCINDEXEDPOLYGONALFACE((294,267,274)); +#870=IFCINDEXEDPOLYGONALFACE((145,144,114)); +#871=IFCINDEXEDPOLYGONALFACE((41,15,203)); +#872=IFCINDEXEDPOLYGONALFACE((13,11,43)); +#873=IFCINDEXEDPOLYGONALFACE((234,143,125)); +#874=IFCINDEXEDPOLYGONALFACE((40,16,38)); +#875=IFCINDEXEDPOLYGONALFACE((272,41,85)); +#876=IFCINDEXEDPOLYGONALFACE((215,49,48)); +#877=IFCINDEXEDPOLYGONALFACE((39,137,241)); +#878=IFCINDEXEDPOLYGONALFACE((311,274,292)); +#879=IFCINDEXEDPOLYGONALFACE((69,251,86)); +#880=IFCINDEXEDPOLYGONALFACE((310,87,161)); +#881=IFCINDEXEDPOLYGONALFACE((248,256,250)); +#882=IFCINDEXEDPOLYGONALFACE((68,78,17)); +#883=IFCINDEXEDPOLYGONALFACE((156,231,171)); +#884=IFCINDEXEDPOLYGONALFACE((59,27,8)); +#885=IFCINDEXEDPOLYGONALFACE((54,33,371)); +#886=IFCINDEXEDPOLYGONALFACE((237,192,228)); +#887=IFCINDEXEDPOLYGONALFACE((362,363,105)); +#888=IFCINDEXEDPOLYGONALFACE((291,293,314)); +#889=IFCINDEXEDPOLYGONALFACE((45,112,70)); +#890=IFCINDEXEDPOLYGONALFACE((62,40,63)); +#891=IFCINDEXEDPOLYGONALFACE((91,31,19)); +#892=IFCINDEXEDPOLYGONALFACE((269,270,260)); +#893=IFCINDEXEDPOLYGONALFACE((53,3,35)); +#894=IFCINDEXEDPOLYGONALFACE((67,47,187)); +#895=IFCINDEXEDPOLYGONALFACE((135,73,66)); +#896=IFCINDEXEDPOLYGONALFACE((333,329,330)); +#897=IFCINDEXEDPOLYGONALFACE((315,261,254)); +#898=IFCINDEXEDPOLYGONALFACE((127,23,12)); +#899=IFCINDEXEDPOLYGONALFACE((100,121,128)); +#900=IFCINDEXEDPOLYGONALFACE((319,375,5)); +#901=IFCINDEXEDPOLYGONALFACE((374,83,158)); +#902=IFCINDEXEDPOLYGONALFACE((375,139,83)); +#903=IFCINDEXEDPOLYGONALFACE((314,293,274)); +#904=IFCCARTESIANPOINTLIST3D(((-0.0757642686367035,-0.0121694896370173,0.220662087202072),(-0.105255022644997,-0.0141069469973445,0.230906546115875),(-0.164038479328156,-0.0962571799755096,0.263201057910919),(-0.0149683114141226,-0.0434482358396053,0.228664547204971),(-0.0426693223416805,-0.0120228659361601,0.222334340214729),(0.0788992568850517,-0.0767349451780319,0.173714026808739),(0.0953715369105339,-0.0409212671220303,0.16986283659935),(-0.0719772353768349,-0.0949608311057091,0.171763256192207),(0.0735535696148872,-0.0462111458182335,0.199328601360321),(-0.160245850682259,0.0397466160356998,0.298533588647842),(0.106730677187443,-0.0124975387006998,0.138676866889),(0.0139651391655207,-0.0423045344650745,0.229461222887039),(0.0967235639691353,-0.0144418459385633,0.168111309409142),(-0.219927728176117,-0.0414205342531204,0.239053592085838),(-0.198184996843338,-0.0742136090993881,0.172668352723122),(-0.162167191505432,-0.0434498824179173,0.289568781852722),(-0.189809292554855,-0.0716947764158249,0.281713783740997),(0.0152298724278808,-0.0849794447422028,0.205268412828445),(-0.123513199388981,-0.0452961064875126,0.264716774225235),(-0.188629180192947,-0.119135543704033,0.233101561665535),(-0.0130218090489507,-0.0651145428419113,0.222954735159874),(-0.196876853704453,0.0119782146066427,0.138698890805244),(0.0431601963937283,-0.0451620146632195,0.22145189344883),(-0.216075524687767,-0.016599427908659,0.204968154430389),(-0.0582821778953075,0.0224160328507423,0.331800371408463),(-0.190823614597321,-0.102445237338543,0.260164886713028),(-0.0431380830705166,-0.0991964489221573,0.176975786685944),(-0.0522686094045639,0.0494366958737373,0.351232975721359),(-0.0895938724279404,0.0322130136191845,0.318689584732056),(0.013082567602396,-0.0668555349111557,0.223062723875046),(-0.106145963072777,-0.0415130592882633,0.22882467508316),(0.0448657646775246,-0.0776780471205711,0.203667193651199),(-0.10371295362711,-0.00366749544627964,0.314385384321213),(-0.21360756456852,-0.0169711355119944,0.233581200242043),(-0.138989388942719,-0.0749303176999092,0.265050023794174),(0.105769321322441,-0.0415658876299858,0.138697892427444),(0.0992072820663452,-0.0677607133984566,0.138679757714272),(-0.135680645704269,-0.0402409471571445,0.287896603345871),(-0.17496183514595,-0.0425181090831757,0.0743281096220016),(-0.161954745650291,-0.0129314502701163,0.289540559053421),(-0.208628505468369,-0.103418782353401,0.201527774333954),(0.0640031322836876,-0.0677034556865692,0.197900995612144),(0.100172616541386,0.0126537960022688,0.138708665966988),(-0.168615952134132,0.0482185557484627,0.30722576379776),(-0.0140691194683313,-0.0847146064043045,0.205532997846603),(0.0702492073178291,-0.1020467877388,0.138582319021225),(-0.181213811039925,0.0992056727409363,0.328065633773804),(-0.0152021609246731,-0.112156376242638,0.0183885656297207),(0.0162124074995518,-0.111216500401497,0.021827794611454),(-0.133747041225433,-0.0159911345690489,0.290624916553497),(-0.216561943292618,-0.0709330290555954,0.202728658914566),(-0.0427242144942284,-0.0426300838589668,0.222017183899879),(-0.159124106168747,-0.0738818794488907,0.283847242593765),(-0.103956542909145,0.0154779236763716,0.320181280374527),(-0.136982098221779,-0.102321907877922,0.0194435473531485),(-0.183684900403023,0.0396271869540215,0.295159220695496),(-0.107928916811943,-0.010153891518712,0.291135489940643),(-0.103886745870113,-0.101836994290352,0.0180104468017817),(-0.0461161360144615,-0.119219377636909,0.138967230916023),(-0.0461340732872486,-0.061420276761055,0.21500451862812),(-0.211329713463783,-0.0169732719659805,0.138692498207092),(-0.165825873613358,0.0170033983886242,0.294365167617798),(-0.162926822900772,0.0167535953223705,0.259086668491364),(0.044605728238821,-0.0985531806945801,0.171382486820221),(-0.0834082290530205,0.00335463741794229,0.315553486347198),(-0.15971240401268,0.0247225016355515,0.197611734271049),(-0.16489240527153,0.105032727122307,0.322820842266083),(-0.215148985385895,-0.0462404675781727,0.266269713640213),(0.074162483215332,0.0414574705064297,0.138786911964417),(0.0142031144350767,-0.105447888374329,0.170478105545044),(0.0141690038144588,-0.0131895141676068,0.229208543896675),(0.0433205515146255,-0.101634204387665,0.0178499221801758),(-0.194831639528275,0.00855887122452259,0.19867131114006),(-0.190071240067482,0.00837886054068804,0.263859361410141),(0.0146396514028311,0.0503562577068806,0.171330958604813),(-0.0466328002512455,-0.0789417400956154,0.203323245048523),(-0.0142267476767302,-0.0157651714980602,0.22864143550396),(-0.214272990822792,-0.0700500085949898,0.258544147014618),(-0.0187377445399761,0.0234869290143251,0.211539566516876),(-0.169090524315834,0.130419373512268,0.343455374240875),(-0.0730840340256691,-0.0585213899612427,0.211252138018608),(-0.211533859372139,-0.0429056100547314,0.138715773820877),(-0.0739177912473679,0.0154376216232777,0.210008263587952),(-0.07377789914608,-0.0735882744193077,0.200627535581589),(-0.186267927289009,-0.121167339384556,0.205986142158508),(0.0892870724201202,0.0163372419774532,0.167569145560265),(-0.163796290755272,0.0387952998280525,0.138641089200974),(-0.197594255208969,-0.07469642162323,0.138668864965439),(-0.157580107450485,0.132616892457008,0.328512966632843),(-0.0735077708959579,0.0393004417419434,0.326341509819031),(-0.133432641625404,-0.0800390690565109,0.240147277712822),(-0.161642774939537,-0.107512913644314,0.235317841172218),(-0.103187024593353,0.0151489116251469,0.293316811323166),(-0.131257891654968,-0.0962524563074112,0.0883080363273621),(-0.0977480411529541,0.0540151223540306,0.138882651925087),(-0.015323237515986,-0.12871652841568,0.138334348797798),(0.102820813655853,-0.0720862969756126,0.0782168358564377),(0.0691742300987244,0.00961552746593952,0.196848139166832),(-0.0784864947199821,-0.104707300662994,0.0244421008974314),(-0.129387423396111,-0.0837726294994354,0.201711267232895),(0.10028512775898,0.0147631969302893,0.106750056147575),(0.0725274235010147,-0.0733503252267838,0.0162904672324657),(0.0907945036888123,-0.0631996393203735,0.166820541024208),(-0.0685850381851196,0.0688069462776184,0.138255223631859),(-0.0430277064442635,-0.107757613062859,0.022122398018837),(0.102449595928192,-0.0650743395090103,0.0278087817132473),(-0.0123228346928954,-0.128916323184967,0.0516869872808456),(0.0133168455213308,-0.126367673277855,0.0497013293206692),(-0.211436733603477,-0.0425778105854988,0.171008050441742),(-0.135128378868103,-0.0737440511584282,0.028781833127141),(-0.0713493376970291,-0.0974928066134453,0.048680767416954),(-0.0144545361399651,-0.10740352421999,0.169533520936966),(-0.0520200654864311,-0.106458351016045,0.0468626022338867),(-0.0383422300219536,-0.121899470686913,0.0537898242473602),(-0.135303497314453,0.00472360569983721,0.269406676292419),(-0.222012773156166,-0.0435851588845253,0.201951056718826),(-0.150152832269669,0.0706916153430939,0.296226799488068),(-0.205232128500938,-0.0530128739774227,0.172492980957031),(0.0815067514777184,-0.0842671692371368,0.0463023483753204),(0.101917430758476,-0.0744422674179077,0.0511590167880058),(-0.104162633419037,-0.0769466981291771,0.197300210595131),(-0.165175527334213,0.100392691791058,0.295828104019165),(0.0624474883079529,-0.0914158597588539,0.172223627567291),(-0.0696270391345024,0.0371879562735558,0.345104366540909),(-0.129096910357475,-0.0715842396020889,0.0532362163066864),(-0.102229714393616,-0.0918472409248352,0.0500270053744316),(0.0328243598341942,-0.0628630220890045,0.219847500324249),(-0.0929397568106651,-0.0598123446106911,0.212814390659332),(-0.140351414680481,-0.0651696026325226,0.281688511371613),(-0.0299176927655935,0.0646412074565887,0.345614969730377),(-0.210334226489067,-0.019161444157362,0.170468419790268),(-0.189835593104362,-0.0147899463772774,0.284663945436478),(-0.0706062465906143,-0.0353134833276272,0.219783633947372),(-0.196250692009926,-0.0419037826359272,0.286000579595566),(-0.189289301633835,0.015417193993926,0.167268991470337),(-0.165491297841072,0.119253136217594,0.309156060218811),(-0.188711583614349,-0.0422543436288834,0.0857931450009346),(-0.137549817562103,-0.0175594426691532,0.048555850982666),(-0.0439321398735046,0.0188035927712917,0.209587976336479),(-0.166142821311951,0.0438390895724297,0.269286632537842),(-0.100659042596817,0.0212050415575504,0.210695147514343),(-0.165524810552597,0.0681574642658234,0.275103896856308),(-0.131917878985405,-0.0432314537465572,0.0469778589904308),(-0.0393056124448776,-0.127956256270409,0.0805243328213692),(-0.0148295955732465,-0.134464859962463,0.078124076128006),(0.0156515818089247,-0.132012516260147,0.0775675550103188),(0.128680378198624,-0.0638554841279984,0.0486980155110359),(0.011726126074791,-0.12689021229744,0.138521879911423),(-0.104669205844402,-0.0973712056875229,0.0786209478974342),(-0.0722803771495819,-0.0994613841176033,0.0782437026500702),(-0.0900976955890656,0.0289249792695045,0.304527103900909),(-0.131665915250778,-0.0805337652564049,0.0729337483644485),(-0.17888680100441,0.0127522293478251,0.288278430700302),(-0.131906762719154,0.0216084867715836,0.211986422538757),(0.0438910871744156,0.0446652211248875,0.170035198330879),(0.126842275261879,-0.0620891898870468,0.0720244571566582),(-0.181458547711372,0.0720020085573196,0.30515855550766),(-0.105359517037868,0.0106867477297783,0.222205132246017),(-0.0755681917071342,-0.105624243617058,0.10775239020586),(-0.130771055817604,0.0436740666627884,0.171749204397202),(-0.133024662733078,0.049973726272583,0.138679206371307),(-0.11655567586422,-0.016352504491806,0.262825727462769),(-0.192813113331795,0.00962049700319767,0.228011801838875),(-0.0995994955301285,0.0463632792234421,0.169919461011887),(-0.0153328543528914,0.07756557315588,0.138280719518661),(-0.0149811441078782,0.0544508099555969,0.170514196157455),(-0.0777326822280884,0.0189591310918331,0.297642737627029),(-0.0429378487169743,0.0526389256119728,0.171193689107895),(-0.210668057203293,-0.0934961810708046,0.245899826288223),(-0.162400558590889,0.0198477655649185,0.223333954811096),(0.112556174397469,-0.0415905937552452,0.087884321808815),(-0.0984991043806076,0.0341813936829567,0.19681504368782),(-0.125417664647102,0.00907643139362335,0.292186677455902),(0.0127286352217197,0.0715995132923126,0.138794869184494),(-0.184464573860168,-0.063567191362381,0.0917578190565109),(-0.159845903515816,0.0349735803902149,0.277037382125854),(-0.163954228162766,-0.073273241519928,0.079649306833744),(-0.130220845341682,0.0479081235826015,0.111017473042011),(-0.105627626180649,-0.103251308202744,0.104907594621181),(-0.0447412990033627,-0.130966305732727,0.105820834636688),(-0.0145897325128317,-0.137667417526245,0.107010833919048),(0.0177259147167206,-0.133680522441864,0.11051332205534),(-0.20410780608654,-0.015498636290431,0.265768945217133),(-0.163662612438202,-0.0963144749403,0.108248025178909),(-0.133774682879448,-0.102946348488331,0.108776144683361),(-0.152653515338898,-0.093793697655201,0.0112244309857488),(-0.169374197721481,0.0769077241420746,0.31595915555954),(-0.15337011218071,0.0495448186993599,0.289855599403381),(-0.14865180850029,0.0935175195336342,0.306516766548157),(-0.163774311542511,-0.100279614329338,0.138708546757698),(-0.114786863327026,-0.0349755696952343,0.251059830188751),(0.0435214228928089,-0.123003117740154,0.107089169323444),(0.0122568001970649,0.0234032459557056,0.212896287441254),(-0.132915586233139,-0.105148307979107,0.138666361570358),(-0.103796437382698,-0.10418801009655,0.13867013156414),(-0.0721595510840416,-0.1059859842062,0.138681977987289),(0.0412953048944473,-0.0123581402003765,0.221496060490608),(-0.0697300583124161,0.0507166534662247,0.170578330755234),(0.0441036224365234,-0.114852353930473,0.138935402035713),(-0.0128488391637802,0.0388977639377117,0.196252673864365),(-0.124916173517704,-0.00659546442329884,0.306106418371201),(-0.218161851167679,-0.071009561419487,0.230814844369888),(-0.163197606801987,-0.0973011329770088,0.173606932163239),(-0.106259688735008,-0.0960564464330673,0.167294099926949),(-0.134439319372177,-0.0996981337666512,0.164969086647034),(-0.160570159554482,-0.110724151134491,0.202919006347656),(-0.120365753769875,-0.00549432123079896,0.253050655126572),(-0.133883744478226,0.0106024611741304,0.23326064646244),(-0.0365464128553867,0.062771737575531,0.351498425006866),(-0.0698662772774696,0.0357129909098148,0.305281817913055),(-0.135447904467583,-0.0874549821019173,0.184239640831947),(-0.112891294062138,0.00657996907830238,0.271908432245255),(-0.0499069318175316,0.0498133301734924,0.325594484806061),(-0.135738432407379,-0.100006818771362,-7.45058059692383E-09),(0.0123523958027363,-0.101531967520714,-7.45058059692383E-09),(-0.102930329740047,-0.0987276136875153,-7.45058059692383E-09),(-0.158383101224899,0.0353976972401142,0.167762398719788),(0.0585155189037323,-0.0887269079685211,0.0169257298111916),(-0.202236160635948,-0.0440891794860363,0.107780121266842),(0.12652799487114,-0.0424845181405544,0.0317913927137852),(0.0445115864276886,-0.111490845680237,0.0452388003468513),(0.0178857706487179,0.0359265469014645,0.199328750371933),(0.0685334727168083,-0.0978689268231392,0.0533365905284882),(0.138488471508026,-0.0432419404387474,0.0493728704750538),(0.0406565591692924,0.062880277633667,0.138536900281906),(0.0871811881661415,-0.0870387107133865,0.138694822788239),(-0.0505233928561211,0.0300182458013296,0.313426643610001),(0.0435324311256409,-0.119963906705379,0.0792121887207031),(0.0723142325878143,-0.100660108029842,0.0801471099257469),(0.0880676060914993,-0.086207315325737,0.078484445810318),(0.136276960372925,-0.0405644066631794,0.078633114695549),(0.0735301449894905,0.0462804175913334,0.10518267005682),(-0.180783584713936,0.120272636413574,0.33598318696022),(-0.155802026391029,-0.042164009064436,0.0622472763061523),(-0.192451253533363,-0.0732510983943939,0.112686090171337),(0.0313579067587852,0.0240139346569777,0.208784699440002),(0.0728883668780327,-0.103513494133949,0.107350297272205),(0.0885002017021179,-0.0885679498314857,0.105739302933216),(0.100790202617645,-0.0713259652256966,0.10683286935091),(0.109439946711063,-0.0426978133618832,0.107300646603107),(-0.18864569067955,-0.0167884975671768,0.0867345333099365),(-0.0709428116679192,0.0352016389369965,0.19365206360817),(-0.0357190407812595,0.0615072995424271,0.335724234580994),(0.0447911284863949,0.0144118629395962,-7.45058059692383E-09),(0.0369860865175724,0.0369828194379807,-7.45058059692383E-09),(0.0461129434406757,-0.0748821049928665,-7.45058059692383E-09),(0.104031659662724,-0.0135611081495881,0.0148804550990462),(0.0986066535115242,0.0066530667245388,0.0271508432924747),(0.103960558772087,-0.0420542061328888,0.0150693515315652),(0.121874935925007,-0.0147962821647525,0.0282622296363115),(0.0696230307221413,0.0340555869042873,0.168976783752441),(0.0729203075170517,0.015480482019484,0.0226278305053711),(-0.0445376336574554,0.0741409137845039,0.139188349246979),(0.046685803681612,0.0460076108574867,0.0192816369235516),(0.132462680339813,-0.0147683853283525,0.079218864440918),(0.123972199857235,0.00519884005188942,0.0471794344484806),(0.13483801484108,-0.0135693158954382,0.0477543026208878),(0.101557418704033,0.0150842368602753,0.0500984787940979),(-0.151446789503098,0.125798091292381,0.318272113800049),(0.0826703608036041,0.023927254602313,0.0464257299900055),(0.0693408101797104,0.0435765013098717,0.0500893704593182),(-0.0420871675014496,0.0380131863057613,0.193471923470497),(-0.0971032008528709,0.0616641864180565,-7.45058059692383E-09),(-0.0130963791161776,0.064698226749897,0.0197515171021223),(-0.157119512557983,0.00803167372941971,-7.45058059692383E-09),(0.113602519035339,-0.0132037419825792,0.0879008769989014),(-0.069912314414978,0.066078893840313,0.0191369466483593),(0.0389328189194202,0.0351467467844486,0.194373697042465),(0.0768988505005836,0.0420413166284561,0.0788332372903824),(0.10157422721386,0.0134498169645667,0.0773250162601471),(0.123080961406231,0.00395354814827442,0.0694246292114258),(-0.211960434913635,-0.102200835943222,0.224356546998024),(0.110181555151939,-0.0136255938559771,0.109196342527866),(-0.102282598614693,0.0414383597671986,0.019612405449152),(-0.172445297241211,0.11539913713932,0.340771019458771),(-0.181048646569252,0.112369157373905,0.34296378493309),(0.0725264996290207,-0.0152853392064571,0.200319215655327),(-0.183978870511055,0.0709394812583923,0.317676812410355),(-0.153028383851051,-0.0384657420217991,-7.45058059692383E-09),(-0.154637187719345,-0.0691222250461578,-7.45058059692383E-09),(-0.152765303850174,-0.0738510563969612,0.015262059867382),(-0.153248697519302,-0.0919284746050835,-7.45058059692383E-09),(-0.16192090511322,-0.0145302480086684,-7.45058059692383E-09),(-0.161076262593269,-0.0149271814152598,0.0173035766929388),(-0.139386385679245,-0.0480194091796875,0.0201432537287474),(-0.15407682955265,-0.0336258858442307,0.0154564278200269),(-0.141747921705246,-0.0158547051250935,0.0288874395191669),(-0.0563743449747562,-0.108996540307999,0.0737379342317581),(-0.0461691729724407,0.0890766233205795,0.110146202147007),(-0.0146415047347546,0.0512426868081093,-7.45058059692383E-09),(-0.156508177518845,0.00872325897216797,0.0127747664228082),(-0.0932494476437569,0.0622886717319489,0.0157215017825365),(-0.134241998195648,0.0180515833199024,0.0220324043184519),(-0.0754619538784027,0.0455531552433968,0.0509162880480289),(-0.103701874613762,0.0273517612367868,0.0517874732613564),(-0.131066977977753,0.0115249017253518,0.0524038933217525),(-0.062931016087532,0.0692232176661491,0.0536416172981262),(-0.132335588335991,0.0322872921824455,0.19751612842083),(-0.0452888980507851,0.0760203972458839,0.0472172982990742),(-0.163926124572754,0.0142420912161469,0.0823174566030502),(-0.174691706895828,-0.0135900285094976,0.0736509189009666),(-0.0486402213573456,0.0849898308515549,0.0788175389170647),(-0.0689510703086853,0.0702485665678978,0.0784279331564903),(-0.0810153111815453,0.0491584502160549,0.0748984813690186),(-0.0429749675095081,0.0617619827389717,-7.45058059692383E-09),(0.0349937379360199,0.00642204098403454,0.219141826033592),(-0.202323064208031,-0.0122631303966045,0.109208643436432),(-0.188646167516708,0.0148954978212714,0.108683586120605),(-0.0744052901864052,0.0735662579536438,0.106222227215767),(-0.161729156970978,0.0381991006433964,0.108166508376598),(-0.104008600115776,0.0453929454088211,-7.45058059692383E-09),(0.0388389863073826,0.0702219158411026,0.10972835123539),(-0.0412575826048851,0.0688836574554443,0.0204634200781584),(-0.132600158452988,0.0162683837115765,-7.45058059692383E-09),(0.0419384241104126,0.0643723532557487,0.0487342029809952),(-0.0230755694210529,0.0902970731258392,0.106796741485596),(0.0122685618698597,0.0503091886639595,-7.45058059692383E-09),(0.0420029424130917,0.0680971890687943,0.0789963230490685),(-0.0132175851613283,0.00625489093363285,0.222308561205864),(0.0146723045036197,0.00723757036030293,0.223271667957306),(0.0720149055123329,-0.0120490025728941,0.00231547281146049),(0.013688700273633,0.0642379224300385,0.0262222941964865),(0.033619936555624,0.0599825419485569,0.0301631242036819),(0.0156846102327108,0.0726122707128525,0.0497567467391491),(-0.0139973452314734,0.0766579210758209,0.0474896989762783),(-0.0166601836681366,0.0857705846428871,0.0790435597300529),(0.0127416122704744,0.0787845030426979,0.0776184424757957),(-0.137325063347816,0.0222998633980751,0.0709470063447952),(-0.103061355650425,0.0392319709062576,0.082869827747345),(-0.133015736937523,0.0387391112744808,0.0904415026307106),(-0.151931047439575,0.0321191623806953,0.0878717452287674),(0.0125869233161211,0.0806632563471794,0.105742789804935),(-0.0995742082595825,0.049370177090168,0.106232292950153),(-0.0746603757143021,0.0656085163354874,-7.45058059692383E-09),(0.0122953318059444,0.017735980451107,-7.45058059692383E-09),(-0.0146934473887086,0.0262711010873318,-7.45058059692383E-09),(-0.0429374538362026,0.0298651698976755,-7.45058059692383E-09),(-0.103215932846069,0.0137835666537285,-7.45058059692383E-09),(0.0444422401487827,-0.0129836350679398,-7.45058059692383E-09),(-0.0746518895030022,0.0316607765853405,-7.45058059692383E-09),(0.0123018361628056,-0.0127876792103052,-7.45058059692383E-09),(-0.0147215090692043,-0.0133242877200246,-7.45058059692383E-09),(-0.101430043578148,-0.0147481001913548,-7.45058059692383E-09),(-0.0429213680326939,-0.0151002155616879,-7.45058059692383E-09),(-0.132630944252014,-0.0134387537837029,-7.45058059692383E-09),(-0.0746475011110306,-0.0111579261720181,-7.45058059692383E-09),(0.0461949594318867,-0.0483818538486958,-7.45058059692383E-09),(0.0123028568923473,-0.0431565642356873,-7.45058059692383E-09),(0.0676943361759186,-0.0439984127879143,0.00213921279646456),(-0.0147214606404305,-0.0419304519891739,-7.45058059692383E-09),(-0.0429213680326939,-0.0426230616867542,-7.45058059692383E-09),(-0.134391859173775,-0.0420995727181435,-7.45058059692383E-09),(0.0123003236949444,-0.0714240521192551,-7.45058059692383E-09),(-0.0147217661142349,-0.0719940662384033,-7.45058059692383E-09),(-0.0746477097272873,-0.0698381289839745,-7.45058059692383E-09),(-0.0429213680326939,-0.0721928924322128,-7.45058059692383E-09),(-0.101144231855869,-0.0718697011470795,-7.45058059692383E-09),(0.0347950644791126,-0.096686989068985,-7.45058059692383E-09),(-0.132067084312439,-0.0720017328858376,-7.45058059692383E-09),(-0.159548789262772,-0.0125050684437156,0.0618688985705376),(-0.0169071108102798,-0.107485927641392,-7.45058059692383E-09),(-0.0746394321322441,-0.103576719760895,-7.45058059692383E-09),(-0.0428757518529892,-0.105996340513229,-7.45058059692383E-09),(-0.0746474862098694,-0.0418127365410328,-7.45058059692383E-09),(-0.101288944482803,-0.0456511229276657,-7.45058059692383E-09),(0.061871238052845,0.0245271548628807,0.191577181220055),(-0.0470216795802116,0.0414715930819511,0.344332307577133),(-0.0351001992821693,0.0582603961229324,0.352131396532059),(-0.043320570141077,0.0422725304961205,0.325726985931396),(-0.0332878455519676,0.056865319609642,0.334871053695679),(-0.0782285928726196,0.010980136692524,0.334277510643005),(-0.0612197890877724,0.0185103937983513,0.30783212184906),(-0.0876919776201248,0.0268637835979462,0.333815038204193),(-0.0750949084758759,-0.00158989988267422,0.21651217341423),(-0.0432584583759308,0.000724630663171411,0.217384174466133))); +#905=IFCPOLYGONALFACESET(#904,$,(#191,#192,#193,#194,#195,#196,#197,#198,#199,#200,#201,#202,#203,#204,#205,#206,#207,#208,#209,#210,#211,#212,#213,#214,#215,#216,#217,#218,#219,#220,#221,#222,#223,#224,#225,#226,#227,#228,#229,#230,#231,#232,#233,#234,#235,#236,#237,#238,#239,#240,#241,#242,#243,#244,#245,#246,#247,#248,#249,#250,#251,#252,#253,#254,#255,#256,#257,#258,#259,#260,#261,#262,#263,#264,#265,#266,#267,#268,#269,#270,#271,#272,#273,#274,#275,#276,#277,#278,#279,#280,#281,#282,#283,#284,#285,#286,#287,#288,#289,#290,#291,#292,#293,#294,#295,#296,#297,#298,#299,#300,#301,#302,#303,#304,#305,#306,#307,#308,#309,#310,#311,#312,#313,#314,#315,#316,#317,#318,#319,#320,#321,#322,#323,#324,#325,#326,#327,#328,#329,#330,#331,#332,#333,#334,#335,#336,#337,#338,#339,#340,#341,#342,#343,#344,#345,#346,#347,#348,#349,#350,#351,#352,#353,#354,#355,#356,#357,#358,#359,#360,#361,#362,#363,#364,#365,#366,#367,#368,#369,#370,#371,#372,#373,#374,#375,#376,#377,#378,#379,#380,#381,#382,#383,#384,#385,#386,#387,#388,#389,#390,#391,#392,#393,#394,#395,#396,#397,#398,#399,#400,#401,#402,#403,#404,#405,#406,#407,#408,#409,#410,#411,#412,#413,#414,#415,#416,#417,#418,#419,#420,#421,#422,#423,#424,#425,#426,#427,#428,#429,#430,#431,#432,#433,#434,#435,#436,#437,#438,#439,#440,#441,#442,#443,#444,#445,#446,#447,#448,#449,#450,#451,#452,#453,#454,#455,#456,#457,#458,#459,#460,#461,#462,#463,#464,#465,#466,#467,#468,#469,#470,#471,#472,#473,#474,#475,#476,#477,#478,#479,#480,#481,#482,#483,#484,#485,#486,#487,#488,#489,#490,#491,#492,#493,#494,#495,#496,#497,#498,#499,#500,#501,#502,#503,#504,#505,#506,#507,#508,#509,#510,#511,#512,#513,#514,#515,#516,#517,#518,#519,#520,#521,#522,#523,#524,#525,#526,#527,#528,#529,#530,#531,#532,#533,#534,#535,#536,#537,#538,#539,#540,#541,#542,#543,#544,#545,#546,#547,#548,#549,#550,#551,#552,#553,#554,#555,#556,#557,#558,#559,#560,#561,#562,#563,#564,#565,#566,#567,#568,#569,#570,#571,#572,#573,#574,#575,#576,#577,#578,#579,#580,#581,#582,#583,#584,#585,#586,#587,#588,#589,#590,#591,#592,#593,#594,#595,#596,#597,#598,#599,#600,#601,#602,#603,#604,#605,#606,#607,#608,#609,#610,#611,#612,#613,#614,#615,#616,#617,#618,#619,#620,#621,#622,#623,#624,#625,#626,#627,#628,#629,#630,#631,#632,#633,#634,#635,#636,#637,#638,#639,#640,#641,#642,#643,#644,#645,#646,#647,#648,#649,#650,#651,#652,#653,#654,#655,#656,#657,#658,#659,#660,#661,#662,#663,#664,#665,#666,#667,#668,#669,#670,#671,#672,#673,#674,#675,#676,#677,#678,#679,#680,#681,#682,#683,#684,#685,#686,#687,#688,#689,#690,#691,#692,#693,#694,#695,#696,#697,#698,#699,#700,#701,#702,#703,#704,#705,#706,#707,#708,#709,#710,#711,#712,#713,#714,#715,#716,#717,#718,#719,#720,#721,#722,#723,#724,#725,#726,#727,#728,#729,#730,#731,#732,#733,#734,#735,#736,#737,#738,#739,#740,#741,#742,#743,#744,#745,#746,#747,#748,#749,#750,#751,#752,#753,#754,#755,#756,#757,#758,#759,#760,#761,#762,#763,#764,#765,#766,#767,#768,#769,#770,#771,#772,#773,#774,#775,#776,#777,#778,#779,#780,#781,#782,#783,#784,#785,#786,#787,#788,#789,#790,#791,#792,#793,#794,#795,#796,#797,#798,#799,#800,#801,#802,#803,#804,#805,#806,#807,#808,#809,#810,#811,#812,#813,#814,#815,#816,#817,#818,#819,#820,#821,#822,#823,#824,#825,#826,#827,#828,#829,#830,#831,#832,#833,#834,#835,#836,#837,#838,#839,#840,#841,#842,#843,#844,#845,#846,#847,#848,#849,#850,#851,#852,#853,#854,#855,#856,#857,#858,#859,#860,#861,#862,#863,#864,#865,#866,#867,#868,#869,#870,#871,#872,#873,#874,#875,#876,#877,#878,#879,#880,#881,#882,#883,#884,#885,#886,#887,#888,#889,#890,#891,#892,#893,#894,#895,#896,#897,#898,#899,#900,#901,#902,#903),$); +#906=IFCSHAPEREPRESENTATION(#19,'Body','Tessellation',(#905)); +#907=IFCCARTESIANPOINT((0.,0.,0.)); +#908=IFCDIRECTION((1.,0.,0.)); +#909=IFCDIRECTION((0.,0.,1.)); +#910=IFCAXIS2PLACEMENT3D(#907,#909,#908); +#911=IFCREPRESENTATIONMAP(#910,#906); +ENDSEC; +END-ISO-10303-21; diff --git a/src/blenderbim/libraries/blenderbim-site-library.ifc b/src/blenderbim/libraries/blenderbim-site-library.ifc new file mode 100644 index 0000000000..287b176cbc --- /dev/null +++ b/src/blenderbim/libraries/blenderbim-site-library.ifc @@ -0,0 +1,311 @@ +ISO-10303-21; +HEADER; +FILE_DESCRIPTION(('ViewDefinition[DesignTransferView]'),'2;1'); +FILE_NAME('/dev/null','2021-09-01T08:54:11+10:00',(),(),'IfcOpenShell 0.6.0b0','IfcOpenShell 0.6.0b0','Nobody'); +FILE_SCHEMA(('IFC4')); +ENDSEC; +DATA; +#1=IFCACTORROLE(.USERDEFINED.,'CONTRIBUTOR',$); +#2=IFCTELECOMADDRESS(.USERDEFINED.,'The main webpage of the software collection.','WEBPAGE',$,$,$,$,'https://ifcopenshell.org',$); +#3=IFCTELECOMADDRESS(.USERDEFINED.,'The BlenderBIM Add-on webpage of the software collection.','WEBPAGE',$,$,$,$,'https://blenderbim.org',$); +#4=IFCTELECOMADDRESS(.USERDEFINED.,'The source code repository of the software collection.','REPOSITORY',$,$,$,$,'https://github.com/IfcOpenShell/IfcOpenShell.git',$); +#5=IFCORGANIZATION($,'IfcOpenShell','IfcOpenShell is an open source (LGPL) software library that helps users and software developers to work with the IFC file format.',(#1),(#2,#3,#4)); +#6=IFCAPPLICATION(#5,'0.0.210605','BlenderBIM Add-on','BlenderBIM'); +#7=IFCPROJECT('3ZvT295if4vR5lOICb8lGG',$,'BlenderBIM Demo',$,$,$,$,(#18,#25),#13); +#8=IFCPROJECTLIBRARY('3ukNlWeMfCufxIIv6a2tjG',$,'BlenderBIM Demo Library',$,$,$,$,$,$); +#9=IFCRELDECLARES('2zCOS3dzf5F9da78ZxnBpZ',$,$,$,#8,(#91,#27,#59,#8)); +#10=IFCSIUNIT(*,.LENGTHUNIT.,$,.METRE.); +#11=IFCSIUNIT(*,.AREAUNIT.,$,.SQUARE_METRE.); +#12=IFCSIUNIT(*,.VOLUMEUNIT.,$,.CUBIC_METRE.); +#13=IFCUNITASSIGNMENT((#12,#10,#11)); +#14=IFCCARTESIANPOINT((0.,0.,0.)); +#15=IFCDIRECTION((0.,0.,1.)); +#16=IFCDIRECTION((1.,0.,0.)); +#17=IFCAXIS2PLACEMENT3D(#14,#15,#16); +#18=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Model',3,1.E-05,#17,$); +#19=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Body','Model',*,*,*,*,#18,$,.MODEL_VIEW.,$); +#20=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Clearance','Model',*,*,*,*,#18,$,.MODEL_VIEW.,$); +#21=IFCCARTESIANPOINT((0.,0.,0.)); +#22=IFCDIRECTION((0.,0.,1.)); +#23=IFCDIRECTION((1.,0.,0.)); +#24=IFCAXIS2PLACEMENT3D(#21,#22,#23); +#25=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Plan',2,1.E-05,#24,$); +#26=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Annotation','Model',*,*,*,*,#18,$,.PLAN_VIEW.,$); +#27=IFCBUILDINGELEMENTPROXYTYPE('2YQNXdYf18kBzxu3tjWlgr',$,'Site Shed 3x6m',$,$,$,(#58),$,$,$); +#28=IFCINDEXEDPOLYGONALFACE((11,5,7,9)); +#29=IFCINDEXEDPOLYGONALFACE((13,7,5,15)); +#30=IFCINDEXEDPOLYGONALFACE((12,1,3,14)); +#31=IFCINDEXEDPOLYGONALFACE((6,2,4,10,20,8)); +#32=IFCINDEXEDPOLYGONALFACE((1,11,9,3)); +#33=IFCINDEXEDPOLYGONALFACE((6,15,16,12,2)); +#34=IFCINDEXEDPOLYGONALFACE((8,13,15,6)); +#35=IFCINDEXEDPOLYGONALFACE((14,3,9,7,13,17)); +#36=IFCINDEXEDPOLYGONALFACE((4,14,17,13,8,20,19,18,10)); +#37=IFCINDEXEDPOLYGONALFACE((10,18,19,20)); +#38=IFCINDEXEDPOLYGONALFACE((15,5,11,1,12,16)); +#39=IFCINDEXEDPOLYGONALFACE((2,12,14,4)); +#40=IFCCARTESIANPOINTLIST3D(((6.,3.,2.60000014305115),(6.,3.,0.),(6.,0.,2.60000014305115),(6.,0.,0.),(0.,3.,2.60000014305115),(0.,3.,0.),(0.,0.,2.60000014305115),(0.,0.,0.),(3.,0.,2.70000004768372),(1.92000007629395,0.,0.),(3.,3.,2.70000004768372),(6.,3.,2.39861273765564),(0.,0.,2.39861273765564),(6.,0.,2.39861273765564),(0.,3.,2.39861273765564),(3.,3.,2.49861264228821),(3.,0.,2.49861264228821),(1.92000007629395,0.,2.09999990463257),(1.,0.,2.09999990463257),(1.,0.,0.))); +#41=IFCPOLYGONALFACESET(#40,$,(#31,#33,#34,#36,#39),$); +#42=IFCPOLYGONALFACESET(#40,$,(#28,#29,#30,#32,#35,#37,#38),$); +#43=IFCSHAPEREPRESENTATION(#19,'Body','Tessellation',(#41,#42)); +#44=IFCCOLOURRGB($,1.,1.,1.); +#45=IFCCOLOURRGB($,1.,1.,1.); +#46=IFCSURFACESTYLERENDERING(#44,0.,#45,$,$,$,$,$,.NOTDEFINED.); +#47=IFCSURFACESTYLE('Site Shed White',.BOTH.,(#46)); +#48=IFCCOLOURRGB($,0.0500000007450581,0.100000001490116,0.129999995231628); +#49=IFCCOLOURRGB($,0.0500000007450581,0.100000001490116,0.129999995231628); +#50=IFCSURFACESTYLERENDERING(#48,0.,#49,$,$,$,$,$,.NOTDEFINED.); +#51=IFCSURFACESTYLE('Site Shed Grey',.BOTH.,(#50)); +#52=IFCSTYLEDITEM(#41,(#47),'Site Shed White'); +#53=IFCSTYLEDITEM(#42,(#51),'Site Shed Grey'); +#54=IFCCARTESIANPOINT((0.,0.,0.)); +#55=IFCDIRECTION((1.,0.,0.)); +#56=IFCDIRECTION((0.,0.,1.)); +#57=IFCAXIS2PLACEMENT3D(#54,#56,#55); +#58=IFCREPRESENTATIONMAP(#57,#43); +#59=IFCBUILDINGELEMENTPROXYTYPE('0Q0hvi_v97dhIU2pPJfAJ5',$,'Site Shed 3x12m',$,$,$,(#90),$,$,$); +#60=IFCINDEXEDPOLYGONALFACE((11,5,7,9)); +#61=IFCINDEXEDPOLYGONALFACE((13,7,5,15)); +#62=IFCINDEXEDPOLYGONALFACE((12,1,3,14)); +#63=IFCINDEXEDPOLYGONALFACE((6,2,4,10,20,8)); +#64=IFCINDEXEDPOLYGONALFACE((1,11,9,3)); +#65=IFCINDEXEDPOLYGONALFACE((6,15,16,12,2)); +#66=IFCINDEXEDPOLYGONALFACE((8,13,15,6)); +#67=IFCINDEXEDPOLYGONALFACE((14,3,9,7,13,17)); +#68=IFCINDEXEDPOLYGONALFACE((4,14,17,13,8,20,19,18,10)); +#69=IFCINDEXEDPOLYGONALFACE((10,18,19,20)); +#70=IFCINDEXEDPOLYGONALFACE((15,5,11,1,12,16)); +#71=IFCINDEXEDPOLYGONALFACE((2,12,14,4)); +#72=IFCCARTESIANPOINTLIST3D(((12.,3.,2.60000014305115),(12.,3.,0.),(12.,0.,2.60000014305115),(12.,0.,0.),(0.,3.,2.60000014305115),(0.,3.,0.),(0.,0.,2.60000014305115),(0.,0.,0.),(6.,0.,2.70000004768372),(1.92000007629395,0.,0.),(6.,3.,2.70000004768372),(12.,3.,2.39861273765564),(0.,0.,2.39861273765564),(12.,0.,2.39861273765564),(0.,3.,2.39861273765564),(6.,3.,2.49861264228821),(6.,0.,2.49861264228821),(1.92000007629395,0.,2.09999990463257),(1.,0.,2.09999990463257),(1.,0.,0.))); +#73=IFCPOLYGONALFACESET(#72,$,(#63,#65,#66,#68,#71),$); +#74=IFCPOLYGONALFACESET(#72,$,(#60,#61,#62,#64,#67,#69,#70),$); +#75=IFCSHAPEREPRESENTATION(#19,'Body','Tessellation',(#73,#74)); +#76=IFCCOLOURRGB($,1.,1.,1.); +#77=IFCCOLOURRGB($,1.,1.,1.); +#78=IFCSURFACESTYLERENDERING(#76,0.,#77,$,$,$,$,$,.NOTDEFINED.); +#79=IFCSURFACESTYLE('Site Shed White',.BOTH.,(#78)); +#80=IFCCOLOURRGB($,0.0500000007450581,0.100000001490116,0.129999995231628); +#81=IFCCOLOURRGB($,0.0500000007450581,0.100000001490116,0.129999995231628); +#82=IFCSURFACESTYLERENDERING(#80,0.,#81,$,$,$,$,$,.NOTDEFINED.); +#83=IFCSURFACESTYLE('Site Shed Grey',.BOTH.,(#82)); +#84=IFCSTYLEDITEM(#73,(#79),'Site Shed White'); +#85=IFCSTYLEDITEM(#74,(#83),'Site Shed Grey'); +#86=IFCCARTESIANPOINT((0.,0.,0.)); +#87=IFCDIRECTION((1.,0.,0.)); +#88=IFCDIRECTION((0.,0.,1.)); +#89=IFCAXIS2PLACEMENT3D(#86,#88,#87); +#90=IFCREPRESENTATIONMAP(#89,#75); +#91=IFCBUILDINGELEMENTPROXYTYPE('0FMiZScTPFog7h7dFJ1g95',$,'Mobile Crane 50T',$,$,$,(#227,#302),$,$,$); +#92=IFCINDEXEDPOLYGONALFACE((1,167,166,2)); +#93=IFCINDEXEDPOLYGONALFACE((28,29,30,31,32,33,34,35,36,37,38,27)); +#94=IFCINDEXEDPOLYGONALFACE((16,17,18,19,20,21,22,23,24,25,26,15)); +#95=IFCINDEXEDPOLYGONALFACE((4,5,6,7,8,9,10,11,12,13,14,3)); +#96=IFCINDEXEDPOLYGONALFACE((77,78,80,79)); +#97=IFCINDEXEDPOLYGONALFACE((81,82,84,83)); +#98=IFCINDEXEDPOLYGONALFACE((85,87,88,86)); +#99=IFCINDEXEDPOLYGONALFACE((89,91,92,90)); +#100=IFCINDEXEDPOLYGONALFACE((57,169,168,67,68,69,70,71,64,65)); +#101=IFCINDEXEDPOLYGONALFACE((67,168,60,61,62,40,39,63,64,71,72)); +#102=IFCINDEXEDPOLYGONALFACE((68,67,72,71,70,69)); +#103=IFCINDEXEDPOLYGONALFACE((42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,65,64,63,41)); +#104=IFCINDEXEDPOLYGONALFACE((112,119,118,124,123,122,121,120,170,171)); +#105=IFCINDEXEDPOLYGONALFACE((120,125,124,118,95,93,94,117,116,115,170)); +#106=IFCINDEXEDPOLYGONALFACE((121,122,123,124,125,120)); +#107=IFCINDEXEDPOLYGONALFACE((96,97,95,118,119,112,111,110,109,108,107,106,105,104,103,102,101,100,99,98)); +#108=IFCINDEXEDPOLYGONALFACE((45,100,101,46)); +#109=IFCINDEXEDPOLYGONALFACE((60,115,116,61)); +#110=IFCINDEXEDPOLYGONALFACE((53,108,109,54)); +#111=IFCINDEXEDPOLYGONALFACE((46,101,102,47)); +#112=IFCINDEXEDPOLYGONALFACE((61,116,117,62)); +#113=IFCINDEXEDPOLYGONALFACE((40,94,93,39)); +#114=IFCINDEXEDPOLYGONALFACE((54,109,110,55)); +#115=IFCINDEXEDPOLYGONALFACE((47,102,103,48)); +#116=IFCINDEXEDPOLYGONALFACE((62,117,94,40)); +#117=IFCINDEXEDPOLYGONALFACE((39,93,95,63)); +#118=IFCINDEXEDPOLYGONALFACE((55,110,111,56)); +#119=IFCINDEXEDPOLYGONALFACE((48,103,104,49)); +#120=IFCINDEXEDPOLYGONALFACE((63,95,97,41)); +#121=IFCINDEXEDPOLYGONALFACE((41,97,96,42)); +#122=IFCINDEXEDPOLYGONALFACE((56,111,112,57)); +#123=IFCINDEXEDPOLYGONALFACE((49,104,105,50)); +#124=IFCINDEXEDPOLYGONALFACE((42,96,98,43)); +#125=IFCINDEXEDPOLYGONALFACE((57,112,171,169)); +#126=IFCINDEXEDPOLYGONALFACE((50,105,106,51)); +#127=IFCINDEXEDPOLYGONALFACE((43,98,99,44)); +#128=IFCINDEXEDPOLYGONALFACE((58,113,114,59)); +#129=IFCINDEXEDPOLYGONALFACE((51,106,107,52)); +#130=IFCINDEXEDPOLYGONALFACE((44,99,100,45)); +#131=IFCINDEXEDPOLYGONALFACE((168,170,115,60)); +#132=IFCINDEXEDPOLYGONALFACE((52,107,108,53)); +#133=IFCINDEXEDPOLYGONALFACE((151,150,161,160,159,158,157,156,155,154,153,152)); +#134=IFCINDEXEDPOLYGONALFACE((139,138,149,148,147,146,145,144,143,142,141,140)); +#135=IFCINDEXEDPOLYGONALFACE((127,126,137,136,135,134,133,132,131,130,129,128)); +#136=IFCINDEXEDPOLYGONALFACE((162,163,164,165)); +#137=IFCINDEXEDPOLYGONALFACE((163,2,166,164)); +#138=IFCINDEXEDPOLYGONALFACE((162,1,2,163)); +#139=IFCINDEXEDPOLYGONALFACE((165,167,1,162)); +#140=IFCINDEXEDPOLYGONALFACE((164,166,167,165)); +#141=IFCINDEXEDPOLYGONALFACE((59,168,169)); +#142=IFCINDEXEDPOLYGONALFACE((113,171,170,114)); +#143=IFCINDEXEDPOLYGONALFACE((73,66,76,75,74)); +#144=IFCINDEXEDPOLYGONALFACE((173,174,175,176,172)); +#145=IFCINDEXEDPOLYGONALFACE((74,174,173,73)); +#146=IFCINDEXEDPOLYGONALFACE((66,172,176,76)); +#147=IFCINDEXEDPOLYGONALFACE((75,175,174,74)); +#148=IFCINDEXEDPOLYGONALFACE((73,173,172,66)); +#149=IFCINDEXEDPOLYGONALFACE((76,176,175,75)); +#150=IFCINDEXEDPOLYGONALFACE((177,178,180,179)); +#151=IFCINDEXEDPOLYGONALFACE((181,183,184,182)); +#152=IFCINDEXEDPOLYGONALFACE((178,182,184,180)); +#153=IFCINDEXEDPOLYGONALFACE((177,181,182,178)); +#154=IFCINDEXEDPOLYGONALFACE((179,183,181,177)); +#155=IFCINDEXEDPOLYGONALFACE((180,184,183,179)); +#156=IFCINDEXEDPOLYGONALFACE((186,185,188,187)); +#157=IFCINDEXEDPOLYGONALFACE((190,189,192,191)); +#158=IFCINDEXEDPOLYGONALFACE((92,91,191,192)); +#159=IFCINDEXEDPOLYGONALFACE((87,85,186,187)); +#160=IFCINDEXEDPOLYGONALFACE((88,87,187,188)); +#161=IFCINDEXEDPOLYGONALFACE((90,92,192,189)); +#162=IFCINDEXEDPOLYGONALFACE((89,90,189,190)); +#163=IFCINDEXEDPOLYGONALFACE((86,88,188,185)); +#164=IFCINDEXEDPOLYGONALFACE((85,86,185,186)); +#165=IFCINDEXEDPOLYGONALFACE((91,89,190,191)); +#166=IFCINDEXEDPOLYGONALFACE((193,195,196,194)); +#167=IFCINDEXEDPOLYGONALFACE((198,199,200,197)); +#168=IFCINDEXEDPOLYGONALFACE((78,194,196,80)); +#169=IFCINDEXEDPOLYGONALFACE((77,193,194,78)); +#170=IFCINDEXEDPOLYGONALFACE((83,199,198,81)); +#171=IFCINDEXEDPOLYGONALFACE((84,200,199,83)); +#172=IFCINDEXEDPOLYGONALFACE((79,195,193,77)); +#173=IFCINDEXEDPOLYGONALFACE((80,196,195,79)); +#174=IFCINDEXEDPOLYGONALFACE((82,197,200,84)); +#175=IFCINDEXEDPOLYGONALFACE((81,198,197,82)); +#176=IFCINDEXEDPOLYGONALFACE((202,203,204,201)); +#177=IFCINDEXEDPOLYGONALFACE((206,205,208,207)); +#178=IFCINDEXEDPOLYGONALFACE((201,204,208,205)); +#179=IFCINDEXEDPOLYGONALFACE((202,201,205,206)); +#180=IFCINDEXEDPOLYGONALFACE((203,202,206,207)); +#181=IFCINDEXEDPOLYGONALFACE((204,203,207,208)); +#182=IFCINDEXEDPOLYGONALFACE((210,211,212,209)); +#183=IFCINDEXEDPOLYGONALFACE((214,213,216,215)); +#184=IFCINDEXEDPOLYGONALFACE((209,212,216,213)); +#185=IFCINDEXEDPOLYGONALFACE((210,209,213,214)); +#186=IFCINDEXEDPOLYGONALFACE((211,210,214,215)); +#187=IFCINDEXEDPOLYGONALFACE((212,211,215,216)); +#188=IFCINDEXEDPOLYGONALFACE((218,219,220,217)); +#189=IFCINDEXEDPOLYGONALFACE((222,221,224,223)); +#190=IFCINDEXEDPOLYGONALFACE((217,220,224,221)); +#191=IFCINDEXEDPOLYGONALFACE((218,217,221,222)); +#192=IFCINDEXEDPOLYGONALFACE((219,218,222,223)); +#193=IFCINDEXEDPOLYGONALFACE((220,219,223,224)); +#194=IFCINDEXEDPOLYGONALFACE((226,227,228,225)); +#195=IFCINDEXEDPOLYGONALFACE((230,229,232,231)); +#196=IFCINDEXEDPOLYGONALFACE((225,228,232,229)); +#197=IFCINDEXEDPOLYGONALFACE((226,225,229,230)); +#198=IFCINDEXEDPOLYGONALFACE((227,226,230,231)); +#199=IFCINDEXEDPOLYGONALFACE((228,227,231,232)); +#200=IFCINDEXEDPOLYGONALFACE((59,114,170)); +#201=IFCINDEXEDPOLYGONALFACE((169,171,113)); +#202=IFCINDEXEDPOLYGONALFACE((169,113,58)); +#203=IFCINDEXEDPOLYGONALFACE((59,170,168)); +#204=IFCINDEXEDPOLYGONALFACE((169,58,59)); +#205=IFCCARTESIANPOINTLIST3D(((-7.20000171661377,0.400000512599945,3.80000019073486),(-7.20000171661377,0.400000780820847,3.20000028610229),(-4.40000152587891,-1.29999935626984,1.40000033378601),(-4.75000190734863,-1.29999923706055,1.30621814727783),(-5.0062198638916,-1.29999923706055,1.05000030994415),(-5.1000018119812,-1.29999923706055,0.70000022649765),(-5.00621938705444,-1.29999923706055,0.350000262260437),(-4.75000190734863,-1.29999923706055,0.0937825441360474),(-4.40000152587891,-1.29999923706055,3.00803094432922E-07),(-4.0500020980835,-1.29999923706055,0.0937825441360474),(-3.79378390312195,-1.29999923706055,0.350000321865082),(-3.70000171661377,-1.29999923706055,0.700000286102295),(-3.79378390312195,-1.29999923706055,1.05000030994415),(-4.05000162124634,-1.29999923706055,1.30621814727783),(-2.80000162124634,-1.29999935626984,1.40000033378601),(-3.15000176429749,-1.29999923706055,1.30621814727783),(-3.40621948242188,-1.29999923706055,1.05000030994415),(-3.50000190734863,-1.29999923706055,0.70000022649765),(-3.40621948242188,-1.29999923706055,0.350000262260437),(-3.15000176429749,-1.29999923706055,0.0937825441360474),(-2.80000162124634,-1.29999923706055,3.00803094432922E-07),(-2.45000171661377,-1.29999923706055,0.0937825441360474),(-2.1937837600708,-1.29999923706055,0.350000321865082),(-2.1000018119812,-1.29999923706055,0.700000286102295),(-2.1937837600708,-1.29999923706055,1.05000030994415),(-2.45000171661377,-1.29999923706055,1.30621814727783),(-1.9073486328125E-06,-1.29999935626984,1.40000033378601),(-0.350001811981201,-1.29999923706055,1.30621814727783),(-0.606219291687012,-1.29999923706055,1.05000030994415),(-0.70000171661377,-1.29999923706055,0.70000022649765),(-0.606219291687012,-1.29999923706055,0.350000262260437),(-0.350001811981201,-1.29999923706055,0.0937825441360474),(-1.9073486328125E-06,-1.29999923706055,3.00803094432922E-07),(0.349998474121094,-1.29999923706055,0.0937825441360474),(0.606216430664062,-1.29999923706055,0.350000321865082),(0.699997901916504,-1.29999923706055,0.700000286102295),(0.606216430664062,-1.29999923706055,1.05000030994415),(0.349998474121094,-1.29999923706055,1.30621814727783),(-6.40000152587891,-1.29999923706055,1.80000019073486),(-1.20000123977661,-1.29999923706055,1.80000019073486),(-6.40000152587891,-1.29999923706055,0.500000059604645),(-5.30000162124634,-1.29999923706055,0.500000059604645),(-5.30000162124634,-1.29999923706055,1.10000014305115),(-4.6000018119812,-1.29999923706055,1.50000011920929),(-2.70000171661377,-1.29999923706055,1.50000011920929),(-2.00000190734863,-1.29999923706055,1.10000014305115),(-2.00000190734863,-1.29999923706055,0.500000059604645),(-0.800002098083496,-1.29999923706055,0.500000059604645),(-0.800002098083496,-1.29999923706055,1.10000014305115),(-0.100002288818359,-1.29999923706055,1.50000011920929),(0.0999975204467773,-1.29999923706055,1.50000011920929),(0.799997329711914,-1.29999923706055,1.10000014305115),(0.799997329711914,-1.29999923706055,0.500000059604645),(1.59999752044678,-1.29999923706055,0.500000059604645),(1.59999752044678,-1.29999923706055,0.700000047683716),(2.09999752044678,-1.29999923706055,0.700000047683716),(2.79999828338623,-1.29999923706055,1.20000016689301),(2.72572040557861,-1.09999930858612,1.98569548130035),(2.47427654266357,-1.09999930858612,2.6143045425415),(0.0999984741210938,-1.29999923706055,2.80000019073486),(-0.300001621246338,-1.29999923706055,2.30000019073486),(-0.900001525878906,-1.29999923706055,2.30000019073486),(-6.40000152587891,-1.29999923706055,1.60000014305115),(1.09999847412109,-1.29999923706055,1.60000014305115),(1.49999856948853,-1.29999923706055,1.20000016689301),(-3.70000171661377,-1.29999923706055,1.80000019073486),(2.29999828338623,-1.29999923706055,2.59999990463257),(2.49999809265137,-1.29999923706055,2.09999990463257),(2.49999809265137,-1.29999923706055,1.90000009536743),(1.6999979019165,-1.29999923706055,1.90000009536743),(1.49999809265137,-1.29999923706055,2.09999990463257),(1.49999809265137,-1.29999923706055,2.59999990463257),(-3.70000171661377,-1.29999923706055,3.60000038146973),(-2.20000171661377,-1.29999923706055,3.60000038146973),(-1.70000171661377,-1.29999923706055,2.80000019073486),(-1.70000171661377,-1.29999923706055,1.80000030994415),(-5.90000152587891,1.30000066757202,0.5),(-5.6000018119812,1.30000066757202,0.5),(-5.90000152587891,1.30000066757202,0.800000190734863),(-5.6000018119812,1.30000066757202,0.800000190734863),(1.29999876022339,1.30000066757202,0.5),(1.59999847412109,1.30000066757202,0.5),(1.29999876022339,1.30000066757202,0.800000190734863),(1.59999847412109,1.30000066757202,0.800000190734863),(-6.20000171661377,-1.29999923706055,0.5),(-5.90000152587891,-1.29999923706055,0.5),(-6.20000171661377,-1.29999923706055,0.800000190734863),(-5.90000152587891,-1.29999923706055,0.800000190734863),(0.999998569488525,-1.29999923706055,0.5),(1.29999876022339,-1.29999923706055,0.5),(0.999998569488525,-1.29999923706055,0.800000190734863),(1.29999876022339,-1.29999923706055,0.800000190734863),(-6.40000152587891,1.30000054836273,1.80000007152557),(-1.20000123977661,1.30000054836273,1.80000007152557),(-6.40000152587891,1.30000054836273,1.60000002384186),(-5.30000162124634,1.30000066757202,0.500000059604645),(-6.40000152587891,1.30000066757202,0.500000059604645),(-5.30000162124634,1.30000066757202,1.10000002384186),(-4.6000018119812,1.30000054836273,1.5),(-2.70000171661377,1.30000054836273,1.5),(-2.00000190734863,1.30000066757202,1.10000002384186),(-2.00000190734863,1.30000066757202,0.500000059604645),(-0.800002098083496,1.30000066757202,0.500000059604645),(-0.800002098083496,1.30000066757202,1.10000002384186),(-0.100002288818359,1.30000054836273,1.5),(0.0999975204467773,1.30000054836273,1.5),(0.799997329711914,1.30000066757202,1.10000002384186),(0.799997329711914,1.30000066757202,0.500000059604645),(1.59999752044678,1.30000066757202,0.500000059604645),(1.59999752044678,1.30000066757202,0.700000047683716),(2.09999752044678,1.30000066757202,0.700000047683716),(2.79999828338623,1.30000066757202,1.20000004768372),(2.72572040557861,1.10000050067902,1.98569536209106),(2.47427654266357,1.1000007390976,2.6143045425415),(0.0999984741210938,1.30000078678131,2.79999995231628),(-0.300001621246338,1.30000054836273,2.29999995231628),(-0.900001525878906,1.30000054836273,2.29999995231628),(1.09999847412109,1.30000054836273,1.60000002384186),(1.49999856948853,1.30000066757202,1.20000004768372),(2.29999828338623,1.30000054836273,2.59999990463257),(2.49999809265137,1.30000054836273,2.09999990463257),(2.49999809265137,1.30000054836273,1.89999985694885),(1.6999979019165,1.30000054836273,1.89999985694885),(1.49999809265137,1.30000054836273,2.09999990463257),(1.49999809265137,1.30000054836273,2.59999990463257),(-4.40000152587891,1.30000054836273,1.40000021457672),(-4.75000190734863,1.30000066757202,1.30621790885925),(-5.0062198638916,1.30000066757202,1.05000019073486),(-5.1000018119812,1.30000066757202,0.70000022649765),(-5.00621938705444,1.30000066757202,0.350000262260437),(-4.75000190734863,1.30000066757202,0.0937825441360474),(-4.40000152587891,1.30000066757202,2.95243410164403E-07),(-4.0500020980835,1.30000066757202,0.0937825441360474),(-3.79378390312195,1.30000066757202,0.350000321865082),(-3.70000171661377,1.30000066757202,0.700000286102295),(-3.79378390312195,1.30000066757202,1.05000019073486),(-4.05000162124634,1.30000066757202,1.30621790885925),(-2.80000162124634,1.30000054836273,1.40000021457672),(-3.15000176429749,1.30000066757202,1.30621790885925),(-3.40621948242188,1.30000066757202,1.05000019073486),(-3.50000190734863,1.30000066757202,0.70000022649765),(-3.40621948242188,1.30000066757202,0.350000262260437),(-3.15000176429749,1.30000066757202,0.0937825441360474),(-2.80000162124634,1.30000066757202,2.95243410164403E-07),(-2.45000171661377,1.30000066757202,0.0937825441360474),(-2.1937837600708,1.30000066757202,0.350000321865082),(-2.1000018119812,1.30000066757202,0.700000286102295),(-2.1937837600708,1.30000066757202,1.05000019073486),(-2.45000171661377,1.30000066757202,1.30621790885925),(-1.9073486328125E-06,1.30000054836273,1.40000021457672),(-0.350001811981201,1.30000066757202,1.30621790885925),(-0.606219291687012,1.30000066757202,1.05000019073486),(-0.70000171661377,1.30000066757202,0.70000022649765),(-0.606219291687012,1.30000066757202,0.350000262260437),(-0.350001811981201,1.30000066757202,0.0937825441360474),(-1.9073486328125E-06,1.30000066757202,2.95243410164403E-07),(0.349998474121094,1.30000066757202,0.0937825441360474),(0.606216430664062,1.30000066757202,0.350000321865082),(0.699997901916504,1.30000066757202,0.700000286102295),(0.606216430664062,1.30000066757202,1.05000019073486),(0.349998474121094,1.30000066757202,1.30621790885925),(-7.20000171661377,-0.39999932050705,3.80000019073486),(-7.20000171661377,-0.399999290704727,3.20000028610229),(4.99999809265137,-0.399999290704727,3.20000028610229),(4.99999809265137,-0.39999932050705,3.80000019073486),(4.99999809265137,0.400000780820847,3.20000028610229),(4.99999809265137,0.400000512599945,3.80000019073486),(2.39999866485596,-1.29999923706055,2.80000019073486),(2.79999828338623,-1.29999923706055,1.80000019073486),(2.39999866485596,1.30000078678131,2.79999995231628),(2.79999828338623,1.30000054836273,1.79999995231628),(-3.70000171661377,-0.399999350309372,1.80000019073486),(-3.70000171661377,-0.399999290704727,3.60000038146973),(-2.20000171661377,-0.399999290704727,3.60000038146973),(-1.70000171661377,-0.399999380111694,2.80000019073486),(-1.70000171661377,-0.399999350309372,1.80000019073486),(-3.70000171661377,-0.399999350309372,1.80000019073486),(-1.70000171661377,-0.399999350309372,1.80000019073486),(-3.70000171661377,-0.39999932050705,2.80000019073486),(-1.70000171661377,-0.39999932050705,2.80000019073486),(-3.70000171661377,0.400000721216202,1.80000007152557),(-1.70000171661377,0.400000721216202,1.80000019073486),(-3.70000171661377,0.400000691413879,2.80000019073486),(-1.70000171661377,0.400000691413879,2.80000019073486),(-5.90000152587891,-3.29999923706055,0.500000059604645),(-6.20000171661377,-3.29999923706055,0.500000059604645),(-6.20000171661377,-3.29999923706055,0.800000190734863),(-5.90000152587891,-3.29999923706055,0.800000190734863),(1.29999876022339,-3.29999923706055,0.500000059604645),(0.999998569488525,-3.29999923706055,0.500000059604645),(0.999998569488525,-3.29999923706055,0.800000190734863),(1.29999876022339,-3.29999923706055,0.800000190734863),(-5.90000152587891,3.30000066757202,0.500000059604645),(-5.6000018119812,3.30000066757202,0.500000059604645),(-5.90000152587891,3.30000066757202,0.800000250339508),(-5.6000018119812,3.30000066757202,0.800000250339508),(1.59999847412109,3.30000066757202,0.500000059604645),(1.29999876022339,3.30000066757202,0.500000059604645),(1.29999876022339,3.30000066757202,0.800000250339508),(1.59999847412109,3.30000066757202,0.800000250339508),(1.39999866485596,-2.9499990940094,0.500000059604645),(0.899998664855957,-2.9499990940094,0.500000059604645),(0.899998664855957,-3.44999933242798,0.5),(1.39999866485596,-3.44999933242798,0.5),(1.39999866485596,-2.9499990940094,0.300000071525574),(0.899998664855957,-2.9499990940094,0.300000071525574),(0.899998664855957,-3.44999933242798,0.300000041723251),(1.39999866485596,-3.44999933242798,0.300000041723251),(-5.80000162124634,-2.9499990940094,0.500000059604645),(-6.30000162124634,-2.9499990940094,0.500000059604645),(-6.30000162124634,-3.44999933242798,0.5),(-5.80000162124634,-3.44999933242798,0.5),(-5.80000162124634,-2.9499990940094,0.300000071525574),(-6.30000162124634,-2.9499990940094,0.300000071525574),(-6.30000162124634,-3.44999933242798,0.300000041723251),(-5.80000162124634,-3.44999933242798,0.300000041723251),(1.69999885559082,3.45000076293945,0.500000059604645),(1.19999885559082,3.45000076293945,0.500000059604645),(1.19999885559082,2.95000076293945,0.5),(1.69999885559082,2.95000076293945,0.5),(1.69999885559082,3.45000076293945,0.300000041723251),(1.19999885559082,3.45000076293945,0.300000041723251),(1.19999885559082,2.95000076293945,0.300000011920929),(1.69999885559082,2.95000076293945,0.300000011920929),(-5.50000143051147,3.45000076293945,0.500000059604645),(-6.00000143051147,3.45000076293945,0.500000059604645),(-6.00000143051147,2.95000076293945,0.5),(-5.50000143051147,2.95000076293945,0.5),(-5.50000143051147,3.45000076293945,0.300000041723251),(-6.00000143051147,3.45000076293945,0.300000041723251),(-6.00000143051147,2.95000076293945,0.300000011920929),(-5.50000143051147,2.95000076293945,0.300000011920929),(-1.9073486328125E-06,8.34465026855469E-07,1.19209332183345E-07))); +#206=IFCPOLYGONALFACESET(#205,$,(#92,#100,#101,#104,#105,#109,#112,#113,#116,#117,#125,#131,#136,#137,#138,#139,#140,#141,#142,#143,#144,#145,#146,#147,#148,#149,#150,#151,#152,#153,#154,#155,#200,#201,#202,#203,#204),$); +#207=IFCPOLYGONALFACESET(#205,$,(#93,#94,#95,#96,#97,#98,#99,#102,#103,#106,#107,#108,#110,#111,#114,#115,#118,#119,#120,#121,#122,#123,#124,#126,#127,#128,#129,#130,#132,#133,#134,#135,#156,#157,#158,#159,#160,#161,#162,#163,#164,#165,#166,#167,#168,#169,#170,#171,#172,#173,#174,#175,#176,#177,#178,#179,#180,#181,#182,#183,#184,#185,#186,#187,#188,#189,#190,#191,#192,#193,#194,#195,#196,#197,#198,#199),$); +#208=IFCSHAPEREPRESENTATION(#19,'Body','Tessellation',(#206,#207)); +#209=IFCCOLOURRGB($,0.600000023841858,0.449999988079071,0.); +#210=IFCCOLOURRGB($,0.600000023841858,0.449999988079071,0.); +#211=IFCSURFACESTYLERENDERING(#209,0.,#210,$,$,$,$,$,.NOTDEFINED.); +#212=IFCSURFACESTYLE('Mobile Crane Yellow',.BOTH.,(#211)); +#213=IFCCOLOURRGB($,0.100000001490116,0.100000001490116,0.100000001490116); +#214=IFCCOLOURRGB($,0.100000001490116,0.100000001490116,0.100000001490116); +#215=IFCSURFACESTYLERENDERING(#213,0.,#214,$,$,$,$,$,.NOTDEFINED.); +#216=IFCSURFACESTYLE('Mobile Crane Grey',.BOTH.,(#215)); +#217=IFCCOLOURRGB($,1.,0.,0.); +#218=IFCCOLOURRGB($,1.,0.,0.); +#219=IFCSURFACESTYLERENDERING(#217,0.899999998509884,#218,$,$,$,$,$,.NOTDEFINED.); +#220=IFCSURFACESTYLE('Mobile Crane Clearance',.BOTH.,(#219)); +#221=IFCSTYLEDITEM(#206,(#212),'Mobile Crane Yellow'); +#222=IFCSTYLEDITEM(#207,(#216),'Mobile Crane Grey'); +#223=IFCCARTESIANPOINT((0.,0.,0.)); +#224=IFCDIRECTION((1.,0.,0.)); +#225=IFCDIRECTION((0.,0.,1.)); +#226=IFCAXIS2PLACEMENT3D(#223,#225,#224); +#227=IFCREPRESENTATIONMAP(#226,#208); +#228=IFCINDEXEDPOLYGONALFACE((15,7,3)); +#229=IFCINDEXEDPOLYGONALFACE((4,23,3)); +#230=IFCINDEXEDPOLYGONALFACE((18,39,38)); +#231=IFCINDEXEDPOLYGONALFACE((12,31,11)); +#232=IFCINDEXEDPOLYGONALFACE((5,24,4)); +#233=IFCINDEXEDPOLYGONALFACE((19,20,40,39)); +#234=IFCINDEXEDPOLYGONALFACE((13,32,12)); +#235=IFCINDEXEDPOLYGONALFACE((6,25,5)); +#236=IFCINDEXEDPOLYGONALFACE((1,40,20)); +#237=IFCINDEXEDPOLYGONALFACE((14,33,13)); +#238=IFCINDEXEDPOLYGONALFACE((7,26,6)); +#239=IFCINDEXEDPOLYGONALFACE((15,34,14)); +#240=IFCINDEXEDPOLYGONALFACE((7,8,28,27)); +#241=IFCINDEXEDPOLYGONALFACE((16,35,15)); +#242=IFCINDEXEDPOLYGONALFACE((8,9,29,28)); +#243=IFCINDEXEDPOLYGONALFACE((2,21,1)); +#244=IFCINDEXEDPOLYGONALFACE((17,36,16)); +#245=IFCINDEXEDPOLYGONALFACE((10,29,9)); +#246=IFCINDEXEDPOLYGONALFACE((2,3,23,22)); +#247=IFCINDEXEDPOLYGONALFACE((18,37,17)); +#248=IFCINDEXEDPOLYGONALFACE((11,30,10)); +#249=IFCINDEXEDPOLYGONALFACE((3,2,1)); +#250=IFCINDEXEDPOLYGONALFACE((1,20,19)); +#251=IFCINDEXEDPOLYGONALFACE((19,18,15)); +#252=IFCINDEXEDPOLYGONALFACE((18,17,15)); +#253=IFCINDEXEDPOLYGONALFACE((17,16,15)); +#254=IFCINDEXEDPOLYGONALFACE((15,14,13)); +#255=IFCINDEXEDPOLYGONALFACE((13,12,11)); +#256=IFCINDEXEDPOLYGONALFACE((11,10,7)); +#257=IFCINDEXEDPOLYGONALFACE((10,9,7)); +#258=IFCINDEXEDPOLYGONALFACE((9,8,7)); +#259=IFCINDEXEDPOLYGONALFACE((7,6,5)); +#260=IFCINDEXEDPOLYGONALFACE((5,4,3)); +#261=IFCINDEXEDPOLYGONALFACE((3,1,19)); +#262=IFCINDEXEDPOLYGONALFACE((15,13,11)); +#263=IFCINDEXEDPOLYGONALFACE((7,5,3)); +#264=IFCINDEXEDPOLYGONALFACE((3,19,15)); +#265=IFCINDEXEDPOLYGONALFACE((15,11,7)); +#266=IFCINDEXEDPOLYGONALFACE((4,24,23)); +#267=IFCINDEXEDPOLYGONALFACE((18,19,39)); +#268=IFCINDEXEDPOLYGONALFACE((12,32,31)); +#269=IFCINDEXEDPOLYGONALFACE((5,25,24)); +#270=IFCINDEXEDPOLYGONALFACE((13,33,32)); +#271=IFCINDEXEDPOLYGONALFACE((6,26,25)); +#272=IFCINDEXEDPOLYGONALFACE((1,21,40)); +#273=IFCINDEXEDPOLYGONALFACE((14,34,33)); +#274=IFCINDEXEDPOLYGONALFACE((7,27,26)); +#275=IFCINDEXEDPOLYGONALFACE((15,35,34)); +#276=IFCINDEXEDPOLYGONALFACE((16,36,35)); +#277=IFCINDEXEDPOLYGONALFACE((2,22,21)); +#278=IFCINDEXEDPOLYGONALFACE((17,37,36)); +#279=IFCINDEXEDPOLYGONALFACE((10,30,29)); +#280=IFCINDEXEDPOLYGONALFACE((18,38,37)); +#281=IFCINDEXEDPOLYGONALFACE((11,31,30)); +#282=IFCCARTESIANPOINTLIST3D(((-1.9073486328125E-06,44.,54.),(-13.5967502593994,41.8464889526367,53.9999961853027),(-25.8625526428223,35.5967483520508,54.),(-35.5967483520508,25.8625507354736,54.),(-41.8464889526367,13.5967473983765,54.),(-44.,-1.04911282505782E-06,54.),(-41.8464851379395,-13.5967483520508,54.),(-35.5967483520508,-25.862548828125,54.),(-25.862548828125,-35.596752166748,54.),(-13.5967512130737,-41.8464851379395,54.),(1.9073486328125E-06,-44.,53.9999961853027),(13.5967435836792,-41.8464889526367,54.),(25.8625564575195,-35.5967445373535,54.),(35.596752166748,-25.8625431060791,54.),(41.8464851379395,-13.5967512130737,54.),(44.,1.33507296595781E-06,54.),(41.8464851379395,13.5967540740967,54.),(35.596752166748,25.8625431060791,54.),(25.8625526428223,35.5967445373535,54.),(13.5967435836792,41.8464889526367,53.9999961853027),(-1.9073486328125E-06,44.,-1.53376822709106E-06),(-13.5967502593994,41.8464889526367,-1.62790126978507E-06),(-25.8625526428223,35.5967483520508,1.91361118595523E-06),(-35.5967483520508,25.8625507354736,1.48811591316189E-06),(-41.8464889526367,13.5967473983765,9.51960601014434E-07),(-44.,-1.07288360595703E-06,3.57627811808925E-07),(-41.8464851379395,-13.5967483520508,-2.36704863709747E-07),(-35.5967483520508,-25.862548828125,-7.72860062170366E-07),(-25.862548828125,-35.596752166748,-1.19835556233738E-06),(-13.5967512130737,-41.8464851379395,-1.4715401448484E-06),(1.9073486328125E-06,-44.,-1.56567330122925E-06),(13.5967435836792,-41.8464889526367,-1.47154025853524E-06),(25.8625564575195,-35.5967445373535,-1.19835522127687E-06),(35.596752166748,-25.8625431060791,-7.72859834796691E-07),(41.8464851379395,-13.5967512130737,-2.36704977396585E-07),(44.,1.31130218505859E-06,3.57627925495763E-07),(41.8464851379395,13.5967540740967,9.51960885231529E-07),(35.596752166748,25.8625431060791,1.48811557210138E-06),(25.8625526428223,35.5967445373535,1.91361095858156E-06),(13.5967435836792,41.8464889526367,-1.62790126978507E-06))); +#283=IFCPOLYGONALFACESET(#282,$,(#228,#229,#230,#231,#232,#233,#234,#235,#236,#237,#238,#239,#240,#241,#242,#243,#244,#245,#246,#247,#248,#249,#250,#251,#252,#253,#254,#255,#256,#257,#258,#259,#260,#261,#262,#263,#264,#265,#266,#267,#268,#269,#270,#271,#272,#273,#274,#275,#276,#277,#278,#279,#280,#281),$); +#284=IFCSHAPEREPRESENTATION(#20,'Clearance','Tessellation',(#283)); +#285=IFCCOLOURRGB($,0.600000023841858,0.449999988079071,0.); +#286=IFCCOLOURRGB($,0.600000023841858,0.449999988079071,0.); +#287=IFCSURFACESTYLERENDERING(#285,0.,#286,$,$,$,$,$,.NOTDEFINED.); +#288=IFCSURFACESTYLE('Mobile Crane Yellow',.BOTH.,(#287)); +#289=IFCCOLOURRGB($,0.100000001490116,0.100000001490116,0.100000001490116); +#290=IFCCOLOURRGB($,0.100000001490116,0.100000001490116,0.100000001490116); +#291=IFCSURFACESTYLERENDERING(#289,0.,#290,$,$,$,$,$,.NOTDEFINED.); +#292=IFCSURFACESTYLE('Mobile Crane Grey',.BOTH.,(#291)); +#293=IFCCOLOURRGB($,1.,0.,0.); +#294=IFCCOLOURRGB($,1.,0.,0.); +#295=IFCSURFACESTYLERENDERING(#293,0.899999998509884,#294,$,$,$,$,$,.NOTDEFINED.); +#296=IFCSURFACESTYLE('Mobile Crane Clearance',.BOTH.,(#295)); +#297=IFCSTYLEDITEM(#283,(#288),'Mobile Crane Yellow'); +#298=IFCCARTESIANPOINT((0.,0.,0.)); +#299=IFCDIRECTION((1.,0.,0.)); +#300=IFCDIRECTION((0.,0.,1.)); +#301=IFCAXIS2PLACEMENT3D(#298,#300,#299); +#302=IFCREPRESENTATIONMAP(#301,#284); +ENDSEC; +END-ISO-10303-21; diff --git a/src/blenderbim/runpytest.py b/src/blenderbim/runpytest.py index 259800d0d9..c061734528 100755 --- a/src/blenderbim/runpytest.py +++ b/src/blenderbim/runpytest.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -28,8 +27,8 @@ import pytest argv = [__file__] -if '--' in sys.argv: - i = sys.argv.index('--') - argv += sys.argv[i+1:] +if "--" in sys.argv: + i = sys.argv.index("--") + argv += sys.argv[i + 1 :] pytest.main(argv) diff --git a/src/blenderbim/setup_pytest.py b/src/blenderbim/setup_pytest.py new file mode 100644 index 0000000000..6ac6b6080c --- /dev/null +++ b/src/blenderbim/setup_pytest.py @@ -0,0 +1,29 @@ +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2021 Nathan Hild +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + +import subprocess +import sys + +py_exec = str(sys.executable) +# Ensure pip is installed +subprocess.call([py_exec, "-m", "ensurepip", "--user" ]) +# Update pip +subprocess.call([py_exec, "-m", "pip", "install", "--upgrade", "pip" ]) +# Install packages +subprocess.call([py_exec,"-m", "pip", "install", f"--target={py_exec[:-14]}" + "lib", "pytest"]) +subprocess.call([py_exec,"-m", "pip", "install", f"--target={py_exec[:-14]}" + "lib", "pytest-blender"]) diff --git a/src/blenderbim/test/__init__.py b/src/blenderbim/test/__init__.py new file mode 100644 index 0000000000..fb33323db9 --- /dev/null +++ b/src/blenderbim/test/__init__.py @@ -0,0 +1,17 @@ +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . diff --git a/src/blenderbim/test/bim/__init__.py b/src/blenderbim/test/bim/__init__.py new file mode 100644 index 0000000000..fb33323db9 --- /dev/null +++ b/src/blenderbim/test/bim/__init__.py @@ -0,0 +1,17 @@ +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . diff --git a/src/blenderbim/test/bim/bootstrap.py b/src/blenderbim/test/bim/bootstrap.py new file mode 100644 index 0000000000..f57d35bcde --- /dev/null +++ b/src/blenderbim/test/bim/bootstrap.py @@ -0,0 +1,356 @@ +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + + +import os +import re +import bpy +import pytest +import webbrowser +import blenderbim +import ifcopenshell +import ifcopenshell.util.representation +from blenderbim.bim.ifc import IfcStore +from mathutils import Vector + +# Monkey-patch webbrowser opening since we want to test headlessly +webbrowser.open = lambda x: True + + +class NewFile: + @pytest.fixture(autouse=True) + def setup(self): + IfcStore.purge() + bpy.ops.wm.read_homefile(app_template="") + while bpy.data.objects: + bpy.data.objects.remove(bpy.data.objects[0]) + bpy.ops.outliner.orphans_purge(do_local_ids=True, do_linked_ids=True, do_recursive=True) + + +def scenario(function): + def subfunction(self): + run(function(self)) + + return subfunction + + +def scenario_debug(function): + def subfunction(self): + run_debug(function(self)) + + return subfunction + + +def an_empty_ifc_project(): + bpy.ops.bim.create_project() + + +def i_add_a_cube(): + bpy.ops.mesh.primitive_cube_add() + + +def i_add_a_cube_of_size_size_at_location(size, location): + bpy.ops.mesh.primitive_cube_add(size=float(size), location=[float(co) for co in location.split(",")]) + + +def the_object_name_is_selected(name): + bpy.ops.object.select_all(action="DESELECT") + additionally_the_object_name_is_selected(name) + + +def additionally_the_object_name_is_selected(name): + obj = bpy.context.scene.objects.get(name) + if not obj: + assert False, 'The object "{name}" could not be selected' + bpy.context.view_layer.objects.active = obj + obj.select_set(True) + + +def i_am_on_frame_number(number): + bpy.context.scene.frame_set(int(number)) + + +def i_set_prop_to_value(prop, value): + try: + eval(f"bpy.context.{prop}") + except: + assert False, "Property does not exist" + try: + exec(f'bpy.context.{prop} = "{value}"') + except: + exec(f"bpy.context.{prop} = {value}") + + +def prop_is_value(prop, value): + is_value = False + try: + exec(f'assert bpy.context.{prop} == "{value}"') + is_value = True + except: + try: + exec(f"assert bpy.context.{prop} == {value}") + is_value = True + except: + try: + exec(f"assert list(bpy.context.{prop}) == {value}") + is_value = True + except: + pass + if not is_value: + actual_value = eval(f"bpy.context.{prop}") + assert False, f"Value is {actual_value}" + + +def i_enable_prop(prop): + exec(f"bpy.context.{prop} = True") + + +def i_press_operator(operator): + if "(" in operator: + exec(f"bpy.ops.{operator}") + else: + exec(f"bpy.ops.{operator}()") + + +def i_rename_the_object_name1_to_name2(name1, name2): + the_object_name_exists(name1).name = name2 + + +def the_object_name_exists(name): + obj = bpy.data.objects.get(name) + if not obj: + assert False, f'The object "{name}" does not exist' + return obj + + +def an_ifc_file_exists(): + ifc = IfcStore.get_file() + if not ifc: + assert False, "No IFC file is available" + return ifc + + +def an_ifc_file_does_not_exist(): + ifc = IfcStore.get_file() + if ifc: + assert False, "An IFC is available" + + +def the_object_name_does_not_exist(name): + assert bpy.data.objects.get(name) is None, "Object exists" + + +def the_object_name_is_an_ifc_class(name, ifc_class): + ifc = an_ifc_file_exists() + element = ifc.by_id(the_object_name_exists(name).BIMObjectProperties.ifc_definition_id) + assert element.is_a(ifc_class), f'Object "{name}" is a {element.is_a()}' + + +def the_object_name_is_not_an_ifc_element(name): + id = the_object_name_exists(name).BIMObjectProperties.ifc_definition_id + assert id == 0, f"The ID is {id}" + + +def the_object_name_is_in_the_collection_collection(name, collection): + assert collection in [c.name for c in the_object_name_exists(name).users_collection] + + +def the_object_name_is_not_in_the_collection_collection(name, collection): + assert collection not in [c.name for c in the_object_name_exists(name).users_collection] + + +def the_object_name_has_a_body_of_value(name, value): + assert the_object_name_exists(name).data.body == value + + +def the_collection_name1_is_in_the_collection_name2(name1, name2): + assert bpy.data.collections.get(name2).children.get(name1) + + +def the_collection_name1_is_not_in_the_collection_name2(name1, name2): + assert not bpy.data.collections.get(name2).children.get(name1) + + +def the_object_name_is_placed_in_the_collection_collection(name, collection): + obj = the_object_name_exists(name) + [c.objects.unlink(obj) for c in obj.users_collection] + bpy.data.collections.get(collection).objects.link(obj) + + +def the_object_name_has_a_type_representation_of_context(name, type, context): + ifc = an_ifc_file_exists() + element = ifc.by_id(the_object_name_exists(name).BIMObjectProperties.ifc_definition_id) + context, subcontext, target_view = context.split("/") + assert ifcopenshell.util.representation.get_representation( + element, context, subcontext or None, target_view or None + ) + + +def the_object_name_is_contained_in_container_name(name, container_name): + ifc = an_ifc_file_exists() + element = ifc.by_id(the_object_name_exists(name).BIMObjectProperties.ifc_definition_id) + container = ifcopenshell.util.element.get_container(element) + if not container: + assert False, f'Object "{name}" is not in any container' + assert container.Name == container_name, f'Object "{name}" is in {container}' + + +def i_duplicate_the_selected_objects(): + bpy.ops.object.duplicate_move() + blenderbim.bim.handler.active_object_callback() + + +def i_delete_the_selected_objects(): + bpy.ops.object.delete() + blenderbim.bim.handler.active_object_callback() + + +def the_object_name1_and_name2_are_different_elements(name1, name2): + ifc = an_ifc_file_exists() + element1 = ifc.by_id(the_object_name_exists(name1).BIMObjectProperties.ifc_definition_id) + element2 = ifc.by_id(the_object_name_exists(name2).BIMObjectProperties.ifc_definition_id) + assert element1 != element2, f"Objects {name1} and {name2} have same elements {element1} and {element2}" + + +def the_file_name_should_contain_value(name, value): + with open(name, "r") as f: + assert value in f.read() + + +def the_object_name1_has_a_boolean_difference_by_name2(name1, name2): + obj = the_object_name_exists(name1) + for modifier in obj.modifiers: + if modifier.type == "BOOLEAN" and modifier.object and modifier.object.name == name2: + return True + assert False, "No boolean found" + + +def the_object_name1_has_no_boolean_difference_by_name2(name1, name2): + obj = the_object_name_exists(name1) + for modifier in obj.modifiers: + if modifier.type == "BOOLEAN" and modifier.object and modifier.object.name == name2: + assert False, "A boolean was found" + + +def the_object_name_is_voided_by_void(name, void): + ifc = IfcStore.get_file() + element = ifc.by_id(the_object_name_exists(name).BIMObjectProperties.ifc_definition_id) + for rel in element.HasOpenings: + if rel.RelatedOpeningElement.Name == void: + return True + assert False, "No void found" + + +def the_object_name_is_not_voided_by_void(name, void): + ifc = IfcStore.get_file() + element = ifc.by_id(the_object_name_exists(name).BIMObjectProperties.ifc_definition_id) + for rel in element.HasOpenings: + if rel.RelatedOpeningElement.Name == void: + assert False, "A void was found" + + +def the_object_name_should_display_as_mode(name, mode): + assert the_object_name_exists(name).display_type == mode + + +def the_object_name_has_number_vertices(name, number): + total = len(the_object_name_exists(name).data.vertices) + assert total == int(number), f"We found {total} vertices" + + +def the_object_name_is_at_location(name, location): + obj_location = the_object_name_exists(name).location + assert ( + obj_location - Vector([float(co) for co in location.split(",")]) + ).length < 0.1, f"Object is at {obj_location}" + + +definitions = { + "an empty IFC project": an_empty_ifc_project, + "I add a cube": i_add_a_cube, + 'I add a cube of size "([0-9]+)" at "(.*)"': i_add_a_cube_of_size_size_at_location, + 'the object "(.*)" is selected': the_object_name_is_selected, + 'additionally the object "(.*)" is selected': additionally_the_object_name_is_selected, + 'I am on frame "([0-9]+)"': i_am_on_frame_number, + 'I set "(.*)" to "(.*)"': i_set_prop_to_value, + '"(.*)" is "(.*)"': prop_is_value, + 'I enable "(.*)"': i_enable_prop, + 'I press "(.*)"': i_press_operator, + 'I rename the object "(.*)" to "(.*)"': i_rename_the_object_name1_to_name2, + 'the object "(.*)" exists': the_object_name_exists, + 'the object "(.*)" does not exist': the_object_name_does_not_exist, + 'the object "(.*)" is an "(.*)"': the_object_name_is_an_ifc_class, + 'the object "(.*)" is not an IFC element': the_object_name_is_not_an_ifc_element, + 'the object "(.*)" is in the collection "(.*)"': the_object_name_is_in_the_collection_collection, + 'the object "(.*)" is not in the collection "(.*)"': the_object_name_is_not_in_the_collection_collection, + 'the object "(.*)" has a body of "(.*)"': the_object_name_has_a_body_of_value, + 'the collection "(.*)" is in the collection "(.*)"': the_collection_name1_is_in_the_collection_name2, + 'the collection "(.*)" is not in the collection "(.*)"': the_collection_name1_is_not_in_the_collection_name2, + "an IFC file exists": an_ifc_file_exists, + "an IFC file does not exist": an_ifc_file_does_not_exist, + 'the object "(.*)" has a "(.*)" representation of "(.*)"': the_object_name_has_a_type_representation_of_context, + 'the object "(.*)" is placed in the collection "(.*)"': the_object_name_is_placed_in_the_collection_collection, + 'the object "(.*)" is contained in "(.*)"': the_object_name_is_contained_in_container_name, + "I duplicate the selected objects": i_duplicate_the_selected_objects, + "I delete the selected objects": i_delete_the_selected_objects, + 'the object "(.*)" and "(.*)" are different elements': the_object_name1_and_name2_are_different_elements, + 'the file "(.*)" should contain "(.*)"': the_file_name_should_contain_value, + 'the object "(.*)" has a boolean difference by "(.*)"': the_object_name1_has_a_boolean_difference_by_name2, + 'the object "(.*)" has no boolean difference by "(.*)"': the_object_name1_has_no_boolean_difference_by_name2, + 'the object "(.*)" is voided by "(.*)"': the_object_name_is_voided_by_void, + 'the object "(.*)" is not voided by "(.*)"': the_object_name_is_not_voided_by_void, + 'the object "(.*)" should display as "(.*)"': the_object_name_should_display_as_mode, + 'the object "(.*)" has "([0-9]+)" vertices': the_object_name_has_number_vertices, + 'the object "(.*)" is at "(.*)"': the_object_name_is_at_location, +} + + +# Super lightweight Gherkin implementation +def run(scenario): + keywords = ["Given", "When", "Then", "And", "But"] + for line in scenario.split("\n"): + line = line.replace("{cwd}", os.getcwd()) + for keyword in keywords: + line = line.replace(keyword, "") + line = line.strip() + if not line: + continue + match = None + for definition, callback in definitions.items(): + match = re.search("^" + definition + "$", line) + if match: + try: + callback(*match.groups()) + except AssertionError as e: + assert False, f"Failed: {line}, with error: {e}" + break + if not match: + assert False, f"Definition not implemented: {line}" + return True + + +def run_debug(scenario, blend_filepath=None): + try: + result = run(scenario) + except Exception as e: + if blend_filepath: + bpy.ops.wm.save_as_mainfile(filepath=blend_filepath) + assert False, e + if blend_filepath: + bpy.ops.wm.save_as_mainfile(filepath=blend_filepath) + return result diff --git a/src/blenderbim/test/bim/module/__init__.py b/src/blenderbim/test/bim/module/__init__.py new file mode 100644 index 0000000000..fb33323db9 --- /dev/null +++ b/src/blenderbim/test/bim/module/__init__.py @@ -0,0 +1,17 @@ +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . diff --git a/src/blenderbim/test/bim/module/bimtester/__init__.py b/src/blenderbim/test/bim/module/bimtester/__init__.py new file mode 100644 index 0000000000..fb33323db9 --- /dev/null +++ b/src/blenderbim/test/bim/module/bimtester/__init__.py @@ -0,0 +1,17 @@ +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . diff --git a/src/blenderbim/test/bim/module/bimtester/test_operator.py b/src/blenderbim/test/bim/module/bimtester/test_operator.py new file mode 100644 index 0000000000..0834a8fbb0 --- /dev/null +++ b/src/blenderbim/test/bim/module/bimtester/test_operator.py @@ -0,0 +1,31 @@ +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + +import test.bim.bootstrap + + +class TestExecuteBIMTester(test.bim.bootstrap.NewFile): + @test.bim.bootstrap.scenario + def test_executing_bimtester(self): + return """ + Given an empty IFC project + When I enable "scene.BimTesterProperties.should_load_from_memory" + And I set "scene.BimTesterProperties.feature" to "{cwd}/test/files/sample-ids.xml" + And I press "bim.execute_bim_tester" + Then the file "{cwd}/test/files/sample-ids.xml.html" should contain "Tests passed: 1 / 1 (100%)" + """ diff --git a/src/blenderbim/test/bim/module/drawing/__init__.py b/src/blenderbim/test/bim/module/drawing/__init__.py new file mode 100644 index 0000000000..fb33323db9 --- /dev/null +++ b/src/blenderbim/test/bim/module/drawing/__init__.py @@ -0,0 +1,17 @@ +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . diff --git a/src/blenderbim/tests/test_segment_clipping.py b/src/blenderbim/test/bim/module/drawing/test_segment_clipping.py similarity index 85% rename from src/blenderbim/tests/test_segment_clipping.py rename to src/blenderbim/test/bim/module/drawing/test_segment_clipping.py index f89526f786..4048a6ad23 100644 --- a/src/blenderbim/tests/test_segment_clipping.py +++ b/src/blenderbim/test/bim/module/drawing/test_segment_clipping.py @@ -1,6 +1,5 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on -# Copyright (C) 2020, 2021 Dion Moult +# Copyright (C) 2020, 2021 Maxim Vasilyev # # This file is part of BlenderBIM Add-on. # @@ -19,11 +18,10 @@ import pytest from mathutils import Vector - -from blenderbim.bim.helper import clip_segment +from blenderbim.bim.module.drawing.helper import clip_segment -BOUNDS = (10, 30, 10, 30) +BOUNDS = (10, 30, 10, 30, None, None) SEGMENTS_INSIDE = ( (Vector((15, 20)), Vector((25, 20))), @@ -40,7 +38,7 @@ SEGMENTS_OUTSIDE = ( (Vector((25, 5)), Vector((15, 5))), (Vector((15, 5)), Vector((5, 15))), (Vector((5, 15)), Vector((5, 25))), - (Vector((5, 25)), Vector((15, 35))) + (Vector((5, 25)), Vector((15, 35))), ) SEGMENTS_CLIPPED = ( @@ -54,19 +52,20 @@ SEGMENTS_CLIPPED = ( ((Vector((35, 20)), Vector((20, 5))), (Vector((30, 15)), Vector((25, 10)))), ) -@pytest.mark.parametrize('segment', SEGMENTS_INSIDE) + +@pytest.mark.parametrize("segment", SEGMENTS_INSIDE) def test_clip_inside(segment): clipped = clip_segment(BOUNDS, segment) assert clipped == segment -@pytest.mark.parametrize('segment', SEGMENTS_OUTSIDE) +@pytest.mark.parametrize("segment", SEGMENTS_OUTSIDE) def test_clip_outside(segment): clipped = clip_segment(BOUNDS, segment) assert clipped is None -@pytest.mark.parametrize('segment,expected', SEGMENTS_CLIPPED) +@pytest.mark.parametrize("segment,expected", SEGMENTS_CLIPPED) def test_clip_crossing(segment, expected): clipped = clip_segment(BOUNDS, segment) assert clipped == expected diff --git a/src/blenderbim/test/bim/module/patch/__init__.py b/src/blenderbim/test/bim/module/patch/__init__.py new file mode 100644 index 0000000000..fb33323db9 --- /dev/null +++ b/src/blenderbim/test/bim/module/patch/__init__.py @@ -0,0 +1,17 @@ +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . diff --git a/src/blenderbim/test/bim/module/patch/test_operator.py b/src/blenderbim/test/bim/module/patch/test_operator.py new file mode 100644 index 0000000000..2603b1ee7a --- /dev/null +++ b/src/blenderbim/test/bim/module/patch/test_operator.py @@ -0,0 +1,32 @@ +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + +import test.bim.bootstrap + + +class TestExecuteIfcPatch(test.bim.bootstrap.NewFile): + @test.bim.bootstrap.scenario + def test_executing_ifcpatch(self): + return """ + Given I set "scene.BIMPatchProperties.ifc_patch_recipes" to "OffsetObjectPlacements" + And I set "scene.BIMPatchProperties.ifc_patch_input" to "{cwd}/test/files/basic.ifc" + And I set "scene.BIMPatchProperties.ifc_patch_output" to "{cwd}/test/files/basic-patched.ifc" + And I set "scene.BIMPatchProperties.ifc_patch_args" to "[123454321,0,0,0]" + When I press "bim.execute_ifc_patch" + Then the file "{cwd}/test/files/basic-patched.ifc" should contain "123454321" + """ diff --git a/src/blenderbim/test/bim/module/project/__init__.py b/src/blenderbim/test/bim/module/project/__init__.py new file mode 100644 index 0000000000..fb33323db9 --- /dev/null +++ b/src/blenderbim/test/bim/module/project/__init__.py @@ -0,0 +1,17 @@ +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . diff --git a/src/blenderbim/test/bim/module/project/test_operator.py b/src/blenderbim/test/bim/module/project/test_operator.py new file mode 100644 index 0000000000..34bb377b08 --- /dev/null +++ b/src/blenderbim/test/bim/module/project/test_operator.py @@ -0,0 +1,344 @@ +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + +import test.bim.bootstrap + + +class TestCreateProject(test.bim.bootstrap.NewFile): + @test.bim.bootstrap.scenario + def test_creating_a_project(self): + return """ + When I press "bim.create_project" + Then an IFC file exists + And the object "IfcProject/My Project" is an "IfcProject" + And the object "IfcSite/My Site" is an "IfcSite" + And the object "IfcBuilding/My Building" is an "IfcBuilding" + And the object "IfcBuildingStorey/My Storey" is an "IfcBuildingStorey" + And the object "IfcProject/My Project" is in the collection "IfcProject/My Project" + And the object "IfcSite/My Site" is in the collection "IfcSite/My Site" + And the object "IfcBuilding/My Building" is in the collection "IfcBuilding/My Building" + And the object "IfcBuildingStorey/My Storey" is in the collection "IfcBuildingStorey/My Storey" + """ + + +class TestLoadProject(test.bim.bootstrap.NewFile): + @test.bim.bootstrap.scenario + def test_loading_a_project(self): + return """ + When I press "bim.load_project(filepath='{cwd}/test/files/basic.ifc')" + Then an IFC file exists + And the object "IfcProject/My Project" is an "IfcProject" + And the object "IfcSite/My Site" is an "IfcSite" + And the object "IfcBuilding/My Building" is an "IfcBuilding" + And the object "IfcBuildingStorey/Ground Floor" is an "IfcBuildingStorey" + And the object "IfcBuildingStorey/Level 1" is an "IfcBuildingStorey" + And the object "IfcSlab/Slab" is an "IfcSlab" + And the object "IfcWall/Wall" is an "IfcWall" + And the object "IfcElementAssembly/Empty" is an "IfcElementAssembly" + And the object "IfcBeam/Beam" is an "IfcBeam" + And the object "IfcSite/My Site" is in the collection "IfcSite/My Site" + And the object "IfcBuilding/My Building" is in the collection "IfcBuilding/My Building" + And the object "IfcBuildingStorey/Ground Floor" is in the collection "IfcBuildingStorey/Ground Floor" + And the object "IfcBuildingStorey/Level 1" is in the collection "IfcBuildingStorey/Level 1" + And the object "IfcElementAssembly/Empty" is in the collection "IfcElementAssembly/Empty" + And the object "IfcBeam/Beam" is in the collection "IfcElementAssembly/Empty" + And the object "IfcSlab/Slab" is in the collection "IfcBuildingStorey/Ground Floor" + And the object "IfcWall/Wall" is in the collection "IfcBuildingStorey/Level 1" + And "scene.BIMProjectProperties.is_loading" is "False" + """ + + @test.bim.bootstrap.scenario + def test_loading_a_project_in_advanced_mode(self): + return """ + When I press "bim.load_project(filepath='{cwd}/test/files/basic.ifc', is_advanced=True)" + Then an IFC file exists + And "scene.BIMProjectProperties.is_loading" is "True" + And the object "IfcProject/My Project" does not exist + """ + + +class TestLoadProjectElements(test.bim.bootstrap.NewFile): + @test.bim.bootstrap.scenario + def test_loading_all_project_elements(self): + return """ + Given I press "bim.load_project(filepath='{cwd}/test/files/basic.ifc', is_advanced=True)" + When I set "scene.BIMProjectProperties.collection_mode" to "DECOMPOSITION" + And I set "scene.BIMProjectProperties.filter_mode" to "NONE" + And I press "bim.load_project_elements" + Then the object "IfcProject/My Project" is an "IfcProject" + And the object "IfcSite/My Site" is an "IfcSite" + And the object "IfcBuilding/My Building" is an "IfcBuilding" + And the object "IfcBuildingStorey/Ground Floor" is an "IfcBuildingStorey" + And the object "IfcBuildingStorey/Level 1" is an "IfcBuildingStorey" + And the object "IfcSlab/Slab" is an "IfcSlab" + And the object "IfcWall/Wall" is an "IfcWall" + And the object "IfcElementAssembly/Empty" is an "IfcElementAssembly" + And the object "IfcBeam/Beam" is an "IfcBeam" + And the object "IfcSite/My Site" is in the collection "IfcSite/My Site" + And the object "IfcBuilding/My Building" is in the collection "IfcBuilding/My Building" + And the object "IfcBuildingStorey/Ground Floor" is in the collection "IfcBuildingStorey/Ground Floor" + And the object "IfcBuildingStorey/Level 1" is in the collection "IfcBuildingStorey/Level 1" + And the object "IfcElementAssembly/Empty" is in the collection "IfcElementAssembly/Empty" + And the object "IfcBeam/Beam" is in the collection "IfcElementAssembly/Empty" + And the object "IfcSlab/Slab" is in the collection "IfcBuildingStorey/Ground Floor" + And the object "IfcWall/Wall" is in the collection "IfcBuildingStorey/Level 1" + And "scene.BIMProjectProperties.is_loading" is "False" + """ + + @test.bim.bootstrap.scenario + def test_loading_objects_filtered_by_decomposition(self): + return """ + Given I press "bim.load_project(filepath='{cwd}/test/files/basic.ifc', is_advanced=True)" + When I set "scene.BIMProjectProperties.collection_mode" to "DECOMPOSITION" + And I set "scene.BIMProjectProperties.filter_mode" to "DECOMPOSITION" + Then "scene.BIMProjectProperties.filter_categories['IfcSite/My Site'].total_elements" is "0" + Then "scene.BIMProjectProperties.filter_categories['IfcBuilding/My Building'].total_elements" is "0" + Then "scene.BIMProjectProperties.filter_categories['IfcBuildingStorey/Ground Floor'].total_elements" is "1" + Then "scene.BIMProjectProperties.filter_categories['IfcBuildingStorey/Level 1'].total_elements" is "2" + When I set "scene.BIMProjectProperties.filter_categories['IfcBuildingStorey/Level 1'].is_selected" to "True" + And I press "bim.load_project_elements" + Then the object "IfcProject/My Project" is an "IfcProject" + And the object "IfcSite/My Site" is an "IfcSite" + And the object "IfcBuilding/My Building" is an "IfcBuilding" + And the object "IfcBuildingStorey/Level 1" is an "IfcBuildingStorey" + And the object "IfcWall/Wall" is an "IfcWall" + And the object "IfcElementAssembly/Empty" is an "IfcElementAssembly" + And the object "IfcBeam/Beam" is an "IfcBeam" + And the object "IfcSite/My Site" is in the collection "IfcSite/My Site" + And the object "IfcBuilding/My Building" is in the collection "IfcBuilding/My Building" + And the object "IfcBuildingStorey/Level 1" is in the collection "IfcBuildingStorey/Level 1" + And the object "IfcWall/Wall" is in the collection "IfcBuildingStorey/Level 1" + And the object "IfcElementAssembly/Empty" is in the collection "IfcElementAssembly/Empty" + And the object "IfcBeam/Beam" is in the collection "IfcElementAssembly/Empty" + And the object "IfcBuildingStorey/Ground Floor" does not exist + And the object "IfcSlab/Slab" does not exist + """ + + @test.bim.bootstrap.scenario + def test_loading_objects_filtered_by_ifc_class(self): + return """ + Given I press "bim.load_project(filepath='{cwd}/test/files/basic.ifc', is_advanced=True)" + When I set "scene.BIMProjectProperties.collection_mode" to "DECOMPOSITION" + And I set "scene.BIMProjectProperties.filter_mode" to "IFC_CLASS" + Then "scene.BIMProjectProperties.filter_categories['IfcWall'].total_elements" is "1" + And "scene.BIMProjectProperties.filter_categories['IfcSlab'].total_elements" is "1" + And "scene.BIMProjectProperties.filter_categories['IfcElementAssembly'].total_elements" is "1" + And "scene.BIMProjectProperties.filter_categories['IfcBeam'].total_elements" is "1" + When I set "scene.BIMProjectProperties.filter_categories['IfcSlab'].is_selected" to "True" + And I press "bim.load_project_elements" + Then the object "IfcProject/My Project" is an "IfcProject" + And the object "IfcSite/My Site" is an "IfcSite" + And the object "IfcBuilding/My Building" is an "IfcBuilding" + And the object "IfcBuildingStorey/Ground Floor" is an "IfcBuildingStorey" + And the object "IfcSlab/Slab" is an "IfcSlab" + And the object "IfcSite/My Site" is in the collection "IfcSite/My Site" + And the object "IfcBuilding/My Building" is in the collection "IfcBuilding/My Building" + And the object "IfcBuildingStorey/Ground Floor" is in the collection "IfcBuildingStorey/Ground Floor" + And the object "IfcSlab/Slab" is in the collection "IfcBuildingStorey/Ground Floor" + And the object "IfcBuildingStorey/Level 1" does not exist + And the object "IfcWall/Wall" does not exist + """ + + @test.bim.bootstrap.scenario + def test_loading_objects_filtered_by_whitelist(self): + return """ + Given I press "bim.load_project(filepath='{cwd}/test/files/basic.ifc', is_advanced=True)" + When I set "scene.BIMProjectProperties.collection_mode" to "DECOMPOSITION" + And I set "scene.BIMProjectProperties.filter_mode" to "WHITELIST" + And I set "scene.BIMProjectProperties.filter_query" to ".IfcSlab" + And I press "bim.load_project_elements" + Then the object "IfcProject/My Project" is an "IfcProject" + And the object "IfcSite/My Site" is an "IfcSite" + And the object "IfcBuilding/My Building" is an "IfcBuilding" + And the object "IfcBuildingStorey/Ground Floor" is an "IfcBuildingStorey" + And the object "IfcSlab/Slab" is an "IfcSlab" + And the object "IfcSite/My Site" is in the collection "IfcSite/My Site" + And the object "IfcBuilding/My Building" is in the collection "IfcBuilding/My Building" + And the object "IfcBuildingStorey/Ground Floor" is in the collection "IfcBuildingStorey/Ground Floor" + And the object "IfcSlab/Slab" is in the collection "IfcBuildingStorey/Ground Floor" + And the object "IfcBuildingStorey/Level 1" does not exist + And the object "IfcWall/Wall" does not exist + """ + + @test.bim.bootstrap.scenario + def test_loading_objects_filtered_by_blacklist(self): + return """ + Given I press "bim.load_project(filepath='{cwd}/test/files/basic.ifc', is_advanced=True)" + When I set "scene.BIMProjectProperties.collection_mode" to "DECOMPOSITION" + And I set "scene.BIMProjectProperties.filter_mode" to "BLACKLIST" + And I set "scene.BIMProjectProperties.filter_query" to ".IfcSlab" + And I press "bim.load_project_elements" + Then the object "IfcProject/My Project" is an "IfcProject" + And the object "IfcSite/My Site" is an "IfcSite" + And the object "IfcBuilding/My Building" is an "IfcBuilding" + And the object "IfcBuildingStorey/Level 1" is an "IfcBuildingStorey" + And the object "IfcWall/Wall" is an "IfcWall" + And the object "IfcElementAssembly/Empty" is an "IfcElementAssembly" + And the object "IfcBeam/Beam" is an "IfcBeam" + And the object "IfcSite/My Site" is in the collection "IfcSite/My Site" + And the object "IfcBuilding/My Building" is in the collection "IfcBuilding/My Building" + And the object "IfcBuildingStorey/Level 1" is in the collection "IfcBuildingStorey/Level 1" + And the object "IfcWall/Wall" is in the collection "IfcBuildingStorey/Level 1" + And the object "IfcElementAssembly/Empty" is in the collection "IfcElementAssembly/Empty" + And the object "IfcBeam/Beam" is in the collection "IfcElementAssembly/Empty" + And the object "IfcBuildingStorey/Ground Floor" does not exist + And the object "IfcSlab/Slab" does not exist + """ + + @test.bim.bootstrap.scenario + def test_loading_no_objects_due_to_filter(self): + return """ + Given I press "bim.load_project(filepath='{cwd}/test/files/basic.ifc', is_advanced=True)" + When I set "scene.BIMProjectProperties.collection_mode" to "DECOMPOSITION" + And I set "scene.BIMProjectProperties.filter_mode" to "IFC_CLASS" + And I press "bim.load_project_elements" + Then the object "IfcProject/My Project" is an "IfcProject" + And the object "IfcSite/My Site" does not exist + And the object "IfcBuilding/My Building" does not exist + And the object "IfcBuildingStorey/Ground Floor" does not exist + And the object "IfcBuildingStorey/Level 1" does not exist + And the object "IfcSlab/Slab" does not exist + And the object "IfcWall/Wall" does not exist + """ + + @test.bim.bootstrap.scenario + def test_loading_with_the_decomposition_collection_mode(self): + return """ + Given I press "bim.load_project(filepath='{cwd}/test/files/decomposition.ifc', is_advanced=True)" + When I set "scene.BIMProjectProperties.collection_mode" to "DECOMPOSITION" + And I set "scene.BIMProjectProperties.filter_mode" to "NONE" + And I press "bim.load_project_elements" + Then the object "IfcProject/My Project" is an "IfcProject" + And the object "IfcSite/My Site" is an "IfcSite" + And the object "IfcBuilding/My Building" is an "IfcBuilding" + And the object "IfcBuildingStorey/My Storey" is an "IfcBuildingStorey" + And the object "IfcSpace/Space" is an "IfcSpace" + And the object "IfcElementAssembly/Assembly" is an "IfcElementAssembly" + And the object "IfcBeam/Beam" is an "IfcBeam" + And the object "IfcSite/My Site" is in the collection "IfcSite/My Site" + And the object "IfcBuilding/My Building" is in the collection "IfcBuilding/My Building" + And the object "IfcBuildingStorey/My Storey" is in the collection "IfcBuildingStorey/My Storey" + And the object "IfcSpace/Space" is in the collection "IfcSpace/Space" + And the object "IfcElementAssembly/Assembly" is in the collection "IfcElementAssembly/Assembly" + And the object "IfcBeam/Beam" is in the collection "IfcElementAssembly/Assembly" + And the collection "IfcSite/My Site" is in the collection "IfcProject/My Project" + And the collection "IfcBuilding/My Building" is in the collection "IfcSite/My Site" + And the collection "IfcBuildingStorey/My Storey" is in the collection "IfcBuilding/My Building" + And the collection "IfcSpace/Space" is in the collection "IfcBuildingStorey/My Storey" + And the collection "IfcElementAssembly/Assembly" is in the collection "IfcSpace/Space" + And "scene.BIMProjectProperties.is_loading" is "False" + """ + + @test.bim.bootstrap.scenario + def test_loading_with_the_spatial_decomposition_collection_mode(self): + return """ + Given I press "bim.load_project(filepath='{cwd}/test/files/decomposition.ifc', is_advanced=True)" + When I set "scene.BIMProjectProperties.collection_mode" to "SPATIAL_DECOMPOSITION" + And I set "scene.BIMProjectProperties.filter_mode" to "NONE" + And I press "bim.load_project_elements" + Then the object "IfcProject/My Project" is an "IfcProject" + And the object "IfcSite/My Site" is an "IfcSite" + And the object "IfcBuilding/My Building" is an "IfcBuilding" + And the object "IfcBuildingStorey/My Storey" is an "IfcBuildingStorey" + And the object "IfcSpace/Space" is an "IfcSpace" + And the object "IfcElementAssembly/Assembly" is an "IfcElementAssembly" + And the object "IfcBeam/Beam" is an "IfcBeam" + And the object "IfcSite/My Site" is in the collection "IfcSite/My Site" + And the object "IfcBuilding/My Building" is in the collection "IfcBuilding/My Building" + And the object "IfcBuildingStorey/My Storey" is in the collection "IfcBuildingStorey/My Storey" + And the object "IfcSpace/Space" is in the collection "IfcSpace/Space" + And the object "IfcElementAssembly/Assembly" is in the collection "IfcSpace/Space" + And the object "IfcBeam/Beam" is in the collection "IfcSpace/Space" + And the collection "IfcSite/My Site" is in the collection "IfcProject/My Project" + And the collection "IfcBuilding/My Building" is in the collection "IfcSite/My Site" + And the collection "IfcBuildingStorey/My Storey" is in the collection "IfcBuilding/My Building" + And the collection "IfcSpace/Space" is in the collection "IfcBuildingStorey/My Storey" + And "scene.BIMProjectProperties.is_loading" is "False" + """ + + @test.bim.bootstrap.scenario + def test_manual_offset_of_object_placements(self): + return """ + Given I press "bim.load_project(filepath='{cwd}/test/files/manual-geolocation.ifc', is_advanced=True)" + When I set "scene.BIMProjectProperties.should_offset_model" to "True" + And I set "scene.BIMProjectProperties.model_offset_coordinates" to "-268388.5, -5774506.0, -21.899999618530273" + And I press "bim.load_project_elements" + Then the object "IfcPlate/1780 x 270 PRECAST WALL" is at "0,0,0" + """ + + +class TestUnloadProject(test.bim.bootstrap.NewFile): + @test.bim.bootstrap.scenario + def test_unloading_a_project(self): + return """ + When I press "bim.load_project(filepath='{cwd}/test/files/basic.ifc', is_advanced=True)" + And I press "bim.unload_project" + Then an IFC file does not exist + And "scene.BIMProjectProperties.is_loading" is "False" + """ + + +class TestLinkIFC(test.bim.bootstrap.NewFile): + @test.bim.bootstrap.scenario + def test_linking_an_ifc(self): + return """ + Given I press "bim.create_project" + When I press "bim.link_ifc(filepath='{cwd}/test/files/basic.blend')" + Then "scene.BIMProjectProperties.links['{cwd}/test/files/basic.blend'].is_loaded" is "True" + And the object "IfcWall/Wall" exists + And the object "IfcSlab/Slab" exists + And the object "IfcElementAssembly/Empty" exists + And the object "IfcBeam/Beam" exists + And the object "IfcBuildingStorey/Ground Floor" exists + And the object "IfcBuildingStorey/Level 1" exists + """ + + +class TestUnloadLink(test.bim.bootstrap.NewFile): + @test.bim.bootstrap.scenario + def test_unloading_a_link(self): + return """ + When I press "bim.link_ifc(filepath='{cwd}/test/files/basic.blend')" + And I press "bim.unload_link(filepath='{cwd}/test/files/basic.blend')" + Then "scene.BIMProjectProperties.links['{cwd}/test/files/basic.blend'].is_loaded" is "False" + And "scene.collection.children.get('IfcProject/My Project')" is "None" + """ + + +class TestLoadLink(test.bim.bootstrap.NewFile): + @test.bim.bootstrap.scenario + def test_loading_a_link(self): + return """ + When I press "bim.link_ifc(filepath='{cwd}/test/files/basic.blend')" + And I press "bim.unload_link(filepath='{cwd}/test/files/basic.blend')" + And I press "bim.load_link(filepath='{cwd}/test/files/basic.blend')" + Then "scene.BIMProjectProperties.links['{cwd}/test/files/basic.blend'].is_loaded" is "True" + And "scene.collection.children['IfcProject/My Project'].users" is "2" + """ + + +class TestUnlinkIFC(test.bim.bootstrap.NewFile): + @test.bim.bootstrap.scenario + def test_unlinking_an_ifc(self): + return """ + When I press "bim.link_ifc(filepath='{cwd}/test/files/basic.blend')" + And I press "bim.unload_link(filepath='{cwd}/test/files/basic.blend')" + And I press "bim.unlink_ifc(filepath='{cwd}/test/files/basic.blend')" + Then "scene.BIMProjectProperties.links.get('{cwd}/test/files/basic.blend')" is "None" + And "scene.collection.children.get('IfcProject/My Project')" is "None" + """ diff --git a/src/blenderbim/test/bim/module/root/__init__.py b/src/blenderbim/test/bim/module/root/__init__.py new file mode 100644 index 0000000000..fb33323db9 --- /dev/null +++ b/src/blenderbim/test/bim/module/root/__init__.py @@ -0,0 +1,17 @@ +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . diff --git a/src/blenderbim/test/bim/module/root/test_operator.py b/src/blenderbim/test/bim/module/root/test_operator.py new file mode 100644 index 0000000000..554b5c7003 --- /dev/null +++ b/src/blenderbim/test/bim/module/root/test_operator.py @@ -0,0 +1,134 @@ +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + +import test.bim.bootstrap + + +class TestAssignClass(test.bim.bootstrap.NewFile): + @test.bim.bootstrap.scenario + def test_assigning_a_class_to_a_cube(self): + return """ + Given an empty IFC project + Given I add a cube + When the object "Cube" is selected + And I set "scene.BIMRootProperties.ifc_class" to "IfcWall" + And I press "bim.assign_class" + Then the object "IfcWall/Cube" is an "IfcWall" + And the object "IfcWall/Cube" is in the collection "Collection" + And the object "IfcWall/Cube" has a "Tessellation" representation of "Model/Body/MODEL_VIEW" + """ + + @test.bim.bootstrap.scenario + def test_assigning_a_type_class_to_a_cube(self): + return """ + Given an empty IFC project + Given I add a cube + When the object "Cube" is selected + And I set "scene.BIMRootProperties.ifc_product" to "IfcElementType" + And I set "scene.BIMRootProperties.ifc_class" to "IfcWallType" + And I press "bim.assign_class" + Then the object "IfcWallType/Cube" is an "IfcWallType" + And the object "IfcWallType/Cube" is in the collection "Types" + And the object "IfcWallType/Cube" has a "Tessellation" representation of "Model/Body/MODEL_VIEW" + """ + + @test.bim.bootstrap.scenario + def test_assigning_a_spatial_class_to_a_cube(self): + return """ + Given an empty IFC project + Given I add a cube + When the object "Cube" is selected + And I set "scene.BIMRootProperties.ifc_product" to "IfcSpatialElement" + And I set "scene.BIMRootProperties.ifc_class" to "IfcBuilding" + And I press "bim.assign_class" + Then the object "IfcBuilding/Cube" is an "IfcBuilding" + And the object "IfcBuilding/Cube" is in the collection "IfcBuilding/Cube" + And the object "IfcBuilding/Cube" has a "Tessellation" representation of "Model/Body/MODEL_VIEW" + """ + + @test.bim.bootstrap.scenario + def test_assigning_an_opening_class_to_a_cube(self): + return """ + Given an empty IFC project + Given I add a cube + When the object "Cube" is selected + And I set "scene.BIMRootProperties.ifc_product" to "IfcElement" + And I set "scene.BIMRootProperties.ifc_class" to "IfcOpeningElement" + And I press "bim.assign_class" + Then the object "IfcOpeningElement/Cube" is an "IfcOpeningElement" + And the object "IfcOpeningElement/Cube" should display as "WIRE" + And the object "IfcOpeningElement/Cube" is in the collection "IfcOpeningElements" + And the object "IfcOpeningElement/Cube" has a "Tessellation" representation of "Model/Body/MODEL_VIEW" + """ + + @test.bim.bootstrap.scenario + def test_assigning_a_class_to_a_cube_in_a_collection(self): + return """ + Given an empty IFC project + Given I add a cube + When the object "Cube" is selected + And the object "Cube" is placed in the collection "IfcBuildingStorey/My Storey" + And I set "scene.BIMRootProperties.ifc_class" to "IfcWall" + And I press "bim.assign_class" + Then the object "IfcWall/Cube" is contained in "My Storey" + """ + + +class TestCopyClass(test.bim.bootstrap.NewFile): + @test.bim.bootstrap.scenario + def test_copying_a_wall(self): + return """ + Given an empty IFC project + Given I add a cube + When the object "Cube" is selected + And I set "scene.BIMRootProperties.ifc_class" to "IfcWall" + And I press "bim.assign_class" + And I duplicate the selected objects + Then the object "IfcWall/Cube" and "IfcWall/Cube.001" are different elements + """ + + @test.bim.bootstrap.scenario + def test_copying_a_storey(self): + return """ + Given an empty IFC project + And the object "IfcBuildingStorey/My Storey" is selected + When I duplicate the selected objects + Then the object "IfcBuildingStorey/My Storey" and "IfcBuildingStorey/My Storey.001" are different elements + And the object "IfcBuildingStorey/My Storey" is in the collection "IfcBuildingStorey/My Storey" + And the object "IfcBuildingStorey/My Storey.001" is in the collection "IfcBuildingStorey/My Storey.001" + And the collection "IfcBuildingStorey/My Storey.001" is in the collection "IfcBuilding/My Building" + """ + + @test.bim.bootstrap.scenario + def test_copying_an_opening(self): + return """ + Given an empty IFC project + Given I add a cube + When the object "Cube" is selected + And I set "scene.BIMRootProperties.ifc_class" to "IfcWall" + And I press "bim.assign_class" + And I add a cube + And the object "Cube" is selected + And additionally the object "IfcWall/Cube" is selected + And I press "bim.add_opening(opening='Cube', obj='IfcWall/Cube')" + And the object "IfcOpeningElement/Cube" is selected + And I duplicate the selected objects + Then the object "IfcOpeningElement/Cube" and "IfcOpeningElement/Cube.001" are different elements + And the object "IfcWall/Cube" has a boolean difference by "IfcOpeningElement/Cube" + And the object "IfcWall/Cube" has a boolean difference by "IfcOpeningElement/Cube.001" + """ diff --git a/src/blenderbim/test/bim/module/sequence/__init__.py b/src/blenderbim/test/bim/module/sequence/__init__.py new file mode 100644 index 0000000000..fb33323db9 --- /dev/null +++ b/src/blenderbim/test/bim/module/sequence/__init__.py @@ -0,0 +1,17 @@ +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . diff --git a/src/blenderbim/test/bim/module/sequence/test_operator.py b/src/blenderbim/test/bim/module/sequence/test_operator.py new file mode 100644 index 0000000000..81fa457ae1 --- /dev/null +++ b/src/blenderbim/test/bim/module/sequence/test_operator.py @@ -0,0 +1,241 @@ +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + +import test.bim.bootstrap + + +class TestVisualiseWorkScheduleDateRange(test.bim.bootstrap.NewFile): + @test.bim.bootstrap.scenario + def test_seeing_the_current_frame_date_as_text(self): + return """ + Given an empty IFC project + And I press "bim.add_work_schedule" + And I press "bim.enable_editing_tasks(work_schedule=72)" + And I set "scene.BIMWorkScheduleProperties.visualisation_start" to "01/01/21" + And I set "scene.BIMWorkScheduleProperties.visualisation_finish" to "01/02/21" + And I set "scene.BIMWorkScheduleProperties.speed_types" to "FRAME_SPEED" + And I set "scene.BIMWorkScheduleProperties.speed_animation_frames" to "7" + And I set "scene.BIMWorkScheduleProperties.speed_real_duration" to "P1W" + And I press "bim.visualise_work_schedule_date_range(work_schedule=72)" + When I am on frame "1" + Then the object "Timeline" has a body of "2021-01-01" + When I am on frame "2" + Then the object "Timeline" has a body of "2021-01-02" + """ + + @test.bim.bootstrap.scenario + def test_animating_the_construction_of_a_wall(self): + return """ + Given an empty IFC project + And I press "bim.add_work_schedule" + And I press "bim.enable_editing_tasks(work_schedule=72)" + And I press "bim.add_summary_task(work_schedule=72)" + And I press "bim.enable_editing_task(task=76)" + And I set "scene.BIMWorkScheduleProperties.task_attributes.get("PredefinedType").enum_value" to "CONSTRUCTION" + And I press "bim.edit_task" + And I press "bim.enable_editing_task_time(task=76)" + And I set "scene.BIMWorkScheduleProperties.task_time_attributes.get("ScheduleStart").string_value" to "2021-01-02" + And I set "scene.BIMWorkScheduleProperties.task_time_attributes.get("ScheduleFinish").string_value" to "2021-01-06" + And I press "bim.edit_task_time" + And I add a cube + And the object "Cube" is selected + And I set "scene.BIMRootProperties.ifc_class" to "IfcWall" + And I press "bim.assign_class" + And the object "IfcWall/Cube" is selected + And I press "bim.assign_product(task=76, relating_product='')" + And I set "scene.BIMWorkScheduleProperties.visualisation_start" to "01/01/21" + And I set "scene.BIMWorkScheduleProperties.visualisation_finish" to "01/02/21" + And I set "scene.BIMWorkScheduleProperties.speed_types" to "FRAME_SPEED" + And I set "scene.BIMWorkScheduleProperties.speed_animation_frames" to "7" + And I set "scene.BIMWorkScheduleProperties.speed_real_duration" to "P1W" + And I press "bim.visualise_work_schedule_date_range(work_schedule=72)" + When I am on frame "1" + Then "scene.objects.get('IfcWall/Cube').hide_viewport" is "True" + And "scene.objects.get('IfcWall/Cube').hide_render" is "True" + When I am on frame "2" + Then "scene.objects.get('IfcWall/Cube').hide_viewport" is "False" + And "scene.objects.get('IfcWall/Cube').hide_render" is "False" + And "scene.objects.get('IfcWall/Cube').color" is "[0.0, 1.0, 0.0, 1]" + When I am on frame "6" + Then "scene.objects.get('IfcWall/Cube').color" is "[1.0, 1.0, 1.0, 1]" + """ + + @test.bim.bootstrap.scenario + def test_animating_the_demolition_of_a_wall(self): + return """ + Given an empty IFC project + And I press "bim.add_work_schedule" + And I press "bim.enable_editing_tasks(work_schedule=72)" + And I press "bim.add_summary_task(work_schedule=72)" + And I press "bim.enable_editing_task(task=76)" + And I set "scene.BIMWorkScheduleProperties.task_attributes.get("PredefinedType").enum_value" to "DEMOLITION" + And I press "bim.edit_task" + And I press "bim.enable_editing_task_time(task=76)" + And I set "scene.BIMWorkScheduleProperties.task_time_attributes.get("ScheduleStart").string_value" to "2021-01-02" + And I set "scene.BIMWorkScheduleProperties.task_time_attributes.get("ScheduleFinish").string_value" to "2021-01-06" + And I press "bim.edit_task_time" + And I add a cube + And the object "Cube" is selected + And I set "scene.BIMRootProperties.ifc_class" to "IfcWall" + And I press "bim.assign_class" + And the object "IfcWall/Cube" is selected + And I press "bim.assign_product(task=76, relating_product='')" + And I set "scene.BIMWorkScheduleProperties.visualisation_start" to "01/01/21" + And I set "scene.BIMWorkScheduleProperties.visualisation_finish" to "01/02/21" + And I set "scene.BIMWorkScheduleProperties.speed_types" to "FRAME_SPEED" + And I set "scene.BIMWorkScheduleProperties.speed_animation_frames" to "7" + And I set "scene.BIMWorkScheduleProperties.speed_real_duration" to "P1W" + And I press "bim.visualise_work_schedule_date_range(work_schedule=72)" + When I am on frame "1" + Then "scene.objects.get('IfcWall/Cube').hide_viewport" is "False" + And "scene.objects.get('IfcWall/Cube').hide_render" is "False" + And "scene.objects.get('IfcWall/Cube').color" is "[1.0, 1.0, 1.0, 1]" + When I am on frame "2" + Then "scene.objects.get('IfcWall/Cube').hide_viewport" is "False" + And "scene.objects.get('IfcWall/Cube').hide_render" is "False" + And "scene.objects.get('IfcWall/Cube').color" is "[1.0, 0.0, 0.0, 1]" + When I am on frame "6" + Then "scene.objects.get('IfcWall/Cube').hide_viewport" is "True" + And "scene.objects.get('IfcWall/Cube').hide_render" is "True" + And "scene.objects.get('IfcWall/Cube').color" is "[0.0, 0.0, 0.0, 1]" + """ + + @test.bim.bootstrap.scenario + def test_animating_the_operation_of_a_wall(self): + return """ + Given an empty IFC project + And I press "bim.add_work_schedule" + And I press "bim.enable_editing_tasks(work_schedule=72)" + And I press "bim.add_summary_task(work_schedule=72)" + And I press "bim.enable_editing_task(task=76)" + And I set "scene.BIMWorkScheduleProperties.task_attributes.get("PredefinedType").enum_value" to "OPERATION" + And I press "bim.edit_task" + And I press "bim.enable_editing_task_time(task=76)" + And I set "scene.BIMWorkScheduleProperties.task_time_attributes.get("ScheduleStart").string_value" to "2021-01-02" + And I set "scene.BIMWorkScheduleProperties.task_time_attributes.get("ScheduleFinish").string_value" to "2021-01-06" + And I press "bim.edit_task_time" + And I add a cube + And the object "Cube" is selected + And I set "scene.BIMRootProperties.ifc_class" to "IfcWall" + And I press "bim.assign_class" + And the object "IfcWall/Cube" is selected + And I press "bim.assign_product(task=76, relating_product='')" + And I set "scene.BIMWorkScheduleProperties.visualisation_start" to "01/01/21" + And I set "scene.BIMWorkScheduleProperties.visualisation_finish" to "01/02/21" + And I set "scene.BIMWorkScheduleProperties.speed_types" to "FRAME_SPEED" + And I set "scene.BIMWorkScheduleProperties.speed_animation_frames" to "7" + And I set "scene.BIMWorkScheduleProperties.speed_real_duration" to "P1W" + And I press "bim.visualise_work_schedule_date_range(work_schedule=72)" + When I am on frame "1" + Then "scene.objects.get('IfcWall/Cube').color" is "[1.0, 1.0, 1.0, 1]" + When I am on frame "2" + Then "scene.objects.get('IfcWall/Cube').color" is "[0.0, 0.0, 1.0, 1]" + When I am on frame "6" + Then "scene.objects.get('IfcWall/Cube').color" is "[1.0, 1.0, 1.0, 1]" + """ + + @test.bim.bootstrap.scenario + def test_animating_the_movement_of_a_wall(self): + return """ + Given an empty IFC project + And I press "bim.add_work_schedule" + And I press "bim.enable_editing_tasks(work_schedule=72)" + And I press "bim.add_summary_task(work_schedule=72)" + And I press "bim.enable_editing_task(task=76)" + And I set "scene.BIMWorkScheduleProperties.task_attributes.get("PredefinedType").enum_value" to "MOVE" + And I press "bim.edit_task" + And I press "bim.enable_editing_task_time(task=76)" + And I set "scene.BIMWorkScheduleProperties.task_time_attributes.get("ScheduleStart").string_value" to "2021-01-02" + And I set "scene.BIMWorkScheduleProperties.task_time_attributes.get("ScheduleFinish").string_value" to "2021-01-06" + And I press "bim.edit_task_time" + And I rename the object "Cube" to "ToObject" + And the object "ToObject" is selected + And I set "scene.BIMRootProperties.ifc_class" to "IfcWall" + And I press "bim.assign_class" + And the object "IfcWall/ToObject" is selected + And I press "bim.assign_product(task=76, relating_product='')" + When I add a cube + And I rename the object "Cube" to "FromObject" + And the object "FromObject" is selected + And I press "bim.assign_class" + And the object "IfcWall/FromObject" is selected + And I press "bim.assign_process(task=76, related_object_type="PRODUCT", related_object='')" + And I set "scene.BIMWorkScheduleProperties.visualisation_start" to "01/01/21" + And I set "scene.BIMWorkScheduleProperties.visualisation_finish" to "01/02/21" + And I set "scene.BIMWorkScheduleProperties.speed_types" to "FRAME_SPEED" + And I set "scene.BIMWorkScheduleProperties.speed_animation_frames" to "7" + And I set "scene.BIMWorkScheduleProperties.speed_real_duration" to "P1W" + And I press "bim.visualise_work_schedule_date_range(work_schedule=72)" + When I am on frame "1" + Then "scene.objects.get('IfcWall/FromObject').color" is "[1.0, 1.0, 1.0, 1]" + And "scene.objects.get('IfcWall/FromObject').hide_viewport" is "False" + And "scene.objects.get('IfcWall/FromObject').hide_render" is "False" + And "scene.objects.get('IfcWall/ToObject').hide_viewport" is "True" + And "scene.objects.get('IfcWall/ToObject').hide_render" is "True" + When I am on frame "2" + Then "scene.objects.get('IfcWall/FromObject').color" is "[1.0, 0.5, 0.0, 1]" + And "scene.objects.get('IfcWall/FromObject').hide_viewport" is "False" + And "scene.objects.get('IfcWall/FromObject').hide_render" is "False" + And "scene.objects.get('IfcWall/ToObject').color" is "[1.0, 1.0, 0.0, 1]" + And "scene.objects.get('IfcWall/ToObject').hide_viewport" is "False" + And "scene.objects.get('IfcWall/ToObject').hide_render" is "False" + When I am on frame "6" + Then "scene.objects.get('IfcWall/FromObject').color" is "[0.0, 0.0, 0.0, 1]" + Then "scene.objects.get('IfcWall/FromObject').hide_viewport" is "True" + Then "scene.objects.get('IfcWall/FromObject').hide_render" is "True" + And "scene.objects.get('IfcWall/ToObject').color" is "[1.0, 1.0, 1.0, 1]" + And "scene.objects.get('IfcWall/ToObject').hide_viewport" is "False" + And "scene.objects.get('IfcWall/ToObject').hide_render" is "False" + """ + + @test.bim.bootstrap.scenario + def test_animating_the_consumption_of_a_wall(self): + return """ + Given an empty IFC project + And I press "bim.add_work_schedule" + And I press "bim.enable_editing_tasks(work_schedule=72)" + And I press "bim.add_summary_task(work_schedule=72)" + And I press "bim.enable_editing_task_time(task=76)" + And I set "scene.BIMWorkScheduleProperties.task_time_attributes.get("ScheduleStart").string_value" to "2021-01-02" + And I set "scene.BIMWorkScheduleProperties.task_time_attributes.get("ScheduleFinish").string_value" to "2021-01-06" + And I press "bim.edit_task_time" + And I add a cube + And the object "Cube" is selected + And I set "scene.BIMRootProperties.ifc_class" to "IfcWall" + And I press "bim.assign_class" + And the object "IfcWall/Cube" is selected + And I press "bim.assign_process(task=76, related_object_type="PRODUCT", related_object='')" + And I set "scene.BIMWorkScheduleProperties.visualisation_start" to "01/01/21" + And I set "scene.BIMWorkScheduleProperties.visualisation_finish" to "01/02/21" + And I set "scene.BIMWorkScheduleProperties.speed_types" to "FRAME_SPEED" + And I set "scene.BIMWorkScheduleProperties.speed_animation_frames" to "7" + And I set "scene.BIMWorkScheduleProperties.speed_real_duration" to "P1W" + And I press "bim.visualise_work_schedule_date_range(work_schedule=72)" + When I am on frame "1" + Then "scene.objects.get('IfcWall/Cube').color" is "[1.0, 1.0, 1.0, 1]" + And "scene.objects.get('IfcWall/Cube').hide_viewport" is "False" + And "scene.objects.get('IfcWall/Cube').hide_render" is "False" + When I am on frame "2" + Then "scene.objects.get('IfcWall/Cube').color" is "[0.0, 1.0, 1.0, 1]" + And "scene.objects.get('IfcWall/Cube').hide_viewport" is "False" + And "scene.objects.get('IfcWall/Cube').hide_render" is "False" + When I am on frame "6" + Then "scene.objects.get('IfcWall/Cube').color" is "[0.0, 0.0, 0.0, 1]" + Then "scene.objects.get('IfcWall/Cube').hide_viewport" is "True" + Then "scene.objects.get('IfcWall/Cube').hide_render" is "True" + """ diff --git a/src/blenderbim/test/bim/module/void/__init__.py b/src/blenderbim/test/bim/module/void/__init__.py new file mode 100644 index 0000000000..fb33323db9 --- /dev/null +++ b/src/blenderbim/test/bim/module/void/__init__.py @@ -0,0 +1,17 @@ +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . diff --git a/src/blenderbim/test/bim/module/void/test_operator.py b/src/blenderbim/test/bim/module/void/test_operator.py new file mode 100644 index 0000000000..96cba2d83e --- /dev/null +++ b/src/blenderbim/test/bim/module/void/test_operator.py @@ -0,0 +1,115 @@ +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + +import test.bim.bootstrap + + +class TestAddOpening(test.bim.bootstrap.NewFile): + @test.bim.bootstrap.scenario + def test_adding_an_opening(self): + return """ + Given an empty IFC project + Given I add a cube + When the object "Cube" is selected + And I set "scene.BIMRootProperties.ifc_class" to "IfcWall" + And I press "bim.assign_class" + And I add a cube + And the object "IfcWall/Cube" is selected + And additionally the object "Cube" is selected + And I press "bim.add_opening(opening='Cube', obj='IfcWall/Cube')" + Then the object "IfcOpeningElement/Cube" is an "IfcOpeningElement" + And the object "IfcOpeningElement/Cube" should display as "WIRE" + And the object "IfcWall/Cube" has a boolean difference by "IfcOpeningElement/Cube" + And the object "IfcWall/Cube" is voided by "Cube" + """ + + +class TestRemoveOpening(test.bim.bootstrap.NewFile): + @test.bim.bootstrap.scenario + def test_removing_an_opening_manually(self): + return """ + Given an empty IFC project + Given I add a cube + When the object "Cube" is selected + And I set "scene.BIMRootProperties.ifc_class" to "IfcWall" + And I press "bim.assign_class" + And I add a cube of size "1" at "1,0,0" + And the object "IfcWall/Cube" is selected + And additionally the object "Cube" is selected + And I press "bim.add_opening(opening='Cube', obj='IfcWall/Cube')" + And I press "bim.remove_opening(opening_id=97, obj='IfcWall/Cube')" + Then the object "IfcWall/Cube" has no boolean difference by "IfcOpeningElement/Cube" + And the object "IfcWall/Cube" is not voided by "Cube" + And the object "Cube" is not an IFC element + """ + + @test.bim.bootstrap.scenario + def test_removing_a_non_dynamic_opening_manually(self): + return """ + Given an empty IFC project + Given I add a cube + When the object "Cube" is selected + And I set "scene.BIMRootProperties.ifc_class" to "IfcWall" + And I press "bim.assign_class" + And I add a cube of size "1" at "1,0,0" + And the object "IfcWall/Cube" is selected + And additionally the object "Cube" is selected + And I press "bim.add_opening(opening='Cube', obj='IfcWall/Cube')" + And the object "IfcWall/Cube" is selected + And I press "bim.switch_representation(ifc_definition_id=86, should_reload=True)" + Then the object "IfcWall/Cube" has no boolean difference by "IfcOpeningElement/Cube" + And the object "IfcWall/Cube" has "16" vertices + When I press "bim.remove_opening(opening_id=97, obj='IfcWall/Cube')" + Then the object "IfcWall/Cube" has "8" vertices + And the object "Cube" is not an IFC element + """ + + @test.bim.bootstrap.scenario + def test_removing_an_opening_using_deletion(self): + return """ + Given an empty IFC project + Given I add a cube + When the object "Cube" is selected + And I set "scene.BIMRootProperties.ifc_class" to "IfcWall" + And I press "bim.assign_class" + And I add a cube of size "1" at "1,0,0" + And the object "IfcWall/Cube" is selected + And additionally the object "Cube" is selected + And I press "bim.add_opening(opening='Cube', obj='IfcWall/Cube')" + And the object "IfcOpeningElement/Cube" is selected + And I delete the selected objects + Then the object "IfcWall/Cube" has no boolean difference by "IfcOpeningElement/Cube" + And the object "IfcWall/Cube" is not voided by "Cube" + """ + + @test.bim.bootstrap.scenario + def test_removing_an_opening_indirectly_by_deleting_its_building_element(self): + return """ + Given an empty IFC project + Given I add a cube + When the object "Cube" is selected + And I set "scene.BIMRootProperties.ifc_class" to "IfcWall" + And I press "bim.assign_class" + And I add a cube of size "1" at "1,0,0" + And the object "IfcWall/Cube" is selected + And additionally the object "Cube" is selected + And I press "bim.add_opening(opening='Cube', obj='IfcWall/Cube')" + And the object "IfcWall/Cube" is selected + And I delete the selected objects + Then the object "Cube" is not an IFC element + """ diff --git a/src/blenderbim/test/files/basic.blend b/src/blenderbim/test/files/basic.blend new file mode 100644 index 0000000000..67eb08d4cb Binary files /dev/null and b/src/blenderbim/test/files/basic.blend differ diff --git a/src/blenderbim/test/files/basic.ifc b/src/blenderbim/test/files/basic.ifc new file mode 100644 index 0000000000..ac80e3976f --- /dev/null +++ b/src/blenderbim/test/files/basic.ifc @@ -0,0 +1,155 @@ +ISO-10303-21; +HEADER; +FILE_DESCRIPTION(('ViewDefinition[DesignTransferView]'),'2;1'); +FILE_NAME('basic.ifc','2021-09-12T16:48:33+10:00',(),(),'IfcOpenShell 0.6.0b0','BlenderBIM 0.0.999999','Nobody'); +FILE_SCHEMA(('IFC4')); +ENDSEC; +DATA; +#1=IFCPERSON('HSeldon','Seldon','Hari',$,$,$,$,$); +#2=IFCORGANIZATION('APTR','Aperture Science',$,$,$); +#3=IFCACTORROLE(.USERDEFINED.,'CONTRIBUTOR',$); +#4=IFCTELECOMADDRESS(.USERDEFINED.,'The main webpage of the software collection.','WEBPAGE',$,$,$,$,'https://ifcopenshell.org',$); +#5=IFCTELECOMADDRESS(.USERDEFINED.,'The BlenderBIM Add-on webpage of the software collection.','WEBPAGE',$,$,$,$,'https://blenderbim.org',$); +#6=IFCTELECOMADDRESS(.USERDEFINED.,'The source code repository of the software collection.','REPOSITORY',$,$,$,$,'https://github.com/IfcOpenShell/IfcOpenShell.git',$); +#7=IFCORGANIZATION($,'IfcOpenShell','IfcOpenShell is an open source (LGPL) software library that helps users and software developers to work with the IFC file format.',(#3),(#4,#5,#6)); +#8=IFCAPPLICATION(#7,'0.0.999999','BlenderBIM Add-on','BlenderBIM'); +#9=IFCPERSONANDORGANIZATION(#1,#2,$); +#10=IFCOWNERHISTORY(#9,#8,.READWRITE.,.ADDED.,1631353388,#9,#8,1631353388); +#11=IFCPROJECT('2xwg5dkcT4T8MlEIX1jLjD',#10,'My Project',$,$,$,$,(#20,#27),#15); +#12=IFCSIUNIT(*,.LENGTHUNIT.,$,.METRE.); +#13=IFCSIUNIT(*,.AREAUNIT.,$,.SQUARE_METRE.); +#14=IFCSIUNIT(*,.VOLUMEUNIT.,$,.CUBIC_METRE.); +#15=IFCUNITASSIGNMENT((#14,#12,#13)); +#16=IFCCARTESIANPOINT((0.,0.,0.)); +#17=IFCDIRECTION((0.,0.,1.)); +#18=IFCDIRECTION((1.,0.,0.)); +#19=IFCAXIS2PLACEMENT3D(#16,#17,#18); +#20=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Model',3,1.E-05,#19,$); +#21=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Body','Model',*,*,*,*,#20,$,.MODEL_VIEW.,$); +#22=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Box','Model',*,*,*,*,#20,$,.MODEL_VIEW.,$); +#23=IFCCARTESIANPOINT((0.,0.,0.)); +#24=IFCDIRECTION((0.,0.,1.)); +#25=IFCDIRECTION((1.,0.,0.)); +#26=IFCAXIS2PLACEMENT3D(#23,#24,#25); +#27=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Plan',2,1.E-05,#26,$); +#28=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Annotation','Plan',*,*,*,*,#27,$,.PLAN_VIEW.,$); +#29=IFCOWNERHISTORY(#9,#8,.READWRITE.,.MODIFIED.,1631353388,#9,#8,1631353388); +#30=IFCSITE('3cz3qtLYbCURHSIUoySdUS',#29,'My Site',$,$,#56,$,$,$,$,$,$,$,$); +#36=IFCOWNERHISTORY(#9,#8,.READWRITE.,.MODIFIED.,1631353388,#9,#8,1631353388); +#37=IFCBUILDING('2dErbdcOP6Nh7ym5xn_$Ec',#36,'My Building',$,$,#63,$,$,$,$,$,$); +#43=IFCOWNERHISTORY(#9,#8,.READWRITE.,.MODIFIED.,1631353388,#9,#8,1631353388); +#44=IFCBUILDINGSTOREY('0MbU9rGEH1LAWUFmJ3gCcg',#43,'Ground Floor',$,$,#70,$,$,$,$); +#50=IFCOWNERHISTORY(#9,#8,.READWRITE.,.ADDED.,1631353388,#9,#8,1631353388); +#51=IFCRELAGGREGATES('1_TFH$83TDGOqH3GdA9HdZ',#50,$,$,#11,(#30)); +#52=IFCCARTESIANPOINT((0.,0.,0.)); +#53=IFCDIRECTION((0.,0.,1.)); +#54=IFCDIRECTION((1.,0.,0.)); +#55=IFCAXIS2PLACEMENT3D(#52,#53,#54); +#56=IFCLOCALPLACEMENT($,#55); +#57=IFCOWNERHISTORY(#9,#8,.READWRITE.,.ADDED.,1631353388,#9,#8,1631353388); +#58=IFCRELAGGREGATES('0rW4qoplT3zBMjr14wc1ok',#57,$,$,#30,(#37)); +#59=IFCCARTESIANPOINT((0.,0.,0.)); +#60=IFCDIRECTION((0.,0.,1.)); +#61=IFCDIRECTION((1.,0.,0.)); +#62=IFCAXIS2PLACEMENT3D(#59,#60,#61); +#63=IFCLOCALPLACEMENT(#56,#62); +#64=IFCOWNERHISTORY(#9,#8,.READWRITE.,.ADDED.,1631353388,#9,#8,1631353388); +#65=IFCRELAGGREGATES('041C6Y06v8$AmAQhXWgREJ',#64,$,$,#37,(#44,#71)); +#66=IFCCARTESIANPOINT((0.,0.,0.)); +#67=IFCDIRECTION((0.,0.,1.)); +#68=IFCDIRECTION((1.,0.,0.)); +#69=IFCAXIS2PLACEMENT3D(#66,#67,#68); +#70=IFCLOCALPLACEMENT(#63,#69); +#71=IFCBUILDINGSTOREY('1Pkxs$2EDD3ApZ07AQn5om',#77,'Level 1',$,$,#152,$,$,$,$); +#72=IFCCARTESIANPOINT((0.,0.,0.)); +#73=IFCDIRECTION((0.,0.,1.)); +#74=IFCDIRECTION((1.,0.,0.)); +#75=IFCAXIS2PLACEMENT3D(#72,#73,#74); +#76=IFCLOCALPLACEMENT(#63,#75); +#77=IFCOWNERHISTORY(#9,#8,.READWRITE.,.MODIFIED.,1631353453,#9,#8,1631353388); +#78=IFCOWNERHISTORY(#9,#8,.READWRITE.,.MODIFIED.,1631353439,#9,#8,1631353439); +#79=IFCSLAB('20Njb8mHv8v9ESSk_1YM2m',#78,'Slab',$,$,#114,#97,$,.BASESLAB.); +#85=IFCINDEXEDPOLYGONALFACE((1,5,7,3)); +#86=IFCINDEXEDPOLYGONALFACE((4,3,7,8)); +#87=IFCINDEXEDPOLYGONALFACE((8,7,5,6)); +#88=IFCINDEXEDPOLYGONALFACE((6,2,4,8)); +#89=IFCINDEXEDPOLYGONALFACE((2,1,3,4)); +#90=IFCINDEXEDPOLYGONALFACE((6,5,1,2)); +#91=IFCCARTESIANPOINTLIST3D(((1.,1.,1.),(1.,1.,-1.),(1.,-1.,1.),(1.,-1.,-1.),(-1.,1.,1.),(-1.,1.,-1.),(-1.,-1.,1.),(-1.,-1.,-1.))); +#92=IFCPOLYGONALFACESET(#91,$,(#85,#86,#87,#88,#89,#90),$); +#93=IFCSHAPEREPRESENTATION(#21,'Body','Tessellation',(#92)); +#94=IFCCARTESIANPOINT((-1.,-1.,-1.)); +#95=IFCBOUNDINGBOX(#94,2.,2.,2.); +#96=IFCSHAPEREPRESENTATION(#22,'Box','BoundingBox',(#95)); +#97=IFCPRODUCTDEFINITIONSHAPE($,$,(#96,#93)); +#98=IFCCOLOURRGB($,0.800000011920929,0.800000011920929,0.800000011920929); +#99=IFCCOLOURRGB($,0.800000011920929,0.800000011920929,0.800000011920929); +#100=IFCSURFACESTYLERENDERING(#98,0.,#99,$,$,$,$,$,.NOTDEFINED.); +#101=IFCSURFACESTYLE('Material',.BOTH.,(#100)); +#102=IFCSTYLEDITEM(#92,(#101),'Material'); +#103=IFCOWNERHISTORY(#9,#8,.READWRITE.,.ADDED.,1631353439,#9,#8,1631353439); +#104=IFCRELCONTAINEDINSPATIALSTRUCTURE('2T4j5EAcn0qQ9vTOZFK9HO',#103,$,$,(#79),#44); +#110=IFCCARTESIANPOINT((0.,0.,0.)); +#111=IFCDIRECTION((0.,0.,1.)); +#112=IFCDIRECTION((1.,0.,0.)); +#113=IFCAXIS2PLACEMENT3D(#110,#111,#112); +#114=IFCLOCALPLACEMENT(#70,#113); +#115=IFCOWNERHISTORY(#9,#8,.READWRITE.,.MODIFIED.,1631353453,#9,#8,1631353443); +#116=IFCWALL('27uW0y7Gr72R8yo2C4vBlV',#115,'Wall',$,$,#157,#134,$,.ELEMENTEDWALL.); +#122=IFCINDEXEDPOLYGONALFACE((1,5,7,3)); +#123=IFCINDEXEDPOLYGONALFACE((4,3,7,8)); +#124=IFCINDEXEDPOLYGONALFACE((8,7,5,6)); +#125=IFCINDEXEDPOLYGONALFACE((6,2,4,8)); +#126=IFCINDEXEDPOLYGONALFACE((2,1,3,4)); +#127=IFCINDEXEDPOLYGONALFACE((6,5,1,2)); +#128=IFCCARTESIANPOINTLIST3D(((1.,1.,1.),(1.,1.,-1.),(1.,-1.,1.),(1.,-1.,-1.),(-1.,1.,1.),(-1.,1.,-1.),(-1.,-1.,1.),(-1.,-1.,-1.))); +#129=IFCPOLYGONALFACESET(#128,$,(#122,#123,#124,#125,#126,#127),$); +#130=IFCSHAPEREPRESENTATION(#21,'Body','Tessellation',(#129)); +#131=IFCCARTESIANPOINT((-1.,-1.,-1.)); +#132=IFCBOUNDINGBOX(#131,2.,2.,2.); +#133=IFCSHAPEREPRESENTATION(#22,'Box','BoundingBox',(#132)); +#134=IFCPRODUCTDEFINITIONSHAPE($,$,(#133,#130)); +#135=IFCSTYLEDITEM(#129,(#101),'Material'); +#136=IFCOWNERHISTORY(#9,#8,.READWRITE.,.MODIFIED.,1631429228,#9,#8,1631353443); +#137=IFCRELCONTAINEDINSPATIALSTRUCTURE('1CgivZt6z1l8IgKaTkZMcT',#136,$,$,(#116,#159),#71); +#148=IFCCARTESIANPOINT((0.,0.,3.)); +#149=IFCDIRECTION((0.,0.,1.)); +#150=IFCDIRECTION((1.,0.,0.)); +#151=IFCAXIS2PLACEMENT3D(#148,#149,#150); +#152=IFCLOCALPLACEMENT(#63,#151); +#153=IFCCARTESIANPOINT((0.,0.,-3.)); +#154=IFCDIRECTION((0.,0.,1.)); +#155=IFCDIRECTION((1.,0.,0.)); +#156=IFCAXIS2PLACEMENT3D(#153,#154,#155); +#157=IFCLOCALPLACEMENT(#152,#156); +#158=IFCOWNERHISTORY(#9,#8,.READWRITE.,.MODIFIED.,1631429175,#9,#8,1631429175); +#159=IFCELEMENTASSEMBLY('122brjtWrFAPe7NkNdFNlG',#158,'Empty',$,$,#174,$,$,$,.ACCESSORY_ASSEMBLY.); +#170=IFCCARTESIANPOINT((0.,0.,0.)); +#171=IFCDIRECTION((0.,0.,1.)); +#172=IFCDIRECTION((1.,0.,0.)); +#173=IFCAXIS2PLACEMENT3D(#170,#171,#172); +#174=IFCLOCALPLACEMENT(#152,#173); +#175=IFCOWNERHISTORY(#9,#8,.READWRITE.,.MODIFIED.,1631429290,#9,#8,1631429215); +#176=IFCBEAM('2f_ancUNT1$Btb3bI4H7gM',#175,'Beam',$,$,#216,#194,$,.BEAM.); +#182=IFCINDEXEDPOLYGONALFACE((1,2,4,3)); +#183=IFCINDEXEDPOLYGONALFACE((3,4,8,7)); +#184=IFCINDEXEDPOLYGONALFACE((7,8,6,5)); +#185=IFCINDEXEDPOLYGONALFACE((5,6,2,1)); +#186=IFCINDEXEDPOLYGONALFACE((3,7,5,1)); +#187=IFCINDEXEDPOLYGONALFACE((8,4,2,6)); +#188=IFCCARTESIANPOINTLIST3D(((-1.,-1.,-1.),(-1.,-1.,1.),(-1.,1.,-1.),(-1.,1.,1.),(1.,-1.,-1.),(1.,-1.,1.),(1.,1.,-1.),(1.,1.,1.))); +#189=IFCPOLYGONALFACESET(#188,$,(#182,#183,#184,#185,#186,#187),$); +#190=IFCSHAPEREPRESENTATION(#21,'Body','Tessellation',(#189)); +#191=IFCCARTESIANPOINT((-1.,-1.,-1.)); +#192=IFCBOUNDINGBOX(#191,2.,2.,2.); +#193=IFCSHAPEREPRESENTATION(#22,'Box','BoundingBox',(#192)); +#194=IFCPRODUCTDEFINITIONSHAPE($,$,(#193,#190)); +#205=IFCOWNERHISTORY(#9,#8,.READWRITE.,.ADDED.,1631429238,#9,#8,1631429238); +#206=IFCRELAGGREGATES('1SGr7ReC13EhuetdWMr2G9',#205,$,$,#159,(#176)); +#212=IFCCARTESIANPOINT((0.,0.,-3.)); +#213=IFCDIRECTION((0.,0.,1.)); +#214=IFCDIRECTION((1.,0.,0.)); +#215=IFCAXIS2PLACEMENT3D(#212,#213,#214); +#216=IFCLOCALPLACEMENT(#174,#215); +ENDSEC; +END-ISO-10303-21; diff --git a/src/blenderbim/test/files/decomposition.ifc b/src/blenderbim/test/files/decomposition.ifc new file mode 100644 index 0000000000..77cb1b2e9f --- /dev/null +++ b/src/blenderbim/test/files/decomposition.ifc @@ -0,0 +1,122 @@ +ISO-10303-21; +HEADER; +FILE_DESCRIPTION(('ViewDefinition[DesignTransferView]'),'2;1'); +FILE_NAME('decomposition.ifc','2021-09-21T09:50:31+10:00',(),(),'IfcOpenShell 0.6.0b0','BlenderBIM 0.0.999999','Nobody'); +FILE_SCHEMA(('IFC4')); +ENDSEC; +DATA; +#1=IFCPERSON('HSeldon','Seldon','Hari',$,$,$,$,$); +#2=IFCORGANIZATION('APTR','Aperture Science',$,$,$); +#3=IFCACTORROLE(.USERDEFINED.,'CONTRIBUTOR',$); +#4=IFCTELECOMADDRESS(.USERDEFINED.,'The main webpage of the software collection.','WEBPAGE',$,$,$,$,'https://ifcopenshell.org',$); +#5=IFCTELECOMADDRESS(.USERDEFINED.,'The BlenderBIM Add-on webpage of the software collection.','WEBPAGE',$,$,$,$,'https://blenderbim.org',$); +#6=IFCTELECOMADDRESS(.USERDEFINED.,'The source code repository of the software collection.','REPOSITORY',$,$,$,$,'https://github.com/IfcOpenShell/IfcOpenShell.git',$); +#7=IFCORGANIZATION($,'IfcOpenShell','IfcOpenShell is an open source (LGPL) software library that helps users and software developers to work with the IFC file format.',(#3),(#4,#5,#6)); +#8=IFCAPPLICATION(#7,'0.0.999999','BlenderBIM Add-on','BlenderBIM'); +#9=IFCPERSONANDORGANIZATION(#1,#2,$); +#10=IFCOWNERHISTORY(#9,#8,.READWRITE.,.ADDED.,1632181704,#9,#8,1632181704); +#11=IFCPROJECT('25mhbqrQzBIhdUUHrZK_q3',#10,'My Project',$,$,$,$,(#20,#27),#15); +#12=IFCSIUNIT(*,.LENGTHUNIT.,$,.METRE.); +#13=IFCSIUNIT(*,.AREAUNIT.,$,.SQUARE_METRE.); +#14=IFCSIUNIT(*,.VOLUMEUNIT.,$,.CUBIC_METRE.); +#15=IFCUNITASSIGNMENT((#12,#13,#14)); +#16=IFCCARTESIANPOINT((0.,0.,0.)); +#17=IFCDIRECTION((0.,0.,1.)); +#18=IFCDIRECTION((1.,0.,0.)); +#19=IFCAXIS2PLACEMENT3D(#16,#17,#18); +#20=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Model',3,1.E-05,#19,$); +#21=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Body','Model',*,*,*,*,#20,$,.MODEL_VIEW.,$); +#22=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Box','Model',*,*,*,*,#20,$,.MODEL_VIEW.,$); +#23=IFCCARTESIANPOINT((0.,0.,0.)); +#24=IFCDIRECTION((0.,0.,1.)); +#25=IFCDIRECTION((1.,0.,0.)); +#26=IFCAXIS2PLACEMENT3D(#23,#24,#25); +#27=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Plan',2,1.E-05,#26,$); +#28=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Annotation','Plan',*,*,*,*,#27,$,.PLAN_VIEW.,$); +#29=IFCOWNERHISTORY(#9,#8,.READWRITE.,.MODIFIED.,1632181704,#9,#8,1632181704); +#30=IFCSITE('2nSisauITAw99Ipgpxnuc1',#29,'My Site',$,$,#56,$,$,$,$,$,$,$,$); +#36=IFCOWNERHISTORY(#9,#8,.READWRITE.,.MODIFIED.,1632181704,#9,#8,1632181704); +#37=IFCBUILDING('3VLkNpYOP6c92$32FgVhz1',#36,'My Building',$,$,#63,$,$,$,$,$,$); +#43=IFCOWNERHISTORY(#9,#8,.READWRITE.,.MODIFIED.,1632181704,#9,#8,1632181704); +#44=IFCBUILDINGSTOREY('2EXifXJjf7IvZ04BvDaAfA',#43,'My Storey',$,$,#70,$,$,$,$); +#50=IFCOWNERHISTORY(#9,#8,.READWRITE.,.ADDED.,1632181704,#9,#8,1632181704); +#51=IFCRELAGGREGATES('0riie8ARHD2P_3P8pg2s0e',#50,$,$,#11,(#30)); +#52=IFCCARTESIANPOINT((0.,0.,0.)); +#53=IFCDIRECTION((0.,0.,1.)); +#54=IFCDIRECTION((1.,0.,0.)); +#55=IFCAXIS2PLACEMENT3D(#52,#53,#54); +#56=IFCLOCALPLACEMENT($,#55); +#57=IFCOWNERHISTORY(#9,#8,.READWRITE.,.ADDED.,1632181704,#9,#8,1632181704); +#58=IFCRELAGGREGATES('3HMkYYf2j01up2RzoG1K$S',#57,$,$,#30,(#37)); +#59=IFCCARTESIANPOINT((0.,0.,0.)); +#60=IFCDIRECTION((0.,0.,1.)); +#61=IFCDIRECTION((1.,0.,0.)); +#62=IFCAXIS2PLACEMENT3D(#59,#60,#61); +#63=IFCLOCALPLACEMENT(#56,#62); +#64=IFCOWNERHISTORY(#9,#8,.READWRITE.,.ADDED.,1632181704,#9,#8,1632181704); +#65=IFCRELAGGREGATES('1r5yHIAoX7ExW8Fb$KK6Rv',#64,$,$,#37,(#44)); +#66=IFCCARTESIANPOINT((0.,0.,0.)); +#67=IFCDIRECTION((0.,0.,1.)); +#68=IFCDIRECTION((1.,0.,0.)); +#69=IFCAXIS2PLACEMENT3D(#66,#67,#68); +#70=IFCLOCALPLACEMENT(#63,#69); +#71=IFCOWNERHISTORY(#9,#8,.READWRITE.,.MODIFIED.,1632181716,#9,#8,1632181716); +#72=IFCSPACE('13wXihTNXCyASBfO8n3YgZ',#71,'Space',$,$,#102,#90,$,$,.EXTERNAL.,$); +#78=IFCINDEXEDPOLYGONALFACE((1,5,7,3)); +#79=IFCINDEXEDPOLYGONALFACE((4,3,7,8)); +#80=IFCINDEXEDPOLYGONALFACE((8,7,5,6)); +#81=IFCINDEXEDPOLYGONALFACE((6,2,4,8)); +#82=IFCINDEXEDPOLYGONALFACE((2,1,3,4)); +#83=IFCINDEXEDPOLYGONALFACE((6,5,1,2)); +#84=IFCCARTESIANPOINTLIST3D(((1.,1.,1.),(1.,1.,-1.),(1.,-1.,1.),(1.,-1.,-1.),(-1.,1.,1.),(-1.,1.,-1.),(-1.,-1.,1.),(-1.,-1.,-1.))); +#85=IFCPOLYGONALFACESET(#84,$,(#78,#79,#80,#81,#82,#83),$); +#86=IFCSHAPEREPRESENTATION(#21,'Body','Tessellation',(#85)); +#87=IFCCARTESIANPOINT((-1.,-1.,-1.)); +#88=IFCBOUNDINGBOX(#87,2.,2.,2.); +#89=IFCSHAPEREPRESENTATION(#22,'Box','BoundingBox',(#88)); +#90=IFCPRODUCTDEFINITIONSHAPE($,$,(#89,#86)); +#91=IFCCOLOURRGB($,0.800000011920929,0.800000011920929,0.800000011920929); +#92=IFCCOLOURRGB($,0.800000011920929,0.800000011920929,0.800000011920929); +#93=IFCSURFACESTYLERENDERING(#91,0.,#92,$,$,$,$,$,.NOTDEFINED.); +#94=IFCSURFACESTYLE('Material',.BOTH.,(#93)); +#95=IFCSTYLEDITEM(#85,(#94),'Material'); +#96=IFCOWNERHISTORY(#9,#8,.READWRITE.,.ADDED.,1632181716,#9,#8,1632181716); +#97=IFCRELAGGREGATES('2NuKgx$4L8OBX9R$MwP3Bf',#96,$,$,#44,(#72)); +#98=IFCCARTESIANPOINT((0.,0.,0.)); +#99=IFCDIRECTION((0.,0.,1.)); +#100=IFCDIRECTION((1.,0.,0.)); +#101=IFCAXIS2PLACEMENT3D(#98,#99,#100); +#102=IFCLOCALPLACEMENT(#70,#101); +#103=IFCOWNERHISTORY(#9,#8,.READWRITE.,.MODIFIED.,1632181774,#9,#8,1632181774); +#104=IFCELEMENTASSEMBLY('0hCUjYJgv6FwS2fAvN21mm',#103,'Assembly',$,$,#120,$,$,$,.ACCESSORY_ASSEMBLY.); +#110=IFCRELCONTAINEDINSPATIALSTRUCTURE('2zdJYp1Tr8qvLmsWlFRgz0',#151,$,$,(#104),#72); +#116=IFCCARTESIANPOINT((0.,0.,0.)); +#117=IFCDIRECTION((0.,0.,1.)); +#118=IFCDIRECTION((1.,0.,0.)); +#119=IFCAXIS2PLACEMENT3D(#116,#117,#118); +#120=IFCLOCALPLACEMENT(#102,#119); +#121=IFCOWNERHISTORY(#9,#8,.READWRITE.,.MODIFIED.,1632181807,#9,#8,1632181786); +#122=IFCBEAM('1KiE3gs9z1sfj9c0spBIGv',#121,'Beam',$,$,#158,#140,$,.BEAM.); +#128=IFCINDEXEDPOLYGONALFACE((1,2,4,3)); +#129=IFCINDEXEDPOLYGONALFACE((3,4,8,7)); +#130=IFCINDEXEDPOLYGONALFACE((7,8,6,5)); +#131=IFCINDEXEDPOLYGONALFACE((5,6,2,1)); +#132=IFCINDEXEDPOLYGONALFACE((3,7,5,1)); +#133=IFCINDEXEDPOLYGONALFACE((8,4,2,6)); +#134=IFCCARTESIANPOINTLIST3D(((-1.,-1.,-1.),(-1.,-1.,1.),(-1.,1.,-1.),(-1.,1.,1.),(1.,-1.,-1.),(1.,-1.,1.),(1.,1.,-1.),(1.,1.,1.))); +#135=IFCPOLYGONALFACESET(#134,$,(#128,#129,#130,#131,#132,#133),$); +#136=IFCSHAPEREPRESENTATION(#21,'Body','Tessellation',(#135)); +#137=IFCCARTESIANPOINT((-1.,-1.,-1.)); +#138=IFCBOUNDINGBOX(#137,2.,2.,2.); +#139=IFCSHAPEREPRESENTATION(#22,'Box','BoundingBox',(#138)); +#140=IFCPRODUCTDEFINITIONSHAPE($,$,(#139,#136)); +#151=IFCOWNERHISTORY(#9,#8,.READWRITE.,.ADDED.,1632181798,#9,#8,1632181798); +#152=IFCOWNERHISTORY(#9,#8,.READWRITE.,.ADDED.,1632181807,#9,#8,1632181807); +#153=IFCRELAGGREGATES('1N$QRzghfDgA1Hw$swtu4S',#152,$,$,#104,(#122)); +#154=IFCCARTESIANPOINT((0.,0.,0.)); +#155=IFCDIRECTION((0.,0.,1.)); +#156=IFCDIRECTION((1.,0.,0.)); +#157=IFCAXIS2PLACEMENT3D(#154,#155,#156); +#158=IFCLOCALPLACEMENT(#120,#157); +ENDSEC; +END-ISO-10303-21; diff --git a/src/blenderbim/test/files/manual-geolocation.ifc b/src/blenderbim/test/files/manual-geolocation.ifc new file mode 100644 index 0000000000..eac7e9f937 --- /dev/null +++ b/src/blenderbim/test/files/manual-geolocation.ifc @@ -0,0 +1,116 @@ +ISO-10303-21; +HEADER; +FILE_DESCRIPTION(('ViewDefinition [CoordinationView]'),'2;1'); +FILE_NAME('','2021-09-03T08:40:25',(),(),'IfcOpenShell 0.6.0b0','IfcOpenShell 0.6.0b0',''); +FILE_SCHEMA(('IFC2X3')); +ENDSEC; +DATA; +#1=IFCPERSON($,$,$,$,$,$,$,$); +#2=IFCORGANIZATION('BSI','Bentley Systems, Incorporated','http://www.bentley.com',$,$); +#3=IFCPERSONANDORGANIZATION(#1,#2,$); +#4=IFCAPPLICATION(#2,'10.05.00.54','ProStructures','ProStructures 10.05.00.54'); +#5=IFCOWNERHISTORY(#3,#4,.READONLY.,.NOCHANGE.,1630562173,$,#4,1630562173); +#6=IFCCARTESIANPOINT((0.,0.,0.)); +#7=IFCDIRECTION((0.,0.,1.)); +#8=IFCDIRECTION((1.,0.,0.)); +#9=IFCAXIS2PLACEMENT3D(#6,#7,#8); +#10=IFCLOCALPLACEMENT($,#9); +#11=IFCCARTESIANPOINT((0.,0.,0.)); +#12=IFCDIRECTION((0.,0.,1.)); +#13=IFCDIRECTION((1.,0.,0.)); +#14=IFCAXIS2PLACEMENT3D(#11,#12,#13); +#15=IFCLOCALPLACEMENT(#10,#14); +#16=IFCELEMENTASSEMBLY('0QaHit1x9DPgfaDyJawBSp',#5,'Assembly','101363101363',$,#15,$,$,$,$); +#17=IFCBUILDING('38KGkNI31AtfPlaIcH6khF',#5,'','',$,#10,$,$,.ELEMENT.,0.,0.,$); +#18=IFCCARTESIANPOINT((0.,0.,0.)); +#19=IFCDIRECTION((0.,0.,1.)); +#20=IFCDIRECTION((1.,0.,0.)); +#21=IFCAXIS2PLACEMENT3D(#18,#19,#20); +#22=IFCDIRECTION((0.,1.)); +#23=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Model',3,0.1,#21,#22); +#24=IFCSIUNIT(*,.LENGTHUNIT.,.MILLI.,.METRE.); +#25=IFCSIUNIT(*,.AREAUNIT.,$,.SQUARE_METRE.); +#26=IFCSIUNIT(*,.VOLUMEUNIT.,$,.CUBIC_METRE.); +#27=IFCDIMENSIONALEXPONENTS(0,0,0,0,0,0,0); +#28=IFCSIUNIT(*,.PLANEANGLEUNIT.,$,.RADIAN.); +#29=IFCMEASUREWITHUNIT(IFCPLANEANGLEMEASURE(0.0174532925199433),#28); +#30=IFCCONVERSIONBASEDUNIT(#27,.PLANEANGLEUNIT.,'DEGREE',#29); +#31=IFCSIUNIT(*,.TIMEUNIT.,$,.SECOND.); +#32=IFCSIUNIT(*,.MASSUNIT.,.KILO.,.GRAM.); +#33=IFCSIUNIT(*,.THERMODYNAMICTEMPERATUREUNIT.,$,.KELVIN.); +#34=IFCSIUNIT(*,.POWERUNIT.,$,.WATT.); +#35=IFCDERIVEDUNITELEMENT(#34,1); +#36=IFCSIUNIT(*,.AREAUNIT.,$,.SQUARE_METRE.); +#37=IFCDERIVEDUNITELEMENT(#36,-1); +#38=IFCSIUNIT(*,.THERMODYNAMICTEMPERATUREUNIT.,$,.KELVIN.); +#39=IFCDERIVEDUNITELEMENT(#38,-1); +#40=IFCDERIVEDUNIT((#35,#37,#39),.THERMALTRANSMITTANCEUNIT.,$); +#41=IFCUNITASSIGNMENT((#24,#25,#26,#30,#31,#32,#33,#40)); +#42=IFCPROJECT('115OE$kfH0mRwCYc1flCyS',#5,'ExampleAU','System: ProStructures 10.05.00.54',$,'',$,(#23),#41); +#43=IFCCARTESIANPOINT((268388506.986707,5774506009.46504,21900.)); +#44=IFCDIRECTION((0.950176625079621,-0.311712016373349,0.)); +#45=IFCDIRECTION((0.311712016373349,0.950176625079621,0.)); +#46=IFCAXIS2PLACEMENT3D(#43,#44,#45); +#47=IFCLOCALPLACEMENT(#15,#46); +#48=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Body','Model',*,*,*,*,#23,1.,.MODEL_VIEW.,$); +#49=IFCCARTESIANPOINT((0.,0.)); +#50=IFCCARTESIANPOINT((1780.00001049042,0.)); +#51=IFCCARTESIANPOINT((1780.00001049042,5345.)); +#52=IFCCARTESIANPOINT((0.,5345.)); +#53=IFCPOLYLINE((#49,#50,#51,#52,#49)); +#54=IFCARBITRARYCLOSEDPROFILEDEF(.AREA.,$,#53); +#55=IFCCARTESIANPOINT((-20992.5596790314,-64.9500000000407,-135.00004196167)); +#56=IFCDIRECTION((-2.03103162066398E-09,0.,1.00000000000038)); +#57=IFCDIRECTION((1.00000000000038,0.,2.03103167617513E-09)); +#58=IFCAXIS2PLACEMENT3D(#55,#56,#57); +#59=IFCDIRECTION((1.18813792138184E-10,0.,1.00000000580532)); +#60=IFCEXTRUDEDAREASOLID(#54,#58,#59,270.000001571459); +#61=IFCSHAPEREPRESENTATION(#48,'Body','SweptSolid',(#60)); +#62=IFCPRODUCTDEFINITIONSHAPE($,$,(#61)); +#63=IFCPLATE('0F$Pk9LP57qh205akVlNyn',#5,'1780 x 270 PRECAST WALL','None','PLATE',#47,#62,$); +#64=IFCCARTESIANPOINT((-10183.0595588684,2607.550000002,134.993501663208)); +#65=IFCDIRECTION((-1.00000000000033,0.,-2.35827894545793E-07)); +#66=IFCDIRECTION((-2.35827894490281E-07,0.,1.00000000000035)); +#67=IFCAXIS2PLACEMENT3D(#64,#65,#66); +#68=IFCLOCALPLACEMENT(#47,#67); +#69=IFCCARTESIANPOINT((-169.999938726425,1652.49999842624,0.)); +#70=IFCCARTESIANPOINT((-169.999938726425,1652.49999842624,20000.)); +#71=IFCCARTESIANPOINT((-416.894422292709,1652.49999842624,19999.9999990463)); +#72=IFCCARTESIANPOINT((-416.894422769547,1652.49999842624,-9.5367431640625E-07)); +#73=IFCPOLYLOOP((#69,#70,#71,#72)); +#74=IFCFACEOUTERBOUND(#73,.T.); +#75=IFCFACE((#74)); +#76=IFCCARTESIANPOINT((-416.894422292709,2832.19421503862,19999.9999990463)); +#77=IFCCARTESIANPOINT((-416.894422769547,2832.19421503862,-9.5367431640625E-07)); +#78=IFCPOLYLOOP((#76,#77,#72,#71)); +#79=IFCFACEOUTERBOUND(#78,.T.); +#80=IFCFACE((#79)); +#81=IFCCARTESIANPOINT((-169.999938726425,2832.19421503862,20000.)); +#82=IFCCARTESIANPOINT((-169.999938726425,2832.19421503862,0.)); +#83=IFCPOLYLOOP((#81,#82,#77,#76)); +#84=IFCFACEOUTERBOUND(#83,.T.); +#85=IFCFACE((#84)); +#86=IFCPOLYLOOP((#82,#81,#70,#69)); +#87=IFCFACEOUTERBOUND(#86,.T.); +#88=IFCFACE((#87)); +#89=IFCPOLYLOOP((#71,#70,#81,#76)); +#90=IFCFACEOUTERBOUND(#89,.T.); +#91=IFCFACE((#90)); +#92=IFCPOLYLOOP((#77,#82,#69,#72)); +#93=IFCFACEOUTERBOUND(#92,.T.); +#94=IFCFACE((#93)); +#95=IFCCLOSEDSHELL((#75,#80,#85,#88,#91,#94)); +#96=IFCFACETEDBREP(#95); +#97=IFCSHAPEREPRESENTATION(#48,'Body','Brep',(#96)); +#98=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Box','Model',*,*,*,*,#23,1.,.SKETCH_VIEW.,$); +#99=IFCCARTESIANPOINT((-416.894422769547,1652.49999842624,-9.5367431640625E-07)); +#100=IFCBOUNDINGBOX(#99,246.894484043121,1179.69421661238,20000.0000009537); +#101=IFCSHAPEREPRESENTATION(#98,'Box','BoundingBox',(#100)); +#102=IFCPRODUCTDEFINITIONSHAPE($,$,(#97,#101)); +#103=IFCOPENINGELEMENT('22TGcEeKjD7RG2nluv0jCh',#5,'Poly Cut','Polycut','Opening',#68,#102,$); +#104=IFCRELVOIDSELEMENT('3rwEh3UJ54rf7tGBMqmsM8',#5,$,$,#63,#103); +#105=IFCRELCONTAINEDINSPATIALSTRUCTURE('1w11zxbin7ofyN4XqMau2m',#5,$,$,(#16),#17); +#106=IFCRELAGGREGATES('18bhJupbH4qg7WQnIR8unA',#5,$,$,#42,(#17)); +#107=IFCRELAGGREGATES('1UBSdkyd9DtQ6wd4bUJ7Nj',#5,$,$,#16,(#63)); +ENDSEC; +END-ISO-10303-21; diff --git a/src/blenderbim/test/files/sample-ids.xml b/src/blenderbim/test/files/sample-ids.xml new file mode 100644 index 0000000000..4b7ee8c15c --- /dev/null +++ b/src/blenderbim/test/files/sample-ids.xml @@ -0,0 +1,37 @@ + + + + + + + IfcBuilding + + + + + attribute + name + My Building + + + + + diff --git a/src/foundationserver/bcfserver/foundation/templates/clientdata.html b/src/foundationserver/bcfserver/foundation/templates/clientdata.html deleted file mode 100644 index ee434e5861..0000000000 --- a/src/foundationserver/bcfserver/foundation/templates/clientdata.html +++ /dev/null @@ -1,17 +0,0 @@ -{%extends 'base.html'%} {%block content%} {% if user %} - -
Logged in as {{user}}
- -{% for client in clients %} -
-{{ client.client_info|tojson }}
-{{ client.client_metadata|tojson }}
-
-
-{% endfor %} {% else %} -
Not logged in
-{% endif %} {% endblock %} diff --git a/src/foundationserver/bcfserver/foundation/templates/index.html b/src/foundationserver/bcfserver/foundation/templates/index.html deleted file mode 100644 index 15d8c3b0ba..0000000000 --- a/src/foundationserver/bcfserver/foundation/templates/index.html +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - Document - - - Home Page - - diff --git a/src/foundationserver/bcfserver/foundation/templates/oauth.html b/src/foundationserver/bcfserver/foundation/templates/oauth.html deleted file mode 100644 index 98df53491d..0000000000 --- a/src/foundationserver/bcfserver/foundation/templates/oauth.html +++ /dev/null @@ -1 +0,0 @@ -{%extends "base.html"%} {%block content%} this is Oauth page {%endblock%} diff --git a/src/foundationserver/bcfserver/run.py b/src/foundationserver/bcfserver/run.py deleted file mode 100644 index d1cb4c7c5d..0000000000 --- a/src/foundationserver/bcfserver/run.py +++ /dev/null @@ -1,25 +0,0 @@ -from flask import Flask -from flask_sqlalchemy import SQLAlchemy -from flask_login import LoginManager -from flask_bcrypt import Bcrypt - - -app = Flask(__name__) -db = SQLAlchemy(app) -login_manager = LoginManager(app) -bcrypt = Bcrypt(app) -app.config["SECRET_KEY"] = "f613729206685405cde0e388" -app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///sqlite.db" -login_manager.login_view = "foundation_obj.login_page" -login_manager.login_message_category = "info" - -from foundation.routes import foundation_obj -from bcf.routes import bcf - - -app.register_blueprint(foundation_obj) -app.register_blueprint(bcf) - - -if __name__ == "__main__": - app.run(host="0.0.0.0", port=5000, debug=True) diff --git a/src/ifcbimtester/bimtester/reports.py b/src/ifcbimtester/bimtester/reports.py index c76cb9b26c..708705eb55 100644 --- a/src/ifcbimtester/bimtester/reports.py +++ b/src/ifcbimtester/bimtester/reports.py @@ -41,13 +41,16 @@ class ReportGenerator: return for scenario in feature["elements"]: - scenario_data = self.process_scenario(scenario) + scenario_data = self.process_scenario(scenario, feature) if scenario_data: data["scenarios"].append(scenario_data) data["total_passes"] = sum([s["total_passes"] for s in data["scenarios"]]) data["total_steps"] = sum([s["total_steps"] for s in data["scenarios"]]) - data["pass_rate"] = round((data["total_passes"] / data["total_steps"]) * 100) + try: + data["pass_rate"] = round((data["total_passes"] / data["total_steps"]) * 100) + except ZeroDivisionError: + data["pass_rate"] = 0 data.update(self.get_template_strings()) @@ -57,7 +60,7 @@ class ReportGenerator: ) as template: out.write(pystache.render(template.read(), data)) - def process_scenario(self, scenario): + def process_scenario(self, scenario, feature): if len(scenario["steps"]) == 0: print("Scenario '{}' in feature '{}' has no steps.".format(scenario["name"], feature["name"])) return diff --git a/src/ifcbimtester/bimtester/run.py b/src/ifcbimtester/bimtester/run.py index a80867ffeb..60d76d4074 100644 --- a/src/ifcbimtester/bimtester/run.py +++ b/src/ifcbimtester/bimtester/run.py @@ -88,7 +88,7 @@ class TestRunner: logging.basicConfig(level=logging.INFO, format="%(message)s") ids_handler = IDSHandler() logger.addHandler(ids_handler) - ids_file = ifcopenshell.ids.ids(args["feature"]) + ids_file = ifcopenshell.ids.ids.open(args["feature"]) ids_file.validate(IfcStore.file, logger) tmpdir = tempfile.mkdtemp() diff --git a/src/ifcconvert/IfcConvert.cpp b/src/ifcconvert/IfcConvert.cpp index 8076eeabf2..cd4397493a 100644 --- a/src/ifcconvert/IfcConvert.cpp +++ b/src/ifcconvert/IfcConvert.cpp @@ -1201,19 +1201,23 @@ bool init_input_file(const std::string& filename, IfcParse::IfcFile*& ifc_file, if (no_progress) { Logger::SetOutput(NULL, &log_stream); } time(&start); -#ifdef USE_MMAP - ifc_file = new IfcParse::IfcFile(filename, mmap); -#else - (void)mmap; #ifdef WITH_IFCXML if (boost::ends_with(boost::to_lower_copy(filename), ".ifcxml")) { ifc_file = IfcParse::parse_ifcxml(filename); } else #endif - ifc_file = new IfcParse::IfcFile(filename); - if (!ifc_file || !ifc_file->good()) { + + { +#ifdef USE_MMAP + ifc_file = new IfcParse::IfcFile(filename, mmap); +#else + (void)mmap; + ifc_file = new IfcParse::IfcFile(filename); #endif + } + + if (!ifc_file || !ifc_file->good()) { Logger::Error("Unable to parse input file '" + filename + "'"); return false; } diff --git a/src/ifcgeom/IfcGeomFunctions.cpp b/src/ifcgeom/IfcGeomFunctions.cpp index 28d01d353d..65d207fc08 100644 --- a/src/ifcgeom/IfcGeomFunctions.cpp +++ b/src/ifcgeom/IfcGeomFunctions.cpp @@ -2968,7 +2968,9 @@ namespace { auto result_shape = split.Shape(); std::list subs; subshapes(result_shape, subs); - if (subs.size() == 1 && operands.Size() - 2 > (int)subs.size() && (subs.front().ShapeType() == TopAbs_COMPSOLID || subs.front().ShapeType() == TopAbs_COMPOUND)) { + + // Sometimes there is more nesting of compounds, so when we find a single compound we again try to explode it into a list. + if (subs.size() == 1 && (subs.front().ShapeType() == TopAbs_COMPSOLID || subs.front().ShapeType() == TopAbs_COMPOUND)) { auto s = subs.front(); subs.clear(); subshapes(s, subs); @@ -3240,6 +3242,13 @@ bool IfcGeom::Kernel::apply_layerset(const IfcRepresentationShapeItems& items, c operands.Append(face); } + /* + // enable this is you want to see how IfcOpenShell has placed the layer surfaces + for (auto& x : operands) { + result.push_back(IfcRepresentationShapeItem(it->ItemId(), it->Placement(), x, nullptr)); + } + */ + std::vector slices; if (split(*this, it->Shape(), operands, getValue(GV_PRECISION), slices) && slices.size() == styles.size()) { for (size_t i = 0; i < slices.size(); ++i) { diff --git a/src/ifcgeom/IfcGeomShapes.cpp b/src/ifcgeom/IfcGeomShapes.cpp index 1f8d5d9b06..a784586db2 100644 --- a/src/ifcgeom/IfcGeomShapes.cpp +++ b/src/ifcgeom/IfcGeomShapes.cpp @@ -1382,7 +1382,7 @@ namespace { // @todo this probably still does not work on a closed wire consisting of one (circular) edge. - while (sorted_edges.size() < num_edges && + while ((int) sorted_edges.size() < num_edges && (!v0.IsSame(v1) || ignore_first_equality_because_closed)) { ignore_first_equality_because_closed = false; diff --git a/src/ifcgeom/IfcGeomTree.h b/src/ifcgeom/IfcGeomTree.h index 190f7e5f3c..dd86cc6ebb 100644 --- a/src/ifcgeom/IfcGeomTree.h +++ b/src/ifcgeom/IfcGeomTree.h @@ -232,7 +232,7 @@ namespace IfcGeom { const TopoDS_Shape& B = shapes_.find(*it)->second; if (extend > 0.0) { BRepExtrema_DistShapeShape dss(v, B); - if (dss.Perform() && dss.NbSolution() >= 1) { + if (dss.Perform() && dss.NbSolution() >= 1 && dss.Value() <= extend) { ts_filtered.push_back(*it); } } else { diff --git a/src/ifcopenshell-python/Makefile b/src/ifcopenshell-python/Makefile new file mode 100644 index 0000000000..7d38c58657 --- /dev/null +++ b/src/ifcopenshell-python/Makefile @@ -0,0 +1,18 @@ +.PHONY: test +test: + pytest -p no:pytest-blender test + +.PHONY: qa +qa: + black . + pylint ./* --output-format=colorized --disable all --enable E + +.PHONY: coverage +coverage: + coverage run --source ifcopenshell -m pytest -p no:pytest-blender test + coverage html + xdg-open htmlcov/index.html + +.PHONY: clean +clean: + rm -rf htmlcov diff --git a/src/ifcopenshell-python/README.md b/src/ifcopenshell-python/README.md new file mode 100644 index 0000000000..0bed56e487 --- /dev/null +++ b/src/ifcopenshell-python/README.md @@ -0,0 +1,3 @@ +# ifcopenshell-python + +Python bindings, utility functions, and high-level API for IfcOpenShell. diff --git a/src/ifcopenshell-python/ifcopenshell/alignment.py b/src/ifcopenshell-python/ifcopenshell/alignment.py index 9dc5c9bf3a..ac34537fcd 100644 --- a/src/ifcopenshell-python/ifcopenshell/alignment.py +++ b/src/ifcopenshell-python/ifcopenshell/alignment.py @@ -34,9 +34,7 @@ class circle: radius: numpy.ndarray def __call__(self, u): - return numpy.array( - [self.radius * numpy.cos(u), self.radius * numpy.sin(u), numpy.nan] - ) + return numpy.array([self.radius * numpy.cos(u), self.radius * numpy.sin(u), numpy.nan]) def place(matrix, func): diff --git a/src/ifcopenshell-python/ifcopenshell/api/attribute/data.py b/src/ifcopenshell-python/ifcopenshell/api/attribute/data.py index a6750c18fd..603bae9ab0 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/attribute/data.py +++ b/src/ifcopenshell-python/ifcopenshell/api/attribute/data.py @@ -37,12 +37,14 @@ class Data: if data_type == "enum": enum_items = ifcopenshell.util.attribute.get_enum_items(attribute) - cls.products[product_id].append({ - "name": attribute.name(), - "value": value, - "type": data_type, - "enum_items": enum_items, - "list_type": list_type, - "is_optional": attribute.optional(), - "is_null": getattr(product, attribute.name()) is None - }) + cls.products[product_id].append( + { + "name": attribute.name(), + "value": value, + "type": data_type, + "enum_items": enum_items, + "list_type": list_type, + "is_optional": attribute.optional(), + "is_null": getattr(product, attribute.name()) is None, + } + ) diff --git a/src/ifcopenshell-python/ifcopenshell/api/classification/data.py b/src/ifcopenshell-python/ifcopenshell/api/classification/data.py index 00b39ab610..30e686dcd1 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/classification/data.py +++ b/src/ifcopenshell-python/ifcopenshell/api/classification/data.py @@ -57,7 +57,7 @@ class Data: for reference in cls._file.by_type("IfcClassificationReference"): data = reference.get_info() if reference.ReferencedSource: - #data["ReferencedSource"] = cls.get_referenced_source(reference.ReferencedSource) + # data["ReferencedSource"] = cls.get_referenced_source(reference.ReferencedSource) data["ReferencedSource"] = reference.ReferencedSource.id() cls.references[reference.id()] = data diff --git a/src/ifcopenshell-python/ifcopenshell/api/classification/edit_classification.py b/src/ifcopenshell-python/ifcopenshell/api/classification/edit_classification.py index 12c60626f7..04088310f6 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/classification/edit_classification.py +++ b/src/ifcopenshell-python/ifcopenshell/api/classification/edit_classification.py @@ -1,10 +1,7 @@ class Usecase: def __init__(self, file, **settings): self.file = file - self.settings = { - "classification": None, - "attributes": {} - } + self.settings = {"classification": None, "attributes": {}} for key, value in settings.items(): self.settings[key] = value diff --git a/src/ifcopenshell-python/ifcopenshell/api/classification/edit_reference.py b/src/ifcopenshell-python/ifcopenshell/api/classification/edit_reference.py index a37391d11b..8c97468943 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/classification/edit_reference.py +++ b/src/ifcopenshell-python/ifcopenshell/api/classification/edit_reference.py @@ -1,10 +1,7 @@ class Usecase: def __init__(self, file, **settings): self.file = file - self.settings = { - "reference": None, - "attributes": {} - } + self.settings = {"reference": None, "attributes": {}} for key, value in settings.items(): self.settings[key] = value diff --git a/src/ifcopenshell-python/ifcopenshell/api/constraint/add_metric.py b/src/ifcopenshell-python/ifcopenshell/api/constraint/add_metric.py index ce273a61ec..272957d9f1 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/constraint/add_metric.py +++ b/src/ifcopenshell-python/ifcopenshell/api/constraint/add_metric.py @@ -1,5 +1,6 @@ import ifcopenshell + class Usecase: def __init__(self, file, **settings): self.file = file @@ -10,11 +11,14 @@ class Usecase: self.settings[key] = value def execute(self): - metric = self.file.create_entity("IfcMetric", **{ - "Name": "Unnamed", - "ConstraintGrade": "NOTDEFINED", - "Benchmark": "EQUALTO", - }) + metric = self.file.create_entity( + "IfcMetric", + **{ + "Name": "Unnamed", + "ConstraintGrade": "NOTDEFINED", + "Benchmark": "EQUALTO", + } + ) if self.settings["objective"]: benchmark_values = list(self.settings["objective"].BenchmarkValues or []) benchmark_values.append(metric) diff --git a/src/ifcopenshell-python/ifcopenshell/api/constraint/add_objective.py b/src/ifcopenshell-python/ifcopenshell/api/constraint/add_objective.py index a6dc8e2108..e53c8e2355 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/constraint/add_objective.py +++ b/src/ifcopenshell-python/ifcopenshell/api/constraint/add_objective.py @@ -1,5 +1,6 @@ import ifcopenshell + class Usecase: def __init__(self, file, **settings): self.file = file @@ -8,8 +9,6 @@ class Usecase: self.settings[key] = value def execute(self): - return self.file.create_entity("IfcObjective", **{ - "Name": "Unnamed", - "ConstraintGrade": "NOTDEFINED", - "ObjectiveQualifier": "NOTDEFINED" - }) + return self.file.create_entity( + "IfcObjective", **{"Name": "Unnamed", "ConstraintGrade": "NOTDEFINED", "ObjectiveQualifier": "NOTDEFINED"} + ) diff --git a/src/ifcopenshell-python/ifcopenshell/api/constraint/assign_constraint.py b/src/ifcopenshell-python/ifcopenshell/api/constraint/assign_constraint.py index 0489af80d6..e37385f170 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/constraint/assign_constraint.py +++ b/src/ifcopenshell-python/ifcopenshell/api/constraint/assign_constraint.py @@ -22,8 +22,11 @@ class Usecase: for rel in self.file.by_type("IfcRelAssociatesConstraint"): if rel.RelatingConstraint == self.settings["constraint"]: return rel - return self.file.create_entity("IfcRelAssociatesConstraint", **{ - "GlobalId": ifcopenshell.guid.new(), - # TODO: owner history - "RelatingConstraint": self.settings["constraint"] - }) + return self.file.create_entity( + "IfcRelAssociatesConstraint", + **{ + "GlobalId": ifcopenshell.guid.new(), + # TODO: owner history + "RelatingConstraint": self.settings["constraint"], + } + ) diff --git a/src/ifcopenshell-python/ifcopenshell/api/constraint/data.py b/src/ifcopenshell-python/ifcopenshell/api/constraint/data.py index 3a4791feeb..2817d79e4a 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/constraint/data.py +++ b/src/ifcopenshell-python/ifcopenshell/api/constraint/data.py @@ -15,10 +15,9 @@ class Data: cls.is_loaded = False cls.products = {} cls.objectives = {} - cls.metrics ={} + cls.metrics = {} cls.references = {} - @classmethod def load(cls, file, product_id=None): cls._file = file diff --git a/src/ifcopenshell-python/ifcopenshell/api/constraint/edit_metric.py b/src/ifcopenshell-python/ifcopenshell/api/constraint/edit_metric.py index f2859769c2..d88c6fdeb7 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/constraint/edit_metric.py +++ b/src/ifcopenshell-python/ifcopenshell/api/constraint/edit_metric.py @@ -1,10 +1,7 @@ class Usecase: def __init__(self, file, **settings): self.file = file - self.settings = { - "metric": None, - "attributes": {} - } + self.settings = {"metric": None, "attributes": {}} for key, value in settings.items(): self.settings[key] = value diff --git a/src/ifcopenshell-python/ifcopenshell/api/constraint/edit_objective.py b/src/ifcopenshell-python/ifcopenshell/api/constraint/edit_objective.py index 4b569581ee..dfb2dc593d 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/constraint/edit_objective.py +++ b/src/ifcopenshell-python/ifcopenshell/api/constraint/edit_objective.py @@ -1,10 +1,7 @@ class Usecase: def __init__(self, file, **settings): self.file = file - self.settings = { - "objective": None, - "attributes": {} - } + self.settings = {"objective": None, "attributes": {}} for key, value in settings.items(): self.settings[key] = value diff --git a/src/ifcopenshell-python/ifcopenshell/api/context/add_context.py b/src/ifcopenshell-python/ifcopenshell/api/context/add_context.py index fe96c66ded..cbe9e0b389 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/context/add_context.py +++ b/src/ifcopenshell-python/ifcopenshell/api/context/add_context.py @@ -35,12 +35,15 @@ class Usecase: project.RepresentationContexts = contexts return context parent = parent[0] - return self.file.create_entity("IfcGeometricRepresentationSubContext", **{ - "ContextIdentifier": self.settings["subcontext"], - "ContextType": self.settings["context"], - "ParentContext": parent, - "TargetView": self.settings["target_view"], - }) + return self.file.create_entity( + "IfcGeometricRepresentationSubContext", + **{ + "ContextIdentifier": self.settings["subcontext"], + "ContextType": self.settings["context"], + "ParentContext": parent, + "TargetView": self.settings["target_view"], + } + ) def create_origin(self): self.origin = self.file.createIfcAxis2Placement3D( diff --git a/src/ifcopenshell-python/ifcopenshell/api/context/data.py b/src/ifcopenshell-python/ifcopenshell/api/context/data.py index cd2380bbcf..383d520a14 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/context/data.py +++ b/src/ifcopenshell-python/ifcopenshell/api/context/data.py @@ -20,8 +20,5 @@ class Data: "ContextIdentifier": subcontext.ContextIdentifier, "TargetView": subcontext.TargetView, } - cls.contexts[int(context.id())] = { - "ContextType": context.ContextType, - "HasSubContexts": subcontexts - } + cls.contexts[int(context.id())] = {"ContextType": context.ContextType, "HasSubContexts": subcontexts} cls.is_loaded = True diff --git a/src/ifcopenshell-python/ifcopenshell/api/cost/add_cost_item_quantity.py b/src/ifcopenshell-python/ifcopenshell/api/cost/add_cost_item_quantity.py index dff2d825a0..744409687d 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/cost/add_cost_item_quantity.py +++ b/src/ifcopenshell-python/ifcopenshell/api/cost/add_cost_item_quantity.py @@ -14,8 +14,10 @@ class Usecase: # This is a bold assumption # https://forums.buildingsmart.org/t/how-does-a-cost-item-know-that-it-is-counting-a-controlled-product/3564 if self.settings["ifc_class"] == "IfcQuantityCount" and self.settings["cost_item"].Controls: + count = 0 for rel in self.settings["cost_item"].Controls: - quantity[3] += len(rel.RelatedObjects) + count += len(rel.RelatedObjects) + quantity[3] = count quantities = list(self.settings["cost_item"].CostQuantities or []) quantities.append(quantity) self.settings["cost_item"].CostQuantities = quantities diff --git a/src/ifcopenshell-python/ifcopenshell/api/cost/add_cost_value.py b/src/ifcopenshell-python/ifcopenshell/api/cost/add_cost_value.py index eb3c44f2c7..5eaaa80384 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/cost/add_cost_value.py +++ b/src/ifcopenshell-python/ifcopenshell/api/cost/add_cost_value.py @@ -11,6 +11,10 @@ class Usecase: values = list(self.settings["parent"].CostValues or []) values.append(value) self.settings["parent"].CostValues = values + elif self.settings["parent"].is_a("IfcConstructionResource"): + values = list(self.settings["parent"].BaseCosts or []) + values.append(value) + self.settings["parent"].BaseCosts = values elif self.settings["parent"].is_a("IfcCostValue"): values = list(self.settings["parent"].Components or []) values.append(value) diff --git a/src/ifcopenshell-python/ifcopenshell/api/cost/assign_cost_item_quantity.py b/src/ifcopenshell-python/ifcopenshell/api/cost/assign_cost_item_quantity.py index 21f4447383..41518aa011 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/cost/assign_cost_item_quantity.py +++ b/src/ifcopenshell-python/ifcopenshell/api/cost/assign_cost_item_quantity.py @@ -9,7 +9,8 @@ class Usecase: self.settings[key] = value def execute(self): - self.quantities = set(self.settings["cost_item"].CostQuantities or []) + if self.settings["prop_name"]: + self.quantities = set(self.settings["cost_item"].CostQuantities or []) for product in self.settings["products"]: ifcopenshell.api.run( "control.assign_control", @@ -17,8 +18,12 @@ class Usecase: related_object=product, relating_control=self.settings["cost_item"], ) - self.add_quantity_from_related_object(product) - self.settings["cost_item"].CostQuantities = list(self.quantities) + if self.settings["prop_name"]: + self.add_quantity_from_related_object(product) + if self.settings["prop_name"]: + self.settings["cost_item"].CostQuantities = list(self.quantities) + else: + self.update_cost_item_count() def add_quantity_from_related_object(self, element): if not element.is_a("IfcObject"): @@ -33,3 +38,21 @@ class Usecase: for prop in qto.Quantities: if prop.is_a("IfcPhysicalSimpleQuantity") and prop.Name.lower() == self.settings["prop_name"].lower(): self.quantities.add(prop) + + def update_cost_item_count(self): + # This is a bold assumption + # https://forums.buildingsmart.org/t/how-does-a-cost-item-know-that-it-is-counting-a-controlled-product/3564 + if not self.settings["cost_item"].CostQuantities: + return ifcopenshell.api.run( + "cost.add_cost_item_quantity", + self.file, + cost_item=self.settings["cost_item"], + ifc_class="IfcQuantityCount", + ) + if len(self.settings["cost_item"].CostQuantities) == 1: + quantity = self.settings["cost_item"].CostQuantities[0] + if quantity.is_a("IfcQuantityCount"): + count = 0 + for rel in self.settings["cost_item"].Controls: + count += len(rel.RelatedObjects) + quantity[3] = count diff --git a/src/ifcopenshell-python/ifcopenshell/api/cost/calculate_cost_item_resource_value.py b/src/ifcopenshell-python/ifcopenshell/api/cost/calculate_cost_item_resource_value.py new file mode 100644 index 0000000000..98423308f7 --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/api/cost/calculate_cost_item_resource_value.py @@ -0,0 +1,56 @@ +import ifcopenshell.api +import ifcopenshell.util.date + + +class Usecase: + def __init__(self, file, **settings): + self.file = file + self.settings = {"cost_item": None} + for key, value in settings.items(): + self.settings[key] = value + + def execute(self): + for cost_value in self.settings["cost_item"].CostValues or []: + ifcopenshell.api.run( + "cost.remove_cost_value", self.file, parent=self.settings["cost_item"], cost_value=cost_value + ) + + resources = [] + for rel in self.settings["cost_item"].Controls or []: + for related_object in rel.RelatedObjects: + if related_object.is_a("IfcConstructionResource"): + resources.append(related_object) + elif related_object.is_a("IfcTask"): + for rel2 in related_object.OperatesOn or []: + for related_object2 in rel2.RelatedObjects: + if related_object2.is_a("IfcConstructionResource"): + resources.append(related_object2) + + total_cost = 0 + for resource in resources: + cost = self.get_cost(resource) + quantity = self.get_quantity(resource) + if not cost or not quantity: + continue + total_cost += cost * quantity + + if total_cost: + cost_value = ifcopenshell.api.run("cost.add_cost_value", self.file, parent=self.settings["cost_item"]) + cost_value.AppliedValue = self.file.createIfcMonetaryMeasure(total_cost) + + def get_cost(self, resource): + total = 0 + for cost_value in resource.BaseCosts or []: + total += cost_value.AppliedValue.wrappedValue if cost_value.AppliedValue else 0 + return total + + def get_quantity(self, resource): + total = 0 + if resource.BaseQuantity: + return resource.BaseQuantity[3] + if resource.Usage and resource.Usage.ScheduleWork: + # For now we assume either hourly or daily depending on how duration is stored + duration = ifcopenshell.util.date.ifc2datetime(resource.Usage.ScheduleWork) + if duration.days: + return duration.days + return duration.seconds / 60 / 60 diff --git a/src/ifcopenshell-python/ifcopenshell/api/cost/data.py b/src/ifcopenshell-python/ifcopenshell/api/cost/data.py index fdd8d811ec..d402ed423e 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/cost/data.py +++ b/src/ifcopenshell-python/ifcopenshell/api/cost/data.py @@ -1,8 +1,139 @@ import ifcopenshell.util.date import ifcopenshell.util.unit +import ifcopenshell.util.cost -class Data: +class CostValueTrait: + @classmethod + def load_cost_values(cls, root_element, data): + data["CostValues"] = [] + data["CategoryValues"] = {} + data["UnitBasisValueComponent"] = None + data["UnitBasisUnitSymbol"] = None + data["TotalAppliedValue"] = 0.0 + data["TotalCost"] = 0.0 + if root_element.is_a("IfcCostItem"): + values = root_element.CostValues + elif root_element.is_a("IfcConstructionResource"): + values = root_element.BaseCosts + for cost_value in values or []: + cls.load_cost_value(root_element, data, cost_value) + data["CostValues"].append(cost_value.id()) + data["TotalAppliedValue"] += cls.cost_values[cost_value.id()]["AppliedValue"] + if cost_value.UnitBasis: + cost_value_data = cls.cost_values[cost_value.id()] + data["UnitBasisValueComponent"] = cost_value_data["UnitBasis"]["ValueComponent"] + data["UnitBasisUnitSymbol"] = cost_value_data["UnitBasis"]["UnitSymbol"] + if data["UnitBasisValueComponent"]: + data["TotalCost"] = data["TotalCostQuantity"] / data["UnitBasisValueComponent"] * data["TotalAppliedValue"] + else: + data["TotalCost"] = data["TotalCostQuantity"] * data["TotalAppliedValue"] + + @classmethod + def load_cost_value(cls, root_element, root_element_data, cost_value): + value_data = cost_value.get_info() + del value_data["AppliedValue"] + if value_data["UnitBasis"]: + data = cost_value.UnitBasis.get_info() + data["ValueComponent"] = data["ValueComponent"].wrappedValue + data["UnitComponent"] = data["UnitComponent"].id() + data["UnitSymbol"] = ifcopenshell.util.unit.get_unit_symbol(cost_value.UnitBasis.UnitComponent) + value_data["UnitBasis"] = data + if value_data["ApplicableDate"]: + value_data["ApplicableDate"] = ifcopenshell.util.date.ifc2datetime(value_data["ApplicableDate"]) + if value_data["FixedUntilDate"]: + value_data["FixedUntilDate"] = ifcopenshell.util.date.ifc2datetime(value_data["FixedUntilDate"]) + value_data["Components"] = [c.id() for c in value_data["Components"] or []] + value_data["AppliedValue"] = cls.calculate_applied_value(root_element, cost_value) + + if cost_value.Category not in [None, "*"]: + root_element_data["CategoryValues"].setdefault(cost_value.Category, 0) + root_element_data["CategoryValues"][cost_value.Category] += value_data["AppliedValue"] + + value_data["Formula"] = ifcopenshell.util.cost.serialise_cost_value(cost_value) + + cls.cost_values[cost_value.id()] = value_data + for component in cost_value.Components or []: + cls.load_cost_value(root_element, root_element_data, component) + + @classmethod + def calculate_applied_value(cls, root_element, cost_value, category_filter=None): + if cost_value.ArithmeticOperator and cost_value.Components: + component_values = [] + for component in cost_value.Components: + component_values.append(cls.calculate_applied_value(root_element, component, category_filter)) + if cost_value.ArithmeticOperator == "ADD": + return sum(component_values) + result = component_values.pop(0) + if cost_value.ArithmeticOperator == "DIVIDE": + for value in component_values: + try: + result /= value + except ZeroDivisionError: + pass + elif cost_value.ArithmeticOperator == "MULTIPLY": + for value in component_values: + result *= value + elif cost_value.ArithmeticOperator == "SUBTRACT": + for value in component_values: + result -= value + return result + if cost_value.Category is None: + return cls.get_primitive_applied_value(cost_value.AppliedValue) + elif cost_value.Category == "*": + if root_element.IsNestedBy: + return cls.sum_child_root_elements(root_element) + else: + return cls.get_primitive_applied_value(cost_value.AppliedValue) + elif cost_value.Category: + if root_element.IsNestedBy: + return cls.sum_child_root_elements(root_element, category_filter=cost_value.Category) + else: + return cls.get_primitive_applied_value(cost_value.AppliedValue) + return 0 + + @classmethod + def sum_child_root_elements(cls, root_element, category_filter=None): + result = 0 + for rel in root_element.IsNestedBy: + for child_root_element in rel.RelatedObjects: + if root_element.is_a("IfcCostItem"): + values = child_root_element.CostValues + elif root_element.is_a("IfcConstructionResource"): + values = child_root_element.BaseCosts + for child_cost_value in values or []: + if category_filter and child_cost_value.Category != category_filter: + continue + child_applied_value = cls.calculate_applied_value(child_root_element, child_cost_value) + child_quantity = cls.get_total_quantity(child_root_element) + if child_cost_value.UnitBasis: + value_component = child_cost_value.UnitBasis.ValueComponent.wrappedValue + result += child_quantity / value_component * child_applied_value + else: + result += child_quantity * child_applied_value + return result + + @classmethod + def get_total_quantity(cls, root_element): + if root_element.is_a("IfcCostItem"): + return sum([q[3] for q in root_element.CostQuantities or []]) or 1.0 + elif root_element.is_a("IfcConstructionResource"): + return root_element.BaseQuantity[3] if root_element.BaseQuantity else 1.0 + + @classmethod + def get_primitive_applied_value(cls, applied_value): + if not applied_value: + return 0.0 + elif isinstance(applied_value, float): + return applied_value + elif hasattr(applied_value, "wrappedValue") and isinstance(applied_value.wrappedValue, float): + return applied_value.wrappedValue + elif applied_value.is_a("IfcMeasureWithUnit"): + return applied_value.ValueComponent + assert False, "Applied value {applied_value} not implemented" + + +class Data(CostValueTrait): is_loaded = False cost_schedules = {} cost_items = {} @@ -62,7 +193,7 @@ class Data: parametric_quantities.extend(quantities) cls.cost_items[cost_item.id()] = data cls.load_cost_item_quantities(cost_item, data, parametric_quantities) - cls.load_cost_item_values(cost_item, data) + cls.load_cost_values(cost_item, data) cls.is_loaded = True @classmethod @@ -103,120 +234,3 @@ class Data: else: data["Unit"] = None data["UnitSymbol"] = None - - @classmethod - def load_cost_item_values(cls, cost_item, data): - data["CostValues"] = [] - data["CategoryValues"] = {} - data["UnitBasisValueComponent"] = None - data["UnitBasisUnitSymbol"] = None - data["TotalAppliedValue"] = 0.0 - data["TotalCost"] = 0.0 - for cost_value in cost_item.CostValues or []: - cls.load_cost_item_value(cost_item, data, cost_value) - data["CostValues"].append(cost_value.id()) - data["TotalAppliedValue"] += cls.cost_values[cost_value.id()]["AppliedValue"] - if cost_value.UnitBasis: - cost_value_data = cls.cost_values[cost_value.id()] - data["UnitBasisValueComponent"] = cost_value_data["UnitBasis"]["ValueComponent"] - data["UnitBasisUnitSymbol"] = cost_value_data["UnitBasis"]["UnitSymbol"] - if data["UnitBasisValueComponent"]: - data["TotalCost"] = ( - data["TotalCostQuantity"] / data["UnitBasisValueComponent"] * data["TotalAppliedValue"] - ) - else: - data["TotalCost"] = data["TotalCostQuantity"] * data["TotalAppliedValue"] - - @classmethod - def load_cost_item_value(cls, cost_item, cost_item_data, cost_value): - value_data = cost_value.get_info() - del value_data["AppliedValue"] - if value_data["UnitBasis"]: - data = cost_value.UnitBasis.get_info() - data["ValueComponent"] = data["ValueComponent"].wrappedValue - data["UnitComponent"] = data["UnitComponent"].id() - data["UnitSymbol"] = ifcopenshell.util.unit.get_unit_symbol(cost_value.UnitBasis.UnitComponent) - value_data["UnitBasis"] = data - if value_data["ApplicableDate"]: - value_data["ApplicableDate"] = ifcopenshell.util.date.ifc2datetime(value_data["ApplicableDate"]) - if value_data["FixedUntilDate"]: - value_data["FixedUntilDate"] = ifcopenshell.util.date.ifc2datetime(value_data["FixedUntilDate"]) - value_data["Components"] = [c.id() for c in value_data["Components"] or []] - value_data["AppliedValue"] = cls.calculate_applied_value(cost_item, cost_value) - - if cost_value.Category not in [None, "*"]: - cost_item_data["CategoryValues"].setdefault(cost_value.Category, 0) - cost_item_data["CategoryValues"][cost_value.Category] += value_data["AppliedValue"] - - cls.cost_values[cost_value.id()] = value_data - for component in cost_value.Components or []: - cls.load_cost_item_value(cost_item, cost_item_data, component) - - @classmethod - def calculate_applied_value(cls, cost_item, cost_value, category_filter=None): - if cost_value.ArithmeticOperator and cost_value.Components: - component_values = [] - for component in cost_value.Components: - component_values.append(cls.calculate_applied_value(cost_item, component, category_filter)) - if cost_value.ArithmeticOperator == "ADD": - return sum(component_values) - result = component_values.pop(0) - if cost_value.ArithmeticOperator == "DIVIDE": - for value in component_values: - try: - result /= value - except ZeroDivisionError: - pass - elif cost_value.ArithmeticOperator == "MULTIPLY": - for value in component_values: - result *= value - elif cost_value.ArithmeticOperator == "SUBTRACT": - for value in component_values: - result -= value - return result - if cost_value.Category is None: - return cls.get_primitive_applied_value(cost_value.AppliedValue) - elif cost_value.Category == "*": - if cost_item.IsNestedBy: - return cls.sum_child_cost_items(cost_item) - else: - return cls.get_primitive_applied_value(cost_value.AppliedValue) - elif cost_value.Category: - if cost_item.IsNestedBy: - return cls.sum_child_cost_items(cost_item, category_filter=cost_value.Category) - else: - return cls.get_primitive_applied_value(cost_value.AppliedValue) - return 0 - - @classmethod - def sum_child_cost_items(cls, cost_item, category_filter=None): - result = 0 - for rel in cost_item.IsNestedBy: - for child_cost_item in rel.RelatedObjects: - for child_cost_value in child_cost_item.CostValues or []: - if category_filter and child_cost_value.Category != category_filter: - continue - child_applied_value = cls.calculate_applied_value(child_cost_item, child_cost_value) - child_quantity = cls.get_total_quantity(child_cost_item) - if child_cost_value.UnitBasis: - value_component = child_cost_value.UnitBasis.ValueComponent.wrappedValue - result += child_quantity / value_component * child_applied_value - else: - result += child_quantity * child_applied_value - return result - - @classmethod - def get_total_quantity(cls, cost_item): - return sum([q[3] for q in cost_item.CostQuantities or []]) or 1.0 - - @classmethod - def get_primitive_applied_value(cls, applied_value): - if not applied_value: - return 0.0 - elif isinstance(applied_value, float): - return applied_value - elif hasattr(applied_value, "wrappedValue") and isinstance(applied_value.wrappedValue, float): - return applied_value.wrappedValue - elif applied_value.is_a("IfcMeasureWithUnit"): - return applied_value.ValueComponent - assert False, "Applied value {applied_value} not implemented" diff --git a/src/ifcopenshell-python/ifcopenshell/api/cost/edit_cost_value_formula.py b/src/ifcopenshell-python/ifcopenshell/api/cost/edit_cost_value_formula.py new file mode 100644 index 0000000000..057b7202fe --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/api/cost/edit_cost_value_formula.py @@ -0,0 +1,34 @@ +import ifcopenshell +import ifcopenshell.util.cost +import ifcopenshell.util.unit +import ifcopenshell.util.element + + +class Usecase: + def __init__(self, file, **settings): + self.file = file + self.settings = {"cost_value": None, "formula": {}} + for key, value in settings.items(): + self.settings[key] = value + + def execute(self): + try: + data = ifcopenshell.util.cost.unserialise_cost_value(self.settings["formula"], self.settings["cost_value"]) + except: + return + self.edit_cost_value(data) + + def edit_cost_value(self, data, parent=None): + ifc = data.get("ifc", None) + if not ifc: + ifc = ifcopenshell.api.run("cost.add_cost_value", self.file, parent=parent) + if "AppliedValue" in data: + if data["AppliedValue"]: + ifc.AppliedValue = self.file.createIfcMonetaryMeasure(data["AppliedValue"]) + else: + ifc.AppliedValue = None + ifc.Category = data["Category"] if "Category" in data else None + ifc.ArithmeticOperator = data["ArithmeticOperator"] if "ArithmeticOperator" in data else None + if "Components" in data: + for component in data["Components"]: + self.edit_cost_value(component, ifc) diff --git a/src/ifcopenshell-python/ifcopenshell/api/cost/remove_cost_item_value.py b/src/ifcopenshell-python/ifcopenshell/api/cost/remove_cost_value.py similarity index 78% rename from src/ifcopenshell-python/ifcopenshell/api/cost/remove_cost_item_value.py rename to src/ifcopenshell-python/ifcopenshell/api/cost/remove_cost_value.py index 58eb0b9675..e3cab8dc5f 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/cost/remove_cost_item_value.py +++ b/src/ifcopenshell-python/ifcopenshell/api/cost/remove_cost_value.py @@ -13,6 +13,10 @@ class Usecase: values = list(self.settings["parent"].CostValues) values.remove(self.settings["cost_value"]) self.settings["parent"].CostValues = values if values else None + elif self.settings["parent"].is_a("IfcConstructionResource"): + values = list(self.settings["parent"].BaseCosts) + values.remove(self.settings["cost_value"]) + self.settings["parent"].BaseCosts = values if values else None elif self.settings["parent"].is_a("IfcCostValue"): components = list(self.settings["parent"].Components) components.remove(self.settings["cost_value"]) diff --git a/src/ifcopenshell-python/ifcopenshell/api/cost/unassign_cost_item_quantity.py b/src/ifcopenshell-python/ifcopenshell/api/cost/unassign_cost_item_quantity.py index ec652a0e10..ed7b555d77 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/cost/unassign_cost_item_quantity.py +++ b/src/ifcopenshell-python/ifcopenshell/api/cost/unassign_cost_item_quantity.py @@ -19,7 +19,6 @@ class Usecase: if related_object in self.settings["products"]: self.quantities.remove(quantity) self.settings["cost_item"].CostQuantities = list(self.quantities) - for product in self.settings["products"]: ifcopenshell.api.run( "control.unassign_control", @@ -27,3 +26,15 @@ class Usecase: related_object=product, relating_control=self.settings["cost_item"], ) + self.update_cost_item_count() + + def update_cost_item_count(self): + # This is a bold assumption + # https://forums.buildingsmart.org/t/how-does-a-cost-item-know-that-it-is-counting-a-controlled-product/3564 + if len(self.settings["cost_item"].CostQuantities) == 1: + quantity = self.settings["cost_item"].CostQuantities[0] + if quantity.is_a("IfcQuantityCount"): + count = 0 + for rel in self.settings["cost_item"].Controls: + count += len(rel.RelatedObjects) + quantity[3] = count diff --git a/src/ifcopenshell-python/ifcopenshell/api/document/add_information.py b/src/ifcopenshell-python/ifcopenshell/api/document/add_information.py index bb3013e0b9..fac508ff28 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/document/add_information.py +++ b/src/ifcopenshell-python/ifcopenshell/api/document/add_information.py @@ -1,5 +1,6 @@ import ifcopenshell + class Usecase: def __init__(self, file, **settings): self.file = file @@ -9,7 +10,6 @@ class Usecase: def execute(self): id_attribute = "DocumentId" if self.file.schema == "IFC2X3" else "Identification" - return self.file.create_entity("IfcDocumentInformation", **{ - id_attribute: ifcopenshell.guid.new(), - "Name": "Unnamed" - }) + return self.file.create_entity( + "IfcDocumentInformation", **{id_attribute: ifcopenshell.guid.new(), "Name": "Unnamed"} + ) diff --git a/src/ifcopenshell-python/ifcopenshell/api/document/add_reference.py b/src/ifcopenshell-python/ifcopenshell/api/document/add_reference.py index 0e631fefa9..489721e760 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/document/add_reference.py +++ b/src/ifcopenshell-python/ifcopenshell/api/document/add_reference.py @@ -1,5 +1,6 @@ import ifcopenshell + class Usecase: def __init__(self, file, **settings): self.file = file @@ -9,6 +10,4 @@ class Usecase: def execute(self): id_attribute = "ItemReference" if self.file.schema == "IFC2X3" else "Identification" - return self.file.create_entity("IfcDocumentReference", **{ - id_attribute: ifcopenshell.guid.new() - }) + return self.file.create_entity("IfcDocumentReference", **{id_attribute: ifcopenshell.guid.new()}) diff --git a/src/ifcopenshell-python/ifcopenshell/api/document/assign_document.py b/src/ifcopenshell-python/ifcopenshell/api/document/assign_document.py index c056b988b0..5c12610ee4 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/document/assign_document.py +++ b/src/ifcopenshell-python/ifcopenshell/api/document/assign_document.py @@ -34,8 +34,11 @@ class Usecase: ): return self.settings["document"].DocumentInfoForObjects[0] - return self.file.create_entity("IfcRelAssociatesDocument", **{ - "GlobalId": ifcopenshell.guid.new(), - # TODO: owner history - "RelatingDocument": self.settings["document"] - }) + return self.file.create_entity( + "IfcRelAssociatesDocument", + **{ + "GlobalId": ifcopenshell.guid.new(), + # TODO: owner history + "RelatingDocument": self.settings["document"], + } + ) diff --git a/src/ifcopenshell-python/ifcopenshell/api/document/edit_information.py b/src/ifcopenshell-python/ifcopenshell/api/document/edit_information.py index 1a86df4186..4c19e1708f 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/document/edit_information.py +++ b/src/ifcopenshell-python/ifcopenshell/api/document/edit_information.py @@ -1,10 +1,7 @@ class Usecase: def __init__(self, file, **settings): self.file = file - self.settings = { - "information": None, - "attributes": {} - } + self.settings = {"information": None, "attributes": {}} for key, value in settings.items(): self.settings[key] = value diff --git a/src/ifcopenshell-python/ifcopenshell/api/document/edit_reference.py b/src/ifcopenshell-python/ifcopenshell/api/document/edit_reference.py index a37391d11b..8c97468943 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/document/edit_reference.py +++ b/src/ifcopenshell-python/ifcopenshell/api/document/edit_reference.py @@ -1,10 +1,7 @@ class Usecase: def __init__(self, file, **settings): self.file = file - self.settings = { - "reference": None, - "attributes": {} - } + self.settings = {"reference": None, "attributes": {}} for key, value in settings.items(): self.settings[key] = value diff --git a/src/ifcopenshell-python/ifcopenshell/api/geometry/data.py b/src/ifcopenshell-python/ifcopenshell/api/geometry/data.py index 5a58b91eaa..6a97e0d9cf 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/geometry/data.py +++ b/src/ifcopenshell-python/ifcopenshell/api/geometry/data.py @@ -28,6 +28,6 @@ class Data: "ContextType": c.ContextType, "ContextIdentifier": c.ContextIdentifier, "TargetView": c.TargetView if c.is_a("IfcGeometricRepresentationSubContext") else "", - } + }, } cls.products[product_id].append(rep_id) diff --git a/src/ifcopenshell-python/ifcopenshell/api/geometry/edit_object_placement.py b/src/ifcopenshell-python/ifcopenshell/api/geometry/edit_object_placement.py index 1400a768b3..5e0053c9aa 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/geometry/edit_object_placement.py +++ b/src/ifcopenshell-python/ifcopenshell/api/geometry/edit_object_placement.py @@ -8,7 +8,7 @@ import ifcopenshell.util.placement class Usecase: def __init__(self, file, **settings): self.file = file - self.settings = {"product": None, "matrix": np.eye(4), "should_transform_children": False} + self.settings = {"product": None, "matrix": np.eye(4), "is_si": True, "should_transform_children": False} for key, value in settings.items(): self.settings[key] = value @@ -17,31 +17,24 @@ class Usecase: return self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(self.file) + if not self.settings["is_si"]: + self.settings["matrix"][0][3] *= self.unit_scale + self.settings["matrix"][1][3] *= self.unit_scale + self.settings["matrix"][2][3] *= self.unit_scale + children_settings = [] if not self.settings["should_transform_children"]: children_settings = self.get_children_settings(self.settings["product"].ObjectPlacement) - placement_rel_to = None - if hasattr(self.settings["product"], "ContainedInStructure") and self.settings["product"].ContainedInStructure: - placement_rel_to = self.settings["product"].ContainedInStructure[0].RelatingStructure.ObjectPlacement - elif hasattr(self.settings["product"], "Decomposes") and self.settings["product"].Decomposes: - relating_object = self.settings["product"].Decomposes[0].RelatingObject - placement_rel_to = relating_object.ObjectPlacement if hasattr(relating_object, "ObjectPlacement") else None - elif hasattr(self.settings["product"], "VoidsElements") and self.settings["product"].VoidsElements: - relating_object = self.settings["product"].VoidsElements[0].RelatingBuildingElement - placement_rel_to = relating_object.ObjectPlacement if hasattr(relating_object, "ObjectPlacement") else None - elif hasattr(self.settings["product"], "FillsVoids") and self.settings["product"].FillsVoids: - relating_object = self.settings["product"].FillsVoids[0].RelatingOpeningElement - placement_rel_to = relating_object.ObjectPlacement if hasattr(relating_object, "ObjectPlacement") else None - elif hasattr(self.settings["product"], "ProjectsElements") and self.settings["product"].ProjectsElements: - relating_object = self.settings["product"].ProjectsElements[0].RelatingElement - placement_rel_to = relating_object.ObjectPlacement if hasattr(relating_object, "ObjectPlacement") else None - + placement_rel_to = self.get_placement_rel_to() placement = self.file.createIfcLocalPlacement(placement_rel_to, self.get_relative_placement(placement_rel_to)) old_placement = self.settings["product"].ObjectPlacement - if old_placement and len(self.file.get_inverse(old_placement)) == 1: - old_placement.PlacementRelTo = None + if old_placement: self.settings["product"].ObjectPlacement = None + inverses = self.file.get_inverse(old_placement) + for inverse in inverses: + ifcopenshell.util.element.replace_attribute(inverse, old_placement, placement) + old_placement.PlacementRelTo = None ifcopenshell.util.element.remove_deep(self.file, old_placement) self.settings["product"].ObjectPlacement = placement @@ -53,6 +46,22 @@ class Usecase: return placement + def get_placement_rel_to(self): + if getattr(self.settings["product"], "ContainedInStructure", None): + return self.settings["product"].ContainedInStructure[0].RelatingStructure.ObjectPlacement + elif getattr(self.settings["product"], "Decomposes", None): + relating_object = self.settings["product"].Decomposes[0].RelatingObject + return relating_object.ObjectPlacement if hasattr(relating_object, "ObjectPlacement") else None + elif getattr(self.settings["product"], "VoidsElements", None): + relating_object = self.settings["product"].VoidsElements[0].RelatingBuildingElement + return relating_object.ObjectPlacement if hasattr(relating_object, "ObjectPlacement") else None + elif getattr(self.settings["product"], "FillsVoids", None): + relating_object = self.settings["product"].FillsVoids[0].RelatingOpeningElement + return relating_object.ObjectPlacement if hasattr(relating_object, "ObjectPlacement") else None + elif getattr(self.settings["product"], "ProjectsElements", None): + relating_object = self.settings["product"].ProjectsElements[0].RelatingElement + return relating_object.ObjectPlacement if hasattr(relating_object, "ObjectPlacement") else None + def get_children_settings(self, placement): if not placement: return [] @@ -60,7 +69,7 @@ class Usecase: for referenced_placement in placement.ReferencedByPlacements: matrix = ifcopenshell.util.placement.get_local_placement(referenced_placement) for obj in referenced_placement.PlacesObject: - results.append({"product": obj, "matrix": matrix, "should_transform_children": False}) + results.append({"product": obj, "matrix": matrix, "is_si": self.settings["is_si"], "should_transform_children": False}) results.extend(self.get_children_settings(referenced_placement)) return results diff --git a/src/ifcopenshell-python/ifcopenshell/api/georeference/add_georeferencing.py b/src/ifcopenshell-python/ifcopenshell/api/georeference/add_georeferencing.py index 4d31164426..71467e7e11 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/georeference/add_georeferencing.py +++ b/src/ifcopenshell-python/ifcopenshell/api/georeference/add_georeferencing.py @@ -11,10 +11,13 @@ class Usecase: if not source_crs: return projected_crs = self.file.create_entity("IfcProjectedCRS", **{"Name": ""}) - self.file.create_entity("IfcMapConversion", **{ - "SourceCRS": source_crs, - "TargetCRS": projected_crs, - "Eastings": 0, - "Northings": 0, - "OrthogonalHeight": 0, - }) + self.file.create_entity( + "IfcMapConversion", + **{ + "SourceCRS": source_crs, + "TargetCRS": projected_crs, + "Eastings": 0, + "Northings": 0, + "OrthogonalHeight": 0, + } + ) diff --git a/src/ifcopenshell-python/ifcopenshell/api/georeference/edit_georeferencing.py b/src/ifcopenshell-python/ifcopenshell/api/georeference/edit_georeferencing.py index e4ac84221a..afbcd82a53 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/georeference/edit_georeferencing.py +++ b/src/ifcopenshell-python/ifcopenshell/api/georeference/edit_georeferencing.py @@ -1,6 +1,3 @@ -import ifcopenshell.util.unit - - class Usecase: def __init__(self, file, **settings): self.file = file @@ -8,7 +5,6 @@ class Usecase: "map_conversion": {}, "projected_crs": {}, "true_north": [], - "map_unit": "", } for key, value in settings.items(): self.settings[key] = value @@ -20,39 +16,8 @@ class Usecase: setattr(map_conversion, name, value) for name, value in self.settings["projected_crs"].items(): setattr(projected_crs, name, value) - self.remove_existing_map_unit(projected_crs) - self.set_map_unit(projected_crs) self.set_true_north() - def remove_existing_map_unit(self, projected_crs): - if projected_crs.MapUnit and len(self.file.get_inverse(projected_crs.MapUnit)) == 1: - # TODO: go deeper for conversion units - self.file.remove(projected_crs.MapUnit) - - def set_map_unit(self, projected_crs): - if not self.settings["map_unit"]: - return - - if "METRE" in self.settings["map_unit"]: - projected_crs.MapUnit = self.file.createIfcSIUnit( - None, - "LENGTHUNIT", - ifcopenshell.util.unit.get_prefix(self.settings["map_unit"]), - ifcopenshell.util.unit.get_unit_name(self.settings["map_unit"]), - ) - return - - value_component = self.file.create_entity( - "IfcReal", **{"wrappedValue": ifcopenshell.util.unit.si_conversions[self.settings["map_unit"]]} - ) - si_unit = self.file.createIfcSIUnit(None, "LENGTHUNIT", None, "METRE") - projected_crs.MapUnit = self.file.createIfcConversionBasedUnit( - self.file.createIfcDimensionalExponents(1, 0, 0, 0, 0, 0, 0), - "LENGTHUNIT", - self.settings["map_unit"], - self.file.createIfcMeasureWithUnit(value_component, si_unit), - ) - def set_true_north(self): if self.settings["true_north"] == []: return diff --git a/src/ifcopenshell-python/ifcopenshell/api/grid/create_grid_axis.py b/src/ifcopenshell-python/ifcopenshell/api/grid/create_grid_axis.py index c9817760b5..c7cd97671a 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/grid/create_grid_axis.py +++ b/src/ifcopenshell-python/ifcopenshell/api/grid/create_grid_axis.py @@ -11,10 +11,9 @@ class Usecase: self.settings[key] = value def execute(self): - element = self.file.create_entity("IfcGridAxis", **{ - "axis_tag": self.settings["axis_tag"], - "SameSense": self.settings["same_sense"] - }) + element = self.file.create_entity( + "IfcGridAxis", **{"axis_tag": self.settings["axis_tag"], "SameSense": self.settings["same_sense"]} + ) axes = list(getattr(self.settings["grid"], self.settings["uvw_axes"]) or []) axes.append(element) setattr(self.settings["grid"], self.settings["uvw_axes"], axes) diff --git a/src/ifcopenshell-python/ifcopenshell/api/group/add_group.py b/src/ifcopenshell-python/ifcopenshell/api/group/add_group.py index 1a35093ff0..5dcaadc95c 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/group/add_group.py +++ b/src/ifcopenshell-python/ifcopenshell/api/group/add_group.py @@ -10,8 +10,11 @@ class Usecase: self.settings[key] = value def execute(self): - return self.file.create_entity("IfcGroup", **{ - "GlobalId": ifcopenshell.guid.new(), - "OwnerHistory": ifcopenshell.api.run("owner.create_owner_history", self.file), - "Name": "Unnamed" - }) + return self.file.create_entity( + "IfcGroup", + **{ + "GlobalId": ifcopenshell.guid.new(), + "OwnerHistory": ifcopenshell.api.run("owner.create_owner_history", self.file), + "Name": "Unnamed", + } + ) diff --git a/src/ifcopenshell-python/ifcopenshell/api/group/assign_group.py b/src/ifcopenshell-python/ifcopenshell/api/group/assign_group.py index 34bf010531..b7d01344e3 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/group/assign_group.py +++ b/src/ifcopenshell-python/ifcopenshell/api/group/assign_group.py @@ -14,12 +14,15 @@ class Usecase: def execute(self): if not self.settings["group"].IsGroupedBy: - return self.file.create_entity("IfcRelAssignsToGroup", **{ - "GlobalId": ifcopenshell.guid.new(), - "OwnerHistory": ifcopenshell.api.run("owner.create_owner_history", self.file), - "RelatedObjects": [self.settings["product"]], - "RelatingGroup": self.settings["group"] - }) + return self.file.create_entity( + "IfcRelAssignsToGroup", + **{ + "GlobalId": ifcopenshell.guid.new(), + "OwnerHistory": ifcopenshell.api.run("owner.create_owner_history", self.file), + "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"]) diff --git a/src/ifcopenshell-python/ifcopenshell/api/group/data.py b/src/ifcopenshell-python/ifcopenshell/api/group/data.py index bcfffbe5b2..9714b36bc0 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/group/data.py +++ b/src/ifcopenshell-python/ifcopenshell/api/group/data.py @@ -21,4 +21,4 @@ class Data: data = group.get_info() del data["OwnerHistory"] cls.groups[group.id()] = data - cls.is_loaded=True + cls.is_loaded = True diff --git a/src/ifcopenshell-python/ifcopenshell/api/group/edit_group.py b/src/ifcopenshell-python/ifcopenshell/api/group/edit_group.py index 1111ce7f35..928eb6a4ee 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/group/edit_group.py +++ b/src/ifcopenshell-python/ifcopenshell/api/group/edit_group.py @@ -1,10 +1,7 @@ class Usecase: def __init__(self, file, **settings): self.file = file - self.settings = { - "group": None, - "attributes": {} - } + self.settings = {"group": None, "attributes": {}} for key, value in settings.items(): self.settings[key] = value diff --git a/src/ifcopenshell-python/ifcopenshell/api/layer/add_layer.py b/src/ifcopenshell-python/ifcopenshell/api/layer/add_layer.py index edfe94275a..1a72e5a289 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/layer/add_layer.py +++ b/src/ifcopenshell-python/ifcopenshell/api/layer/add_layer.py @@ -6,6 +6,4 @@ class Usecase: self.settings[key] = value def execute(self): - return self.file.create_entity("IfcPresentationLayerAssignment", **{ - "Name": "Unnamed" - }) + return self.file.create_entity("IfcPresentationLayerAssignment", **{"Name": "Unnamed"}) diff --git a/src/ifcopenshell-python/ifcopenshell/api/layer/edit_layer.py b/src/ifcopenshell-python/ifcopenshell/api/layer/edit_layer.py index 0c2da2e637..6404c8dfc8 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/layer/edit_layer.py +++ b/src/ifcopenshell-python/ifcopenshell/api/layer/edit_layer.py @@ -1,10 +1,7 @@ class Usecase: def __init__(self, file, **settings): self.file = file - self.settings = { - "layer": None, - "attributes": {} - } + self.settings = {"layer": None, "attributes": {}} for key, value in settings.items(): self.settings[key] = value diff --git a/src/ifcopenshell-python/ifcopenshell/api/material/add_layer.py b/src/ifcopenshell-python/ifcopenshell/api/material/add_layer.py index 5ced4395ed..eaf377ccc3 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/material/add_layer.py +++ b/src/ifcopenshell-python/ifcopenshell/api/material/add_layer.py @@ -7,10 +7,9 @@ class Usecase: def execute(self): layers = list(self.settings["layer_set"].MaterialLayers or []) - layer = self.file.create_entity("IfcMaterialLayer", **{ - "Material": self.settings["material"], - "LayerThickness": 1. - }) + layer = self.file.create_entity( + "IfcMaterialLayer", **{"Material": self.settings["material"], "LayerThickness": 1.0} + ) layers.append(layer) self.settings["layer_set"].MaterialLayers = layers return layer diff --git a/src/ifcopenshell-python/ifcopenshell/api/material/edit_assigned_material.py b/src/ifcopenshell-python/ifcopenshell/api/material/edit_assigned_material.py index f8dca15b0e..668d93fae2 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/material/edit_assigned_material.py +++ b/src/ifcopenshell-python/ifcopenshell/api/material/edit_assigned_material.py @@ -1,10 +1,7 @@ -class Usecase(): +class Usecase: def __init__(self, file, **settings): self.file = file - self.settings = { - "element": None, - "attributes": {} - } + self.settings = {"element": None, "attributes": {}} for key, value in settings.items(): self.settings[key] = value diff --git a/src/ifcopenshell-python/ifcopenshell/api/material/edit_constituent.py b/src/ifcopenshell-python/ifcopenshell/api/material/edit_constituent.py index 3ba02305d1..8f9c19361c 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/material/edit_constituent.py +++ b/src/ifcopenshell-python/ifcopenshell/api/material/edit_constituent.py @@ -1,11 +1,7 @@ -class Usecase(): +class Usecase: def __init__(self, file, **settings): self.file = file - self.settings = { - "constituent": None, - "attributes": {}, - "material": None - } + self.settings = {"constituent": None, "attributes": {}, "material": None} for key, value in settings.items(): self.settings[key] = value diff --git a/src/ifcopenshell-python/ifcopenshell/api/material/edit_layer.py b/src/ifcopenshell-python/ifcopenshell/api/material/edit_layer.py index fa4fdffca2..36ecd57b8b 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/material/edit_layer.py +++ b/src/ifcopenshell-python/ifcopenshell/api/material/edit_layer.py @@ -1,11 +1,7 @@ -class Usecase(): +class Usecase: def __init__(self, file, **settings): self.file = file - self.settings = { - "layer": None, - "attributes": {}, - "material": None - } + self.settings = {"layer": None, "attributes": {}, "material": None} for key, value in settings.items(): self.settings[key] = value diff --git a/src/ifcopenshell-python/ifcopenshell/api/material/edit_profile.py b/src/ifcopenshell-python/ifcopenshell/api/material/edit_profile.py index 00777c6dba..9ed60cd546 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/material/edit_profile.py +++ b/src/ifcopenshell-python/ifcopenshell/api/material/edit_profile.py @@ -1,12 +1,7 @@ -class Usecase(): +class Usecase: def __init__(self, file, **settings): self.file = file - self.settings = { - "profile": None, - "attributes": {}, - "profile_attributes": {}, - "material": None - } + self.settings = {"profile": None, "attributes": {}, "profile_attributes": {}, "material": None} for key, value in settings.items(): self.settings[key] = value diff --git a/src/ifcopenshell-python/ifcopenshell/api/material/edit_profile_usage.py b/src/ifcopenshell-python/ifcopenshell/api/material/edit_profile_usage.py index 2c2039e2ce..3e6e287254 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/material/edit_profile_usage.py +++ b/src/ifcopenshell-python/ifcopenshell/api/material/edit_profile_usage.py @@ -46,11 +46,14 @@ class Usecase: name = dummy_profile.attribute_name(i) if "Radius" in name and name != "RoundingRadius": dummy_profile[i] = None - dummy_solid = self.dummy.create_entity("IfcExtrudedAreaSolid", **{ - "SweptArea": dummy_profile, - "ExtrudedDirection": self.dummy.createIfcDirection((0., 0., 1.)), - "Depth": 1 - }) + dummy_solid = self.dummy.create_entity( + "IfcExtrudedAreaSolid", + **{ + "SweptArea": dummy_profile, + "ExtrudedDirection": self.dummy.createIfcDirection((0.0, 0.0, 1.0)), + "Depth": 1, + } + ) self.settings_2d = ifcopenshell.geom.settings() self.settings_2d.set(self.settings_2d.INCLUDE_CURVES, True) shape = ifcopenshell.geom.create_shape(self.settings_2d, dummy_solid) @@ -77,61 +80,61 @@ class Usecase: def get_bottom_left(self, shape): v = shape.verts x = [v[i] for i in range(0, len(v), 3)] - y = [v[i+1] for i in range(0, len(v), 3)] + y = [v[i + 1] for i in range(0, len(v), 3)] width = max(x) - min(x) height = max(y) - min(y) - return self.file.createIfcAxis2Placement3D(self.file.createIfcCartesianPoint((-width/2, height/2, 0.))) + return self.file.createIfcAxis2Placement3D(self.file.createIfcCartesianPoint((-width / 2, height / 2, 0.0))) def get_bottom_centre(self, shape): v = shape.verts - y = [v[i+1] for i in range(0, len(v), 3)] + y = [v[i + 1] for i in range(0, len(v), 3)] height = max(y) - min(y) - return self.file.createIfcAxis2Placement3D(self.file.createIfcCartesianPoint((0., height/2, 0.))) + return self.file.createIfcAxis2Placement3D(self.file.createIfcCartesianPoint((0.0, height / 2, 0.0))) def get_bottom_right(self, shape): v = shape.verts x = [v[i] for i in range(0, len(v), 3)] - y = [v[i+1] for i in range(0, len(v), 3)] + y = [v[i + 1] for i in range(0, len(v), 3)] width = max(x) - min(x) height = max(y) - min(y) - return self.file.createIfcAxis2Placement3D(self.file.createIfcCartesianPoint((width/2, height/2, 0.))) + return self.file.createIfcAxis2Placement3D(self.file.createIfcCartesianPoint((width / 2, height / 2, 0.0))) def get_mid_depth_left(self, shape): v = shape.verts x = [v[i] for i in range(0, len(v), 3)] width = max(x) - min(x) - return self.file.createIfcAxis2Placement3D(self.file.createIfcCartesianPoint((-width/2, 0., 0.))) + return self.file.createIfcAxis2Placement3D(self.file.createIfcCartesianPoint((-width / 2, 0.0, 0.0))) def get_mid_depth_centre(self, shape): - return self.file.createIfcAxis2Placement3D(self.file.createIfcCartesianPoint((0., 0., 0.))) + return self.file.createIfcAxis2Placement3D(self.file.createIfcCartesianPoint((0.0, 0.0, 0.0))) def get_mid_depth_right(self, shape): v = shape.verts x = [v[i] for i in range(0, len(v), 3)] width = max(x) - min(x) - return self.file.createIfcAxis2Placement3D(self.file.createIfcCartesianPoint((width/2, 0., 0.))) + return self.file.createIfcAxis2Placement3D(self.file.createIfcCartesianPoint((width / 2, 0.0, 0.0))) def get_top_left(self, shape): v = shape.verts x = [v[i] for i in range(0, len(v), 3)] - y = [v[i+1] for i in range(0, len(v), 3)] + y = [v[i + 1] for i in range(0, len(v), 3)] width = max(x) - min(x) height = max(y) - min(y) - return self.file.createIfcAxis2Placement3D(self.file.createIfcCartesianPoint((-width/2, -height/2, 0.))) + return self.file.createIfcAxis2Placement3D(self.file.createIfcCartesianPoint((-width / 2, -height / 2, 0.0))) def get_top_centre(self, shape): v = shape.verts - y = [v[i+1] for i in range(0, len(v), 3)] + y = [v[i + 1] for i in range(0, len(v), 3)] height = max(y) - min(y) - return self.file.createIfcAxis2Placement3D(self.file.createIfcCartesianPoint((0., -height/2, 0.))) + return self.file.createIfcAxis2Placement3D(self.file.createIfcCartesianPoint((0.0, -height / 2, 0.0))) def get_top_right(self, shape): v = shape.verts x = [v[i] for i in range(0, len(v), 3)] - y = [v[i+1] for i in range(0, len(v), 3)] + y = [v[i + 1] for i in range(0, len(v), 3)] width = max(x) - min(x) height = max(y) - min(y) - return self.file.createIfcAxis2Placement3D(self.file.createIfcCartesianPoint((width/2, -height/2, 0.))) + return self.file.createIfcAxis2Placement3D(self.file.createIfcCartesianPoint((width / 2, -height / 2, 0.0))) def update_representation(self, element): representation = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW") diff --git a/src/ifcopenshell-python/ifcopenshell/api/material/remove_list_item.py b/src/ifcopenshell-python/ifcopenshell/api/material/remove_list_item.py index 6cafec8420..bbde25bd5d 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/material/remove_list_item.py +++ b/src/ifcopenshell-python/ifcopenshell/api/material/remove_list_item.py @@ -4,10 +4,11 @@ import ifcopenshell class Usecase: def __init__(self, file, **settings): self.file = file - self.settings = {"material_list": None, "material": None} + self.settings = {"material_list": None, "material_index": None} for key, value in settings.items(): self.settings[key] = value def execute(self): - materials = [m for m in self.settings["material_list"].Materials if m != self.settings["material"]] + materials = list(self.settings["material_list"].Materials) + materials.pop(self.settings["material_index"]) self.settings["material_list"].Materials = materials diff --git a/src/ifcopenshell-python/ifcopenshell/api/owner/add_address.py b/src/ifcopenshell-python/ifcopenshell/api/owner/add_address.py index 4c00df396b..d5ae706e66 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/owner/add_address.py +++ b/src/ifcopenshell-python/ifcopenshell/api/owner/add_address.py @@ -7,7 +7,9 @@ class Usecase: def execute(self): address = self.file.create_entity(self.settings["ifc_class"], "OFFICE") - addresses = list(self.settings["assigned_object"].Addresses) if self.settings["assigned_object"].Addresses else [] + addresses = ( + list(self.settings["assigned_object"].Addresses) if self.settings["assigned_object"].Addresses else [] + ) addresses.append(address) self.settings["assigned_object"].Addresses = addresses return address diff --git a/src/ifcopenshell-python/ifcopenshell/api/owner/edit_address.py b/src/ifcopenshell-python/ifcopenshell/api/owner/edit_address.py index 4abb9d5542..9363cfdecc 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/owner/edit_address.py +++ b/src/ifcopenshell-python/ifcopenshell/api/owner/edit_address.py @@ -1,10 +1,7 @@ class Usecase: def __init__(self, file, **settings): self.file = file - self.settings = { - "address": None, - "attributes": {} - } + self.settings = {"address": None, "attributes": {}} for key, value in settings.items(): self.settings[key] = value diff --git a/src/ifcopenshell-python/ifcopenshell/api/owner/edit_organisation.py b/src/ifcopenshell-python/ifcopenshell/api/owner/edit_organisation.py index c9705ac7cb..fec153ca5c 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/owner/edit_organisation.py +++ b/src/ifcopenshell-python/ifcopenshell/api/owner/edit_organisation.py @@ -1,10 +1,7 @@ class Usecase: def __init__(self, file, **settings): self.file = file - self.settings = { - "organisation": None, - "attributes": {} - } + self.settings = {"organisation": None, "attributes": {}} for key, value in settings.items(): self.settings[key] = value diff --git a/src/ifcopenshell-python/ifcopenshell/api/owner/edit_person.py b/src/ifcopenshell-python/ifcopenshell/api/owner/edit_person.py index f8f96afe94..aa209ad31c 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/owner/edit_person.py +++ b/src/ifcopenshell-python/ifcopenshell/api/owner/edit_person.py @@ -1,10 +1,7 @@ class Usecase: def __init__(self, file, **settings): self.file = file - self.settings = { - "person": None, - "attributes": {} - } + self.settings = {"person": None, "attributes": {}} for key, value in settings.items(): self.settings[key] = value diff --git a/src/ifcopenshell-python/ifcopenshell/api/owner/edit_role.py b/src/ifcopenshell-python/ifcopenshell/api/owner/edit_role.py index fda91bee32..3b4c99d2b0 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/owner/edit_role.py +++ b/src/ifcopenshell-python/ifcopenshell/api/owner/edit_role.py @@ -1,10 +1,7 @@ class Usecase: def __init__(self, file, **settings): self.file = file - self.settings = { - "role": None, - "attributes": {} - } + self.settings = {"role": None, "attributes": {}} for key, value in settings.items(): self.settings[key] = value diff --git a/src/ifcopenshell-python/ifcopenshell/api/owner/settings.py b/src/ifcopenshell-python/ifcopenshell/api/owner/settings.py index 92ff983ad0..32519a0b6b 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/owner/settings.py +++ b/src/ifcopenshell-python/ifcopenshell/api/owner/settings.py @@ -1,9 +1,10 @@ # Note: it is the intent for you to override these with your own functions +users = {} def get_person(ifc): people = ifc.by_type("IfcPerson") or [None] - return people [0] + return people[0] def get_organisation(ifc): @@ -14,3 +15,20 @@ def get_organisation(ifc): def get_application(ifc): applications = ifc.by_type("IfcApplication") or [None] return applications[0] + + +def get_user(ifc): + person = get_person(ifc) + organisation = get_organisation(ifc) + if not person or not organisation: + return + key = f"{person.id()}-{organisation.id()}" + user = users.get(key) + if not user: + for element in ifc.by_type("IfcPersonAndOrganization"): + if element.ThePerson == person and element.TheOrganization == organisation: + users[key] = element + user = element + if not user: + return ifc.create_entity("IfcPersonAndOrganization", ThePerson=person, TheOrganization=organisation) + return user diff --git a/src/ifcopenshell-python/ifcopenshell/api/owner/update_owner_history.py b/src/ifcopenshell-python/ifcopenshell/api/owner/update_owner_history.py index f6354abe2f..e3e469f169 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/owner/update_owner_history.py +++ b/src/ifcopenshell-python/ifcopenshell/api/owner/update_owner_history.py @@ -1,6 +1,7 @@ import time import ifcopenshell import ifcopenshell.api +import ifcopenshell.util.element class Usecase: @@ -13,34 +14,20 @@ class Usecase: def execute(self): if not hasattr(self.settings["element"], "OwnerHistory"): return - self.settings["person"] = ifcopenshell.api.owner.settings.get_person(self.file) - self.settings["organisation"] = ifcopenshell.api.owner.settings.get_organisation(self.file) + user = ifcopenshell.api.owner.settings.get_user(self.file) + application = ifcopenshell.api.owner.settings.get_application(self.file) + if not user or not application: + return if not self.settings["element"].OwnerHistory: self.settings["element"].OwnerHistory = ifcopenshell.api.run( "owner.create_owner_history", self.file, **self.settings ) return self.settings["element"].OwnerHistory if len(self.file.get_inverse(self.settings["element"].OwnerHistory)) > 1: - old_history = self.settings["element"].OwnerHistory - self.settings["element"].OwnerHistory = self.file.create_entity("IfcOwnerHistory") - for i, attribute in enumerate(old_history): - self.settings["element"].OwnerHistory[i] = attribute - user = self.get_user() - application = ifcopenshell.api.owner.settings.get_application(self.file) + new = ifcopenshell.util.element.copy(self.file, self.settings["element"].OwnerHistory) + self.settings["element"].OwnerHistory = new self.settings["element"].OwnerHistory.ChangeAction = "MODIFIED" self.settings["element"].OwnerHistory.LastModifiedDate = int(time.time()) self.settings["element"].OwnerHistory.LastModifyingUser = user self.settings["element"].OwnerHistory.LastModifyingApplication = application return self.settings["element"].OwnerHistory - - def get_user(self): - for element in self.file.by_type("IfcPersonAndOrganization"): - if ( - element.ThePerson == self.settings["person"] - and element.TheOrganization == self.settings["organisation"] - ): - return element - return self.file.create_entity( - "IfcPersonAndOrganization", - **{"ThePerson": self.settings["person"], "TheOrganization": self.settings["organisation"]}, - ) diff --git a/src/ifcopenshell-python/ifcopenshell/api/profile/edit_profile.py b/src/ifcopenshell-python/ifcopenshell/api/profile/edit_profile.py index 1dd4eac2b8..58585515c8 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/profile/edit_profile.py +++ b/src/ifcopenshell-python/ifcopenshell/api/profile/edit_profile.py @@ -1,4 +1,4 @@ -class Usecase(): +class Usecase: def __init__(self, file, **settings): self.file = file self.settings = {"profile": None, "attributes": {}} diff --git a/src/ifcopenshell-python/ifcopenshell/api/pset/edit_pset.py b/src/ifcopenshell-python/ifcopenshell/api/pset/edit_pset.py index dc4d2f6c35..91d48b3a95 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/pset/edit_pset.py +++ b/src/ifcopenshell-python/ifcopenshell/api/pset/edit_pset.py @@ -39,7 +39,7 @@ class Usecase: if value is None: prop.NominalValue = None else: - primary_measure_type = self.get_primary_measure_type(prop.Name, previous_value=prop.NominalValue) + primary_measure_type = self.get_primary_measure_type(prop.Name, old_value=prop.NominalValue, new_value=value) prop.NominalValue = self.file.create_entity(primary_measure_type, value) del self.settings["properties"][prop.Name] @@ -48,7 +48,9 @@ class Usecase: for name, value in self.settings["properties"].items(): if value is None: continue - primary_measure_type = self.get_primary_measure_type(name) + primary_measure_type = self.get_primary_measure_type(name, new_value=value) + if hasattr(value, "is_a"): + value = value.wrappedValue properties.append( self.file.create_entity( "IfcPropertySingleValue", @@ -71,11 +73,22 @@ class Usecase: elif hasattr(self.settings["pset"], "Properties"): # For IfcMaterialProperties return self.settings["pset"].Properties or [] - def get_primary_measure_type(self, name, previous_value=None): - if not self.pset_template: - return previous_value.is_a() if previous_value else "IfcLabel" - for prop_template in self.pset_template.HasPropertyTemplates: - if prop_template.Name != name: - continue - return prop_template.PrimaryMeasureType or "IfcLabel" - return previous_value.is_a() if previous_value else "IfcLabel" + def get_primary_measure_type(self, name, old_value=None, new_value=None): + if self.pset_template: + for prop_template in self.pset_template.HasPropertyTemplates: + if prop_template.Name != name: + continue + return prop_template.PrimaryMeasureType or "IfcLabel" + if old_value: + return old_value.is_a() + elif new_value and hasattr(new_value, "is_a"): + return new_value.is_a() + elif new_value is not None: + if isinstance(new_value, str): + return "IfcLabel" + elif isinstance(new_value, float): + return "IfcReal" + elif isinstance(new_value, bool): + return "IfcBoolean" + elif isinstance(new_value, int): + return "IfcInteger" diff --git a/src/ifcopenshell-python/ifcopenshell/api/pset_template/add_prop_template.py b/src/ifcopenshell-python/ifcopenshell/api/pset_template/add_prop_template.py index b57b36cf82..5238609250 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/pset_template/add_prop_template.py +++ b/src/ifcopenshell-python/ifcopenshell/api/pset_template/add_prop_template.py @@ -9,13 +9,16 @@ class Usecase: 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" - }) + 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 diff --git a/src/ifcopenshell-python/ifcopenshell/api/pset_template/add_pset_template.py b/src/ifcopenshell-python/ifcopenshell/api/pset_template/add_pset_template.py index 341790790b..17cc92ee33 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/pset_template/add_pset_template.py +++ b/src/ifcopenshell-python/ifcopenshell/api/pset_template/add_pset_template.py @@ -9,9 +9,12 @@ class Usecase: self.settings[key] = value def execute(self): - self.file.create_entity("IfcPropertySetTemplate", **{ - "GlobalId": ifcopenshell.guid.new(), - "Name": "New_Pset", - "TemplateType": "PSET_TYPEDRIVENONLY", - "ApplicableEntity": "IfcTypeObject" - }) + self.file.create_entity( + "IfcPropertySetTemplate", + **{ + "GlobalId": ifcopenshell.guid.new(), + "Name": "New_Pset", + "TemplateType": "PSET_TYPEDRIVENONLY", + "ApplicableEntity": "IfcTypeObject", + } + ) diff --git a/src/ifcopenshell-python/ifcopenshell/api/pset_template/data.py b/src/ifcopenshell-python/ifcopenshell/api/pset_template/data.py index d3c9ccb040..13d7a90047 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/pset_template/data.py +++ b/src/ifcopenshell-python/ifcopenshell/api/pset_template/data.py @@ -29,6 +29,6 @@ class Data: "GlobalId": data["GlobalId"], "Name": data["Name"], "Description": data["Description"], - "PrimaryMeasureType": data["PrimaryMeasureType"] + "PrimaryMeasureType": data["PrimaryMeasureType"], } cls.is_loaded = True diff --git a/src/ifcopenshell-python/ifcopenshell/api/pset_template/remove_prop_template.py b/src/ifcopenshell-python/ifcopenshell/api/pset_template/remove_prop_template.py index 0221852eea..f08855622e 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/pset_template/remove_prop_template.py +++ b/src/ifcopenshell-python/ifcopenshell/api/pset_template/remove_prop_template.py @@ -4,9 +4,7 @@ import ifcopenshell.util.element class Usecase: def __init__(self, file, **settings): self.file = file - self.settings = { - "prop_template": None - } + self.settings = {"prop_template": None} for key, value in settings.items(): self.settings[key] = value diff --git a/src/ifcopenshell-python/ifcopenshell/api/pset_template/remove_pset_template.py b/src/ifcopenshell-python/ifcopenshell/api/pset_template/remove_pset_template.py index 16f0246782..36be2c41da 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/pset_template/remove_pset_template.py +++ b/src/ifcopenshell-python/ifcopenshell/api/pset_template/remove_pset_template.py @@ -4,9 +4,7 @@ import ifcopenshell.util.element class Usecase: def __init__(self, file, **settings): self.file = file - self.settings = { - "pset_template": None - } + self.settings = {"pset_template": None} for key, value in settings.items(): self.settings[key] = value diff --git a/src/ifcopenshell-python/ifcopenshell/api/resource/add_resource.py b/src/ifcopenshell-python/ifcopenshell/api/resource/add_resource.py index ccba773efc..2230f865b7 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/resource/add_resource.py +++ b/src/ifcopenshell-python/ifcopenshell/api/resource/add_resource.py @@ -25,11 +25,12 @@ class Usecase: # https://forums.buildingsmart.org/t/what-are-allowed-to-be-root-level-construction-resources/3550 if self.settings["parent_resource"]: ifcopenshell.api.run( - "nest.assign_object", self.file, related_object=resource, relating_object=self.settings["parent_resource"] + "nest.assign_object", + self.file, + related_object=resource, + relating_object=self.settings["parent_resource"], ) else: context = self.file.by_type("IfcContext")[0] - ifcopenshell.api.run( - "project.assign_declaration", self.file, definition=resource, relating_context=context - ) + ifcopenshell.api.run("project.assign_declaration", self.file, definition=resource, relating_context=context) return resource diff --git a/src/ifcopenshell-python/ifcopenshell/api/resource/add_resource_quantity.py b/src/ifcopenshell-python/ifcopenshell/api/resource/add_resource_quantity.py new file mode 100644 index 0000000000..6549e6d92c --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/api/resource/add_resource_quantity.py @@ -0,0 +1,18 @@ +import ifcopenshell.util.element + + +class Usecase: + def __init__(self, file, **settings): + self.file = file + self.settings = {"resource": None, "ifc_class": "IfcQuantityCount"} + for key, value in settings.items(): + self.settings[key] = value + + def execute(self): + quantity = self.file.create_entity(self.settings["ifc_class"], Name="Unnamed") + quantity[3] = 0.0 + old_quantity = self.settings["resource"].BaseQuantity + self.settings["resource"].BaseQuantity = quantity + if old_quantity: + ifcopenshell.util.element.remove_deep(self.file, old_quantity) + return quantity diff --git a/src/ifcopenshell-python/ifcopenshell/api/resource/add_resource_time.py b/src/ifcopenshell-python/ifcopenshell/api/resource/add_resource_time.py index fa2553b723..fc231d2707 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/resource/add_resource_time.py +++ b/src/ifcopenshell-python/ifcopenshell/api/resource/add_resource_time.py @@ -1,5 +1,6 @@ import ifcopenshell.util.date + class Usecase: def __init__(self, file, **settings): self.file = file diff --git a/src/ifcopenshell-python/ifcopenshell/api/resource/calculate_resource_work.py b/src/ifcopenshell-python/ifcopenshell/api/resource/calculate_resource_work.py new file mode 100644 index 0000000000..e690b79d35 --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/api/resource/calculate_resource_work.py @@ -0,0 +1,59 @@ +import math +import ifcopenshell.api +import ifcopenshell.util.date +import ifcopenshell.util.element + + +class Usecase: + def __init__(self, file, **settings): + self.file = file + self.settings = {"resource": None} + for key, value in settings.items(): + self.settings[key] = value + + def execute(self): + self.productivity = ifcopenshell.util.element.get_psets(self.settings["resource"]).get( + "EPset_Productivity", None + ) + if not self.productivity: + return + unit_consumed = self.get_unit_consumed() + self.unit_produced_name = self.productivity.get("BaseQuantityProducedName", None) + unit_produced = self.productivity.get("BaseQuantityProducedValue", None) + total_produced = self.get_total_produced() + if not unit_consumed or not unit_produced or not total_produced: + return + if not self.settings["resource"].Usage: + ifcopenshell.api.run("resource.add_resource_time", self.file, resource=self.settings["resource"]) + if "T" in self.productivity.get("BaseQuantityConsumed", None): + seconds = (unit_consumed.days * 24 * 60 * 60) + unit_consumed.seconds + amount_worked = total_produced / unit_produced * seconds + self.settings["resource"].Usage.ScheduleWork = f"PT{amount_worked / 60 / 60}H" + else: + days = unit_consumed.days + (unit_consumed.seconds / (24 * 60 * 60)) + amount_worked = total_produced / unit_produced * days + self.settings["resource"].Usage.ScheduleWork = f"P{amount_worked}D" + + def get_unit_consumed(self): + duration = self.productivity.get("BaseQuantityConsumed", None) + if not duration: + return + return ifcopenshell.util.date.ifc2datetime(duration) + + def get_total_produced(self): + total = 0 + for rel in self.settings["resource"].HasAssignments or []: + if not rel.is_a("IfcRelAssignsToProcess"): + continue + for rel2 in rel.RelatingProcess.HasAssignments or []: + if not rel2.is_a("IfcRelAssignsToProduct"): + continue + if self.unit_produced_name == "Count": + total += 1 + else: + psets = ifcopenshell.util.element.get_psets(rel2.RelatingProduct) + for pset in psets.values(): + for name, value in pset.items(): + if name == self.unit_produced_name: + total += float(value) + return total diff --git a/src/ifcopenshell-python/ifcopenshell/api/resource/data.py b/src/ifcopenshell-python/ifcopenshell/api/resource/data.py index b917e18920..410bfdaee5 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/resource/data.py +++ b/src/ifcopenshell-python/ifcopenshell/api/resource/data.py @@ -1,17 +1,20 @@ import ifcopenshell import ifcopenshell.util.date +from ifcopenshell.api.cost.data import CostValueTrait -class Data: +class Data(CostValueTrait): is_loaded = False resources = {} resource_times = {} + cost_values = {} @classmethod def purge(cls): cls.is_loaded = False cls.resources = {} cls.resource_times = {} + cls.cost_values = {} @classmethod def load(cls, file): @@ -20,11 +23,12 @@ class Data: return cls.load_resources() cls.load_resource_times() - cls.is_loaded=True + cls.is_loaded = True @classmethod def load_resources(cls): cls.resources = {} + cls.cost_values = {} for resource in cls._file.by_type("IfcResource"): data = resource.get_info() del data["OwnerHistory"] @@ -40,6 +44,13 @@ class Data: data["HasContext"] = resource.HasContext[0].RelatingContext.id() if resource.HasContext else None if resource.Usage: data["Usage"] = data["Usage"].id() + data["TotalCostQuantity"] = cls.get_total_quantity(resource) + if resource.BaseQuantity: + data["BaseQuantity"] = resource.BaseQuantity.get_info() + del data["BaseQuantity"]["Unit"] + if resource.BaseCosts: + data["BaseCosts"] = [e.id() for e in resource.BaseCosts] + cls.load_cost_values(resource, data) cls.resources[resource.id()] = data @classmethod @@ -52,6 +63,6 @@ class Data: continue if "Start" in key or "Finish" in key or key == "StatusTime": data[key] = ifcopenshell.util.date.ifc2datetime(value) - elif "Work" in key or key =="LevelingDelay": + elif "Work" in key or key == "LevelingDelay": data[key] = ifcopenshell.util.date.ifc2datetime(value) cls.resource_times[resource_time.id()] = data diff --git a/src/ifcopenshell-python/ifcopenshell/api/resource/edit_resource_quantity.py b/src/ifcopenshell-python/ifcopenshell/api/resource/edit_resource_quantity.py new file mode 100644 index 0000000000..bbf845d1f7 --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/api/resource/edit_resource_quantity.py @@ -0,0 +1,10 @@ +class Usecase: + def __init__(self, file, **settings): + self.file = file + self.settings = {"physical_quantity": 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["physical_quantity"], name, value) diff --git a/src/ifcopenshell-python/ifcopenshell/api/resource/edit_resource_time.py b/src/ifcopenshell-python/ifcopenshell/api/resource/edit_resource_time.py index b56d8c2947..46701b67d1 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/resource/edit_resource_time.py +++ b/src/ifcopenshell-python/ifcopenshell/api/resource/edit_resource_time.py @@ -18,10 +18,7 @@ class Usecase: and "ScheduleFinish" in self.settings["attributes"].keys() ): del self.settings["attributes"]["ScheduleFinish"] - if ( - self.settings["attributes"].get("ActualWork", None) - and "ActualFinish" in self.settings["attributes"].keys() - ): + if self.settings["attributes"].get("ActualWork", None) and "ActualFinish" in self.settings["attributes"].keys(): del self.settings["attributes"]["ActualFinish"] for name, value in self.settings["attributes"].items(): diff --git a/src/ifcopenshell-python/ifcopenshell/api/resource/remove_resource_quantity.py b/src/ifcopenshell-python/ifcopenshell/api/resource/remove_resource_quantity.py new file mode 100644 index 0000000000..ef16cc5669 --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/api/resource/remove_resource_quantity.py @@ -0,0 +1,15 @@ +import ifcopenshell.util.element + + +class Usecase: + def __init__(self, file, **settings): + self.file = file + self.settings = {"resource": None} + for key, value in settings.items(): + self.settings[key] = value + + def execute(self): + old_quantity = self.settings["resource"].BaseQuantity + self.settings["resource"].BaseQuantity = None + if old_quantity: + ifcopenshell.util.element.remove_deep(self.file, old_quantity) diff --git a/src/ifcopenshell-python/ifcopenshell/api/root/copy_class.py b/src/ifcopenshell-python/ifcopenshell/api/root/copy_class.py index 74a7084168..94cb1eef14 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/root/copy_class.py +++ b/src/ifcopenshell-python/ifcopenshell/api/root/copy_class.py @@ -20,12 +20,16 @@ class Usecase: def copy_indirect_attributes(self, from_element, to_element): for inverse in self.file.get_inverse(from_element): if inverse.is_a("IfcRelDefinesByProperties"): + # Properties must not be shared between objects for convenience of authoring inverse = ifcopenshell.util.element.copy(self.file, inverse) inverse.RelatedObjects = [to_element] pset = ifcopenshell.util.element.copy_deep(self.file, inverse.RelatingPropertyDefinition) inverse.RelatingPropertyDefinition = pset + elif inverse.is_a("IfcRelAggregates") and inverse.RelatingObject == from_element: + continue + elif inverse.is_a("IfcRelFillsElement"): + continue else: - # TODO: Consider whether this general approach is good or not. Maybe it isn't. for i, value in enumerate(inverse): if value == from_element: new_inverse = ifcopenshell.util.element.copy(self.file, inverse) diff --git a/src/ifcopenshell-python/ifcopenshell/api/root/create_entity.py b/src/ifcopenshell-python/ifcopenshell/api/root/create_entity.py index b0ed4132a2..a6167d6002 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/root/create_entity.py +++ b/src/ifcopenshell-python/ifcopenshell/api/root/create_entity.py @@ -14,10 +14,13 @@ class Usecase: self.settings[key] = value def execute(self): - element = self.file.create_entity(self.settings["ifc_class"], **{ - "GlobalId": ifcopenshell.guid.new(), - "OwnerHistory": ifcopenshell.api.run("owner.create_owner_history", self.file) - }) + element = self.file.create_entity( + self.settings["ifc_class"], + **{ + "GlobalId": ifcopenshell.guid.new(), + "OwnerHistory": ifcopenshell.api.run("owner.create_owner_history", self.file), + } + ) element.Name = self.settings["name"] or None if self.settings["predefined_type"]: if hasattr(element, "PredefinedType"): diff --git a/src/ifcopenshell-python/ifcopenshell/api/root/data.py b/src/ifcopenshell-python/ifcopenshell/api/root/data.py index 2fe987f5dc..f4dfafa911 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/root/data.py +++ b/src/ifcopenshell-python/ifcopenshell/api/root/data.py @@ -13,5 +13,5 @@ class Data: cls.products[product_id] = { "type": product.is_a(), "PredefinedType": product.PredefinedType if hasattr(product, "PredefinedType") else None, - "ObjectType": product.ObjectType if hasattr(product, "ObjectType") else None + "ObjectType": product.ObjectType if hasattr(product, "ObjectType") else None, } diff --git a/src/ifcopenshell-python/ifcopenshell/api/sequence/add_task_time.py b/src/ifcopenshell-python/ifcopenshell/api/sequence/add_task_time.py index b83545d38e..856fcbd592 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/sequence/add_task_time.py +++ b/src/ifcopenshell-python/ifcopenshell/api/sequence/add_task_time.py @@ -1,5 +1,6 @@ import ifcopenshell.util.date + class Usecase: def __init__(self, file, **settings): self.file = file diff --git a/src/ifcopenshell-python/ifcopenshell/api/sequence/add_work_plan.py b/src/ifcopenshell-python/ifcopenshell/api/sequence/add_work_plan.py index 3d8e2cc3ca..c205f8f333 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/sequence/add_work_plan.py +++ b/src/ifcopenshell-python/ifcopenshell/api/sequence/add_work_plan.py @@ -25,7 +25,5 @@ class Usecase: work_plan.StartTime = ifcopenshell.util.date.datetime2ifc(self.settings["start_time"], "IfcDateTime") context = self.file.by_type("IfcContext")[0] - ifcopenshell.api.run( - "project.assign_declaration", self.file, definition=work_plan, relating_context=context - ) + ifcopenshell.api.run("project.assign_declaration", self.file, definition=work_plan, relating_context=context) return work_plan diff --git a/src/ifcopenshell-python/ifcopenshell/api/sequence/calculate_task_duration.py b/src/ifcopenshell-python/ifcopenshell/api/sequence/calculate_task_duration.py new file mode 100644 index 0000000000..2c53705e74 --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/api/sequence/calculate_task_duration.py @@ -0,0 +1,70 @@ +import math +import ifcopenshell.api +import ifcopenshell.util.date +import ifcopenshell.util.element + + +class Usecase: + def __init__(self, file, **settings): + self.file = file + self.settings = {"task": None} + for key, value in settings.items(): + self.settings[key] = value + + def execute(self): + self.seconds_per_workday = self.calculate_seconds_per_workday() + duration = self.calculate_max_resource_usage_duration() + if duration: + self.set_task_duration(duration) + + def calculate_max_resource_usage_duration(self): + max_duration = 0 + for rel in self.settings["task"].OperatesOn or []: + for related_object in rel.RelatedObjects: + if related_object.is_a("IfcConstructionResource"): + duration = self.calculate_duration_in_days(related_object) + if duration and duration > max_duration: + max_duration = duration + return max_duration + + def set_task_duration(self, duration): + if not self.settings["task"].TaskTime: + ifcopenshell.api.run("sequence.add_task_time", self.file, task=self.settings["task"]) + self.settings["task"].TaskTime.ScheduleDuration = f"P{duration}D" + + def calculate_seconds_per_workday(self): + default_seconds_per_workday = 8 * 60 * 60 + work_schedule = self.get_work_schedule(self.settings["task"]) + if not work_schedule: + return default_seconds_per_workday + psets = ifcopenshell.util.element.get_psets(work_schedule) + if ( + not psets + or "Pset_WorkControlCommon" not in psets + or "WorkDayDuration" not in psets["Pset_WorkControlCommon"] + ): + return default_seconds_per_workday + work_day_duration = ifcopenshell.util.date.ifc2datetime(psets["Pset_WorkControlCommon"]["WorkDayDuration"]) + return work_day_duration.seconds + + def get_work_schedule(self, task): + for rel in task.HasAssignments or []: + if rel.is_a("IfcRelAssignsToControl") and rel.RelatingControl.is_a("IfcWorkSchedule"): + return rel.RelatingControl + for rel in task.Nests or []: + return self.get_work_schedule(rel.RelatingObject) + + def calculate_duration_in_days(self, resource): + if not resource.Usage or not resource.Usage.ScheduleWork: + return + schedule_usage = resource.Usage.ScheduleUsage or 1 + schedule_duration = ifcopenshell.util.date.ifc2datetime(resource.Usage.ScheduleWork) + if self.is_hourly_work(resource.Usage.ScheduleWork): + schedule_seconds = (schedule_duration.days * 24 * 60 * 60) + schedule_duration.seconds + else: + partial_days = schedule_duration.seconds / (24 * 60 * 60) + schedule_seconds = (schedule_duration.days + partial_days) * self.seconds_per_workday + return math.ceil((schedule_seconds / self.seconds_per_workday) / schedule_usage) + + def is_hourly_work(self, schedule_work): + return "T" in schedule_work diff --git a/src/ifcopenshell-python/ifcopenshell/api/sequence/data.py b/src/ifcopenshell-python/ifcopenshell/api/sequence/data.py index 90b70af5b3..a58fd731b2 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/sequence/data.py +++ b/src/ifcopenshell-python/ifcopenshell/api/sequence/data.py @@ -142,8 +142,14 @@ class Data: for r in task.HasAssignments if r.is_a("IfcRelAssignsToProduct") ] - [data["Resources"].extend([o.id() for o in r.RelatedObjects if o.is_a("IfcResource")]) for r in task.OperatesOn] - [data["Controls"].extend([o.id() for o in r.RelatedObjects if o.is_a("IfcControl")]) for r in task.OperatesOn] + [ + data["Resources"].extend([o.id() for o in r.RelatedObjects if o.is_a("IfcResource")]) + for r in task.OperatesOn + ] + [ + data["Controls"].extend([o.id() for o in r.RelatedObjects if o.is_a("IfcControl")]) + for r in task.OperatesOn + ] [data["Inputs"].extend([o.id() for o in r.RelatedObjects if o.is_a("IfcProduct")]) for r in task.OperatesOn] [data["IsPredecessorTo"].append(rel.id()) for rel in task.IsPredecessorTo or []] [data["IsSuccessorFrom"].append(rel.id()) for rel in task.IsSuccessorFrom or []] diff --git a/src/ifcopenshell-python/ifcopenshell/api/sequence/edit_task_time.py b/src/ifcopenshell-python/ifcopenshell/api/sequence/edit_task_time.py index 9204323477..dd9da22c15 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/sequence/edit_task_time.py +++ b/src/ifcopenshell-python/ifcopenshell/api/sequence/edit_task_time.py @@ -55,13 +55,10 @@ class Usecase: elif "ScheduleFinish" in self.settings["attributes"].keys() and self.settings["task_time"].ScheduleStart: self.calculate_duration() - if ( - self.settings["task_time"].ScheduleDuration - and ( - "ScheduleStart" in self.settings["attributes"].keys() - or "ScheduleFinish" in self.settings["attributes"].keys() - or "ScheduleDuration" in self.settings["attributes"].keys() - ) + if self.settings["task_time"].ScheduleDuration and ( + "ScheduleStart" in self.settings["attributes"].keys() + or "ScheduleFinish" in self.settings["attributes"].keys() + or "ScheduleDuration" in self.settings["attributes"].keys() ): ifcopenshell.api.run("sequence.cascade_schedule", self.file, task=self.task) diff --git a/src/ifcopenshell-python/ifcopenshell/api/sequence/remove_work_schedule.py b/src/ifcopenshell-python/ifcopenshell/api/sequence/remove_work_schedule.py index b9323223b5..acc122f79e 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/sequence/remove_work_schedule.py +++ b/src/ifcopenshell-python/ifcopenshell/api/sequence/remove_work_schedule.py @@ -19,9 +19,5 @@ class Usecase: for rel in self.settings["work_schedule"].Controls: for related_object in rel.RelatedObjects: if related_object.is_a("IfcTask"): - ifcopenshell.api.run( - "sequence.remove_task", - self.file, - task=related_object - ) + ifcopenshell.api.run("sequence.remove_task", self.file, task=related_object) self.file.remove(self.settings["work_schedule"]) diff --git a/src/ifcopenshell-python/ifcopenshell/api/sequence/unassign_lag_time.py b/src/ifcopenshell-python/ifcopenshell/api/sequence/unassign_lag_time.py index 9cf40abec1..99beeffda7 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/sequence/unassign_lag_time.py +++ b/src/ifcopenshell-python/ifcopenshell/api/sequence/unassign_lag_time.py @@ -15,6 +15,4 @@ class Usecase: self.file.remove(self.settings["rel_sequence"].TimeLag) else: self.settings["rel_sequence"].TimeLag = None - ifcopenshell.api.run( - "sequence.cascade_schedule", self.file, task=self.settings["rel_sequence"].RelatedProcess - ) + ifcopenshell.api.run("sequence.cascade_schedule", self.file, task=self.settings["rel_sequence"].RelatedProcess) diff --git a/src/ifcopenshell-python/ifcopenshell/api/spatial/assign_container.py b/src/ifcopenshell-python/ifcopenshell/api/spatial/assign_container.py index fe46877cae..40c8744b94 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/spatial/assign_container.py +++ b/src/ifcopenshell-python/ifcopenshell/api/spatial/assign_container.py @@ -51,4 +51,5 @@ class Usecase: self.file, product=self.settings["product"], matrix=ifcopenshell.util.placement.get_local_placement(self.settings["product"].ObjectPlacement), + is_si=False, ) diff --git a/src/ifcopenshell-python/ifcopenshell/api/spatial/data.py b/src/ifcopenshell-python/ifcopenshell/api/spatial/data.py index 03118e0ab0..ca8fcf716f 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/spatial/data.py +++ b/src/ifcopenshell-python/ifcopenshell/api/spatial/data.py @@ -46,5 +46,5 @@ class Data: "Name": element.Name, "LongName": element.LongName, "Decomposes": decomposes, - "IsDecomposedBy": is_decomposed_by + "IsDecomposedBy": is_decomposed_by, } diff --git a/src/ifcopenshell-python/ifcopenshell/api/structural/assign_structural_analysis_model.py b/src/ifcopenshell-python/ifcopenshell/api/structural/assign_structural_analysis_model.py index a2925e7981..a55e222db4 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/structural/assign_structural_analysis_model.py +++ b/src/ifcopenshell-python/ifcopenshell/api/structural/assign_structural_analysis_model.py @@ -14,12 +14,15 @@ class Usecase: def execute(self): if not self.settings["structural_analysis_model"].IsGroupedBy: - return self.file.create_entity("IfcRelAssignsToGroup", **{ - "GlobalId": ifcopenshell.guid.new(), - "OwnerHistory": ifcopenshell.api.run("owner.create_owner_history", self.file), - "RelatedObjects": [self.settings["product"]], - "RelatingGroup": self.settings["structural_analysis_model"] - }) + return self.file.create_entity( + "IfcRelAssignsToGroup", + **{ + "GlobalId": ifcopenshell.guid.new(), + "OwnerHistory": ifcopenshell.api.run("owner.create_owner_history", self.file), + "RelatedObjects": [self.settings["product"]], + "RelatingGroup": self.settings["structural_analysis_model"], + } + ) rel = self.settings["structural_analysis_model"].IsGroupedBy[0] related_objects = set(rel.RelatedObjects) or set() related_objects.add(self.settings["product"]) diff --git a/src/ifcopenshell-python/ifcopenshell/api/structural/data.py b/src/ifcopenshell-python/ifcopenshell/api/structural/data.py index 290f2b956c..fecb9e3ff2 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/structural/data.py +++ b/src/ifcopenshell-python/ifcopenshell/api/structural/data.py @@ -144,8 +144,6 @@ class Data: @classmethod def load_structural_connection(cls, product_id): - cls.connects_structural_members = {} - connection = cls._file.by_id(product_id) data = connection.get_info() del data["OwnerHistory"] @@ -155,7 +153,9 @@ class Data: if connection.is_a("IfcStructuralCurveConnection"): data["Axis"] = data["Axis"].id() if data["Axis"] is not None else None if connection.is_a("IfcStructuralPointConnection"): - data["ConditionCoordinateSystem"] = data["ConditionCoordinateSystem"].id() if data["ConditionCoordinateSystem"] is not None else None + data["ConditionCoordinateSystem"] = ( + data["ConditionCoordinateSystem"].id() if data["ConditionCoordinateSystem"] is not None else None + ) data["ConnectsStructuralMembers"] = [] @@ -238,7 +238,7 @@ class Data: data["Representation"] = data["Representation"].id() if data["Representation"] is not None else None if member.is_a("IfcStructuralCurveMember"): data["Axis"] = data["Axis"].id() if data["Axis"] is not None else None - + data["ConnectsStructuralActivities"] = [] data["ConnectedBy"] = [] @@ -249,6 +249,5 @@ class Data: for rel in member.ConnectedBy or []: cls.load_connects_structural_member(rel) data["ConnectedBy"].append(rel.id()) - cls.members[member.id()] = data diff --git a/src/ifcopenshell-python/ifcopenshell/api/structural/edit_structural_analysis_model.py b/src/ifcopenshell-python/ifcopenshell/api/structural/edit_structural_analysis_model.py index 9fe0dfd108..5ff29bb296 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/structural/edit_structural_analysis_model.py +++ b/src/ifcopenshell-python/ifcopenshell/api/structural/edit_structural_analysis_model.py @@ -1,10 +1,7 @@ class Usecase: def __init__(self, file, **settings): self.file = file - self.settings = { - "structural_analysis_model": None, - "attributes": {} - } + self.settings = {"structural_analysis_model": None, "attributes": {}} for key, value in settings.items(): self.settings[key] = value diff --git a/src/ifcopenshell-python/ifcopenshell/api/structural/edit_structural_boundary_condition.py b/src/ifcopenshell-python/ifcopenshell/api/structural/edit_structural_boundary_condition.py index 0862310e6f..02c0f3a612 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/structural/edit_structural_boundary_condition.py +++ b/src/ifcopenshell-python/ifcopenshell/api/structural/edit_structural_boundary_condition.py @@ -1,10 +1,7 @@ class Usecase: def __init__(self, file, **settings): self.file = file - self.settings = { - "condition": None, - "attributes": {} - } + self.settings = {"condition": None, "attributes": {}} for key, value in settings.items(): self.settings[key] = value diff --git a/src/ifcopenshell-python/ifcopenshell/api/structural/edit_structural_connection_cs.py b/src/ifcopenshell-python/ifcopenshell/api/structural/edit_structural_connection_cs.py index f466bbbe92..79c916d407 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/structural/edit_structural_connection_cs.py +++ b/src/ifcopenshell-python/ifcopenshell/api/structural/edit_structural_connection_cs.py @@ -11,7 +11,7 @@ class Usecase: ccs = self.file.createIfcAxis2Placement3D(point, None, None) self.settings["structural_item"].ConditionCoordinateSystem = ccs print(ccs) - + ccs = self.settings["structural_item"].ConditionCoordinateSystem print("use case") print(ccs) @@ -20,4 +20,4 @@ class Usecase: ccs.Axis = self.file.createIfcDirection(self.settings["axis"]) if ccs.RefDirection and len(self.file.get_inverse(ccs.RefDirection)) == 1: self.file.remove(ccs.RefDirection) - ccs.RefDirection = self.file.createIfcDirection(self.settings["ref_direction"]) \ No newline at end of file + ccs.RefDirection = self.file.createIfcDirection(self.settings["ref_direction"]) diff --git a/src/ifcopenshell-python/ifcopenshell/api/structural/edit_structural_load.py b/src/ifcopenshell-python/ifcopenshell/api/structural/edit_structural_load.py index bf60bc339c..a98d94e8fa 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/structural/edit_structural_load.py +++ b/src/ifcopenshell-python/ifcopenshell/api/structural/edit_structural_load.py @@ -1,10 +1,7 @@ class Usecase: def __init__(self, file, **settings): self.file = file - self.settings = { - "structural_load": None, - "attributes": {} - } + self.settings = {"structural_load": None, "attributes": {}} for key, value in settings.items(): self.settings[key] = value diff --git a/src/ifcopenshell-python/ifcopenshell/api/structural/remove_structural_connection_condition.py b/src/ifcopenshell-python/ifcopenshell/api/structural/remove_structural_connection_condition.py index 0b376f9895..b01dbf1443 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/structural/remove_structural_connection_condition.py +++ b/src/ifcopenshell-python/ifcopenshell/api/structural/remove_structural_connection_condition.py @@ -16,4 +16,3 @@ class Usecase: **{"connection": self.settings["relation"].RelatedStructuralConnection} ) self.file.remove(self.settings["relation"]) - diff --git a/src/ifcopenshell-python/ifcopenshell/api/style/assign_material_style.py b/src/ifcopenshell-python/ifcopenshell/api/style/assign_material_style.py index a00b623ea5..c47ca6e0e3 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/style/assign_material_style.py +++ b/src/ifcopenshell-python/ifcopenshell/api/style/assign_material_style.py @@ -29,7 +29,7 @@ class Usecase: new_items = [] removed_items = [] for item in items: - if not i.is_a("IfcStyledItem"): + if not item.is_a("IfcStyledItem"): continue if self.has_proposed_style(item): return @@ -63,8 +63,8 @@ class Usecase: def get_styled_representation(self, definition_representation): representations = [ r - for r in definition_representation.Representations if r.is_a("IfcStyledRepresentation") - and r.ContextOfItems == self.settings["context"] + for r in definition_representation.Representations + if r.is_a("IfcStyledRepresentation") and r.ContextOfItems == self.settings["context"] ] if representations: return representations[0] diff --git a/src/ifcopenshell-python/ifcopenshell/api/style/edit_style.py b/src/ifcopenshell-python/ifcopenshell/api/style/edit_presentation_style.py similarity index 100% rename from src/ifcopenshell-python/ifcopenshell/api/style/edit_style.py rename to src/ifcopenshell-python/ifcopenshell/api/style/edit_presentation_style.py diff --git a/src/ifcopenshell-python/ifcopenshell/api/style/edit_style_colours.py b/src/ifcopenshell-python/ifcopenshell/api/style/edit_style_colours.py deleted file mode 100644 index abb1e9a4ea..0000000000 --- a/src/ifcopenshell-python/ifcopenshell/api/style/edit_style_colours.py +++ /dev/null @@ -1,67 +0,0 @@ -class Usecase: - def __init__(self, file, **settings): - self.file = file - self.settings = { - "style": None, - "surface_colour": [], # RGB - "diffuse_colour": [], # RGB - "transparency": 0, - "external_definition": { - "location": None, - "identification": None, - "name": "Name" - }, - } - for key, value in settings.items(): - self.settings[key] = value - - def execute(self): - #has_external_definition = None - for element in self.file.traverse(self.settings["style"]): - if element.is_a("IfcSurfaceStyleShading"): - if element.SurfaceColour: - self.update_colour_rgb(element.SurfaceColour, self.settings["surface_colour"]) - else: - element.SurfaceColour = self.create_colour_rgb(self.settings["surface_colour"]) - element.Transparency = self.settings["transparency"] - if element.is_a("IfcSurfaceStyleRendering"): - if element.DiffuseColour: - self.update_colour_rgb(element.DiffuseColour, self.settings["diffuse_colour"]) - else: - element.DiffuseColour = self.create_colour_rgb(self.settings["diffuse_colour"]) - # TODO: Move to separate usecase - #if element.is_a("IfcExternallyDefinedSurfaceStyle"): - # element.Location = self.settings["location"] - # element.Identification = self.settings["identification"] - # element.Name = self.settings["name"] - # has_external_definition = True - #if not has_external_definition: - # styles = list(self.settings["style"].Styles) - # styles.append(self.create_externally_defined_surface_style()) - # self.settings["style"].Styles = styles - return self.settings["style"] - - def create_surface_style_rendering(self): - return self.file.create_entity("IfcSurfaceStyleRendering", **{ - "SurfaceColour": self.create_colour_rgb(self.settings["surface_colour"]), - "Transparency": self.settings["transparency"], - "ReflectanceMethod": "NOTDEFINED", - "DiffuseColour": self.create_colour_rgb(self.settings["diffuse_colour"]) - }) - - def create_externally_defined_surface_style(self): - self.file.create_entity( - "IfcExternallyDefinedSurfaceStyle", **{ - "Location": self.settings["location"], - "Identification": self.settings["identification"], - "Name": self.settings["name"], - } - ) - - def create_colour_rgb(self, colour): - return self.file.createIfcColourRgb(None, colour[0], colour[1], colour[2]) - - def update_colour_rgb(self, element, colour): - element[1] = colour[0] - element[2] = colour[1] - element[3] = colour[2] diff --git a/src/ifcopenshell-python/ifcopenshell/api/style/edit_surface_style.py b/src/ifcopenshell-python/ifcopenshell/api/style/edit_surface_style.py new file mode 100644 index 0000000000..52ae3df70e --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/api/style/edit_surface_style.py @@ -0,0 +1,45 @@ +class Usecase: + def __init__(self, file, **settings): + self.file = file + self.settings = {"style": None, "attributes": {}} + for key, value in settings.items(): + self.settings[key] = value + + def execute(self): + for key, value in self.settings["attributes"].items(): + if key == "SurfaceColour": + self.edit_surface_colour(value) + elif self.is_colour_or_factor(key): + self.edit_colour_or_factor(key, value) + else: + setattr(self.settings["style"], key, value) + + def edit_surface_colour(self, value): + self.settings["style"].SurfaceColour[1] = value[0] + self.settings["style"].SurfaceColour[2] = value[1] + self.settings["style"].SurfaceColour[3] = value[2] + + def is_colour_or_factor(self, name): + return name in [ + "DiffuseColour", + "TransmissionColour", + "DiffuseTransmissionColour", + "ReflectionColour", + "SpecularColour", + ] + + def edit_colour_or_factor(self, name, value): + if isinstance(value, (list, tuple)): + attribute = getattr(self.settings["style"], name) + if not attribute or not attribute.is_a("IfcColourRgb"): + colour = self.file.createIfcColourRgb(None, 0, 0, 0) + setattr(self.settings["style"], name, colour) + attribute = getattr(self.settings["style"], name) + attribute[1] = value[0] + attribute[2] = value[1] + attribute[3] = value[2] + else: + existing_value = getattr(self.settings["style"], name) + if existing_value and existing_value.id(): + self.file.remove(existing_value) + setattr(self.settings["style"], name, self.file.createIfcNormalisedRatioMeasure(value)) diff --git a/src/ifcopenshell-python/ifcopenshell/api/system/add_system.py b/src/ifcopenshell-python/ifcopenshell/api/system/add_system.py index b3226724c5..b340d0b3e6 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/system/add_system.py +++ b/src/ifcopenshell-python/ifcopenshell/api/system/add_system.py @@ -10,8 +10,11 @@ class Usecase: self.settings[key] = value def execute(self): - return self.file.create_entity("IfcSystem", **{ - "GlobalId": ifcopenshell.guid.new(), - "OwnerHistory": ifcopenshell.api.run("owner.create_owner_history", self.file), - "Name": "Unnamed" - }) + return self.file.create_entity( + "IfcSystem", + **{ + "GlobalId": ifcopenshell.guid.new(), + "OwnerHistory": ifcopenshell.api.run("owner.create_owner_history", self.file), + "Name": "Unnamed", + } + ) diff --git a/src/ifcopenshell-python/ifcopenshell/api/system/assign_system.py b/src/ifcopenshell-python/ifcopenshell/api/system/assign_system.py index 265afac4bd..d3c15b08c3 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/system/assign_system.py +++ b/src/ifcopenshell-python/ifcopenshell/api/system/assign_system.py @@ -14,12 +14,15 @@ class Usecase: def execute(self): if not self.settings["system"].IsGroupedBy: - return self.file.create_entity("IfcRelAssignsToGroup", **{ - "GlobalId": ifcopenshell.guid.new(), - "OwnerHistory": ifcopenshell.api.run("owner.create_owner_history", self.file), - "RelatedObjects": [self.settings["product"]], - "RelatingGroup": self.settings["system"] - }) + return self.file.create_entity( + "IfcRelAssignsToGroup", + **{ + "GlobalId": ifcopenshell.guid.new(), + "OwnerHistory": ifcopenshell.api.run("owner.create_owner_history", self.file), + "RelatedObjects": [self.settings["product"]], + "RelatingGroup": self.settings["system"], + } + ) rel = self.settings["system"].IsGroupedBy[0] related_objects = set(rel.RelatedObjects) or set() related_objects.add(self.settings["product"]) diff --git a/src/ifcopenshell-python/ifcopenshell/api/system/data.py b/src/ifcopenshell-python/ifcopenshell/api/system/data.py index 2a171f6a50..24c4fc67a9 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/system/data.py +++ b/src/ifcopenshell-python/ifcopenshell/api/system/data.py @@ -21,4 +21,4 @@ class Data: data = system.get_info() del data["OwnerHistory"] cls.systems[system.id()] = data - cls.is_loaded=True + cls.is_loaded = True diff --git a/src/ifcopenshell-python/ifcopenshell/api/system/edit_system.py b/src/ifcopenshell-python/ifcopenshell/api/system/edit_system.py index 734e9937c3..a9021e312f 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/system/edit_system.py +++ b/src/ifcopenshell-python/ifcopenshell/api/system/edit_system.py @@ -1,10 +1,7 @@ class Usecase: def __init__(self, file, **settings): self.file = file - self.settings = { - "system": None, - "attributes": {} - } + self.settings = {"system": None, "attributes": {}} for key, value in settings.items(): self.settings[key] = value diff --git a/src/ifcopenshell-python/ifcopenshell/api/type/unassign_type.py b/src/ifcopenshell-python/ifcopenshell/api/type/unassign_type.py index 3bb7d06d7f..27dd97e80b 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/type/unassign_type.py +++ b/src/ifcopenshell-python/ifcopenshell/api/type/unassign_type.py @@ -5,9 +5,7 @@ import ifcopenshell.api class Usecase: def __init__(self, file, **settings): self.file = file - self.settings = { - "related_object": None - } + self.settings = {"related_object": None} for key, value in settings.items(): self.settings[key] = value diff --git a/src/ifcopenshell-python/ifcopenshell/api/unit/assign_unit.py b/src/ifcopenshell-python/ifcopenshell/api/unit/assign_unit.py index 8ae42c7f53..5d0ad513ba 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/unit/assign_unit.py +++ b/src/ifcopenshell-python/ifcopenshell/api/unit/assign_unit.py @@ -83,6 +83,10 @@ class Usecase: name = "{}inch".format(name_prefix + " " if name_prefix else "") elif data["raw"] == "FEET": name = "{}foot".format(name_prefix + " " if name_prefix else "") + elif data["raw"] == "MILES": + name = "{}mile".format(name_prefix + " " if name_prefix else "") + elif data["raw"] == "THOU": + name = "{}thou".format(name_prefix + " " if name_prefix else "") value_component = self.file.create_entity( "IfcReal", **{"wrappedValue": ifcopenshell.util.unit.si_conversions[name]} ) diff --git a/src/ifcopenshell-python/ifcopenshell/api/unit/remove_unit.py b/src/ifcopenshell-python/ifcopenshell/api/unit/remove_unit.py index ceb8d7d401..292b2ab777 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/unit/remove_unit.py +++ b/src/ifcopenshell-python/ifcopenshell/api/unit/remove_unit.py @@ -2,7 +2,7 @@ import ifcopenshell.util.unit import ifcopenshell.util.element -class Usecase(): +class Usecase: def __init__(self, file, **settings): self.file = file self.settings = {"unit": None} diff --git a/src/ifcopenshell-python/ifcopenshell/api/void/add_filling.py b/src/ifcopenshell-python/ifcopenshell/api/void/add_filling.py index 48393d1874..64fee4a19e 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/void/add_filling.py +++ b/src/ifcopenshell-python/ifcopenshell/api/void/add_filling.py @@ -9,8 +9,18 @@ class Usecase: self.settings[key] = value def execute(self): - self.file.create_entity("IfcRelFillsElement", **{ - "GlobalId": ifcopenshell.guid.new(), - "RelatingOpeningElement": self.settings["opening"], - "RelatedBuildingElement": self.settings["element"] - }) + fills_voids = self.settings["element"].FillsVoids + + if fills_voids: + if fills_voids[0].RelatingOpeningElement == self.settings["opening"]: + return + self.file.remove(fills_voids[0]) + + self.file.create_entity( + "IfcRelFillsElement", + **{ + "GlobalId": ifcopenshell.guid.new(), + "RelatingOpeningElement": self.settings["opening"], + "RelatedBuildingElement": self.settings["element"], + } + ) diff --git a/src/ifcopenshell-python/ifcopenshell/api/void/add_opening.py b/src/ifcopenshell-python/ifcopenshell/api/void/add_opening.py index 541a35d1f3..878dfa8f46 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/void/add_opening.py +++ b/src/ifcopenshell-python/ifcopenshell/api/void/add_opening.py @@ -10,9 +10,19 @@ class Usecase: self.settings[key] = value def execute(self): - self.file.create_entity("IfcRelVoidsElement", **{ - "GlobalId": ifcopenshell.guid.new(), - "OwnerHistory": ifcopenshell.api.run("owner.create_owner_history", self.file), - "RelatingBuildingElement": self.settings["element"], - "RelatedOpeningElement": self.settings["opening"] - }) + voids_elements = self.settings["opening"].VoidsElements + + if voids_elements: + if voids_elements[0].RelatingBuildingElement == self.settings["element"]: + return + self.file.remove(voids_elements[0]) + + self.file.create_entity( + "IfcRelVoidsElement", + **{ + "GlobalId": ifcopenshell.guid.new(), + "OwnerHistory": ifcopenshell.api.run("owner.create_owner_history", self.file), + "RelatingBuildingElement": self.settings["element"], + "RelatedOpeningElement": self.settings["opening"], + } + ) diff --git a/src/ifcopenshell-python/ifcopenshell/api/void/data.py b/src/ifcopenshell-python/ifcopenshell/api/void/data.py index 1f8da638ee..9f9caad55f 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/void/data.py +++ b/src/ifcopenshell-python/ifcopenshell/api/void/data.py @@ -40,9 +40,9 @@ class Data: cls.fillings[filling_id] = {"Name": filling.Name, "FillsVoid": opening_id} cls.openings[opening_id]["HasFillings"].add(filling_id) return # See bug #1224 - #if not hasattr(product, "HasOpenings") or not product.HasOpenings: + # if not hasattr(product, "HasOpenings") or not product.HasOpenings: # return - #for rel_voids_element in product.HasOpenings: + # for rel_voids_element in product.HasOpenings: # opening = rel_voids_element.RelatedOpeningElement # opening_id = int(opening.id()) # fillings = [] diff --git a/src/ifcopenshell-python/ifcopenshell/api/void/remove_opening.py b/src/ifcopenshell-python/ifcopenshell/api/void/remove_opening.py index 4b0ac52048..ed4174e19f 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/void/remove_opening.py +++ b/src/ifcopenshell-python/ifcopenshell/api/void/remove_opening.py @@ -9,15 +9,8 @@ class Usecase: self.settings[key] = value def execute(self): - to_remove = [] # See bug #1224 - for rel in self.file.by_type("IfcRelVoidsElement"): - if rel.RelatedOpeningElement == self.settings["opening"]: - to_remove.append(rel) - break - for rel in self.file.by_type("IfcRelFillsElement"): - if rel.RelatingOpeningElement == self.settings["opening"]: - to_remove.append(rel) - break + for rel in self.settings["opening"].VoidsElements: + self.file.remove(rel) + for rel in self.settings["opening"].HasFillings: + self.file.remove(rel) ifcopenshell.api.run("root.remove_product", self.file, product=self.settings["opening"]) - for element in to_remove: - self.file.remove(element) diff --git a/src/ifcopenshell-python/ifcopenshell/express/express_parser.py b/src/ifcopenshell-python/ifcopenshell/express/express_parser.py index ddf9cf697c..2c9e30364f 100644 --- a/src/ifcopenshell-python/ifcopenshell/express/express_parser.py +++ b/src/ifcopenshell-python/ifcopenshell/express/express_parser.py @@ -1,4 +1,3 @@ - # This file is generated by IfcOpenShell ifcexpressparser bootstrap.py import os @@ -11,16 +10,21 @@ import mapping from pyparsing import * from nodes import * + def parse(fn): cache_file = fn + ".cache.dat" if os.path.exists(cache_file) and os.path.getmtime(cache_file) >= os.path.getmtime(fn): with open(cache_file, "rb") as f: m = pickle.load(f) - else: + else: ABS = (CaselessKeyword("abs")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="ABS"))("ABS") - ABSTRACT = (CaselessKeyword("abstract")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="ABSTRACT"))("ABSTRACT") + ABSTRACT = (CaselessKeyword("abstract")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="ABSTRACT"))( + "ABSTRACT" + ) ACOS = (CaselessKeyword("acos")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="ACOS"))("ACOS") - AGGREGATE = (CaselessKeyword("aggregate")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="AGGREGATE"))("AGGREGATE") + AGGREGATE = (CaselessKeyword("aggregate")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="AGGREGATE"))( + "AGGREGATE" + ) ALIAS = (CaselessKeyword("alias")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="ALIAS"))("ALIAS") AND = (CaselessKeyword("and")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="AND"))("AND") ANDOR = (CaselessKeyword("andor")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="ANDOR"))("ANDOR") @@ -29,64 +33,122 @@ def parse(fn): ASIN = (CaselessKeyword("asin")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="ASIN"))("ASIN") ATAN = (CaselessKeyword("atan")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="ATAN"))("ATAN") BAG = (CaselessKeyword("bag")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="BAG"))("BAG") - BASED_ON = (CaselessKeyword("based_on")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="BASED_ON"))("BASED_ON") + BASED_ON = (CaselessKeyword("based_on")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="BASED_ON"))( + "BASED_ON" + ) BEGIN = (CaselessKeyword("begin")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="BEGIN"))("BEGIN") BINARY = (CaselessKeyword("binary")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="BINARY"))("BINARY") - BLENGTH = (CaselessKeyword("blength")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="BLENGTH"))("BLENGTH") - BOOLEAN = (CaselessKeyword("boolean")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="BOOLEAN"))("BOOLEAN") + BLENGTH = (CaselessKeyword("blength")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="BLENGTH"))( + "BLENGTH" + ) + BOOLEAN = (CaselessKeyword("boolean")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="BOOLEAN"))( + "BOOLEAN" + ) BY = (CaselessKeyword("by")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="BY"))("BY") CASE = (CaselessKeyword("case")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="CASE"))("CASE") - CONSTANT = (CaselessKeyword("constant")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="CONSTANT"))("CONSTANT") - CONST_E = (CaselessKeyword("const_e")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="CONST_E"))("CONST_E") + CONSTANT = (CaselessKeyword("constant")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="CONSTANT"))( + "CONSTANT" + ) + CONST_E = (CaselessKeyword("const_e")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="CONST_E"))( + "CONST_E" + ) COS = (CaselessKeyword("cos")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="COS"))("COS") DERIVE = (CaselessKeyword("derive")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="DERIVE"))("DERIVE") DIV = (CaselessKeyword("div")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="DIV"))("DIV") ELSE = (CaselessKeyword("else")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="ELSE"))("ELSE") END = (CaselessKeyword("end")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="END"))("END") - END_ALIAS = (CaselessKeyword("end_alias")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="END_ALIAS"))("END_ALIAS") - END_CASE = (CaselessKeyword("end_case")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="END_CASE"))("END_CASE") - END_CONSTANT = (CaselessKeyword("end_constant")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="END_CONSTANT"))("END_CONSTANT") - END_ENTITY = (CaselessKeyword("end_entity")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="END_ENTITY"))("END_ENTITY") - END_FUNCTION = (CaselessKeyword("end_function")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="END_FUNCTION"))("END_FUNCTION") + END_ALIAS = (CaselessKeyword("end_alias")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="END_ALIAS"))( + "END_ALIAS" + ) + END_CASE = (CaselessKeyword("end_case")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="END_CASE"))( + "END_CASE" + ) + END_CONSTANT = (CaselessKeyword("end_constant")).setParseAction( + lambda s, loc, t: Node(s, loc, t, rule="END_CONSTANT") + )("END_CONSTANT") + END_ENTITY = (CaselessKeyword("end_entity")).setParseAction( + lambda s, loc, t: Node(s, loc, t, rule="END_ENTITY") + )("END_ENTITY") + END_FUNCTION = (CaselessKeyword("end_function")).setParseAction( + lambda s, loc, t: Node(s, loc, t, rule="END_FUNCTION") + )("END_FUNCTION") END_IF = (CaselessKeyword("end_if")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="END_IF"))("END_IF") - END_LOCAL = (CaselessKeyword("end_local")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="END_LOCAL"))("END_LOCAL") - END_PROCEDURE = (CaselessKeyword("end_procedure")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="END_PROCEDURE"))("END_PROCEDURE") - END_REPEAT = (CaselessKeyword("end_repeat")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="END_REPEAT"))("END_REPEAT") - END_RULE = (CaselessKeyword("end_rule")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="END_RULE"))("END_RULE") - END_SCHEMA = (CaselessKeyword("end_schema")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="END_SCHEMA"))("END_SCHEMA") - END_SUBTYPE_CONSTRAINT = (CaselessKeyword("end_subtype_constraint")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="END_SUBTYPE_CONSTRAINT"))("END_SUBTYPE_CONSTRAINT") - END_TYPE = (CaselessKeyword("end_type")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="END_TYPE"))("END_TYPE") + END_LOCAL = (CaselessKeyword("end_local")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="END_LOCAL"))( + "END_LOCAL" + ) + END_PROCEDURE = (CaselessKeyword("end_procedure")).setParseAction( + lambda s, loc, t: Node(s, loc, t, rule="END_PROCEDURE") + )("END_PROCEDURE") + END_REPEAT = (CaselessKeyword("end_repeat")).setParseAction( + lambda s, loc, t: Node(s, loc, t, rule="END_REPEAT") + )("END_REPEAT") + END_RULE = (CaselessKeyword("end_rule")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="END_RULE"))( + "END_RULE" + ) + END_SCHEMA = (CaselessKeyword("end_schema")).setParseAction( + lambda s, loc, t: Node(s, loc, t, rule="END_SCHEMA") + )("END_SCHEMA") + END_SUBTYPE_CONSTRAINT = (CaselessKeyword("end_subtype_constraint")).setParseAction( + lambda s, loc, t: Node(s, loc, t, rule="END_SUBTYPE_CONSTRAINT") + )("END_SUBTYPE_CONSTRAINT") + END_TYPE = (CaselessKeyword("end_type")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="END_TYPE"))( + "END_TYPE" + ) ENTITY = (CaselessKeyword("entity")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="ENTITY"))("ENTITY") - ENUMERATION = (CaselessKeyword("enumeration")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="ENUMERATION"))("ENUMERATION") + ENUMERATION = (CaselessKeyword("enumeration")).setParseAction( + lambda s, loc, t: Node(s, loc, t, rule="ENUMERATION") + )("ENUMERATION") ESCAPE = (CaselessKeyword("escape")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="ESCAPE"))("ESCAPE") EXISTS = (CaselessKeyword("exists")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="EXISTS"))("EXISTS") - EXTENSIBLE = (CaselessKeyword("extensible")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="EXTENSIBLE"))("EXTENSIBLE") + EXTENSIBLE = (CaselessKeyword("extensible")).setParseAction( + lambda s, loc, t: Node(s, loc, t, rule="EXTENSIBLE") + )("EXTENSIBLE") EXP = (CaselessKeyword("exp")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="EXP"))("EXP") FALSE = (CaselessKeyword("false")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="FALSE"))("FALSE") FIXED = (CaselessKeyword("fixed")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="FIXED"))("FIXED") FOR = (CaselessKeyword("for")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="FOR"))("FOR") FORMAT = (CaselessKeyword("format")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="FORMAT"))("FORMAT") FROM = (CaselessKeyword("from")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="FROM"))("FROM") - FUNCTION = (CaselessKeyword("function")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="FUNCTION"))("FUNCTION") - GENERIC = (CaselessKeyword("generic")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="GENERIC"))("GENERIC") - GENERIC_ENTITY = (CaselessKeyword("generic_entity")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="GENERIC_ENTITY"))("GENERIC_ENTITY") - HIBOUND = (CaselessKeyword("hibound")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="HIBOUND"))("HIBOUND") - HIINDEX = (CaselessKeyword("hiindex")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="HIINDEX"))("HIINDEX") + FUNCTION = (CaselessKeyword("function")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="FUNCTION"))( + "FUNCTION" + ) + GENERIC = (CaselessKeyword("generic")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="GENERIC"))( + "GENERIC" + ) + GENERIC_ENTITY = (CaselessKeyword("generic_entity")).setParseAction( + lambda s, loc, t: Node(s, loc, t, rule="GENERIC_ENTITY") + )("GENERIC_ENTITY") + HIBOUND = (CaselessKeyword("hibound")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="HIBOUND"))( + "HIBOUND" + ) + HIINDEX = (CaselessKeyword("hiindex")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="HIINDEX"))( + "HIINDEX" + ) IF = (CaselessKeyword("if")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="IF"))("IF") IN = (CaselessKeyword("in")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="IN"))("IN") INSERT = (CaselessKeyword("insert")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="INSERT"))("INSERT") - INTEGER = (CaselessKeyword("integer")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="INTEGER"))("INTEGER") - INVERSE = (CaselessKeyword("inverse")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="INVERSE"))("INVERSE") + INTEGER = (CaselessKeyword("integer")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="INTEGER"))( + "INTEGER" + ) + INVERSE = (CaselessKeyword("inverse")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="INVERSE"))( + "INVERSE" + ) LENGTH = (CaselessKeyword("length")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="LENGTH"))("LENGTH") LIKE = (CaselessKeyword("like")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="LIKE"))("LIKE") LIST = (CaselessKeyword("list")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="LIST"))("LIST") - LOBOUND = (CaselessKeyword("lobound")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="LOBOUND"))("LOBOUND") + LOBOUND = (CaselessKeyword("lobound")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="LOBOUND"))( + "LOBOUND" + ) LOCAL = (CaselessKeyword("local")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="LOCAL"))("LOCAL") LOG = (CaselessKeyword("log")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="LOG"))("LOG") LOG10 = (CaselessKeyword("log10")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="LOG10"))("LOG10") LOG2 = (CaselessKeyword("log2")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="LOG2"))("LOG2") - LOGICAL = (CaselessKeyword("logical")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="LOGICAL"))("LOGICAL") - LOINDEX = (CaselessKeyword("loindex")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="LOINDEX"))("LOINDEX") + LOGICAL = (CaselessKeyword("logical")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="LOGICAL"))( + "LOGICAL" + ) + LOINDEX = (CaselessKeyword("loindex")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="LOINDEX"))( + "LOINDEX" + ) MOD = (CaselessKeyword("mod")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="MOD"))("MOD") NOT = (CaselessKeyword("not")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="NOT"))("NOT") NUMBER = (CaselessKeyword("number")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="NUMBER"))("NUMBER") @@ -94,19 +156,31 @@ def parse(fn): ODD = (CaselessKeyword("odd")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="ODD"))("ODD") OF = (CaselessKeyword("of")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="OF"))("OF") ONEOF = (CaselessKeyword("oneof")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="ONEOF"))("ONEOF") - OPTIONAL = (CaselessKeyword("optional")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="OPTIONAL"))("OPTIONAL") + OPTIONAL = (CaselessKeyword("optional")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="OPTIONAL"))( + "OPTIONAL" + ) OR = (CaselessKeyword("or")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="OR"))("OR") - OTHERWISE = (CaselessKeyword("otherwise")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="OTHERWISE"))("OTHERWISE") + OTHERWISE = (CaselessKeyword("otherwise")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="OTHERWISE"))( + "OTHERWISE" + ) PI = (CaselessKeyword("pi")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="PI"))("PI") - PROCEDURE = (CaselessKeyword("procedure")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="PROCEDURE"))("PROCEDURE") + PROCEDURE = (CaselessKeyword("procedure")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="PROCEDURE"))( + "PROCEDURE" + ) QUERY = (CaselessKeyword("query")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="QUERY"))("QUERY") REAL = (CaselessKeyword("real")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="REAL"))("REAL") - REFERENCE = (CaselessKeyword("reference")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="REFERENCE"))("REFERENCE") + REFERENCE = (CaselessKeyword("reference")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="REFERENCE"))( + "REFERENCE" + ) REMOVE = (CaselessKeyword("remove")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="REMOVE"))("REMOVE") - RENAMED = (CaselessKeyword("renamed")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="RENAMED"))("RENAMED") + RENAMED = (CaselessKeyword("renamed")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="RENAMED"))( + "RENAMED" + ) REPEAT = (CaselessKeyword("repeat")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="REPEAT"))("REPEAT") RETURN = (CaselessKeyword("return")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="RETURN"))("RETURN") - ROLESOF = (CaselessKeyword("rolesof")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="ROLESOF"))("ROLESOF") + ROLESOF = (CaselessKeyword("rolesof")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="ROLESOF"))( + "ROLESOF" + ) RULE = (CaselessKeyword("rule")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="RULE"))("RULE") SCHEMA = (CaselessKeyword("schema")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="SCHEMA"))("SCHEMA") SELECT = (CaselessKeyword("select")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="SELECT"))("SELECT") @@ -117,84 +191,433 @@ def parse(fn): SKIP = (CaselessKeyword("skip")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="SKIP"))("SKIP") SQRT = (CaselessKeyword("sqrt")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="SQRT"))("SQRT") STRING = (CaselessKeyword("string")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="STRING"))("STRING") - SUBTYPE = (CaselessKeyword("subtype")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="SUBTYPE"))("SUBTYPE") - SUBTYPE_CONSTRAINT = (CaselessKeyword("subtype_constraint")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="SUBTYPE_CONSTRAINT"))("SUBTYPE_CONSTRAINT") - SUPERTYPE = (CaselessKeyword("supertype")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="SUPERTYPE"))("SUPERTYPE") + SUBTYPE = (CaselessKeyword("subtype")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="SUBTYPE"))( + "SUBTYPE" + ) + SUBTYPE_CONSTRAINT = (CaselessKeyword("subtype_constraint")).setParseAction( + lambda s, loc, t: Node(s, loc, t, rule="SUBTYPE_CONSTRAINT") + )("SUBTYPE_CONSTRAINT") + SUPERTYPE = (CaselessKeyword("supertype")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="SUPERTYPE"))( + "SUPERTYPE" + ) TAN = (CaselessKeyword("tan")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="TAN"))("TAN") THEN = (CaselessKeyword("then")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="THEN"))("THEN") TO = (CaselessKeyword("to")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="TO"))("TO") - TOTAL_OVER = (CaselessKeyword("total_over")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="TOTAL_OVER"))("TOTAL_OVER") + TOTAL_OVER = (CaselessKeyword("total_over")).setParseAction( + lambda s, loc, t: Node(s, loc, t, rule="TOTAL_OVER") + )("TOTAL_OVER") TRUE = (CaselessKeyword("true")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="TRUE"))("TRUE") TYPE = (CaselessKeyword("type")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="TYPE"))("TYPE") TYPEOF = (CaselessKeyword("typeof")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="TYPEOF"))("TYPEOF") UNIQUE = (CaselessKeyword("unique")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="UNIQUE"))("UNIQUE") - UNKNOWN = (CaselessKeyword("unknown")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="UNKNOWN"))("UNKNOWN") + UNKNOWN = (CaselessKeyword("unknown")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="UNKNOWN"))( + "UNKNOWN" + ) UNTIL = (CaselessKeyword("until")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="UNTIL"))("UNTIL") USE = (CaselessKeyword("use")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="USE"))("USE") USEDIN = (CaselessKeyword("usedin")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="USEDIN"))("USEDIN") VALUE = (CaselessKeyword("value")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="VALUE"))("VALUE") - VALUE_IN = (CaselessKeyword("value_in")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="VALUE_IN"))("VALUE_IN") - VALUE_UNIQUE = (CaselessKeyword("value_unique")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="VALUE_UNIQUE"))("VALUE_UNIQUE") + VALUE_IN = (CaselessKeyword("value_in")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="VALUE_IN"))( + "VALUE_IN" + ) + VALUE_UNIQUE = (CaselessKeyword("value_unique")).setParseAction( + lambda s, loc, t: Node(s, loc, t, rule="VALUE_UNIQUE") + )("VALUE_UNIQUE") VAR = (CaselessKeyword("var")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="VAR"))("VAR") WHERE = (CaselessKeyword("where")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="WHERE"))("WHERE") WHILE = (CaselessKeyword("while")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="WHILE"))("WHILE") WITH = (CaselessKeyword("with")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="WITH"))("WITH") XOR = (CaselessKeyword("xor")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="XOR"))("XOR") - bit = ((CaselessLiteral("0") | CaselessLiteral("1"))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="bit"))("bit") - digit = ((CaselessLiteral("0") | CaselessLiteral("1") | CaselessLiteral("2") | CaselessLiteral("3") | CaselessLiteral("4") | CaselessLiteral("5") | CaselessLiteral("6") | CaselessLiteral("7") | CaselessLiteral("8") | CaselessLiteral("9")))("digit") + bit = ((CaselessLiteral("0") | CaselessLiteral("1"))).setParseAction( + lambda s, loc, t: Node(s, loc, t, rule="bit") + )("bit") + digit = ( + ( + CaselessLiteral("0") + | CaselessLiteral("1") + | CaselessLiteral("2") + | CaselessLiteral("3") + | CaselessLiteral("4") + | CaselessLiteral("5") + | CaselessLiteral("6") + | CaselessLiteral("7") + | CaselessLiteral("8") + | CaselessLiteral("9") + ) + )("digit") digits = ((digit + ZeroOrMore(digit)))("digits") - hex_digit = ((digit | CaselessLiteral("a") | CaselessLiteral("b") | CaselessLiteral("c") | CaselessLiteral("d") | CaselessLiteral("e") | CaselessLiteral("f"))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="hex_digit"))("hex_digit") - letter = ((CaselessLiteral("a") | CaselessLiteral("b") | CaselessLiteral("c") | CaselessLiteral("d") | CaselessLiteral("e") | CaselessLiteral("f") | CaselessLiteral("g") | CaselessLiteral("h") | CaselessLiteral("i") | CaselessLiteral("j") | CaselessLiteral("k") | CaselessLiteral("l") | CaselessLiteral("m") | CaselessLiteral("n") | CaselessLiteral("o") | CaselessLiteral("p") | CaselessLiteral("q") | CaselessLiteral("r") | CaselessLiteral("s") | CaselessLiteral("t") | CaselessLiteral("u") | CaselessLiteral("v") | CaselessLiteral("w") | CaselessLiteral("x") | CaselessLiteral("y") | CaselessLiteral("z")))("letter") - not_paren_star_quote_special = ((CaselessLiteral("!") | CaselessLiteral("#") | CaselessLiteral("$") | CaselessLiteral("%") | CaselessLiteral("&") | CaselessLiteral("+") | CaselessLiteral(",") | CaselessLiteral("-") | CaselessLiteral(".") | CaselessLiteral("/") | CaselessLiteral(":") | CaselessLiteral(";") | CaselessLiteral("<") | CaselessLiteral("=") | CaselessLiteral(">") | CaselessLiteral("?") | CaselessLiteral("@") | CaselessLiteral("[") | CaselessLiteral("\\") | CaselessLiteral("]") | CaselessLiteral("^") | CaselessLiteral("_") | CaselessLiteral("{") | CaselessLiteral("|") | CaselessLiteral("}") | CaselessLiteral("~"))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="not_paren_star_quote_special"))("not_paren_star_quote_special") - not_paren_star_special = ((not_paren_star_quote_special | CaselessLiteral("\"\""))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="not_paren_star_special"))("not_paren_star_special") - not_quote = ((not_paren_star_quote_special | letter | digit | CaselessLiteral("(") | CaselessLiteral(")") | CaselessLiteral("*"))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="not_quote"))("not_quote") + hex_digit = ( + ( + digit + | CaselessLiteral("a") + | CaselessLiteral("b") + | CaselessLiteral("c") + | CaselessLiteral("d") + | CaselessLiteral("e") + | CaselessLiteral("f") + ) + ).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="hex_digit"))("hex_digit") + letter = ( + ( + CaselessLiteral("a") + | CaselessLiteral("b") + | CaselessLiteral("c") + | CaselessLiteral("d") + | CaselessLiteral("e") + | CaselessLiteral("f") + | CaselessLiteral("g") + | CaselessLiteral("h") + | CaselessLiteral("i") + | CaselessLiteral("j") + | CaselessLiteral("k") + | CaselessLiteral("l") + | CaselessLiteral("m") + | CaselessLiteral("n") + | CaselessLiteral("o") + | CaselessLiteral("p") + | CaselessLiteral("q") + | CaselessLiteral("r") + | CaselessLiteral("s") + | CaselessLiteral("t") + | CaselessLiteral("u") + | CaselessLiteral("v") + | CaselessLiteral("w") + | CaselessLiteral("x") + | CaselessLiteral("y") + | CaselessLiteral("z") + ) + )("letter") + not_paren_star_quote_special = ( + ( + CaselessLiteral("!") + | CaselessLiteral("#") + | CaselessLiteral("$") + | CaselessLiteral("%") + | CaselessLiteral("&") + | CaselessLiteral("+") + | CaselessLiteral(",") + | CaselessLiteral("-") + | CaselessLiteral(".") + | CaselessLiteral("/") + | CaselessLiteral(":") + | CaselessLiteral(";") + | CaselessLiteral("<") + | CaselessLiteral("=") + | CaselessLiteral(">") + | CaselessLiteral("?") + | CaselessLiteral("@") + | CaselessLiteral("[") + | CaselessLiteral("\\") + | CaselessLiteral("]") + | CaselessLiteral("^") + | CaselessLiteral("_") + | CaselessLiteral("{") + | CaselessLiteral("|") + | CaselessLiteral("}") + | CaselessLiteral("~") + ) + ).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="not_paren_star_quote_special"))( + "not_paren_star_quote_special" + ) + not_paren_star_special = ((not_paren_star_quote_special | CaselessLiteral('""'))).setParseAction( + lambda s, loc, t: Node(s, loc, t, rule="not_paren_star_special") + )("not_paren_star_special") + not_quote = ( + ( + not_paren_star_quote_special + | letter + | digit + | CaselessLiteral("(") + | CaselessLiteral(")") + | CaselessLiteral("*") + ) + ).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="not_quote"))("not_quote") octet = ((hex_digit + hex_digit)).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="octet"))("octet") - special = ((not_paren_star_quote_special | CaselessLiteral("(") | CaselessLiteral(")") | CaselessLiteral("*") | CaselessLiteral("\"\""))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="special"))("special") - binary_literal = ((CaselessLiteral("%") + bit + ZeroOrMore(bit))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="binary_literal"))("binary_literal") + special = ( + ( + not_paren_star_quote_special + | CaselessLiteral("(") + | CaselessLiteral(")") + | CaselessLiteral("*") + | CaselessLiteral('""') + ) + ).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="special"))("special") + binary_literal = ((CaselessLiteral("%") + bit + ZeroOrMore(bit))).setParseAction( + lambda s, loc, t: ListNode(s, loc, t, rule="binary_literal") + )("binary_literal") integer_literal = (digits)("integer_literal") - simple_id = ~CaselessKeyword("supertype") + ~CaselessKeyword("generic") + ~CaselessKeyword("true") + ~CaselessKeyword("rolesof") + ~CaselessKeyword("local") + ~CaselessKeyword("enumeration") + ~CaselessKeyword("in") + ~CaselessKeyword("subtype_constraint") + ~CaselessKeyword("while") + ~CaselessKeyword("var") + ~CaselessKeyword("unique") + ~CaselessKeyword("type") + ~CaselessKeyword("format") + ~CaselessKeyword("log2") + ~CaselessKeyword("set") + ~CaselessKeyword("string") + ~CaselessKeyword("exp") + ~CaselessKeyword("inverse") + ~CaselessKeyword("sizeof") + ~CaselessKeyword("function") + ~CaselessKeyword("of") + ~CaselessKeyword("value_in") + ~CaselessKeyword("procedure") + ~CaselessKeyword("subtype") + ~CaselessKeyword("for") + ~CaselessKeyword("const_e") + ~CaselessKeyword("acos") + ~CaselessKeyword("asin") + ~CaselessKeyword("return") + ~CaselessKeyword("optional") + ~CaselessKeyword("usedin") + ~CaselessKeyword("log") + ~CaselessKeyword("not") + ~CaselessKeyword("from") + ~CaselessKeyword("and") + ~CaselessKeyword("pi") + ~CaselessKeyword("begin") + ~CaselessKeyword("end") + ~CaselessKeyword("end_procedure") + ~CaselessKeyword("loindex") + ~CaselessKeyword("bag") + ~CaselessKeyword("log10") + ~CaselessKeyword("aggregate") + ~CaselessKeyword("number") + ~CaselessKeyword("by") + ~CaselessKeyword("until") + ~CaselessKeyword("array") + ~CaselessKeyword("renamed") + ~CaselessKeyword("entity") + ~CaselessKeyword("andor") + ~CaselessKeyword("mod") + ~CaselessKeyword("end_function") + ~CaselessKeyword("cos") + ~CaselessKeyword("sin") + ~CaselessKeyword("list") + ~CaselessKeyword("as") + ~CaselessKeyword("binary") + ~CaselessKeyword("escape") + ~CaselessKeyword("value_unique") + ~CaselessKeyword("sqrt") + ~CaselessKeyword("real") + ~CaselessKeyword("atan") + ~CaselessKeyword("with") + ~CaselessKeyword("unknown") + ~CaselessKeyword("boolean") + ~CaselessKeyword("abs") + ~CaselessKeyword("fixed") + ~CaselessKeyword("use") + ~CaselessKeyword("repeat") + ~CaselessKeyword("self") + ~CaselessKeyword("value") + ~CaselessKeyword("insert") + ~CaselessKeyword("integer") + ~CaselessKeyword("rule") + ~CaselessKeyword("total_over") + ~CaselessKeyword("tan") + ~CaselessKeyword("case") + ~CaselessKeyword("else") + ~CaselessKeyword("schema") + ~CaselessKeyword("derive") + ~CaselessKeyword("remove") + ~CaselessKeyword("like") + ~CaselessKeyword("select") + ~CaselessKeyword("alias") + ~CaselessKeyword("abstract") + ~CaselessKeyword("blength") + ~CaselessKeyword("end_if") + ~CaselessKeyword("xor") + ~CaselessKeyword("skip") + ~CaselessKeyword("generic_entity") + ~CaselessKeyword("based_on") + ~CaselessKeyword("exists") + ~CaselessKeyword("or") + ~CaselessKeyword("odd") + ~CaselessKeyword("length") + ~CaselessKeyword("constant") + ~CaselessKeyword("end_type") + ~CaselessKeyword("false") + ~CaselessKeyword("end_subtype_constraint") + ~CaselessKeyword("then") + ~CaselessKeyword("end_repeat") + ~CaselessKeyword("nvl") + ~CaselessKeyword("where") + ~CaselessKeyword("hibound") + ~CaselessKeyword("lobound") + ~CaselessKeyword("end_rule") + ~CaselessKeyword("div") + ~CaselessKeyword("query") + ~CaselessKeyword("reference") + ~CaselessKeyword("end_alias") + ~CaselessKeyword("end_local") + ~CaselessKeyword("logical") + ~CaselessKeyword("if") + ~CaselessKeyword("hiindex") + ~CaselessKeyword("end_entity") + ~CaselessKeyword("extensible") + ~CaselessKeyword("end_case") + ~CaselessKeyword("oneof") + ~CaselessKeyword("otherwise") + ~CaselessKeyword("end_constant") + ~CaselessKeyword("end_schema") + ~CaselessKeyword("to") + ~CaselessKeyword("typeof") + originalTextFor(Combine((letter + ZeroOrMore((letter | digit | CaselessLiteral("_"))))))("simple_id") - simple_string_literal = ((CaselessLiteral("'") + ZeroOrMore(((CaselessLiteral("'") + CaselessLiteral("'")) | not_quote)) + CaselessLiteral("'"))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="simple_string_literal"))("simple_string_literal") + simple_id = ( + ~CaselessKeyword("supertype") + + ~CaselessKeyword("generic") + + ~CaselessKeyword("true") + + ~CaselessKeyword("rolesof") + + ~CaselessKeyword("local") + + ~CaselessKeyword("enumeration") + + ~CaselessKeyword("in") + + ~CaselessKeyword("subtype_constraint") + + ~CaselessKeyword("while") + + ~CaselessKeyword("var") + + ~CaselessKeyword("unique") + + ~CaselessKeyword("type") + + ~CaselessKeyword("format") + + ~CaselessKeyword("log2") + + ~CaselessKeyword("set") + + ~CaselessKeyword("string") + + ~CaselessKeyword("exp") + + ~CaselessKeyword("inverse") + + ~CaselessKeyword("sizeof") + + ~CaselessKeyword("function") + + ~CaselessKeyword("of") + + ~CaselessKeyword("value_in") + + ~CaselessKeyword("procedure") + + ~CaselessKeyword("subtype") + + ~CaselessKeyword("for") + + ~CaselessKeyword("const_e") + + ~CaselessKeyword("acos") + + ~CaselessKeyword("asin") + + ~CaselessKeyword("return") + + ~CaselessKeyword("optional") + + ~CaselessKeyword("usedin") + + ~CaselessKeyword("log") + + ~CaselessKeyword("not") + + ~CaselessKeyword("from") + + ~CaselessKeyword("and") + + ~CaselessKeyword("pi") + + ~CaselessKeyword("begin") + + ~CaselessKeyword("end") + + ~CaselessKeyword("end_procedure") + + ~CaselessKeyword("loindex") + + ~CaselessKeyword("bag") + + ~CaselessKeyword("log10") + + ~CaselessKeyword("aggregate") + + ~CaselessKeyword("number") + + ~CaselessKeyword("by") + + ~CaselessKeyword("until") + + ~CaselessKeyword("array") + + ~CaselessKeyword("renamed") + + ~CaselessKeyword("entity") + + ~CaselessKeyword("andor") + + ~CaselessKeyword("mod") + + ~CaselessKeyword("end_function") + + ~CaselessKeyword("cos") + + ~CaselessKeyword("sin") + + ~CaselessKeyword("list") + + ~CaselessKeyword("as") + + ~CaselessKeyword("binary") + + ~CaselessKeyword("escape") + + ~CaselessKeyword("value_unique") + + ~CaselessKeyword("sqrt") + + ~CaselessKeyword("real") + + ~CaselessKeyword("atan") + + ~CaselessKeyword("with") + + ~CaselessKeyword("unknown") + + ~CaselessKeyword("boolean") + + ~CaselessKeyword("abs") + + ~CaselessKeyword("fixed") + + ~CaselessKeyword("use") + + ~CaselessKeyword("repeat") + + ~CaselessKeyword("self") + + ~CaselessKeyword("value") + + ~CaselessKeyword("insert") + + ~CaselessKeyword("integer") + + ~CaselessKeyword("rule") + + ~CaselessKeyword("total_over") + + ~CaselessKeyword("tan") + + ~CaselessKeyword("case") + + ~CaselessKeyword("else") + + ~CaselessKeyword("schema") + + ~CaselessKeyword("derive") + + ~CaselessKeyword("remove") + + ~CaselessKeyword("like") + + ~CaselessKeyword("select") + + ~CaselessKeyword("alias") + + ~CaselessKeyword("abstract") + + ~CaselessKeyword("blength") + + ~CaselessKeyword("end_if") + + ~CaselessKeyword("xor") + + ~CaselessKeyword("skip") + + ~CaselessKeyword("generic_entity") + + ~CaselessKeyword("based_on") + + ~CaselessKeyword("exists") + + ~CaselessKeyword("or") + + ~CaselessKeyword("odd") + + ~CaselessKeyword("length") + + ~CaselessKeyword("constant") + + ~CaselessKeyword("end_type") + + ~CaselessKeyword("false") + + ~CaselessKeyword("end_subtype_constraint") + + ~CaselessKeyword("then") + + ~CaselessKeyword("end_repeat") + + ~CaselessKeyword("nvl") + + ~CaselessKeyword("where") + + ~CaselessKeyword("hibound") + + ~CaselessKeyword("lobound") + + ~CaselessKeyword("end_rule") + + ~CaselessKeyword("div") + + ~CaselessKeyword("query") + + ~CaselessKeyword("reference") + + ~CaselessKeyword("end_alias") + + ~CaselessKeyword("end_local") + + ~CaselessKeyword("logical") + + ~CaselessKeyword("if") + + ~CaselessKeyword("hiindex") + + ~CaselessKeyword("end_entity") + + ~CaselessKeyword("extensible") + + ~CaselessKeyword("end_case") + + ~CaselessKeyword("oneof") + + ~CaselessKeyword("otherwise") + + ~CaselessKeyword("end_constant") + + ~CaselessKeyword("end_schema") + + ~CaselessKeyword("to") + + ~CaselessKeyword("typeof") + + originalTextFor(Combine((letter + ZeroOrMore((letter | digit | CaselessLiteral("_"))))))("simple_id") + ) + simple_string_literal = ( + ( + CaselessLiteral("'") + + ZeroOrMore(((CaselessLiteral("'") + CaselessLiteral("'")) | not_quote)) + + CaselessLiteral("'") + ) + ).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="simple_string_literal"))("simple_string_literal") abstract_entity_declaration = (ABSTRACT)("abstract_entity_declaration") - abstract_supertype = ((ABSTRACT + SUPERTYPE + CaselessLiteral(";"))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="abstract_supertype"))("abstract_supertype") - add_like_op = ((CaselessLiteral("+") | CaselessLiteral("-") | OR | XOR)).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="add_like_op"))("add_like_op") + abstract_supertype = ((ABSTRACT + SUPERTYPE + CaselessLiteral(";"))).setParseAction( + lambda s, loc, t: Node(s, loc, t, rule="abstract_supertype") + )("abstract_supertype") + add_like_op = ((CaselessLiteral("+") | CaselessLiteral("-") | OR | XOR)).setParseAction( + lambda s, loc, t: Node(s, loc, t, rule="add_like_op") + )("add_like_op") attribute_id = (simple_id)("attribute_id") boolean_type = (BOOLEAN)("boolean_type") - built_in_constant = ((CONST_E | PI | SELF | CaselessLiteral("?"))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="built_in_constant"))("built_in_constant") - built_in_function = ((ABS | ACOS | ASIN | ATAN | BLENGTH | COS | EXISTS | EXP | FORMAT | HIBOUND | HIINDEX | LENGTH | LOBOUND | LOINDEX | LOG | LOG2 | LOG10 | NVL | ODD | ROLESOF | SIN | SIZEOF | SQRT | TAN | TYPEOF | USEDIN | VALUE | VALUE_IN | VALUE_UNIQUE)).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="built_in_function"))("built_in_function") - built_in_procedure = ((INSERT | REMOVE)).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="built_in_procedure"))("built_in_procedure") + built_in_constant = ((CONST_E | PI | SELF | CaselessLiteral("?"))).setParseAction( + lambda s, loc, t: Node(s, loc, t, rule="built_in_constant") + )("built_in_constant") + built_in_function = ( + ( + ABS + | ACOS + | ASIN + | ATAN + | BLENGTH + | COS + | EXISTS + | EXP + | FORMAT + | HIBOUND + | HIINDEX + | LENGTH + | LOBOUND + | LOINDEX + | LOG + | LOG2 + | LOG10 + | NVL + | ODD + | ROLESOF + | SIN + | SIZEOF + | SQRT + | TAN + | TYPEOF + | USEDIN + | VALUE + | VALUE_IN + | VALUE_UNIQUE + ) + ).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="built_in_function"))("built_in_function") + built_in_procedure = ((INSERT | REMOVE)).setParseAction( + lambda s, loc, t: Node(s, loc, t, rule="built_in_procedure") + )("built_in_procedure") constant_id = (simple_id)("constant_id") entity_id = (simple_id)("entity_id") enumeration_id = (simple_id)("enumeration_id") - enumeration_items = ((CaselessLiteral("(") + enumeration_id + ZeroOrMore((CaselessLiteral(",") + enumeration_id)) + CaselessLiteral(")"))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="enumeration_items"))("enumeration_items") - escape_stmt = ((ESCAPE + CaselessLiteral(";"))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="escape_stmt"))("escape_stmt") + enumeration_items = ( + ( + CaselessLiteral("(") + + enumeration_id + + ZeroOrMore((CaselessLiteral(",") + enumeration_id)) + + CaselessLiteral(")") + ) + ).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="enumeration_items"))("enumeration_items") + escape_stmt = ((ESCAPE + CaselessLiteral(";"))).setParseAction( + lambda s, loc, t: Node(s, loc, t, rule="escape_stmt") + )("escape_stmt") function_id = (simple_id)("function_id") integer_type = (INTEGER)("integer_type") - interval_op = ((CaselessLiteral("<=") | CaselessLiteral("<"))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="interval_op"))("interval_op") - logical_literal = ((FALSE | TRUE | UNKNOWN)).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="logical_literal"))("logical_literal") + interval_op = ((CaselessLiteral("<=") | CaselessLiteral("<"))).setParseAction( + lambda s, loc, t: Node(s, loc, t, rule="interval_op") + )("interval_op") + logical_literal = ((FALSE | TRUE | UNKNOWN)).setParseAction( + lambda s, loc, t: Node(s, loc, t, rule="logical_literal") + )("logical_literal") logical_type = (LOGICAL)("logical_type") - multiplication_like_op = ((CaselessLiteral("*") | CaselessLiteral("/") | DIV | MOD | AND | CaselessLiteral("||"))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="multiplication_like_op"))("multiplication_like_op") - null_stmt = (CaselessLiteral(";")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="null_stmt"))("null_stmt") + multiplication_like_op = ( + (CaselessLiteral("*") | CaselessLiteral("/") | DIV | MOD | AND | CaselessLiteral("||")) + ).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="multiplication_like_op"))("multiplication_like_op") + null_stmt = (CaselessLiteral(";")).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="null_stmt"))( + "null_stmt" + ) number_type = (NUMBER)("number_type") parameter_id = (simple_id)("parameter_id") procedure_id = (simple_id)("procedure_id") - rel_op = ((CaselessLiteral("<=") | CaselessLiteral(">=") | CaselessLiteral("<>") | CaselessLiteral("=") | CaselessLiteral(":<>:") | CaselessLiteral(":=:") | CaselessLiteral("<") | CaselessLiteral(">"))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="rel_op"))("rel_op") - rel_op_extended = ((rel_op | IN | LIKE)).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="rel_op_extended"))("rel_op_extended") + rel_op = ( + ( + CaselessLiteral("<=") + | CaselessLiteral(">=") + | CaselessLiteral("<>") + | CaselessLiteral("=") + | CaselessLiteral(":<>:") + | CaselessLiteral(":=:") + | CaselessLiteral("<") + | CaselessLiteral(">") + ) + ).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="rel_op"))("rel_op") + rel_op_extended = ((rel_op | IN | LIKE)).setParseAction( + lambda s, loc, t: Node(s, loc, t, rule="rel_op_extended") + )("rel_op_extended") rule_id = (simple_id)("rule_id") rule_label_id = (simple_id)("rule_label_id") schema_id = (simple_id)("schema_id") - sign = ((CaselessLiteral("+") | CaselessLiteral("-"))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="sign"))("sign") - skip_stmt = ((SKIP + CaselessLiteral(";"))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="skip_stmt"))("skip_stmt") + sign = ((CaselessLiteral("+") | CaselessLiteral("-"))).setParseAction( + lambda s, loc, t: Node(s, loc, t, rule="sign") + )("sign") + skip_stmt = ((SKIP + CaselessLiteral(";"))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="skip_stmt"))( + "skip_stmt" + ) subtype_constraint_id = (simple_id)("subtype_constraint_id") type_id = (simple_id)("type_id") type_label_id = (simple_id)("type_label_id") - unary_op = ((CaselessLiteral("+") | CaselessLiteral("-") | NOT)).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="unary_op"))("unary_op") + unary_op = ((CaselessLiteral("+") | CaselessLiteral("-") | NOT)).setParseAction( + lambda s, loc, t: Node(s, loc, t, rule="unary_op") + )("unary_op") variable_id = (simple_id)("variable_id") - encoded_character = ((octet + octet + octet + octet)).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="encoded_character"))("encoded_character") - not_paren_star = ((letter | digit | not_paren_star_special)).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="not_paren_star"))("not_paren_star") - not_rparen_star = ((not_paren_star | CaselessLiteral("("))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="not_rparen_star"))("not_rparen_star") - not_rparen_star_then_rparen = ((not_rparen_star + ZeroOrMore(not_rparen_star) + CaselessLiteral(")") + ZeroOrMore(CaselessLiteral(")")))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="not_rparen_star_then_rparen"))("not_rparen_star_then_rparen") - encoded_string_literal = ((CaselessLiteral("\"") + encoded_character + ZeroOrMore(encoded_character) + CaselessLiteral("\""))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="encoded_string_literal"))("encoded_string_literal") - real_literal = (((digits + CaselessLiteral(".") + Optional(digits) + Optional((CaselessLiteral("e") + Optional(sign) + digits))) | integer_literal))("real_literal") + encoded_character = ((octet + octet + octet + octet)).setParseAction( + lambda s, loc, t: Node(s, loc, t, rule="encoded_character") + )("encoded_character") + not_paren_star = ((letter | digit | not_paren_star_special)).setParseAction( + lambda s, loc, t: Node(s, loc, t, rule="not_paren_star") + )("not_paren_star") + not_rparen_star = ((not_paren_star | CaselessLiteral("("))).setParseAction( + lambda s, loc, t: Node(s, loc, t, rule="not_rparen_star") + )("not_rparen_star") + not_rparen_star_then_rparen = ( + (not_rparen_star + ZeroOrMore(not_rparen_star) + CaselessLiteral(")") + ZeroOrMore(CaselessLiteral(")"))) + ).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="not_rparen_star_then_rparen"))( + "not_rparen_star_then_rparen" + ) + encoded_string_literal = ( + (CaselessLiteral('"') + encoded_character + ZeroOrMore(encoded_character) + CaselessLiteral('"')) + ).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="encoded_string_literal"))("encoded_string_literal") + real_literal = ( + ( + ( + digits + + CaselessLiteral(".") + + Optional(digits) + + Optional((CaselessLiteral("e") + Optional(sign) + digits)) + ) + | integer_literal + ) + )("real_literal") attribute_ref = (attribute_id)("attribute_ref") constant_ref = (constant_id)("constant_ref") entity_ref = (entity_id)("entity_ref") @@ -209,47 +632,198 @@ def parse(fn): type_label_ref = (type_label_id)("type_label_ref") type_ref = (type_id)("type_ref") variable_ref = (variable_id)("variable_ref") - attribute_qualifier = ((CaselessLiteral(".") + attribute_ref)).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="attribute_qualifier"))("attribute_qualifier") - constant_factor = ((built_in_constant | constant_ref)).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="constant_factor"))("constant_factor") - enumeration_extension = ((BASED_ON + type_ref + Optional((WITH + enumeration_items)))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="enumeration_extension"))("enumeration_extension") - enumeration_reference = ((Optional((type_ref + CaselessLiteral("."))) + enumeration_ref)).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="enumeration_reference"))("enumeration_reference") - enumeration_type = ((Optional(EXTENSIBLE) + ENUMERATION + Optional(((OF + enumeration_items) | enumeration_extension)))).setParseAction(EnumerationType)("enumeration_type") - general_ref = ((parameter_ref | variable_ref)).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="general_ref"))("general_ref") - group_qualifier = ((CaselessLiteral("\\") + entity_ref)).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="group_qualifier"))("group_qualifier") + attribute_qualifier = ((CaselessLiteral(".") + attribute_ref)).setParseAction( + lambda s, loc, t: Node(s, loc, t, rule="attribute_qualifier") + )("attribute_qualifier") + constant_factor = ((built_in_constant | constant_ref)).setParseAction( + lambda s, loc, t: Node(s, loc, t, rule="constant_factor") + )("constant_factor") + enumeration_extension = ((BASED_ON + type_ref + Optional((WITH + enumeration_items)))).setParseAction( + lambda s, loc, t: Node(s, loc, t, rule="enumeration_extension") + )("enumeration_extension") + enumeration_reference = ((Optional((type_ref + CaselessLiteral("."))) + enumeration_ref)).setParseAction( + lambda s, loc, t: Node(s, loc, t, rule="enumeration_reference") + )("enumeration_reference") + enumeration_type = ( + (Optional(EXTENSIBLE) + ENUMERATION + Optional(((OF + enumeration_items) | enumeration_extension))) + ).setParseAction(EnumerationType)("enumeration_type") + general_ref = ((parameter_ref | variable_ref)).setParseAction( + lambda s, loc, t: Node(s, loc, t, rule="general_ref") + )("general_ref") + group_qualifier = ((CaselessLiteral("\\") + entity_ref)).setParseAction( + lambda s, loc, t: Node(s, loc, t, rule="group_qualifier") + )("group_qualifier") named_types = ((entity_ref | type_ref)).setParseAction(NamedType)("named_types") - named_type_or_rename = ((named_types + Optional((AS + (entity_id | type_id))))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="named_type_or_rename"))("named_type_or_rename") + named_type_or_rename = ((named_types + Optional((AS + (entity_id | type_id))))).setParseAction( + lambda s, loc, t: Node(s, loc, t, rule="named_type_or_rename") + )("named_type_or_rename") population = (entity_ref)("population") - qualified_attribute = ((SELF + group_qualifier + attribute_qualifier)).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="qualified_attribute"))("qualified_attribute") - redeclared_attribute = ((qualified_attribute + Optional((RENAMED + attribute_id)))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="redeclared_attribute"))("redeclared_attribute") - referenced_attribute = ((attribute_ref | qualified_attribute)).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="referenced_attribute"))("referenced_attribute") - rename_id = ((constant_id | entity_id | function_id | procedure_id | type_id)).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="rename_id"))("rename_id") - resource_ref = ((constant_ref | entity_ref | function_ref | procedure_ref | type_ref)).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="resource_ref"))("resource_ref") - rule_head = ((RULE + rule_id + FOR + CaselessLiteral("(") + entity_ref + ZeroOrMore((CaselessLiteral(",") + entity_ref)) + CaselessLiteral(")") + CaselessLiteral(";"))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="rule_head"))("rule_head") - select_list = ((CaselessLiteral("(") + named_types + ZeroOrMore((CaselessLiteral(",") + named_types)) + CaselessLiteral(")"))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="select_list"))("select_list") - string_literal = ((simple_string_literal | encoded_string_literal)).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="string_literal"))("string_literal") - subtype_constraint_head = ((SUBTYPE_CONSTRAINT + subtype_constraint_id + FOR + entity_ref + CaselessLiteral(";"))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="subtype_constraint_head"))("subtype_constraint_head") - subtype_declaration = ((SUBTYPE + OF + CaselessLiteral("(") + entity_ref + ZeroOrMore((CaselessLiteral(",") + entity_ref)) + CaselessLiteral(")"))).setParseAction(SubTypeExpression)("subtype_declaration") - total_over = ((TOTAL_OVER + CaselessLiteral("(") + entity_ref + ZeroOrMore((CaselessLiteral(",") + entity_ref)) + CaselessLiteral(")") + CaselessLiteral(";"))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="total_over"))("total_over") - type_label = ((type_label_id | type_label_ref)).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="type_label"))("type_label") - unique_rule = ((Optional((rule_label_id + CaselessLiteral(":"))) + referenced_attribute + ZeroOrMore((CaselessLiteral(",") + referenced_attribute)))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="unique_rule"))("unique_rule") - use_clause = ((USE + FROM + schema_ref + Optional((CaselessLiteral("(") + named_type_or_rename + ZeroOrMore((CaselessLiteral(",") + named_type_or_rename)) + CaselessLiteral(")"))) + CaselessLiteral(";"))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="use_clause"))("use_clause") - not_lparen_star = ((not_paren_star | CaselessLiteral(")"))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="not_lparen_star"))("not_lparen_star") - remark_ref = ((attribute_ref | constant_ref | entity_ref | enumeration_ref | function_ref | parameter_ref | procedure_ref | rule_label_ref | rule_ref | schema_ref | subtype_constraint_ref | type_label_ref | type_ref | variable_ref)).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="remark_ref"))("remark_ref") - attribute_decl = ((redeclared_attribute | attribute_id)).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="attribute_decl"))("attribute_decl") - generic_entity_type = ((GENERIC_ENTITY + Optional((CaselessLiteral(":") + type_label)))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="generic_entity_type"))("generic_entity_type") - generic_type = ((GENERIC + Optional((CaselessLiteral(":") + type_label)))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="generic_type"))("generic_type") - literal = ((binary_literal | logical_literal | real_literal | string_literal)).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="literal"))("literal") - resource_or_rename = ((resource_ref + Optional((AS + rename_id)))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="resource_or_rename"))("resource_or_rename") + qualified_attribute = ((SELF + group_qualifier + attribute_qualifier)).setParseAction( + lambda s, loc, t: Node(s, loc, t, rule="qualified_attribute") + )("qualified_attribute") + redeclared_attribute = ((qualified_attribute + Optional((RENAMED + attribute_id)))).setParseAction( + lambda s, loc, t: Node(s, loc, t, rule="redeclared_attribute") + )("redeclared_attribute") + referenced_attribute = ((attribute_ref | qualified_attribute)).setParseAction( + lambda s, loc, t: Node(s, loc, t, rule="referenced_attribute") + )("referenced_attribute") + rename_id = ((constant_id | entity_id | function_id | procedure_id | type_id)).setParseAction( + lambda s, loc, t: Node(s, loc, t, rule="rename_id") + )("rename_id") + resource_ref = ((constant_ref | entity_ref | function_ref | procedure_ref | type_ref)).setParseAction( + lambda s, loc, t: Node(s, loc, t, rule="resource_ref") + )("resource_ref") + rule_head = ( + ( + RULE + + rule_id + + FOR + + CaselessLiteral("(") + + entity_ref + + ZeroOrMore((CaselessLiteral(",") + entity_ref)) + + CaselessLiteral(")") + + CaselessLiteral(";") + ) + ).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="rule_head"))("rule_head") + select_list = ( + ( + CaselessLiteral("(") + + named_types + + ZeroOrMore((CaselessLiteral(",") + named_types)) + + CaselessLiteral(")") + ) + ).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="select_list"))("select_list") + string_literal = ((simple_string_literal | encoded_string_literal)).setParseAction( + lambda s, loc, t: Node(s, loc, t, rule="string_literal") + )("string_literal") + subtype_constraint_head = ( + (SUBTYPE_CONSTRAINT + subtype_constraint_id + FOR + entity_ref + CaselessLiteral(";")) + ).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="subtype_constraint_head"))("subtype_constraint_head") + subtype_declaration = ( + ( + SUBTYPE + + OF + + CaselessLiteral("(") + + entity_ref + + ZeroOrMore((CaselessLiteral(",") + entity_ref)) + + CaselessLiteral(")") + ) + ).setParseAction(SubTypeExpression)("subtype_declaration") + total_over = ( + ( + TOTAL_OVER + + CaselessLiteral("(") + + entity_ref + + ZeroOrMore((CaselessLiteral(",") + entity_ref)) + + CaselessLiteral(")") + + CaselessLiteral(";") + ) + ).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="total_over"))("total_over") + type_label = ((type_label_id | type_label_ref)).setParseAction( + lambda s, loc, t: Node(s, loc, t, rule="type_label") + )("type_label") + unique_rule = ( + ( + Optional((rule_label_id + CaselessLiteral(":"))) + + referenced_attribute + + ZeroOrMore((CaselessLiteral(",") + referenced_attribute)) + ) + ).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="unique_rule"))("unique_rule") + use_clause = ( + ( + USE + + FROM + + schema_ref + + Optional( + ( + CaselessLiteral("(") + + named_type_or_rename + + ZeroOrMore((CaselessLiteral(",") + named_type_or_rename)) + + CaselessLiteral(")") + ) + ) + + CaselessLiteral(";") + ) + ).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="use_clause"))("use_clause") + not_lparen_star = ((not_paren_star | CaselessLiteral(")"))).setParseAction( + lambda s, loc, t: Node(s, loc, t, rule="not_lparen_star") + )("not_lparen_star") + remark_ref = ( + ( + attribute_ref + | constant_ref + | entity_ref + | enumeration_ref + | function_ref + | parameter_ref + | procedure_ref + | rule_label_ref + | rule_ref + | schema_ref + | subtype_constraint_ref + | type_label_ref + | type_ref + | variable_ref + ) + ).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="remark_ref"))("remark_ref") + attribute_decl = ((redeclared_attribute | attribute_id)).setParseAction( + lambda s, loc, t: Node(s, loc, t, rule="attribute_decl") + )("attribute_decl") + generic_entity_type = ((GENERIC_ENTITY + Optional((CaselessLiteral(":") + type_label)))).setParseAction( + lambda s, loc, t: Node(s, loc, t, rule="generic_entity_type") + )("generic_entity_type") + generic_type = ((GENERIC + Optional((CaselessLiteral(":") + type_label)))).setParseAction( + lambda s, loc, t: Node(s, loc, t, rule="generic_type") + )("generic_type") + literal = ((binary_literal | logical_literal | real_literal | string_literal)).setParseAction( + lambda s, loc, t: Node(s, loc, t, rule="literal") + )("literal") + resource_or_rename = ((resource_ref + Optional((AS + rename_id)))).setParseAction( + lambda s, loc, t: Node(s, loc, t, rule="resource_or_rename") + )("resource_or_rename") schema_version_id = (string_literal)("schema_version_id") - select_extension = ((BASED_ON + type_ref + Optional((WITH + select_list)))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="select_extension"))("select_extension") - select_type = ((Optional((EXTENSIBLE + Optional(GENERIC_ENTITY))) + SELECT + Optional((select_list | select_extension)))).setParseAction(SelectType)("select_type") - unique_clause = ((UNIQUE + unique_rule + CaselessLiteral(";") + ZeroOrMore((unique_rule + CaselessLiteral(";"))))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="unique_clause"))("unique_clause") - lparen_then_not_lparen_star = ((CaselessLiteral("(") + ZeroOrMore(CaselessLiteral("(")) + not_lparen_star + ZeroOrMore(not_lparen_star))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="lparen_then_not_lparen_star"))("lparen_then_not_lparen_star") - remark_tag = ((CaselessLiteral("\"") + remark_ref + ZeroOrMore((CaselessLiteral(".") + remark_ref)) + CaselessLiteral("\""))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="remark_tag"))("remark_tag") - tail_remark = ((CaselessLiteral("--") + Optional(remark_tag))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="tail_remark"))("tail_remark") - constructed_types = ((enumeration_type | select_type)).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="constructed_types"))("constructed_types") - reference_clause = ((REFERENCE + FROM + schema_ref + Optional((CaselessLiteral("(") + resource_or_rename + ZeroOrMore((CaselessLiteral(",") + resource_or_rename)) + CaselessLiteral(")"))) + CaselessLiteral(";"))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="reference_clause"))("reference_clause") - interface_specification = ((reference_clause | use_clause)).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="interface_specification"))("interface_specification") + select_extension = ((BASED_ON + type_ref + Optional((WITH + select_list)))).setParseAction( + lambda s, loc, t: Node(s, loc, t, rule="select_extension") + )("select_extension") + select_type = ( + (Optional((EXTENSIBLE + Optional(GENERIC_ENTITY))) + SELECT + Optional((select_list | select_extension))) + ).setParseAction(SelectType)("select_type") + unique_clause = ( + (UNIQUE + unique_rule + CaselessLiteral(";") + ZeroOrMore((unique_rule + CaselessLiteral(";")))) + ).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="unique_clause"))("unique_clause") + lparen_then_not_lparen_star = ( + (CaselessLiteral("(") + ZeroOrMore(CaselessLiteral("(")) + not_lparen_star + ZeroOrMore(not_lparen_star)) + ).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="lparen_then_not_lparen_star"))( + "lparen_then_not_lparen_star" + ) + remark_tag = ( + (CaselessLiteral('"') + remark_ref + ZeroOrMore((CaselessLiteral(".") + remark_ref)) + CaselessLiteral('"')) + ).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="remark_tag"))("remark_tag") + tail_remark = ((CaselessLiteral("--") + Optional(remark_tag))).setParseAction( + lambda s, loc, t: Node(s, loc, t, rule="tail_remark") + )("tail_remark") + constructed_types = ((enumeration_type | select_type)).setParseAction( + lambda s, loc, t: Node(s, loc, t, rule="constructed_types") + )("constructed_types") + reference_clause = ( + ( + REFERENCE + + FROM + + schema_ref + + Optional( + ( + CaselessLiteral("(") + + resource_or_rename + + ZeroOrMore((CaselessLiteral(",") + resource_or_rename)) + + CaselessLiteral(")") + ) + ) + + CaselessLiteral(";") + ) + ).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="reference_clause"))("reference_clause") + interface_specification = ((reference_clause | use_clause)).setParseAction( + lambda s, loc, t: Node(s, loc, t, rule="interface_specification") + )("interface_specification") binary_type = Forward()("binary_type") function_decl = Forward()("function_decl") general_set_type = Forward()("general_set_type") @@ -360,114 +934,528 @@ def parse(fn): expression = Forward()("expression") simple_types = Forward()("simple_types") binary_type << (((BINARY + Optional(width_spec)))).setParseAction(BinaryType) - function_decl << (((function_head + algorithm_head + stmt + ZeroOrMore(stmt) + END_FUNCTION + CaselessLiteral(";")))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="function_decl")) - general_set_type << (((SET + Optional(bound_spec) + OF + parameter_type))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="general_set_type")) - actual_parameter_list << (((CaselessLiteral("(") + Optional(parameter) + ZeroOrMore((CaselessLiteral(",") + parameter)) + CaselessLiteral(")")))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="actual_parameter_list")) - if_stmt << (((IF + logical_expression + THEN + stmt + ZeroOrMore(stmt) + Optional((ELSE + stmt + ZeroOrMore(stmt))) + END_IF + CaselessLiteral(";")))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="if_stmt")) - simple_factor << (((aggregate_initializer | interval | query_expression | (Optional(unary_op) + ((CaselessLiteral("(") + expression + CaselessLiteral(")")) | primary)) | entity_constructor | enumeration_reference))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="simple_factor")) - case_stmt << (((CASE + selector + OF + ZeroOrMore(case_action) + Optional((OTHERWISE + CaselessLiteral(":") + stmt)) + END_CASE + CaselessLiteral(";")))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="case_stmt")) - qualifier << (((attribute_qualifier | group_qualifier | index_qualifier))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="qualifier")) - general_list_type << (((LIST + Optional(bound_spec) + OF + Optional(UNIQUE) + parameter_type))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="general_list_type")) - interval << (((CaselessLiteral("{") + interval_low + interval_op + interval_item + interval_op + interval_high + CaselessLiteral("}")))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="interval")) - set_type << (((SET + Optional(bound_spec) + OF + instantiable_type))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="set_type")) - return_stmt << (((RETURN + Optional((CaselessLiteral("(") + expression + CaselessLiteral(")"))) + CaselessLiteral(";")))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="return_stmt")) - schema_decl << (((SCHEMA + schema_id + Optional(schema_version_id) + CaselessLiteral(";") + schema_body + END_SCHEMA + CaselessLiteral(";")))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="schema_decl")) - bound_spec << (((CaselessLiteral("[") + bound_1 + CaselessLiteral(":") + bound_2 + CaselessLiteral("]")))).setParseAction(BoundSpecification) - supertype_factor << (((supertype_term + ZeroOrMore((AND + supertype_term))))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="supertype_factor")) + function_decl << ( + ((function_head + algorithm_head + stmt + ZeroOrMore(stmt) + END_FUNCTION + CaselessLiteral(";"))) + ).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="function_decl")) + general_set_type << (((SET + Optional(bound_spec) + OF + parameter_type))).setParseAction( + lambda s, loc, t: Node(s, loc, t, rule="general_set_type") + ) + actual_parameter_list << ( + ( + ( + CaselessLiteral("(") + + Optional(parameter) + + ZeroOrMore((CaselessLiteral(",") + parameter)) + + CaselessLiteral(")") + ) + ) + ).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="actual_parameter_list")) + if_stmt << ( + ( + ( + IF + + logical_expression + + THEN + + stmt + + ZeroOrMore(stmt) + + Optional((ELSE + stmt + ZeroOrMore(stmt))) + + END_IF + + CaselessLiteral(";") + ) + ) + ).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="if_stmt")) + simple_factor << ( + ( + ( + aggregate_initializer + | interval + | query_expression + | (Optional(unary_op) + ((CaselessLiteral("(") + expression + CaselessLiteral(")")) | primary)) + | entity_constructor + | enumeration_reference + ) + ) + ).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="simple_factor")) + case_stmt << ( + ( + ( + CASE + + selector + + OF + + ZeroOrMore(case_action) + + Optional((OTHERWISE + CaselessLiteral(":") + stmt)) + + END_CASE + + CaselessLiteral(";") + ) + ) + ).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="case_stmt")) + qualifier << (((attribute_qualifier | group_qualifier | index_qualifier))).setParseAction( + lambda s, loc, t: Node(s, loc, t, rule="qualifier") + ) + general_list_type << (((LIST + Optional(bound_spec) + OF + Optional(UNIQUE) + parameter_type))).setParseAction( + lambda s, loc, t: Node(s, loc, t, rule="general_list_type") + ) + interval << ( + ( + ( + CaselessLiteral("{") + + interval_low + + interval_op + + interval_item + + interval_op + + interval_high + + CaselessLiteral("}") + ) + ) + ).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="interval")) + set_type << (((SET + Optional(bound_spec) + OF + instantiable_type))).setParseAction( + lambda s, loc, t: Node(s, loc, t, rule="set_type") + ) + return_stmt << ( + ((RETURN + Optional((CaselessLiteral("(") + expression + CaselessLiteral(")"))) + CaselessLiteral(";"))) + ).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="return_stmt")) + schema_decl << ( + ( + ( + SCHEMA + + schema_id + + Optional(schema_version_id) + + CaselessLiteral(";") + + schema_body + + END_SCHEMA + + CaselessLiteral(";") + ) + ) + ).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="schema_decl")) + bound_spec << ( + ((CaselessLiteral("[") + bound_1 + CaselessLiteral(":") + bound_2 + CaselessLiteral("]"))) + ).setParseAction(BoundSpecification) + supertype_factor << (((supertype_term + ZeroOrMore((AND + supertype_term))))).setParseAction( + lambda s, loc, t: ListNode(s, loc, t, rule="supertype_factor") + ) logical_expression << (expression) numeric_expression << (simple_expression) - repeat_control << (((Optional(increment_control) + Optional(while_control) + Optional(until_control)))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="repeat_control")) - qualifiable_factor << (((function_call | attribute_ref | constant_factor | general_ref | population))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="qualifiable_factor")) - inverse_attr << (((attribute_decl + CaselessLiteral(":") + Optional(((SET | BAG) + Optional(bound_spec) + OF)) + entity_ref + FOR + Optional((entity_ref + CaselessLiteral("."))) + attribute_ref + CaselessLiteral(";")))).setParseAction(InverseAttribute) - increment_control << (((variable_id + CaselessLiteral(":=") + bound_1 + TO + bound_2 + Optional((BY + increment))))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="increment_control")) - compound_stmt << (((BEGIN + stmt + ZeroOrMore(stmt) + END + CaselessLiteral(";")))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="compound_stmt")) - until_control << (((UNTIL + logical_expression))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="until_control")) + repeat_control << ( + ((Optional(increment_control) + Optional(while_control) + Optional(until_control))) + ).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="repeat_control")) + qualifiable_factor << ( + ((function_call | attribute_ref | constant_factor | general_ref | population)) + ).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="qualifiable_factor")) + inverse_attr << ( + ( + ( + attribute_decl + + CaselessLiteral(":") + + Optional(((SET | BAG) + Optional(bound_spec) + OF)) + + entity_ref + + FOR + + Optional((entity_ref + CaselessLiteral("."))) + + attribute_ref + + CaselessLiteral(";") + ) + ) + ).setParseAction(InverseAttribute) + increment_control << ( + ((variable_id + CaselessLiteral(":=") + bound_1 + TO + bound_2 + Optional((BY + increment)))) + ).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="increment_control")) + compound_stmt << (((BEGIN + stmt + ZeroOrMore(stmt) + END + CaselessLiteral(";")))).setParseAction( + lambda s, loc, t: ListNode(s, loc, t, rule="compound_stmt") + ) + until_control << (((UNTIL + logical_expression))).setParseAction( + lambda s, loc, t: Node(s, loc, t, rule="until_control") + ) remark << (((embedded_remark | tail_remark))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="remark")) - simple_expression << (((term + ZeroOrMore((add_like_op + term))))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="simple_expression")) - instantiable_type << (((concrete_types | entity_ref))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="instantiable_type")) - constant_body << (((constant_id + CaselessLiteral(":") + instantiable_type + CaselessLiteral(":=") + expression + CaselessLiteral(";")))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="constant_body")) + simple_expression << (((term + ZeroOrMore((add_like_op + term))))).setParseAction( + lambda s, loc, t: ListNode(s, loc, t, rule="simple_expression") + ) + instantiable_type << (((concrete_types | entity_ref))).setParseAction( + lambda s, loc, t: Node(s, loc, t, rule="instantiable_type") + ) + constant_body << ( + ( + ( + constant_id + + CaselessLiteral(":") + + instantiable_type + + CaselessLiteral(":=") + + expression + + CaselessLiteral(";") + ) + ) + ).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="constant_body")) inverse_clause << (((INVERSE + inverse_attr + ZeroOrMore(inverse_attr)))).setParseAction(AttributeList) - query_expression << (((QUERY + CaselessLiteral("(") + variable_id + CaselessLiteral("<*") + aggregate_source + CaselessLiteral("|") + logical_expression + CaselessLiteral(")")))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="query_expression")) - repeat_stmt << (((REPEAT + repeat_control + CaselessLiteral(";") + stmt + ZeroOrMore(stmt) + END_REPEAT + CaselessLiteral(";")))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="repeat_stmt")) + query_expression << ( + ( + ( + QUERY + + CaselessLiteral("(") + + variable_id + + CaselessLiteral("<*") + + aggregate_source + + CaselessLiteral("|") + + logical_expression + + CaselessLiteral(")") + ) + ) + ).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="query_expression")) + repeat_stmt << ( + ( + ( + REPEAT + + repeat_control + + CaselessLiteral(";") + + stmt + + ZeroOrMore(stmt) + + END_REPEAT + + CaselessLiteral(";") + ) + ) + ).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="repeat_stmt")) increment << (numeric_expression) - aggregate_type << (((AGGREGATE + Optional((CaselessLiteral(":") + type_label)) + OF + parameter_type))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="aggregate_type")) + aggregate_type << ( + ((AGGREGATE + Optional((CaselessLiteral(":") + type_label)) + OF + parameter_type)) + ).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="aggregate_type")) index_1 << (index) bound_2 << (numeric_expression) - factor << (((simple_factor + Optional((CaselessLiteral("**") + simple_factor))))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="factor")) + factor << (((simple_factor + Optional((CaselessLiteral("**") + simple_factor))))).setParseAction( + lambda s, loc, t: Node(s, loc, t, rule="factor") + ) interval_item << (simple_expression) - type_decl << (((TYPE + type_id + CaselessLiteral("=") + underlying_type + CaselessLiteral(";") + Optional(where_clause) + END_TYPE + CaselessLiteral(";")))).setParseAction(TypeDeclaration) - supertype_rule << (((SUPERTYPE + subtype_constraint))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="supertype_rule")) - assignment_stmt << (((general_ref + ZeroOrMore(qualifier) + CaselessLiteral(":=") + expression + CaselessLiteral(";")))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="assignment_stmt")) + type_decl << ( + ( + ( + TYPE + + type_id + + CaselessLiteral("=") + + underlying_type + + CaselessLiteral(";") + + Optional(where_clause) + + END_TYPE + + CaselessLiteral(";") + ) + ) + ).setParseAction(TypeDeclaration) + supertype_rule << (((SUPERTYPE + subtype_constraint))).setParseAction( + lambda s, loc, t: Node(s, loc, t, rule="supertype_rule") + ) + assignment_stmt << ( + ((general_ref + ZeroOrMore(qualifier) + CaselessLiteral(":=") + expression + CaselessLiteral(";"))) + ).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="assignment_stmt")) interval_low << (simple_expression) - concrete_types << (((aggregation_types | simple_types | type_ref))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="concrete_types")) - element << (((expression + Optional((CaselessLiteral(":") + repetition))))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="element")) + concrete_types << (((aggregation_types | simple_types | type_ref))).setParseAction( + lambda s, loc, t: Node(s, loc, t, rule="concrete_types") + ) + element << (((expression + Optional((CaselessLiteral(":") + repetition))))).setParseAction( + lambda s, loc, t: Node(s, loc, t, rule="element") + ) string_type << (((STRING + Optional(width_spec)))).setParseAction(StringType) - procedure_decl << (((procedure_head + algorithm_head + ZeroOrMore(stmt) + END_PROCEDURE + CaselessLiteral(";")))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="procedure_decl")) - width_spec << (((CaselessLiteral("(") + width + CaselessLiteral(")") + Optional(FIXED)))).setParseAction(WidthSpec) - alias_stmt << (((ALIAS + variable_id + FOR + general_ref + ZeroOrMore(qualifier) + CaselessLiteral(";") + stmt + ZeroOrMore(stmt) + END_ALIAS + CaselessLiteral(";")))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="alias_stmt")) - subtype_constraint << (((OF + CaselessLiteral("(") + supertype_expression + CaselessLiteral(")")))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="subtype_constraint")) + procedure_decl << ( + ((procedure_head + algorithm_head + ZeroOrMore(stmt) + END_PROCEDURE + CaselessLiteral(";"))) + ).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="procedure_decl")) + width_spec << (((CaselessLiteral("(") + width + CaselessLiteral(")") + Optional(FIXED)))).setParseAction( + WidthSpec + ) + alias_stmt << ( + ( + ( + ALIAS + + variable_id + + FOR + + general_ref + + ZeroOrMore(qualifier) + + CaselessLiteral(";") + + stmt + + ZeroOrMore(stmt) + + END_ALIAS + + CaselessLiteral(";") + ) + ) + ).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="alias_stmt")) + subtype_constraint << ( + ((OF + CaselessLiteral("(") + supertype_expression + CaselessLiteral(")"))) + ).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="subtype_constraint")) index << (numeric_expression) - declaration << (((entity_decl | function_decl | procedure_decl | subtype_constraint_decl | type_decl))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="declaration")) - real_type << (((REAL + Optional((CaselessLiteral("(") + precision_spec + CaselessLiteral(")")))))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="real_type")) - index_qualifier << (((CaselessLiteral("[") + index_1 + Optional((CaselessLiteral(":") + index_2)) + CaselessLiteral("]")))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="index_qualifier")) - generalized_types << (((aggregate_type | general_aggregation_types | generic_entity_type | generic_type))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="generalized_types")) - constant_decl << (((CONSTANT + constant_body + ZeroOrMore(constant_body) + END_CONSTANT + CaselessLiteral(";")))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="constant_decl")) + declaration << ( + ((entity_decl | function_decl | procedure_decl | subtype_constraint_decl | type_decl)) + ).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="declaration")) + real_type << ( + ((REAL + Optional((CaselessLiteral("(") + precision_spec + CaselessLiteral(")"))))) + ).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="real_type")) + index_qualifier << ( + ((CaselessLiteral("[") + index_1 + Optional((CaselessLiteral(":") + index_2)) + CaselessLiteral("]"))) + ).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="index_qualifier")) + generalized_types << ( + ((aggregate_type | general_aggregation_types | generic_entity_type | generic_type)) + ).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="generalized_types")) + constant_decl << ( + ((CONSTANT + constant_body + ZeroOrMore(constant_body) + END_CONSTANT + CaselessLiteral(";"))) + ).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="constant_decl")) precision_spec << (numeric_expression) - function_head << (((FUNCTION + function_id + Optional((CaselessLiteral("(") + formal_parameter + ZeroOrMore((CaselessLiteral(";") + formal_parameter)) + CaselessLiteral(")"))) + CaselessLiteral(":") + parameter_type + CaselessLiteral(";")))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="function_head")) + function_head << ( + ( + ( + FUNCTION + + function_id + + Optional( + ( + CaselessLiteral("(") + + formal_parameter + + ZeroOrMore((CaselessLiteral(";") + formal_parameter)) + + CaselessLiteral(")") + ) + ) + + CaselessLiteral(":") + + parameter_type + + CaselessLiteral(";") + ) + ) + ).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="function_head")) derive_clause << (((DERIVE + derived_attr + ZeroOrMore(derived_attr)))).setParseAction(AttributeList) - function_call << ((((built_in_function | function_ref) + actual_parameter_list))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="function_call")) + function_call << ((((built_in_function | function_ref) + actual_parameter_list))).setParseAction( + lambda s, loc, t: Node(s, loc, t, rule="function_call") + ) case_label << (expression) - supertype_expression << (((supertype_factor + ZeroOrMore((ANDOR + supertype_factor))))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="supertype_expression")) - procedure_head << (((PROCEDURE + procedure_id + Optional((CaselessLiteral("(") + Optional(VAR) + formal_parameter + ZeroOrMore((CaselessLiteral(";") + Optional(VAR) + formal_parameter)) + CaselessLiteral(")"))) + CaselessLiteral(";")))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="procedure_head")) - derived_attr << (((attribute_decl + CaselessLiteral(":") + parameter_type + CaselessLiteral(":=") + expression + CaselessLiteral(";")))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="derived_attr")) - bag_type << (((BAG + Optional(bound_spec) + OF + instantiable_type))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="bag_type")) - term << (((factor + ZeroOrMore((multiplication_like_op + factor))))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="term")) - supertype_constraint << (((abstract_supertype_declaration | abstract_entity_declaration | supertype_rule))).setParseAction(SuperTypeExpression) + supertype_expression << (((supertype_factor + ZeroOrMore((ANDOR + supertype_factor))))).setParseAction( + lambda s, loc, t: ListNode(s, loc, t, rule="supertype_expression") + ) + procedure_head << ( + ( + ( + PROCEDURE + + procedure_id + + Optional( + ( + CaselessLiteral("(") + + Optional(VAR) + + formal_parameter + + ZeroOrMore((CaselessLiteral(";") + Optional(VAR) + formal_parameter)) + + CaselessLiteral(")") + ) + ) + + CaselessLiteral(";") + ) + ) + ).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="procedure_head")) + derived_attr << ( + ( + ( + attribute_decl + + CaselessLiteral(":") + + parameter_type + + CaselessLiteral(":=") + + expression + + CaselessLiteral(";") + ) + ) + ).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="derived_attr")) + bag_type << (((BAG + Optional(bound_spec) + OF + instantiable_type))).setParseAction( + lambda s, loc, t: Node(s, loc, t, rule="bag_type") + ) + term << (((factor + ZeroOrMore((multiplication_like_op + factor))))).setParseAction( + lambda s, loc, t: ListNode(s, loc, t, rule="term") + ) + supertype_constraint << ( + ((abstract_supertype_declaration | abstract_entity_declaration | supertype_rule)) + ).setParseAction(SuperTypeExpression) aggregate_source << (simple_expression) - where_clause << (((WHERE + domain_rule + CaselessLiteral(";") + ZeroOrMore((domain_rule + CaselessLiteral(";")))))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="where_clause")) + where_clause << ( + ((WHERE + domain_rule + CaselessLiteral(";") + ZeroOrMore((domain_rule + CaselessLiteral(";"))))) + ).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="where_clause")) repetition << (numeric_expression) - abstract_supertype_declaration << (((ABSTRACT + SUPERTYPE + Optional(subtype_constraint)))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="abstract_supertype_declaration")) - domain_rule << (((Optional((rule_label_id + CaselessLiteral(":"))) + expression))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="domain_rule")) + abstract_supertype_declaration << (((ABSTRACT + SUPERTYPE + Optional(subtype_constraint)))).setParseAction( + lambda s, loc, t: Node(s, loc, t, rule="abstract_supertype_declaration") + ) + domain_rule << (((Optional((rule_label_id + CaselessLiteral(":"))) + expression))).setParseAction( + lambda s, loc, t: Node(s, loc, t, rule="domain_rule") + ) index_2 << (index) - subsuper << (((Optional(supertype_constraint) + Optional(subtype_declaration)))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="subsuper")) - supertype_term << (((one_of | (CaselessLiteral("(") + supertype_expression + CaselessLiteral(")")) | entity_ref))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="supertype_term")) - underlying_type << (((constructed_types | concrete_types))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="underlying_type")) - subtype_constraint_decl << (((subtype_constraint_head + subtype_constraint_body + END_SUBTYPE_CONSTRAINT + CaselessLiteral(";")))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="subtype_constraint_decl")) + subsuper << (((Optional(supertype_constraint) + Optional(subtype_declaration)))).setParseAction( + lambda s, loc, t: Node(s, loc, t, rule="subsuper") + ) + supertype_term << ( + ((one_of | (CaselessLiteral("(") + supertype_expression + CaselessLiteral(")")) | entity_ref)) + ).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="supertype_term")) + underlying_type << (((constructed_types | concrete_types))).setParseAction( + lambda s, loc, t: Node(s, loc, t, rule="underlying_type") + ) + subtype_constraint_decl << ( + ((subtype_constraint_head + subtype_constraint_body + END_SUBTYPE_CONSTRAINT + CaselessLiteral(";"))) + ).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="subtype_constraint_decl")) parameter << (expression) - rule_decl << (((rule_head + algorithm_head + ZeroOrMore(stmt) + where_clause + END_RULE + CaselessLiteral(";")))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="rule_decl")) - case_action << (((case_label + ZeroOrMore((CaselessLiteral(",") + case_label)) + CaselessLiteral(":") + stmt))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="case_action")) - local_decl << (((LOCAL + local_variable + ZeroOrMore(local_variable) + END_LOCAL + CaselessLiteral(";")))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="local_decl")) - primary << (((literal | (qualifiable_factor + ZeroOrMore(qualifier))))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="primary")) - one_of << (((ONEOF + CaselessLiteral("(") + supertype_expression + ZeroOrMore((CaselessLiteral(",") + supertype_expression)) + CaselessLiteral(")")))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="one_of")) - local_variable << (((variable_id + ZeroOrMore((CaselessLiteral(",") + variable_id)) + CaselessLiteral(":") + parameter_type + Optional((CaselessLiteral(":=") + expression)) + CaselessLiteral(";")))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="local_variable")) - entity_head << (((ENTITY + entity_id + subsuper + CaselessLiteral(";")))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="entity_head")) - formal_parameter << (((parameter_id + ZeroOrMore((CaselessLiteral(",") + parameter_id)) + CaselessLiteral(":") + parameter_type))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="formal_parameter")) - array_type << (((ARRAY + bound_spec + OF + Optional(OPTIONAL) + Optional(UNIQUE) + instantiable_type))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="array_type")) - subtype_constraint_body << (((Optional(abstract_supertype) + Optional(total_over) + Optional((supertype_expression + CaselessLiteral(";")))))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="subtype_constraint_body")) - explicit_attr << (((attribute_decl + ZeroOrMore((CaselessLiteral(",") + attribute_decl)) + CaselessLiteral(":") + Optional(OPTIONAL) + parameter_type + CaselessLiteral(";")))).setParseAction(ExplicitAttribute) - general_bag_type << (((BAG + Optional(bound_spec) + OF + parameter_type))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="general_bag_type")) - while_control << (((WHILE + logical_expression))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="while_control")) - schema_body << (((ZeroOrMore(interface_specification) + Optional(constant_decl) + ZeroOrMore((declaration | rule_decl))))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="schema_body")) - list_type << (((LIST + Optional(bound_spec) + OF + Optional(UNIQUE) + instantiable_type))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="list_type")) - entity_constructor << (((entity_ref + CaselessLiteral("(") + Optional((expression + ZeroOrMore((CaselessLiteral(",") + expression)))) + CaselessLiteral(")")))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="entity_constructor")) - syntax << (((schema_decl + ZeroOrMore(schema_decl)))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="syntax")) - entity_decl << (((entity_head + entity_body + END_ENTITY + CaselessLiteral(";")))).setParseAction(EntityDeclaration) - algorithm_head << (((ZeroOrMore(declaration) + Optional(constant_decl) + Optional(local_decl)))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="algorithm_head")) - general_array_type << (((ARRAY + Optional(bound_spec) + OF + Optional(OPTIONAL) + Optional(UNIQUE) + parameter_type))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="general_array_type")) - entity_body << (((ZeroOrMore(explicit_attr) + Optional(derive_clause) + Optional(inverse_clause) + Optional(unique_clause) + Optional(where_clause)))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="entity_body")) + rule_decl << ( + ((rule_head + algorithm_head + ZeroOrMore(stmt) + where_clause + END_RULE + CaselessLiteral(";"))) + ).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="rule_decl")) + case_action << ( + ((case_label + ZeroOrMore((CaselessLiteral(",") + case_label)) + CaselessLiteral(":") + stmt)) + ).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="case_action")) + local_decl << ( + ((LOCAL + local_variable + ZeroOrMore(local_variable) + END_LOCAL + CaselessLiteral(";"))) + ).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="local_decl")) + primary << (((literal | (qualifiable_factor + ZeroOrMore(qualifier))))).setParseAction( + lambda s, loc, t: ListNode(s, loc, t, rule="primary") + ) + one_of << ( + ( + ( + ONEOF + + CaselessLiteral("(") + + supertype_expression + + ZeroOrMore((CaselessLiteral(",") + supertype_expression)) + + CaselessLiteral(")") + ) + ) + ).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="one_of")) + local_variable << ( + ( + ( + variable_id + + ZeroOrMore((CaselessLiteral(",") + variable_id)) + + CaselessLiteral(":") + + parameter_type + + Optional((CaselessLiteral(":=") + expression)) + + CaselessLiteral(";") + ) + ) + ).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="local_variable")) + entity_head << (((ENTITY + entity_id + subsuper + CaselessLiteral(";")))).setParseAction( + lambda s, loc, t: Node(s, loc, t, rule="entity_head") + ) + formal_parameter << ( + ((parameter_id + ZeroOrMore((CaselessLiteral(",") + parameter_id)) + CaselessLiteral(":") + parameter_type)) + ).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="formal_parameter")) + array_type << ( + ((ARRAY + bound_spec + OF + Optional(OPTIONAL) + Optional(UNIQUE) + instantiable_type)) + ).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="array_type")) + subtype_constraint_body << ( + ( + ( + Optional(abstract_supertype) + + Optional(total_over) + + Optional((supertype_expression + CaselessLiteral(";"))) + ) + ) + ).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="subtype_constraint_body")) + explicit_attr << ( + ( + ( + attribute_decl + + ZeroOrMore((CaselessLiteral(",") + attribute_decl)) + + CaselessLiteral(":") + + Optional(OPTIONAL) + + parameter_type + + CaselessLiteral(";") + ) + ) + ).setParseAction(ExplicitAttribute) + general_bag_type << (((BAG + Optional(bound_spec) + OF + parameter_type))).setParseAction( + lambda s, loc, t: Node(s, loc, t, rule="general_bag_type") + ) + while_control << (((WHILE + logical_expression))).setParseAction( + lambda s, loc, t: Node(s, loc, t, rule="while_control") + ) + schema_body << ( + ((ZeroOrMore(interface_specification) + Optional(constant_decl) + ZeroOrMore((declaration | rule_decl)))) + ).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="schema_body")) + list_type << (((LIST + Optional(bound_spec) + OF + Optional(UNIQUE) + instantiable_type))).setParseAction( + lambda s, loc, t: Node(s, loc, t, rule="list_type") + ) + entity_constructor << ( + ( + ( + entity_ref + + CaselessLiteral("(") + + Optional((expression + ZeroOrMore((CaselessLiteral(",") + expression)))) + + CaselessLiteral(")") + ) + ) + ).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="entity_constructor")) + syntax << (((schema_decl + ZeroOrMore(schema_decl)))).setParseAction( + lambda s, loc, t: ListNode(s, loc, t, rule="syntax") + ) + entity_decl << (((entity_head + entity_body + END_ENTITY + CaselessLiteral(";")))).setParseAction( + EntityDeclaration + ) + algorithm_head << (((ZeroOrMore(declaration) + Optional(constant_decl) + Optional(local_decl)))).setParseAction( + lambda s, loc, t: ListNode(s, loc, t, rule="algorithm_head") + ) + general_array_type << ( + ((ARRAY + Optional(bound_spec) + OF + Optional(OPTIONAL) + Optional(UNIQUE) + parameter_type)) + ).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="general_array_type")) + entity_body << ( + ( + ( + ZeroOrMore(explicit_attr) + + Optional(derive_clause) + + Optional(inverse_clause) + + Optional(unique_clause) + + Optional(where_clause) + ) + ) + ).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="entity_body")) aggregation_types << (((array_type | bag_type | list_type | set_type))).setParseAction(AggregationType) selector << (expression) - embedded_remark << (((CaselessLiteral("(*") + Optional(remark_tag) + ZeroOrMore(((not_paren_star + ZeroOrMore(not_paren_star)) | lparen_then_not_lparen_star | (CaselessLiteral("*") + ZeroOrMore(CaselessLiteral("*"))) | not_rparen_star_then_rparen | embedded_remark)) + CaselessLiteral("*)")))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="embedded_remark")) - aggregate_initializer << (((CaselessLiteral("[") + Optional((element + ZeroOrMore((CaselessLiteral(",") + element)))) + CaselessLiteral("]")))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="aggregate_initializer")) - parameter_type << (((generalized_types | simple_types | named_types))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="parameter_type")) - general_aggregation_types << (((general_array_type | general_bag_type | general_list_type | general_set_type))).setParseAction(AggregationType) + embedded_remark << ( + ( + ( + CaselessLiteral("(*") + + Optional(remark_tag) + + ZeroOrMore( + ( + (not_paren_star + ZeroOrMore(not_paren_star)) + | lparen_then_not_lparen_star + | (CaselessLiteral("*") + ZeroOrMore(CaselessLiteral("*"))) + | not_rparen_star_then_rparen + | embedded_remark + ) + ) + + CaselessLiteral("*)") + ) + ) + ).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="embedded_remark")) + aggregate_initializer << ( + ( + ( + CaselessLiteral("[") + + Optional((element + ZeroOrMore((CaselessLiteral(",") + element)))) + + CaselessLiteral("]") + ) + ) + ).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="aggregate_initializer")) + parameter_type << (((generalized_types | simple_types | named_types))).setParseAction( + lambda s, loc, t: Node(s, loc, t, rule="parameter_type") + ) + general_aggregation_types << ( + ((general_array_type | general_bag_type | general_list_type | general_set_type)) + ).setParseAction(AggregationType) bound_1 << (numeric_expression) - stmt << (((alias_stmt | assignment_stmt | case_stmt | compound_stmt | escape_stmt | if_stmt | null_stmt | procedure_call_stmt | repeat_stmt | return_stmt | skip_stmt))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="stmt")) + stmt << ( + ( + ( + alias_stmt + | assignment_stmt + | case_stmt + | compound_stmt + | escape_stmt + | if_stmt + | null_stmt + | procedure_call_stmt + | repeat_stmt + | return_stmt + | skip_stmt + ) + ) + ).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="stmt")) width << (numeric_expression) - procedure_call_stmt << ((((built_in_procedure | procedure_ref) + actual_parameter_list + CaselessLiteral(";")))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="procedure_call_stmt")) + procedure_call_stmt << ( + (((built_in_procedure | procedure_ref) + actual_parameter_list + CaselessLiteral(";"))) + ).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="procedure_call_stmt")) interval_high << (simple_expression) - expression << (((simple_expression + Optional((rel_op_extended + simple_expression))))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="expression")) - simple_types << (((binary_type | boolean_type | integer_type | logical_type | number_type | real_type | string_type))).setParseAction(SimpleType) + expression << (((simple_expression + Optional((rel_op_extended + simple_expression))))).setParseAction( + lambda s, loc, t: Node(s, loc, t, rule="expression") + ) + simple_types << ( + ((binary_type | boolean_type | integer_type | logical_type | number_type | real_type | string_type)) + ).setParseAction(SimpleType) syntax.ignore("--" + restOfLine) syntax.ignore(Regex(r"\((?:\*(?:[^*]*\*+)+?\))")) @@ -478,12 +1466,13 @@ def parse(fn): with open(cache_file, "wb") as f: pickle.dump(m, f, protocol=0) return m - + + if __name__ == "__main__": m = parse(sys.argv[1]) import importlib + for output in sys.argv[2:]: mdl = importlib.import_module(output) mdl.Generator(m).emit() sys.stdout.write(m.schema.name) - diff --git a/src/ifcopenshell-python/ifcopenshell/express/implementation.py b/src/ifcopenshell-python/ifcopenshell/express/implementation.py index b321ae4345..21ee215a8c 100644 --- a/src/ifcopenshell-python/ifcopenshell/express/implementation.py +++ b/src/ifcopenshell-python/ifcopenshell/express/implementation.py @@ -334,13 +334,17 @@ class Implementation(codegen.Base): ) simple_type_impl.append("") - external_definitions = [ - ("extern entity* %s_%%s_type;" % schema_name_upper) % n for n in mapping.schema.entities.keys() - ] + [ - ("extern type_declaration* %s_%%s_type;" % schema_name_upper) % n for n in mapping.schema.simpletypes.keys() - ] + [ - ("extern enumeration_type* %s_%%s_type;" % schema_name_upper) % n for n in mapping.schema.enumerations.keys() - ] + external_definitions = ( + [("extern entity* %s_%%s_type;" % schema_name_upper) % n for n in mapping.schema.entities.keys()] + + [ + ("extern type_declaration* %s_%%s_type;" % schema_name_upper) % n + for n in mapping.schema.simpletypes.keys() + ] + + [ + ("extern enumeration_type* %s_%%s_type;" % schema_name_upper) % n + for n in mapping.schema.enumerations.keys() + ] + ) self.str = templates.implementation % { "schema_name_upper": schema_name_upper, diff --git a/src/ifcopenshell-python/ifcopenshell/express/schema_class.py b/src/ifcopenshell-python/ifcopenshell/express/schema_class.py index 84b2da5fe6..6160fe88c3 100644 --- a/src/ifcopenshell-python/ifcopenshell/express/schema_class.py +++ b/src/ifcopenshell-python/ifcopenshell/express/schema_class.py @@ -488,10 +488,12 @@ class SchemaClass(codegen.Base): for name, tys in subtypes.items(): x.entity_subtypes(name, tys) - can_be_instantiated_set = set(list(mapping.schema.entities.keys()) + \ - list(mapping.schema.simpletypes.keys()) + \ - list(mapping.schema.enumerations.keys())) - + can_be_instantiated_set = set( + list(mapping.schema.entities.keys()) + + list(mapping.schema.simpletypes.keys()) + + list(mapping.schema.enumerations.keys()) + ) + x.finalize(can_be_instantiated_set) self.str = str(x) diff --git a/src/ifcopenshell-python/ifcopenshell/file.py b/src/ifcopenshell-python/ifcopenshell/file.py index a43157c105..a96c5c7687 100644 --- a/src/ifcopenshell-python/ifcopenshell/file.py +++ b/src/ifcopenshell-python/ifcopenshell/file.py @@ -23,6 +23,7 @@ from __future__ import print_function import numbers import functools +import ifcopenshell.util.element from . import ifcopenshell_wrapper from .entity_instance import entity_instance @@ -111,22 +112,16 @@ class Transaction: for inverse in self.file.get_inverse(element): inverse_references = [] for i, attribute in enumerate(inverse): - if self.has_element_reference(attribute, element): + if ifcopenshell.util.element.has_element_reference(attribute, element): inverse_references.append((i, self.serialise_value(inverse, attribute))) inverses[inverse.id()] = inverse_references return inverses - def has_element_reference(self, value, element): - if isinstance(value, (tuple, list)): - for v in value: - return self.has_element_reference(v, element) - return value == element - def rollback(self): for operation in self.operations[::-1]: if operation["action"] == "create": element = self.file.by_id(operation["value"]["id"]) - if hasattr(element, "GlobalId"): + if hasattr(element, "GlobalId") and element.GlobalId is None: # hack, otherwise ifcopenshell gets upset element.GlobalId = "x" self.file.remove(element) @@ -259,11 +254,7 @@ class file(object): f.create_entity('IfcPerson', Identification='Foobar') >>> #3=IfcPerson('Foobar',$,$,$,$,$,$,$) """ - eid = -1 - try: - eid = kwargs.pop("id", -1) - except: - pass + eid = kwargs.pop("id", -1) e = entity_instance((self.schema, type), self) @@ -340,8 +331,14 @@ class file(object): """Adds an entity including any dependent entities to an IFC file. If the entity already exists, it is not re-added.""" + if self.transaction: + max_id = self.wrapped_data.getMaxId() inst.wrapped_data.this.disown() - return entity_instance(self.wrapped_data.add(inst.wrapped_data, -1 if _id is None else _id), self) + result = entity_instance(self.wrapped_data.add(inst.wrapped_data, -1 if _id is None else _id), self) + if self.transaction: + added_elements = [e for e in self.traverse(result) if e.id() > max_id] + [self.transaction.store_create(e) for e in reversed(added_elements)] + return result def by_type(self, type, include_subtypes=True): """Return IFC objects filtered by IFC Type and wrapped with the entity_instance class. @@ -373,12 +370,12 @@ class file(object): """ if max_levels is None: max_levels = -1 - + if breadth_first: fn = self.wrapped_data.traverse_breadth_first else: fn = self.wrapped_data.traverse - + return [entity_instance(e, self) for e in fn(inst.wrapped_data, max_levels)] def get_inverse(self, inst): diff --git a/src/ifcopenshell-python/ifcopenshell/geom/app.py b/src/ifcopenshell-python/ifcopenshell/geom/app.py index 43d5762584..7af786b763 100644 --- a/src/ifcopenshell-python/ifcopenshell/geom/app.py +++ b/src/ifcopenshell-python/ifcopenshell/geom/app.py @@ -10,7 +10,7 @@ import functools import multiprocessing try: - from OCC.Core import AIS + from OCC.Core import AIS USE_OCCT_HANDLE = False except ImportError: @@ -433,30 +433,30 @@ class application(QtWidgets.QApplication): instanceSelected = QtCore.pyqtSignal([object]) -# @staticmethod -# def ais_to_key(ais_handle): -# def yield_shapes(): -# ais = ais_handle.GetObject() -# if hasattr(ais, "Shape"): -# yield ais.Shape() -# return -# shp = OCC.AIS.Handle_AIS_Shape.DownCast(ais_handle) -# if not shp.IsNull(): -# yield shp.Shape() -# return -# mult = ais_handle -# if mult.IsNull(): -# shp = OCC.AIS.Handle_AIS_Shape.DownCast(ais_handle) -# if not shp.IsNull(): -# yield shp -# else: -# li = mult.GetObject().ConnectedTo() -# for i in range(li.Length()): -# shp = OCC.AIS.Handle_AIS_Shape.DownCast(li.Value(i + 1)) -# if not shp.IsNull(): -# yield shp + # @staticmethod + # def ais_to_key(ais_handle): + # def yield_shapes(): + # ais = ais_handle.GetObject() + # if hasattr(ais, "Shape"): + # yield ais.Shape() + # return + # shp = OCC.AIS.Handle_AIS_Shape.DownCast(ais_handle) + # if not shp.IsNull(): + # yield shp.Shape() + # return + # mult = ais_handle + # if mult.IsNull(): + # shp = OCC.AIS.Handle_AIS_Shape.DownCast(ais_handle) + # if not shp.IsNull(): + # yield shp + # else: + # li = mult.GetObject().ConnectedTo() + # for i in range(li.Length()): + # shp = OCC.AIS.Handle_AIS_Shape.DownCast(li.Value(i + 1)) + # if not shp.IsNull(): + # yield shp -# return tuple(shp.HashCode(1 << 24) for shp in yield_shapes()) + # return tuple(shp.HashCode(1 << 24) for shp in yield_shapes()) def __init__(self, widget): qtViewer3d.__init__(self, widget) @@ -486,8 +486,8 @@ class application(QtWidgets.QApplication): for shape in shapes: ais = display_shape(shape, viewer_handle=v) product = f[shape.data.id] - - if USE_OCCT_HANDLE: + + if USE_OCCT_HANDLE: ais.GetObject().SetSelectionPriority(self.counter) self.ais_to_product[self.counter] = product self.product_to_ais[product] = ais diff --git a/src/ifcopenshell-python/ifcopenshell/geom/main.py b/src/ifcopenshell-python/ifcopenshell/geom/main.py index f5cffbe269..23eee7b696 100644 --- a/src/ifcopenshell-python/ifcopenshell/geom/main.py +++ b/src/ifcopenshell-python/ifcopenshell/geom/main.py @@ -133,7 +133,7 @@ class tree(ifcopenshell_wrapper.tree): def add_file(self, file, settings): ifcopenshell_wrapper.tree.add_file(self, file.wrapped_data, settings) - + def add_iterator(self, iterator): ifcopenshell_wrapper.tree.add_file(self, iterator) diff --git a/src/ifcopenshell-python/ifcopenshell/ids.py b/src/ifcopenshell-python/ifcopenshell/ids.py index 4b0f9f762c..757fb6b199 100644 --- a/src/ifcopenshell-python/ifcopenshell/ids.py +++ b/src/ifcopenshell-python/ifcopenshell/ids.py @@ -1,26 +1,405 @@ -import operator -import ifcopenshell.util.element +# IDS - Information Delivery Specification. +# Copyright (C) 2021 Artur Tomczak , Thomas Krijnen +# +# This file is part of IfcOpenShell. +# +# IfcOpenShell is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# IfcOpenShell is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with IfcOpenShell. If not, see . + +import os import re +import logging +import numpy as np +from datetime import date + +import ifcopenshell.util.element +import ifcopenshell.util.placement + +from bcf.v2.bcfxml import BcfXml +from bcf.v2 import data as bcf + from xmlschema import XMLSchema from xmlschema import etree_tostring -from xmlschema.validators import facets from xmlschema.validators import identities -ids_schema = XMLSchema("http://standards.buildingsmart.org/IDS/ids.xsd") - -class exception(Exception): - pass +cwd = os.path.dirname(os.path.realpath(__file__)) +ids_schema = XMLSchema(os.path.join(cwd, "ids.xsd")) # source: "http://standards.buildingsmart.org/IDS/ids_04.xsd" def error(msg): - raise exception(msg) + raise Exception(msg) + + +class ids: + """Represents the XML root node and its childNodes.""" + + def __init__( + self, + ifcversion=None, + description=None, + author=None, + copyright=None, + version=None, + creation_date=None, + purpose=None, + milestone=None, + ): + """Create an IDS object. + + :param ifcversion: IFC schema version. If None, then schema independent. Options: '2.3.0.1'|'4.0.2.1'|'4.3.0.0'|None, defaults to None + :type ifcversion: str, optional + :param description:, defaults to None + :type description: str, optional + :param author: Email of the IDS author, defaults to None + :type author: str, optional + :param copyright:, defaults to None + :type copyright: str, optional + :param version: IDS file version, defaults to None + :type version: float, optional + :param creation_date: Date in 'yyyy-mm-dd' format, defaults to current date + :type creation_date: str, optional + :param purpose:, defaults to None + :type purpose: str, optional + :param milestone:, defaults to None + :type milestone: str, optional + """ + self.specifications = [] + self.info = {} + if ifcversion: + if ifcversion in ["2.3.0.1", "4.0.2.1", "4.3.0.0"]: + self.info["ifcversion"] = ifcversion + if author: + if "@" in author: + self.info["author"] = author + if description: + self.info["description"] = description + if copyright: + self.info["copyright"] = copyright + if version: + self.info["version"] = version + if creation_date: + if re.match(r"\d\d\d\d-\d\d-\d\d", creation_date): + self.info["date"] = creation_date # date.fromisoformat(creation_date).isoformat() + if "date" not in self.info: + self.info["date"] = date.today().isoformat() + if purpose: + self.info["purpose"] = purpose + if milestone: + self.info["milestone"] = milestone + + def asdict(self): + """Converts object to a dictionary, adding required attributes. + + :return: Xmlschema compliant dictionary. + :rtype: dict + """ + ids_dict = { + "@xmlns": "http://standards.buildingsmart.org/IDS", + "@xmlns:xs": "http://www.w3.org/2001/XMLSchema", + "@xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance", + "@xsi:schemaLocation": "http://standards.buildingsmart.org/IDS/ids_04.xsd", + "specification": [], + "info": self.info, + } + for spec in self.specifications: + ids_dict["specification"].append(spec.asdict()) + return ids_dict + + def to_xml(self, filepath="./", ids_schema=ids_schema): + """Save IDS object as .xml file. + + :param filepath: Path for the new file, defaults to "./" + :type filepath: str, optional + :param ids_schema: XML Schema for an IDS file, defaults to ids_schema object from buildingSMART + :type ids_schema: XMLschema, optional + :return: Result of the newly created file validation against the schema. + :rtype: bool + """ + + if filepath.endswith("/"): + filepath = filepath + "IDS" + if not filepath.endswith(".xml"): + filepath = filepath + ".xml" + + ids_dict = self.asdict() + + ids_xml = ids_schema.encode( + ids_dict, + namespaces={ + "": "http://standards.buildingsmart.org/IDS", + "xs": "http://www.w3.org/2001/XMLSchema", + "xsi": "http://www.w3.org/2001/XMLSchema-instance", + "xsi:schemaLocation": "http://standards.buildingsmart.org/IDS/ids_04.xsd", + }, + ) # validation='skip', + + ids_str = etree_tostring( + ids_xml, + namespaces={ + "": "http://standards.buildingsmart.org/IDS", + # 'xs': 'http://www.w3.org/2001/XMLSchema', + # 'xsi': 'http://www.w3.org/2001/XMLSchema-instance', + # 'xsi:schemaLocation': "http://standards.buildingsmart.org/IDS/ids_04.xsd" + }, + ) + + with open(filepath, "w") as f: + f.write('\n') + f.write("\n") + f.write(ids_str) + f.close() + + # ids_schema.validate(filepath) + return ids_schema.is_valid(filepath) + + @staticmethod + def open(filepath, ids_schema=ids_schema): + """Use to open ids.xml files + + :param filepath: ids file path + :type filepath: str + :param ids_schema: XML Schema for an IDS file, defaults to ids_schema object from buildingSMART + :type ids_schema: XMLschema, optional + :return: IDS file as a python object + :rtype: ids object + """ + + ids_schema.validate(filepath) + ids_content = ids_schema.decode( + filepath, strip_namespaces=True, namespaces={"": "http://standards.buildingsmart.org/IDS"} + ) + ids_file = ids() + ids_file.specifications = [specification.parse(s) for s in ids_content["specification"]] + return ids_file + + def validate(self, ifc_file, logger=None): + """Use to validate IFC model against IDS specifications. + + :param ifc_file: path to ifc file + :type ifc_file: str + :param logger: Logging object with handlers, defaults to None + :type logger: logging, optional + """ + if not isinstance(logger, logging.Logger): + logger = logging.getLogger("IDS_Logger") + logging.basicConfig(level=logging.INFO, format="%(message)s") + logger.setLevel(logging.INFO) + + if "ifcversion" in self.info.keys(): + if self.info["ifcversion"] in ["2.3.0.1", "4.0.2.1", "4.3.0.0"]: + if self.info["ifcversion"][0:3] == "2.3": + if not ifc_file.schema.startswith("IFC2x3"): + logger.error("IFC file is of %s not of %s schema." % (ifc_file.schema, self.info["ifcversion"])) + elif self.info["ifcversion"][0:3] == "4.0": + if not ifc_file.schema == "IFC4": + logger.error("IFC file is of %s not of %s schema." % (ifc_file.schema, self.info["ifcversion"])) + elif self.info["ifcversion"][0:3] == "4.3": + if not ifc_file.schema.startswith("IFC4x3"): + logger.error("IFC file is of %s not of %s schema." % (ifc_file.schema, self.info["ifcversion"])) + else: + logger.error("IFC version not recognized") + + # Consider other way around: for elem, for spec so we can see if an element pass all IDSes? + for spec in self.specifications: + self.ifc_applicable = 0 + self.ifc_passed = 0 + for elem in ifc_file.by_type("IfcObject"): + apply, comply = spec(elem, logger) + if apply: + self.ifc_applicable += 1 + if comply: + self.ifc_passed += 1 + if self.ifc_applicable == 0: + if spec.necessity == "required": + logger.error("No applicable elements found. Minimum 1 applicable element required.") + else: + logger.debug("No applicable elements found. None required.") + + try: + percentage = self.ifc_passed / self.ifc_applicable * 100 + except ZeroDivisionError: + percentage = 0 + + logger.debug( + "Out of %s IFC elements, %s were applicable and %s of them passed (%s)." + % ( + len(ifc_file.by_type("IfcProduct")), + self.ifc_applicable, + self.ifc_passed, + str(percentage) + "%", + ) + ) + for h in logger.handlers: + h.flush() + + +class specification: + """Represents the XML node and its two children and """ + + def __init__(self, name="Specification", necessity="required"): + """Create a specification to be added in ids. + + :param name:, defaults to "Specification" + :type name: str, optional + :param necessity: 'required'|'optional', defaults to "required" + :type necessity: str, optional + """ + self.name = name + self.applicability = None + self.requirements = None + self.necessity = necessity + + def asdict(self): + """Converts object to a dictionary, adding required attributes. + + :return: Xmlschema compliant dictionary. + :rtype: dict + """ + # if older python collections.OrderedDict() + spec_dict = { + "@name": self.name, + "@necessity": self.necessity, + "applicability": {}, + "requirements": {}, + } + for x in ["applicability", "requirements"]: + for fac in (getattr(self, x)).terms: + fclass = type(fac).__name__ + if fclass in spec_dict[x]: + spec_dict[x][fclass].append(fac.asdict()) + else: + spec_dict[x][fclass] = [fac.asdict()] + return spec_dict + + @staticmethod + def parse(ids_dict): + """Parse xml specification to python object. + + :param ids_dict: + :type ids_dict: dict + """ + + def parse_rules(dict): + facet_names = list(dict.keys()) + facet_properties = [v[0] if isinstance(v, list) else v for v in list(dict.values())] + classes = [meta_facet.facets.__getitem__(f) for f in facet_names] + facets = [cls(n) for cls, n in zip(classes, facet_properties)] + return facets + + spec = specification() + spec.name = ids_dict["@name"] + spec.necessity = ids_dict["@necessity"] + spec.applicability = boolean_and(parse_rules(ids_dict["applicability"])) + spec.requirements = boolean_and(parse_rules(ids_dict["requirements"])) + return spec + + def add_applicability(self, facet): + """Applicability specifies what conditions must be meet for an IFC object to be used for validation. Note, that at least one entity facet is required. + + :param facet: any of entity|classification|property|material + :type facet: facet + + Example:: + + i = ids.ids() + i.specifications.append(ids.specification(name="Test_Specification")) + e = ids.entity.create(name="Test_Name", predefinedtype="Test_PredefinedType") + i.specifications[0].add_applicability(e) + """ + if self.applicability: + self.applicability = boolean_and(self.applicability.terms + [facet]) + else: + self.applicability = boolean_and([facet]) + + def add_requirement(self, facet): + """Requirement is validated on all applicable IFC elements. Note, that at least one facet of any type is required. + + :param facet: any of entity|classification|property|material + :type facet: facet + """ + if self.requirements: + self.requirements = boolean_and(self.requirements.terms + [facet]) + else: + self.requirements = boolean_and([facet]) + + def __call__(self, inst, logger): + """When specification is called on an ifc instance, it validates against applicability and requirements. + + :param inst: IFC entity element + :type inst: IFC entity + :param logger: Logging object + :type logger: logging + :return: results of validation on applicability and requirements + :rtype: [bool,bool] + """ + if self.applicability(inst, logger): + + valid = self.requirements(inst, logger) + + if valid: + logger.info( + { + "guid": inst.GlobalId, + "result": valid.success, + "sentence": str(self) + + ".\n" + + inst.is_a() + + " '" + + str(inst.Name) + + "' (#" + + str(inst.id()) + + ") has " + + str(valid) + + " so is compliant", + "ifc_element": inst, + } + ) + return True, True + else: + # BUG "has does not have" + logger.error( + { + "guid": inst.GlobalId, + "result": valid.success, + "sentence": str(self) + + ".\n" + + inst.is_a() + + " '" + + str(inst.Name) + + "' (#" + + str(inst.id()) + + ") has " + + str(valid) + + " so is not compliant", + "ifc_element": inst, + } + ) + return True, False + else: + return False, False + + def __str__(self): + """Represent the specification in human readible sentence. + + :return: sentence + :rtype: str + """ + return "Given an instance with %(applicability)s\nWe expect %(requirements)s" % self.__dict__ class facet_evaluation: - """ - The evaluation of a facet with data from IFC. Converts to bool and has a human readable string format. - """ + """The evaluation of a facet with data from IFC. Converts to bool and has a human readable string format.""" def __init__(self, success, str): self.success = success @@ -34,9 +413,7 @@ class facet_evaluation: class meta_facet(type): - """ - A metaclass for automatically registering facets in a map to be instantiated based on XML tagnames. - """ + """A metaclass for automatically registering facets in a map to be instantiated based on XML tagnames.""" facets = {} @@ -51,27 +428,34 @@ class facet(metaclass=meta_facet): The base class for IDS facets. IDS facets are functors constructed from XML nodes that return True or False. A getattr method is provided for conveniently extracting XML child node text content. - """ + Use child classes instead: entity, classification, property and material. + """ def __init__(self, node=None, location=None): if node: self.node = node - if '@location' in self: - self.location = self.node['@location'] + if "@location" in self: + self.location = self.node["@location"] else: - self.location = 'any' + self.location = "any" if location: self.location = location else: - self.location = 'any' + self.location = "any" def __getattr__(self, k): if k in self.node: v = self.node[k] - if isinstance(v, dict): #is restriction? - return restriction(v['xs:restriction'][0]) + # BUG list of dictionaries should not happen + if isinstance(v, list): + v = v[0] + if "simpleValue" in list(v): + return v["simpleValue"] + elif "restriction" in list(v): + return restriction.parse(v["restriction"][0]) + # TODO handle more than one restriction: return [restriction(r) for r in v["restriction"]] else: - return v + raise Exception("Unknown value declaration.") else: return None @@ -88,38 +472,61 @@ class facet(metaclass=meta_facet): class entity(facet): - """ - The IDS entity facet currently *with* inheritance - """ + """The IDS entity facet currently *with* inheritance""" parameters = ["name", "predefinedtype"] - + + @staticmethod def create(name=None, predefinedtype=None): + """Create an entity facet that can be added to applicability or requirements of IDS specification. + + :param name: IFC entity name that is required. e.g. IfcWall, defaults to None + :type name: str, optional + :param predefinedtype: name of the predefined type, defaults to None + :type predefinedtype: str, optional + :return: entity object + :rtype: entity + """ + inst = entity() inst.name = name inst.predefinedtype = predefinedtype return inst def asdict(self): - fac_dict = {'name': self.name} - if 'predefinedtype' in self: - fac_dict['predefinedtype'] = self.predefinedtype + """Converts object to a dictionary, adding required attributes. + + :return: Xmlschema compliant dictionary. + :rtype: dict + """ + fac_dict = {"name": parameter_asdict(self.name)} + try: + fac_dict["predefinedtype"] = parameter_asdict(self.predefinedtype) + except (RecursionError, UnboundLocalError) as e: + print(e) return fac_dict def __call__(self, inst, logger): + """Validate an ifc instance against that entity facet. + + :param inst: IFC entity element + :type inst: IFC entity + :param logger: Logging object + :type logger: logging + :return: result of the validation as bool and message + :rtype: facet_evaluation(bool, str) + """ + # @nb with inheritance if self.predefinedtype and hasattr(inst, "PredefinedType"): self.message = "an entity name '%(name)s' of predefined type '%(predefinedtype)s'" return facet_evaluation( inst.is_a(self.name) and inst.PredefinedType == self.predefinedtype, - self.message % {"name": inst.is_a(), "predefinedtype": inst.PredefinedType} - ) + self.message % {"name": inst.is_a(), "predefinedtype": inst.PredefinedType}, + ) else: self.message = "an entity name '%(name)s'" - return facet_evaluation( - inst.is_a(self.name), - self.message % {"name": inst.is_a()} - ) + return facet_evaluation(inst.is_a(self.name), self.message % {"name": inst.is_a()}) class classification(facet): @@ -130,7 +537,20 @@ class classification(facet): parameters = ["system", "value", "location"] message = "%(location)sclassification reference %(value)s from '%(system)s'" - def create(location='any', value=None, system=None): + @staticmethod + def create(location="any", value=None, system=None): + """Create a classification facet that can be added to applicability or requirements of IDS specification. + + :param location: Define where to check for the parameter. One of "any"|"instance"|"type", defaults to "any" + :type location: str, optional + :param value: Value that is required. Could be alphanumeric or restriction object, defaults to None + :type value: restriction|alphanumeric, optional + :param system: System that is required. Could be alphanumeric or restriction object, defaults to None + :type system: restriction|alphanumeric, optional + :return: classification object + :rtype: classification + """ + inst = classification() inst.location = location inst.value = value @@ -138,26 +558,41 @@ class classification(facet): return inst def asdict(self): + """Converts object to a dictionary, adding required attributes. + + :return: Xmlschema compliant dictionary. + :rtype: dict + """ fac_dict = { - '@location': self.location, - 'value': self.value, - 'system': self.system - } + "value": parameter_asdict(self.value), + "system": parameter_asdict(self.system), + "@location": self.location, + # "instructions": "SAMPLE_INSTRUCTIONS", + } return fac_dict def __call__(self, inst, logger): - + """Validate an ifc instance against that classification facet. + + :param inst: IFC entity element + :type inst: IFC entity + :param logger: Logging object + :type logger: logging + :return: result of the validation as bool and message + :rtype: facet_evaluation(bool, str) + """ + instance_classiciations = inst.HasAssociations if ifcopenshell.util.element.get_type(inst): type_classifications = ifcopenshell.util.element.get_type(inst).HasAssociations else: type_classifications = () - if self.location == 'instance' and instance_classiciations: + if self.location == "instance" and instance_classiciations: associations = instance_classiciations - elif self.location == 'type' and type_classifications: + elif self.location == "type" and type_classifications: associations = type_classifications - elif self.location == 'any' and (instance_classiciations or type_classifications): + elif self.location == "any" and (instance_classiciations or type_classifications): associations = instance_classiciations + type_classifications else: associations = () @@ -166,23 +601,25 @@ class classification(facet): for association in associations: if association.is_a("IfcRelAssociatesClassification"): cref = association.RelatingClassification - if hasattr(cref, 'ItemReference'): #IFC2x3 + if hasattr(cref, "ItemReference"): # IFC2x3 refs.append((cref.ReferencedSource.Name, cref.ItemReference)) - elif hasattr(cref, 'Identification'): # IFC4 - refs.append((cref.ReferencedSource.Name, cref.Identification)) + elif hasattr(cref, "Identification"): # IFC4 + refs.append((cref.ReferencedSource.Name, cref.Identification)) self.location_msg = location[self.location] if refs: return facet_evaluation( (self.system, self.value) in refs, - self.message % {"system": refs[0][0], "value": "'"+refs[0][1]+"'", "location": self.location_msg} # what if not first item of refs? - ) - else: - return facet_evaluation( - False, - "does not have %sclassification reference" % self.location_msg + self.message + % { + "system": refs[0][0], + "value": "'" + refs[0][1] + "'", + "location": self.location_msg, + }, # what if not first item of refs? ) + else: + return facet_evaluation(False, "does not have %sclassification reference" % self.location_msg) class property(facet): @@ -192,8 +629,22 @@ class property(facet): parameters = ["name", "propertyset", "value", "location"] message = "%(location)sproperty '%(name)s' in '%(propertyset)s' with a value %(value)s" - - def create(location='any', propertyset=None, name=None, value=None): + + @staticmethod + def create(location="any", propertyset=None, name=None, value=None): + """Create a property facet that can be added to applicability or requirements of IDS specification. + + :param location: Define where to check for the parameter. One of "any"|"instance"|"type", defaults to "any" + :type location: str, optional + :param propertyset: Propertyset that is required. Could be alphanumeric or restriction object, defaults to None + :type propertyset: restriction|alphanumeric, optional + :param name: Name that is required. Could be alphanumeric or restriction object, defaults to None + :type name: restriction|alphanumeric, optional + :param value: Value that is required. Could be alphanumeric or restriction object, defaults to None + :type value: restriction|alphanumeric, optional + :return: property object + :rtype: property + """ inst = property() inst.location = location inst.propertyset = propertyset @@ -205,46 +656,59 @@ class property(facet): return inst def asdict(self): + """Converts object to a dictionary, adding required attributes. + + :return: Xmlschema compliant dictionary. + :rtype: dict + """ fac_dict = { - '@location': self.location, - 'propertyset': self.propertyset, - 'name': self.name, - 'value': self.value, + "@location": self.location, + "propertyset": parameter_asdict(self.propertyset), + "name": parameter_asdict(self.name), + "value": parameter_asdict(self.value), + # "instructions": "SAMPLE_INSTRUCTIONS", # TODO '@href': 'http://identifier.buildingsmart.org/uri/buildingsmart/ifc-4.3/prop/FireRating', #https://identifier.buildingsmart.org/uri/something - # TODO 'instructions': 'Please add the desired rating.' - } + } return fac_dict - def __call__(self, inst, logger): + """Validate an ifc instance against that property facet. - self.location = self.node['@location'] + :param inst: IFC entity element + :type inst: IFC entity + :param logger: Logging object + :type logger: logging + :return: result of the validation as bool and message + :rtype: facet_evaluation(bool, str) + """ - instance_props = ifcopenshell.util.element.get_psets(inst) - if ifcopenshell.util.element.get_type(inst): - type_props = ifcopenshell.util.element.get_psets( ifcopenshell.util.element.get_type(inst) ) + self.location = self.node["@location"] + + if self.propertyset == "attribute": + val = {k.lower(): v for k, v in inst.get_info().items()}.get(self.name, None) else: - type_props = {} + # TODO sometimes AttributeError: 'str' object has no attribute 'wrappedValue' + instance_props = ifcopenshell.util.element.get_psets(inst) + + if ifcopenshell.util.element.get_type(inst): + type_props = ifcopenshell.util.element.get_psets(ifcopenshell.util.element.get_type(inst)) + else: + type_props = {} + + if self.location == "instance": + props = instance_props + elif self.location == "type" and type_props: + props = type_props + elif self.location == "any" and (instance_props or type_props): + props = {**instance_props, **type_props} + else: + props = {} + + pset = props.get(self.propertyset) + val = pset.get(self.name) if pset else None - if self.location == 'instance': - props = instance_props - elif self.location == 'type' and type_props: - props = type_props - elif self.location == 'any' and (instance_props or type_props): - props = {**instance_props , **type_props} - else: - props = {} - - pset = props.get(self.propertyset) - val = pset.get(self.name) if pset else None - self.location_msg = location[self.location] - di = { - "name": self.name, - "propertyset": self.propertyset, - "value": "'%s'" % val, - "location": self.location_msg - } + di = {"name": self.name, "propertyset": self.propertyset, "value": "'%s'" % val, "location": self.location_msg} if val is not None: msg = self.message % di @@ -254,55 +718,88 @@ class property(facet): else: msg = "does not have %(location)sset '%(propertyset)s'" % di - #TODO implement data type comparison - return facet_evaluation( - val == self.value, - msg - ) + # TODO implement data type comparison + # xs:string + # xs:decimal + # xs:integer + # xs:boolean + # xs:anyURI + # xs:date YYYY-MM-DD + # xs:time hh:mm:ss + # xs:dateTime YYYY-MM-DDThh:mm:ss + # xs:duration PnYnMnDTnHnMnS + + return facet_evaluation(val == self.value, msg) class material(facet): - """ - The IDS material facet by traversing the HasAssociations inverse attribute - """ + """The IDS material facet used to traverse the HasAssociations inverse attribute.""" + parameters = ["value", "location"] message = "%(location)smaterial '%(value)s'" - - def create(location='any', value=None): + + @staticmethod + def create(location="any", value=None): + """Create a material facet that can be added to applicability or requirements of IDS specification. + + :param location: Define where to check for the parameter. One of "any"|"instance"|"type", defaults to "any" + :type location: str, optional + :param value: Value that is required. Could be alphanumeric or restriction object, defaults to None + :type value: restriction|alphanumeric, optional + :return: material object + :rtype: material + """ inst = material() inst.location = location inst.value = value - # self.attributes = {'@location': location} # 'type', 'instance', 'any' - # # BUG '@use': 'optional' - # # BUG '@href': 'https://identifier.buildingsmart.org/uri/something', - # # BUG 'instructions': 'Please add the desired...', + # TODO '@use': 'optional' + # TODO '@href': 'https://identifier.buildingsmart.org/uri/something', + # TODO 'instructions': 'Please add the desired...', return inst def asdict(self): + """Converts object to a dictionary, adding required attributes. + + :return: Xmlschema compliant dictionary. + :rtype: dict + """ fac_dict = { - '@location': self.location, - 'value': self.value, + "value": parameter_asdict(self.value), + "@location": self.location, + # TODO "instructions": "SAMPLE_INSTRUCTIONS", # TODO '@href': 'http://identifier.buildingsmart.org/uri/buildingsmart/ifc-4.3/prop/FireRating', #https://identifier.buildingsmart.org/uri/something - # TODO 'instructions': 'Please add the desired rating.' # TODO '@use': 'optional' - } + } return fac_dict def __call__(self, inst, logger): + """Validate an ifc instance against that material facet. - self.location = self.node['@location'] + :param inst: IFC entity element + :type inst: IFC entity + :param logger: Logging object + :type logger: logging + :return: result of the validation as bool and message + :rtype: facet_evaluation(bool, str) + """ + + self.location = self.node["@location"] instance_material_rel = [rel for rel in inst.HasAssociations if rel.is_a("IfcRelAssociatesMaterial")] if ifcopenshell.util.element.get_type(inst): - type_material_rel = [rel for rel in ifcopenshell.util.element.get_type(inst).HasAssociations if rel.is_a("IfcRelAssociatesMaterial")] + type_material_rel = [ + rel + for rel in ifcopenshell.util.element.get_type(inst).HasAssociations + if rel.is_a("IfcRelAssociatesMaterial") + ] else: type_material_rel = [] - if self.location == 'instance': + if self.location == "instance": material_relations = list(instance_material_rel) - elif self.location == 'type' and type_material_rel: + elif self.location == "type" and type_material_rel: material_relations = list(type_material_rel) - elif self.location == 'any' and (instance_material_rel or type_material_rel): + elif self.location == "any" and (instance_material_rel or type_material_rel): material_relations = instance_material_rel + type_material_rel else: material_relations = [] @@ -311,7 +808,7 @@ class material(facet): for rel in material_relations: if rel.RelatingMaterial.is_a() == "IfcMaterial": materials.append(rel.RelatingMaterial.Name) - elif rel.RelatingMaterial.is_a() == "IfcMaterialMaterialList": #DEPRECATED in IFC4 + elif rel.RelatingMaterial.is_a() == "IfcMaterialMaterialList": # DEPRECATED in IFC4 [materials.append(mat.Name) for mat in rel.RelatingMaterial] elif rel.RelatingMaterial.is_a() == "IfcMaterialConstituentSet": [materials.append(mat.Material.Name) for mat in rel.RelatingMaterial.MaterialConstituents] @@ -326,10 +823,10 @@ class material(facet): profileSets = rel.RelatingMaterial.ForProfileSet.MaterialProfiles [materials.append(pset.Material.Name) for pset in profileSets] else: - logger.error({'guid':inst.GlobalId, 'result':'ERROR', 'sentence':'IfcRelAssociatesMaterial not implemented'}) + raise Exception("IfcRelAssociatesMaterial not implemented") if not materials: - materials.append('UNDEFINED') + materials.append("UNDEFINED") self.location_msg = location[self.location] @@ -339,10 +836,27 @@ class material(facet): ) +def parameter_asdict(parameter): + """Converts parameter to an IDS compliant dictionary, handling both value and restrictions. + + :return: Xmlschema compliant dictionary. + :rtype: dict + """ + if isinstance(parameter, str): + parameter_dict = {"simpleValue": parameter} + elif isinstance(parameter, restriction): + parameter_dict = {"xs:restriction": [parameter.asdict()]} + elif isinstance(parameter, list): + restrictions = {"@base": "xs:" + parameter[0].base} + for p in parameter: + x = p.asdict() + restrictions[list(x)[1]] = x[list(x)[1]] + parameter_dict = {"xs:restriction": [restrictions]} + return parameter_dict + + class boolean_logic: - """ - Boolean conjunction over a collection of functions - """ + """Boolean conjunction over a collection of functions""" def __init__(self, terms): self.terms = terms @@ -350,10 +864,7 @@ class boolean_logic: def __call__(self, *args): eval = [t(*args) for t in self.terms] join = [" and ", " or "][self.fold == any] - return facet_evaluation( - self.fold(eval), - join.join(map(str, eval)) - ) + return facet_evaluation(self.fold(eval), join.join(map(str, eval))) def __str__(self): return [" and ", " or "][self.fold == any].join(map(str, self.terms)) @@ -372,267 +883,328 @@ class restriction: The value restriction from XSD implemented as a list of values and a containment test """ - def __init__(self, node): - - self.restriction_on = node['@base'][3:] + def __init__(self): + """Create a restriction that can be used instead of value of a parameter.""" self.type = "" self.options = [] - for n in node: - if n[0:3] == "xs:": - if n[3:] == "enumeration": - self.type = "enumeration" - for x in node[n]: - self.options.append(x["@value"]) - elif n[8:] == "clusive": - self.type = "bounds" - if n[3:6] == 'min': - self.options.insert(0,'>') - else: - self.options.insert(0,'<') - if n[6:9] == 'Inc': - self.options[0] += '=' - self.options[0] += node[n]['@value'] + @staticmethod + def parse(ids_dict): + """Parse xml restriction to python object. + + :param ids_dict: + :type ids_dict: dict + """ + r = restriction() + if ids_dict: + # TODO 'base' missing in some IDS?! + r.base = ids_dict["@base"][3:] + for n in ids_dict: + if n == "enumeration": + r.type = "enumeration" + for x in ids_dict[n]: + r.options.append(x["@value"]) + elif n[-7:] == "clusive": + r.type = "bounds" + r.options.append({n: ids_dict[n]["@value"]}) elif n[-5:] == "ength": - self.type = "length" + r.type = "length" if n[3:6] == "min": - self.options.append('>=') + r.options.append(">=") elif n[3:6] == "max": - self.options.append('<=') + r.options.append("<=") else: - self.options.append('==') - self.options[-1] += str(node[n]['@value']) - elif n[3:] == "pattern": - self.type = "pattern" - self.options.append(node[n]['@value']) - #TODO add fractionDigits - #TODO add totalDigits - #TODO add whiteSpace + r.options.append("==") + r.options[-1] += str(ids_dict[n]["@value"]) + elif n == "pattern": + r.type = "pattern" + r.options.append(ids_dict[n]["@value"]) + # TODO add fractionDigits + # TODO add totalDigits + # TODO add whiteSpace + elif n == "@base": + pass else: - logger.error({'result':'ERROR', 'sentence':'Restriction not implemented'}) + print("Error! Restriction not implemented") + return r + + def asdict(self): + """Converts object to a dictionary, adding required attributes. + + :return: Xmlschema compliant dictionary. + :rtype: dict + """ + rest_dict = {"@base": "xs:" + self.base} + if self.type == "enumeration": + for option in self.options: + if "xs:enumeration" not in rest_dict: + rest_dict["xs:enumeration"] = [{"@value": option}] + else: + rest_dict["xs:enumeration"].append({"@value": option}) + elif self.type == "bounds": + for option in self.options: + if "xs:option" not in rest_dict: + rest_dict["xs:" + option] = [{"@value": option}] + else: + rest_dict["xs:" + option].append({"@value": self.options[option], "@fixed": False}) + elif self.type == "pattern": + if "xs:pattern" not in rest_dict: + rest_dict["xs:pattern"] = [{"@value": self.options}] + else: + rest_dict["xs:pattern"].append({"@value": self.options}) + return rest_dict + + @staticmethod + def create(options, type="pattern", base="string"): + """Create restriction instead of simpleValue + + :param type: One of "enumeration"|"pattern"|"bounds", defaults to "pattern" + :type type: str, optional + :param options: if enumeration: list of possible values + if pattern: xml regular expression string + if bounds: dictionary with possible keys: 'minInclusive', 'maxInclusive', 'minExclusive', 'maxExclusive' + :type options: list|str|dict + :param base: One of "string"|"boolean"|"decimal"|"integer", defaults to "string" + :type base: str, optional + :raises Exception: If not properly defined restriction. + :return: restriction object + :rtype: restriction + """ + rest = restriction() + if type in ["enumeration", "pattern", "bounds"]: + rest.type = type + rest.base = base + rest.options = options + if ( + (type == "enumeration" and isinstance(options, list)) + or (type == "bounds" and isinstance(options, dict)) + or (type == "pattern" and isinstance(options, str)) + ): + rest.options = options + else: + Exception("Options were not properly defined.") + return rest + else: + raise Exception( + "Such restriction not implemented. Try: 'enumeration', 'pattern' or 'min/maxInclusive' or 'min/maxExclusive'." + ) def __eq__(self, other): - result=False - #TODO implement data type comparison - if self and other: - if self.type == "enumeration" and self.restriction_on == 'bool': + """Evaluate the restriction using equality sign. + + :param other: value to compare with the restriction. + :type other: str|float|int + :return: True if 'other' match the restriction, False if not. + :rtype: bool + """ + result = False + # TODO implement data type comparison + if self and (other or other == 0): + if self.type == "enumeration" and self.base == "bool": self.options = [x.lower() for x in self.options] result = str(other).lower() in self.options elif self.type == "enumeration": result = other in self.options elif self.type == "bounds": - for op in self.options: - if eval(str(other)+op): #TODO eval not safe? - result = True + result = True + for sign in self.options.keys(): + if sign == "minInclusive" and other < self.options[sign]: + result = False + elif sign == "maxInclusive" and other > self.options[sign]: + result = False + elif sign == "minExclusive" and other <= self.options[sign]: + result = False + elif sign == "maxExclusive" and other >= self.options[sign]: + result = False elif self.type == "length": for op in self.options: - if eval(str(len(other))+op): #TODO eval not safe? + if eval(str(len(other)) + op): # TODO eval not safe? result = True elif self.type == "pattern": - self.options - translated_pattern = identities.translate_pattern(r'[A-Z]{1,3}') # Between one and three capital letters + translated_pattern = identities.translate_pattern(self.options) regex_pattern = re.compile(translated_pattern) if regex_pattern.fullmatch(other) is not None: result = True - #TODO add fractionDigits - #TODO add totalDigits - #TODO add whiteSpace + # TODO add fractionDigits + # TODO add totalDigits + # TODO add whiteSpace return result def __repr__(self): + """Represent the restriction in human readible sentence. + + :return: sentence + :rtype: str + """ + msg = "of type '%s', " % (self.base) if self.type == "enumeration": - return "'%s'" % "' or '".join(self.options) + msg = msg + "of value: '%s'" % "' or '".join(self.options) elif self.type == "bounds": - self.options.sort() - return "of type '%s', having a value %s" % (self.restriction_on, ' and '.join(self.options)) + msg = msg + "of value %s" % ", and ".join([bounds[x] + str(self.options[x]) for x in self.options]) elif self.type == "length": - return "of type '%s' with %s letters" % (self.restriction_on, ' and '.join(self.options)) + msg = msg + "with %s letters" % " and ".join(self.options) elif self.type == "pattern": - return "of type '%s' respecting pattern '%s'" % (self.restriction_on, ' and '.join(self.options)) - #TODO add fractionDigits - #TODO add totalDigits - #TODO add whiteSpace + msg = msg + "respecting the pattern '%s'" % self.options + # TODO add fractionDigits + # TODO add totalDigits + # TODO add whiteSpace + return msg -class specification: - """ - Represents the XML node and its two children and - """ +class SimpleHandler(logging.StreamHandler): + """Logging handler listing all cases in python list.""" - def __init__(self, name='Specification'): - self.name = name - self.applicability = None - self.requirements = None + def __init__(self, report_valid=False): + """Logging handler listing all cases in python list. - def asdict(self): - spec_dict = { - '@name': self.name, - 'applicability': {}, - 'requirements': {} - } - for fac in self.applicability.terms: - fclass = type(fac).__name__ - if fclass in spec_dict['applicability']: - spec_dict['applicability'][fclass].append(fac.asdict()) - else: - spec_dict['applicability'][fclass] = [fac.asdict()] - for fac in self.requirements.terms: - fclass = type(fac).__name__ - if fclass in spec_dict['requirements']: - spec_dict['requirements'][fclass].append(fac.asdict()) - else: - spec_dict['requirements'][fclass] = [fac.asdict()] - return spec_dict - - @staticmethod - def parse(node): - def parse_rules(node): - names = [req for req in node for n in node[req]] - children = [child for req in node for child in node[req]] - classes = map(meta_facet.facets.__getitem__, names) - # return [cls.parse(n) for cls, n in zip(classes, children)] - return [cls(n) for cls, n in zip(classes, children)] # list of facet objects - - spec = specification() - spec.name = node['@name'] - spec.applicability = boolean_and(parse_rules(node['applicability'])) - spec.requirements = boolean_and(parse_rules(node['requirements'])) - return spec - - # TODO adding applicability/requirements to specification. How to avoid repetitions? - def add_applicability(self, facet): + :param report_valid: True if you want to list all the compliant cases as well, defaults to False + :type report_valid: bool, optional """ - Applicability specifies what conditions must be meet for an IFC object to be used for validation. - Takes: entity, classification, property or material objects as an input (at least one entity is required). - """ - if self.applicability: - self.applicability = boolean_and( self.applicability.terms + [facet] ) + logging.StreamHandler.__init__(self) + self.statements = [] + if report_valid: + self.setLevel(logging.INFO) else: - self.applicability = boolean_and([facet]) - - def add_requirement(self, facet): + self.setLevel(logging.ERROR) + + def emit(self, mymsg): + """Triggered on each use of logging with the Simple handler enabled. + + :param log_content: default logger message + :type log_content: string|dict """ - Requirement is validated on all applicable IFC elements. - Takes: entity, classification, property or material objects as an input (at least one of them is required). - """ - if self.requirements: - self.requirements = boolean_and( self.requirements.terms + [facet] ) - else: - self.requirements = boolean_and([facet]) - - def __call__(self, inst, logger): - if self.applicability(inst, logger): - - valid = self.requirements(inst, logger) - - if valid: - logger.info({'guid':inst.GlobalId, 'result':valid.success,'sentence':str(self) + ".\n" + inst.is_a() + " '" + str(inst.Name) + "' (#" + str(inst.id()) + ") has " + str(valid) + " so is compliant"}) - return True, True - else: - # BUG "has does not have" - logger.error({'guid':inst.GlobalId, 'result':valid.success, 'sentence':str(self) + ".\n" + inst.is_a() + " '" + str(inst.Name) + "' (#" + str(inst.id()) + ") has " + str(valid) + " so is not compliant"}) - return True, False - else: - return False, False - - def __str__(self): - return "Given an instance with %(applicability)s\nWe expect %(requirements)s" % self.__dict__ + self.statements.append(mymsg.msg) -class ids: - """ - Represents the XML root node and its childNodes. +class BcfHandler(logging.StreamHandler): + """Logging handler for creation of BCF report files. + + :param project_name: defaults to "IDS Project" + :type project_name: str, optional + :param author: Email of the person creating the BCF report, defaults to "your@email.com" + :type author: str, optional + :param filepath: Path to save the BCF report, defaults to None + :type filepath: str, optional + :param report_valid: True if you want to list all the compliant cases as well, defaults to False + :type report_valid: bool, optional + + Example:: + + bcf_handler = BcfHandler( + project_name="Default IDS Project", + author="your@email.com", + filepath="example.bcf", + ) + logger = logging.getLogger("IDS_Logger") + logging.basicConfig(level=logging.INFO, format="%(message)s") + logger.addHandler(bcf_handler) """ - def __init__(self): - self.specifications = [] - self.info = None - #self.attributes = { - # '@xmlns:xs': 'http://www.w3.org/2001/XMLSchema', - # '@xmlns': 'http://standards.buildingsmart.org/IDS', - # '@xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance', - # '@xsi:schemaLocation': 'http://standards.buildingsmart.org/IDS http://standards.buildingsmart.org/IDS/ids.xsd', - # } + def __init__(self, project_name="IDS Project", author="your@email.com", filepath=None, report_valid=False): - def asdict(self): - ids_dict = {'@xmlns': 'http://standards.buildingsmart.org/IDS', - '@xmlns:xs': 'http://www.w3.org/2001/XMLSchema', - '@xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance', - '@xsi:schemaLocation': 'http://standards.buildingsmart.org/IDS ' - 'http://standards.buildingsmart.org/IDS/ids.xsd', - 'specification': [], - 'info': self.info, - } - for spec in self.specifications: - ids_dict['specification'].append(spec.asdict()) - return ids_dict + logging.StreamHandler.__init__(self) + if report_valid: + self.setLevel(logging.INFO) + else: + self.setLevel(logging.ERROR) + self.bcf = BcfXml() + self.bcf.author = author + self.bcf.new_project() + self.bcf.project.name = project_name + self.filepath = filepath + self.bcf.edit_project() - def to_xml(self, fn='./', ids_schema=ids_schema): - if fn.endswith('/'): - fn = fn + 'IDS' - if not fn.endswith('.xml'): - fn = fn + '.xml' + def emit(self, log_content): + """Triggered on each use of logging with the BCF handler enabled. - ids_dict = self.asdict() + :param log_content: default logger message + :type log_content: string|dict + """ + topic = bcf.Topic() + topic.title = log_content.msg["sentence"].split(".\n")[1] + topic.description = log_content.msg["sentence"].split(".\n")[0] + self.bcf.add_topic(topic) + try: # Add viewpoint and link to ifc object + viewpoint = bcf.Viewpoint() + viewpoint.perspective_camera = bcf.PerspectiveCamera() + ifc_elem = log_content.msg["ifc_element"] + # ifc_elem = ifc_file.by_guid(log_content.msg["guid"]) + target_position = np.array(ifcopenshell.util.placement.get_local_placement(ifc_elem.ObjectPlacement)) + target_position = target_position[:, 3][0:3] + camera_position = target_position + np.array((5, 5, 5)) + viewpoint.perspective_camera.camera_view_point.x = camera_position[0] + viewpoint.perspective_camera.camera_view_point.y = camera_position[1] + viewpoint.perspective_camera.camera_view_point.z = camera_position[2] + camera_direction = camera_position - target_position + camera_direction = camera_direction / np.linalg.norm(camera_direction) + camera_right = np.cross(np.array([0.0, 0.0, 1.0]), camera_direction) + camera_right = camera_right / np.linalg.norm(camera_right) + camera_up = np.cross(camera_direction, camera_right) + camera_up = camera_up / np.linalg.norm(camera_up) + rotation_transform = np.zeros((4, 4)) + rotation_transform[0, :3] = camera_right + rotation_transform[1, :3] = camera_up + rotation_transform[2, :3] = camera_direction + rotation_transform[-1, -1] = 1 + translation_transform = np.eye(4) + translation_transform[:3, -1] = -camera_position + look_at_transform = np.matmul(rotation_transform, translation_transform) + mat = np.linalg.inv(look_at_transform) + viewpoint.perspective_camera.camera_direction.x = mat[0][2] * -1 + viewpoint.perspective_camera.camera_direction.y = mat[1][2] * -1 + viewpoint.perspective_camera.camera_direction.z = mat[2][2] * -1 + viewpoint.perspective_camera.camera_up_vector.x = mat[0][1] + viewpoint.perspective_camera.camera_up_vector.y = mat[1][1] + viewpoint.perspective_camera.camera_up_vector.z = mat[2][1] + viewpoint.components = bcf.Components() + c = bcf.Component() + c.ifc_guid = log_content.msg["guid"] + viewpoint.components.selection.append(c) + viewpoint.components.visibility = bcf.ComponentVisibility() + viewpoint.components.visibility.default_visibility = True + viewpoint.snapshot = None + self.bcf.add_viewpoint(topic, viewpoint) + except: + pass - ids_xml = ids_schema.encode(ids_dict) #, namespaces='http://standards.buildingsmart.org/IDS') - ids_str = etree_tostring(ids_xml, namespaces={'': 'http://standards.buildingsmart.org/IDS'}) # if restrictions, add also: 'xs': 'http://www.w3.org/2001/XMLSchema' - ids_schema.validate(ids_str) - - with open(fn, 'w') as f: - f.write('\n') - f.write('\n') - f.write(ids_str) - f.close() - - ids_schema.validate(fn) - return ids_schema.is_valid(fn) - - @staticmethod - def parse(fn, ids_schema=ids_schema): - ids_schema.validate(fn) - ids_content = ids_schema.decode(fn) - new_ids = ids() - new_ids.specifications = [specification.parse(s) for s in ids_content['specification']] - return new_ids + def flush(self): + """Saves the BCF report to file. Triggered at the end of the validation process.""" + if not self.filepath: + self.filepath = os.getcwd() + r"\IDS_report.bcfzip" + if not (self.filepath.endswith(".bcf") or self.filepath.endswith(".bcfzip")): + self.filepath = self.filepath + r"\IDS_report.bcfzip" + self.bcf.save_project(self.filepath) - def validate(self, ifc_file, logger): - self.ifc_checked = 0 - self.ifc_passed = 0 - for spec in self.specifications: - for elem in ifc_file.by_type("IfcObject"): - apply, comply = spec(elem, logger) - if apply: self.ifc_checked += 1 - if comply: self.ifc_passed += 1 +location = {"instance": "an instance ", "type": "a type ", "any": "a "} - - -location = { - 'instance': 'an instance ', - 'type': 'a type ', - 'any': 'a ' +bounds = { + "minInclusive": "larger or equal ", + "maxInclusive": "smaller or equal ", + "minExclusive": "larger than ", + "maxExclusive": "smaller than ", } - if __name__ == "__main__": - import time - start_time = time.time() import sys, os - import logging import ifcopenshell - from datetime import date - - filename = os.path.join(os.getcwd(), str(date.today())+"_ids_result.txt") - - logger = logging.getLogger("IDS") - logging.basicConfig(filename=filename, level=logging.INFO, format="%(message)s") - logging.FileHandler(filename, mode='w') + ids_file = ids.open(sys.argv[1]) ifc_file = ifcopenshell.open(sys.argv[2]) - ids_file = ids.parse(sys.argv[1]) + filepath = sys.argv[3] + + logger = logging.getLogger("IDS_Logger") + logging.basicConfig(filename=filepath, level=logging.INFO, format="%(message)s") + logging.FileHandler(filepath + r"\report.txt", mode="w") + + bcf_handler = BcfHandler( + project_name="Default IDS Project", + author="your@email.com", + filepath=filepath + r"\report.bcfzip", + ) + logger.addHandler(bcf_handler) + + report = SimpleHandler() + logger.addHandler(report) ids_file.validate(ifc_file, logger) - - print("Out of %s IFC elements, %s were checked against %s requirements in %s specification(s) and %s of them passed (%s).\nRuntime=%ss. Results saved to %s" - % (len(ifc_file.by_type('IfcProduct')), ids_file.ifc_checked, len(ids_file.specifications[0].requirements.terms), len(ids_file.specifications), ids_file.ifc_passed, str(ids_file.ifc_passed/ids_file.ifc_checked*100)+'%', round(time.time() - start_time, 2), filename)) diff --git a/src/ifcopenshell-python/ifcopenshell/ids.xsd b/src/ifcopenshell-python/ifcopenshell/ids.xsd new file mode 100644 index 0000000000..5d8ba0a295 --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/ids.xsd @@ -0,0 +1,232 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Make sure 'Name' value of requirements entity is the same as the 'applicability' node, or a wildcard (inclusive pattern). + + + + + + + + + + Author of the IDS can leave instructions for the authors of the IFC. This text could/should be displayed in the BIM/IFC authoring tool. + + + + + + + + + + + + + + + + + + + + + + + + Author of the IDS can leave instructions for the authors of the IFC. This text could/should be displayed in the BIM/IFC authoring tool. + + + + + + + + + + + + + + + + + + + + + + + + Author of the IDS can leave instructions for the authors of the IFC. This text could/should be displayed in the BIM/IFC authoring tool. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ifcopenshell-python/ifcopenshell/test_ids.py b/src/ifcopenshell-python/ifcopenshell/test_ids.py deleted file mode 100644 index bec6587600..0000000000 --- a/src/ifcopenshell-python/ifcopenshell/test_ids.py +++ /dev/null @@ -1,148 +0,0 @@ -import unittest -import ids -import requests -import os -# from xmlschema.validators.exceptions import XMLSchemaChildrenValidationError - - -def read_web_file(URL): - return requests.get(URL).text - - -class TestIdsParsing(unittest.TestCase): - - def test_basic_ids_parse(self): - IDS_URL = "https://raw.githubusercontent.com/atomczak/Sample-BIM-Files/main/IDS/IDS_Wall_needs_all_fields.xml" - ids_file = ids.ids.parse(read_web_file(IDS_URL)) - self.assertEqual(type(ids_file).__name__, "ids") - - def test_entity_facet(self): - IDS_URL = "https://raw.githubusercontent.com/atomczak/Sample-BIM-Files/main/IDS/IDS_Wall_needs_entity.xml" - ids_file = ids.ids.parse(read_web_file(IDS_URL)) - self.assertEqual(ids_file.specifications[0].requirements.terms[0].node["name"], "IfcWall") - - def test_predefinedtype_facet(self): - IDS_URL = ( - "https://raw.githubusercontent.com/atomczak/Sample-BIM-Files/main/IDS/IDS_Wall_needs_predefinedtype.xml" - ) - ids_file = ids.ids.parse(read_web_file(IDS_URL)) - self.assertEqual(ids_file.specifications[0].requirements.terms[0].node["predefinedtype"], "CLADDING") - - def test_property_facet(self): - IDS_URL = "https://raw.githubusercontent.com/atomczak/Sample-BIM-Files/main/IDS/IDS_Wall_needs_property.xml" - ids_file = ids.ids.parse(read_web_file(IDS_URL)) - self.assertEqual(ids_file.specifications[0].requirements.terms[0].node["propertyset"], "Test_PropertySet") - self.assertEqual(ids_file.specifications[0].requirements.terms[0].node["name"], "Test_Parameter") - self.assertEqual(ids_file.specifications[0].requirements.terms[0].node["value"], "Test_Value") - - def test_material_facet(self): - IDS_URL = "https://raw.githubusercontent.com/atomczak/Sample-BIM-Files/main/IDS/IDS_Wall_needs_material.xml" - ids_file = ids.ids.parse(read_web_file(IDS_URL)) - self.assertEqual(ids_file.specifications[0].requirements.terms[0].node["value"], "Test_Material") - - def test_classification_facet(self): - IDS_URL = ( - "https://raw.githubusercontent.com/atomczak/Sample-BIM-Files/main/IDS/IDS_Wall_needs_classification.xml" - ) - ids_file = ids.ids.parse(read_web_file(IDS_URL)) - self.assertEqual(ids_file.specifications[0].requirements.terms[0].node["value"], "Test_Classification") - self.assertEqual(ids_file.specifications[0].requirements.terms[0].node["system"], "Test_System") - - """ Parsing invalid IDS.xml """ - # TODO - # def test_invalid_classification_facet(self): - # IDS_URL = "https://raw.githubusercontent.com/atomczak/Sample-BIM-Files/main/IDS/Invalid_IDS_Wall_needs_classification.xml" - # self.assertRaises( XMLSchemaChildrenValidationError, ids.parse(read_web_file(IDS_URL)) ) - - """ Saving parsed IDS to IDS.xml """ - - def test_parsed_ids_to_xml(self): - IDS_URL = "https://raw.githubusercontent.com/atomczak/Sample-BIM-Files/main/IDS/IDS_Wall_needs_all_fields.xml" - ids_file = ids.ids.parse(read_web_file(IDS_URL)) - fn = "TEST_FILE.xml" - result = ids_file.to_xml(fn) - os.remove(fn) - self.assertTrue(result) - - -class TestIdsAuthoring(unittest.TestCase): - - def test_entity_create(self): - e = ids.entity.create(name="Test_Name", predefinedtype="Test_PredefinedType") - self.assertEqual(e.name, "Test_Name") - self.assertEqual(e.predefinedtype, "Test_PredefinedType") - - def test_classification_create(self): - c = ids.classification.create(location="any", value="Test_Value", system="Test_System") - self.assertEqual(c.location, "any") - self.assertEqual(c.value, "Test_Value") - self.assertEqual(c.system, "Test_System") - - def test_property_create(self): - p = ids.property.create( - location="any", propertyset="Test_PropertySet", name="Test_Parameter", value="Test_Value" - ) - self.assertEqual(p.location, "any") - self.assertEqual(p.propertyset, "Test_PropertySet") - self.assertEqual(p.name, "Test_Parameter") - self.assertEqual(p.value, "Test_Value") - - def test_material_create(self): - m = ids.material.create(location="any", value="Test_Value") - self.assertEqual(m.location, "any") - self.assertEqual(m.value, "Test_Value") - - def test_specification_create(self): - s = ids.specification(name="Test_Specification") - self.assertEqual(s.name, "Test_Specification") - - def test_ids_create(self): - i = ids.ids() - self.assertEqual(i.specifications, []) - self.assertEqual(i.info, None) - - def test_ids_add_content(self): - i = ids.ids() - i.specifications.append(ids.specification(name="Test_Specification")) - self.assertEqual(i.specifications[0].name, "Test_Specification") - m = ids.material.create(location="any", value="Test_Value") - i.specifications[0].add_applicability(m) - self.assertEqual(i.specifications[0].applicability.terms[0].value, "Test_Value") - i.specifications[0].add_applicability(m) - self.assertEqual(i.specifications[0].applicability.terms[1].value, "Test_Value") - i.specifications[0].add_requirement(m) - self.assertEqual(i.specifications[0].requirements.terms[0].value, "Test_Value") - i.specifications[0].add_requirement(m) - self.assertEqual(i.specifications[0].requirements.terms[1].value, "Test_Value") - - """ Saving created IDS to IDS.xml """ - - def test_created_ids_to_xml(self): - i = ids.ids() - i.specifications.append(ids.specification(name="Test_Specification")) - e = ids.entity.create(name="Test_Name", predefinedtype="Test_PredefinedType") - c = ids.classification.create(location="any", value="Test_Value", system="Test_System") - m = ids.material.create(location="any", value="Test_Value") - p = ids.property.create(location="any", propertyset="Test_PropertySet", name="Test_Parameter", value="Test_Value") - i.specifications[0].add_applicability(e) - i.specifications[0].add_applicability(m) - i.specifications[0].add_requirement(c) - i.specifications[0].add_requirement(p) - fn = "TEST_FILE.xml" - result = i.to_xml(fn) - os.remove(fn) - self.assertTrue(result) - - -class TestIfcValidation(unittest.TestCase): - pass - # TODO - - -class TestIdsResults(unittest.TestCase): - pass - # TODO - - -if __name__ == "__main__": - unittest.main() diff --git a/src/ifcopenshell-python/ifcopenshell/transition_curve.py b/src/ifcopenshell-python/ifcopenshell/transition_curve.py index b06f135f69..ed1b8271ef 100644 --- a/src/ifcopenshell-python/ifcopenshell/transition_curve.py +++ b/src/ifcopenshell-python/ifcopenshell/transition_curve.py @@ -32,6 +32,7 @@ class IfcTransitionCurveType(Enum): The IfcTransitionCurveType indicates the curvature of a transition curve. """ + BIQUADRATICPARABOLA = 1 # NOTE also referred to as Schramm curve. BLOSSCURVE = 2 CLOTHOIDCURVE = 3 @@ -46,6 +47,7 @@ class TransitionCurve: A curve that transitions between a straight line and a circular arc (or the reverse). """ + StartPoint: tuple # IfcSchema::IfcCartesianPoint StartDirection: float # IfcSchema::IfcPlaneAngleMeasure SegmentLength: float # IfcSchema::IfcPositiveLengthMeasure @@ -57,15 +59,15 @@ class TransitionCurve: def _calc_biquadratic_parabola_point(self, lpt, L, R, ccw): x = lpt - if (x <= (L / 2)): - y = x**4 / (6 * R * L**2) + if x <= (L / 2): + y = x ** 4 / (6 * R * L ** 2) else: - yterm_1 = (-1 * x**4) / (6 * R * L**2) - yterm_2 = (2 * x**3) / (3 * R * L) - yterm_3 = x**2 / (2 * R) + yterm_1 = (-1 * x ** 4) / (6 * R * L ** 2) + yterm_2 = (2 * x ** 3) / (3 * R * L) + yterm_3 = x ** 2 / (2 * R) yterm_4 = (L * x) / (6 * R) - yterm_5 = L**2 / (48 * R) + yterm_5 = L ** 2 / (48 * R) y = yterm_1 + yterm_2 - yterm_3 + yterm_4 - yterm_5 @@ -80,16 +82,16 @@ class TransitionCurve: def _calc_clothoid_curve_point(self, lpt, L, R, ccw): RL = R * L xterm_1 = 1 - xterm_2 = lpt**4 / (40 * RL**2) - xterm_3 = lpt**8 / (3456 * RL**4) - xterm_4 = lpt**12 / (599040 * RL**6) + xterm_2 = lpt ** 4 / (40 * RL ** 2) + xterm_3 = lpt ** 8 / (3456 * RL ** 4) + xterm_4 = lpt ** 12 / (599040 * RL ** 6) x = lpt * (xterm_1 - xterm_2 + xterm_3 - xterm_4) - factor = lpt**3 / (6 * RL) + factor = lpt ** 3 / (6 * RL) yterm_1 = 1 - yterm_2 = lpt**4 / (56 * RL**2) - yterm_3 = lpt**8 / (7040 * RL**4) - yterm_4 = lpt**12 / (1612800 * RL**6) + yterm_2 = lpt ** 4 / (56 * RL ** 2) + yterm_3 = lpt ** 8 / (7040 * RL ** 4) + yterm_4 = lpt ** 12 / (1612800 * RL ** 6) y = factor * (yterm_1 - yterm_2 + yterm_3 - yterm_4) @@ -102,14 +104,14 @@ class TransitionCurve: pi = math.pi psi_x = (pi * lpt) / L - xterm_1 = (L**2) / (8.0 * pi**2 * R**2) + xterm_1 = (L ** 2) / (8.0 * pi ** 2 * R ** 2) xterm_2 = L / pi - xterm_3 = psi_x**3 / (3.0) + xterm_3 = psi_x ** 3 / (3.0) xterm_4 = psi_x / (2.0) xterm_5 = (math.sin(psi_x) * math.cos(psi_x)) / (2.0) xterm_6 = psi_x * math.cos(psi_x) - x = lpt - xterm_1 * xterm_2 * ( xterm_3 + xterm_4 - xterm_5 - (2.0 * xterm_6)) + x = lpt - xterm_1 * xterm_2 * (xterm_3 + xterm_4 - xterm_5 - (2.0 * xterm_6)) # TODO: code for y - coordinate y = 0 @@ -170,16 +172,12 @@ class TransitionCurve: lpt = 0.0 # length along the curve at the point to be calculated for _ in range(num_intervals): - points.append(self._calc_transition_curve_point( - lpt, L, R, ccw, trans_type - )) + points.append(self._calc_transition_curve_point(lpt, L, R, ccw, trans_type)) lpt += interval_dist edges = list() for i in range(len(points) - 1): - edges.append(BRepBuilderAPI_MakeEdge2d( - points[i], points[i + 1] - )) + edges.append(BRepBuilderAPI_MakeEdge2d(points[i], points[i + 1])) wire = BRepBuilderAPI_MakeWire() for e in edges: diff --git a/src/ifcopenshell-python/ifcopenshell/util/attribute.py b/src/ifcopenshell-python/ifcopenshell/util/attribute.py index 28b4d4a530..5e8687d4e2 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/attribute.py +++ b/src/ifcopenshell-python/ifcopenshell/util/attribute.py @@ -4,13 +4,13 @@ def get_primitive_type(attribute_or_data_type): else: data_type = str(attribute_or_data_type) if data_type.find(" b2) - or not all(assert_valid(ty, v, schema) for v in val) - ) + invalid = len(val) < b1 or (b2 != -1 and len(val) > b2) or not all(assert_valid(ty, v, schema) for v in val) else: raise NotImplementedError("Not impl %s %s" % (type(attr_type), attr_type)) diff --git a/src/foundationserver/bcfserver/bcf/templates/base.html b/src/ifcopenshell-python/test/__init__.py similarity index 100% rename from src/foundationserver/bcfserver/bcf/templates/base.html rename to src/ifcopenshell-python/test/__init__.py diff --git a/src/ifcopenshell-python/test/api/__init__.py b/src/ifcopenshell-python/test/api/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/ifcopenshell-python/test/api/geometry/__init__.py b/src/ifcopenshell-python/test/api/geometry/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/ifcopenshell-python/test/api/geometry/test_edit_object_placement.py b/src/ifcopenshell-python/test/api/geometry/test_edit_object_placement.py new file mode 100644 index 0000000000..339e06e5aa --- /dev/null +++ b/src/ifcopenshell-python/test/api/geometry/test_edit_object_placement.py @@ -0,0 +1,338 @@ +import numpy +import pytest +import test.bootstrap +import ifcopenshell.api +import ifcopenshell.util.placement + + +class TestEditObjectPlacement(test.bootstrap.IFC4): + def test_attemping_to_edit_the_placement_of_an_invalid_element(self): + project = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcProject") + result = ifcopenshell.api.run("geometry.edit_object_placement", self.file, product=project) + assert result is None + + def test_setting_an_object_placement(self): + ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcProject") + ifcopenshell.api.run("unit.assign_unit", self.file) + element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + matrix = numpy.array( + ( + (1.0, 0.0, 0.0, 1.0), + (0.0, 1.0, 0.0, 2.0), + (0.0, 0.0, 1.0, 3.0), + (0.0, 0.0, 0.0, 1.0), + ) + ) + ifcopenshell.api.run( + "geometry.edit_object_placement", self.file, product=element, matrix=matrix.copy(), is_si=False + ) + assert numpy.array_equal(ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement), matrix) + + def test_setting_an_object_placement_using_si_units(self): + ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcProject") + ifcopenshell.api.run("unit.assign_unit", self.file) + element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + matrix = numpy.array( + ( + (1.0, 0.0, 0.0, 1.0), + (0.0, 1.0, 0.0, 2.0), + (0.0, 0.0, 1.0, 3.0), + (0.0, 0.0, 0.0, 1.0), + ) + ) + matrix_millimeters = numpy.array( + ( + (1.0, 0.0, 0.0, 1000.0), + (0.0, 1.0, 0.0, 2000.0), + (0.0, 0.0, 1.0, 3000.0), + (0.0, 0.0, 0.0, 1.0), + ) + ) + ifcopenshell.api.run("geometry.edit_object_placement", self.file, product=element, matrix=matrix, is_si=True) + assert numpy.array_equal( + ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement), matrix_millimeters + ) + + def test_changing_an_object_placement(self): + ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcProject") + ifcopenshell.api.run("unit.assign_unit", self.file) + element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + matrix1 = numpy.array( + ( + (1.0, 0.0, 0.0, 1.0), + (0.0, 1.0, 0.0, 2.0), + (0.0, 0.0, 1.0, 3.0), + (0.0, 0.0, 0.0, 1.0), + ) + ) + matrix2 = numpy.array( + ( + (1.0, 0.0, 0.0, 4.0), + (0.0, 1.0, 0.0, 5.0), + (0.0, 0.0, 1.0, 6.0), + (0.0, 0.0, 0.0, 1.0), + ) + ) + ifcopenshell.api.run( + "geometry.edit_object_placement", self.file, product=element, matrix=matrix1.copy(), is_si=False + ) + created_element_ids = [e.id() for e in self.file.traverse(element.ObjectPlacement)] + ifcopenshell.api.run( + "geometry.edit_object_placement", self.file, product=element, matrix=matrix2.copy(), is_si=False + ) + assert numpy.array_equal(ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement), matrix2) + for element_id in created_element_ids: + with pytest.raises(RuntimeError): + self.file.by_id(element_id) + + def test_changing_placements_relative_to_a_spatial_container(self): + ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcProject") + ifcopenshell.api.run("unit.assign_unit", self.file) + element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcBuilding") + subelement = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + matrix = numpy.array( + ( + (1.0, 0.0, 0.0, 1.0), + (0.0, 1.0, 0.0, 1.0), + (0.0, 0.0, 1.0, 1.0), + (0.0, 0.0, 0.0, 1.0), + ) + ) + submatrix = numpy.array( + ( + (1.0, 0.0, 0.0, 1.0), + (0.0, 1.0, 0.0, 2.0), + (0.0, 0.0, 1.0, 3.0), + (0.0, 0.0, 0.0, 1.0), + ) + ) + ifcopenshell.api.run("spatial.assign_container", self.file, product=subelement, relating_structure=element) + ifcopenshell.api.run( + "geometry.edit_object_placement", self.file, product=element, matrix=matrix.copy(), is_si=False + ) + ifcopenshell.api.run( + "geometry.edit_object_placement", self.file, product=subelement, matrix=submatrix.copy(), is_si=False + ) + assert numpy.array_equal(ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement), matrix) + assert numpy.array_equal(ifcopenshell.util.placement.get_local_placement(subelement.ObjectPlacement), submatrix) + assert subelement.ObjectPlacement.PlacementRelTo == element.ObjectPlacement + + def test_changing_placements_relative_to_an_aggregate(self): + ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcProject") + ifcopenshell.api.run("unit.assign_unit", self.file) + element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcElementAssembly") + subelement = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcBeam") + matrix = numpy.array( + ( + (1.0, 0.0, 0.0, 1.0), + (0.0, 1.0, 0.0, 1.0), + (0.0, 0.0, 1.0, 1.0), + (0.0, 0.0, 0.0, 1.0), + ) + ) + submatrix = numpy.array( + ( + (1.0, 0.0, 0.0, 1.0), + (0.0, 1.0, 0.0, 2.0), + (0.0, 0.0, 1.0, 3.0), + (0.0, 0.0, 0.0, 1.0), + ) + ) + ifcopenshell.api.run("aggregate.assign_object", self.file, product=subelement, relating_object=element) + ifcopenshell.api.run( + "geometry.edit_object_placement", self.file, product=element, matrix=matrix.copy(), is_si=False + ) + ifcopenshell.api.run( + "geometry.edit_object_placement", self.file, product=subelement, matrix=submatrix.copy(), is_si=False + ) + assert numpy.array_equal(ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement), matrix) + assert numpy.array_equal(ifcopenshell.util.placement.get_local_placement(subelement.ObjectPlacement), submatrix) + assert subelement.ObjectPlacement.PlacementRelTo == element.ObjectPlacement + + def test_changing_placements_relative_to_a_voided_element(self): + ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcProject") + ifcopenshell.api.run("unit.assign_unit", self.file) + element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + subelement = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcOpeningElement") + matrix = numpy.array( + ( + (1.0, 0.0, 0.0, 1.0), + (0.0, 1.0, 0.0, 1.0), + (0.0, 0.0, 1.0, 1.0), + (0.0, 0.0, 0.0, 1.0), + ) + ) + submatrix = numpy.array( + ( + (1.0, 0.0, 0.0, 1.0), + (0.0, 1.0, 0.0, 2.0), + (0.0, 0.0, 1.0, 3.0), + (0.0, 0.0, 0.0, 1.0), + ) + ) + ifcopenshell.api.run("void.add_opening", self.file, opening=subelement, element=element) + ifcopenshell.api.run( + "geometry.edit_object_placement", self.file, product=element, matrix=matrix.copy(), is_si=False + ) + ifcopenshell.api.run( + "geometry.edit_object_placement", self.file, product=subelement, matrix=submatrix.copy(), is_si=False + ) + assert numpy.array_equal(ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement), matrix) + assert numpy.array_equal(ifcopenshell.util.placement.get_local_placement(subelement.ObjectPlacement), submatrix) + assert subelement.ObjectPlacement.PlacementRelTo == element.ObjectPlacement + + def test_changing_placements_relative_to_an_opening(self): + ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcProject") + ifcopenshell.api.run("unit.assign_unit", self.file) + element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcOpeningElement") + subelement = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcDoor") + matrix = numpy.array( + ( + (1.0, 0.0, 0.0, 1.0), + (0.0, 1.0, 0.0, 1.0), + (0.0, 0.0, 1.0, 1.0), + (0.0, 0.0, 0.0, 1.0), + ) + ) + submatrix = numpy.array( + ( + (1.0, 0.0, 0.0, 1.0), + (0.0, 1.0, 0.0, 2.0), + (0.0, 0.0, 1.0, 3.0), + (0.0, 0.0, 0.0, 1.0), + ) + ) + ifcopenshell.api.run("void.add_filling", self.file, element=subelement, opening=element) + ifcopenshell.api.run( + "geometry.edit_object_placement", self.file, product=element, matrix=matrix.copy(), is_si=False + ) + ifcopenshell.api.run( + "geometry.edit_object_placement", self.file, product=subelement, matrix=submatrix.copy(), is_si=False + ) + assert numpy.array_equal(ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement), matrix) + assert numpy.array_equal(ifcopenshell.util.placement.get_local_placement(subelement.ObjectPlacement), submatrix) + assert subelement.ObjectPlacement.PlacementRelTo == element.ObjectPlacement + + def test_changing_placements_relative_to_a_projected_element(self): + ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcProject") + ifcopenshell.api.run("unit.assign_unit", self.file) + element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + subelement = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcProjectionElement") + matrix = numpy.array( + ( + (1.0, 0.0, 0.0, 1.0), + (0.0, 1.0, 0.0, 1.0), + (0.0, 0.0, 1.0, 1.0), + (0.0, 0.0, 0.0, 1.0), + ) + ) + submatrix = numpy.array( + ( + (1.0, 0.0, 0.0, 1.0), + (0.0, 1.0, 0.0, 2.0), + (0.0, 0.0, 1.0, 3.0), + (0.0, 0.0, 0.0, 1.0), + ) + ) + self.file.create_entity( + "IfcRelProjectsElement", + **{ + "GlobalId": ifcopenshell.guid.new(), + "RelatingElement": element, + "RelatedFeatureElement": subelement, + } + ) + ifcopenshell.api.run( + "geometry.edit_object_placement", self.file, product=element, matrix=matrix.copy(), is_si=False + ) + ifcopenshell.api.run( + "geometry.edit_object_placement", self.file, product=subelement, matrix=submatrix.copy(), is_si=False + ) + assert numpy.array_equal(ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement), matrix) + assert numpy.array_equal(ifcopenshell.util.placement.get_local_placement(subelement.ObjectPlacement), submatrix) + assert subelement.ObjectPlacement.PlacementRelTo == element.ObjectPlacement + + def test_changing_placements_without_affecting_children(self): + ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcProject") + ifcopenshell.api.run("unit.assign_unit", self.file) + element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcBuilding") + subelement = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + matrix = numpy.array( + ( + (1.0, 0.0, 0.0, 1.0), + (0.0, 1.0, 0.0, 1.0), + (0.0, 0.0, 1.0, 1.0), + (0.0, 0.0, 0.0, 1.0), + ) + ) + submatrix = numpy.array( + ( + (1.0, 0.0, 0.0, 1.0), + (0.0, 1.0, 0.0, 2.0), + (0.0, 0.0, 1.0, 3.0), + (0.0, 0.0, 0.0, 1.0), + ) + ) + ifcopenshell.api.run("spatial.assign_container", self.file, product=subelement, relating_structure=element) + ifcopenshell.api.run( + "geometry.edit_object_placement", self.file, product=element, matrix=matrix.copy(), is_si=False + ) + ifcopenshell.api.run( + "geometry.edit_object_placement", self.file, product=subelement, matrix=submatrix.copy(), is_si=False + ) + ifcopenshell.api.run( + "geometry.edit_object_placement", self.file, product=element, matrix=submatrix.copy(), is_si=False + ) + assert numpy.array_equal(ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement), submatrix) + assert numpy.array_equal(ifcopenshell.util.placement.get_local_placement(subelement.ObjectPlacement), submatrix) + assert subelement.ObjectPlacement.PlacementRelTo == element.ObjectPlacement + + def test_changing_placements_with_affecting_children(self): + ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcProject") + ifcopenshell.api.run("unit.assign_unit", self.file) + element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcBuilding") + subelement = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + matrix = numpy.array( + ( + (1.0, 0.0, 0.0, 1.0), + (0.0, 1.0, 0.0, 1.0), + (0.0, 0.0, 1.0, 1.0), + (0.0, 0.0, 0.0, 1.0), + ) + ) + submatrix = numpy.array( + ( + (1.0, 0.0, 0.0, 1.0), + (0.0, 1.0, 0.0, 2.0), + (0.0, 0.0, 1.0, 3.0), + (0.0, 0.0, 0.0, 1.0), + ) + ) + shifted_submatrix = numpy.array( + ( + (1.0, 0.0, 0.0, 1.0), + (0.0, 1.0, 0.0, 3.0), + (0.0, 0.0, 1.0, 5.0), + (0.0, 0.0, 0.0, 1.0), + ) + ) + ifcopenshell.api.run("spatial.assign_container", self.file, product=subelement, relating_structure=element) + ifcopenshell.api.run( + "geometry.edit_object_placement", self.file, product=element, matrix=matrix.copy(), is_si=False + ) + ifcopenshell.api.run( + "geometry.edit_object_placement", self.file, product=subelement, matrix=submatrix.copy(), is_si=False + ) + ifcopenshell.api.run( + "geometry.edit_object_placement", + self.file, + product=element, + matrix=submatrix.copy(), + is_si=False, + should_transform_children=True, + ) + assert numpy.array_equal(ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement), submatrix) + assert numpy.array_equal( + ifcopenshell.util.placement.get_local_placement(subelement.ObjectPlacement), shifted_submatrix + ) + assert subelement.ObjectPlacement.PlacementRelTo == element.ObjectPlacement diff --git a/src/ifcopenshell-python/test/api/owner/__init__.py b/src/ifcopenshell-python/test/api/owner/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/ifcopenshell-python/test/api/owner/test_update_owner_history.py b/src/ifcopenshell-python/test/api/owner/test_update_owner_history.py new file mode 100644 index 0000000000..1d03c25628 --- /dev/null +++ b/src/ifcopenshell-python/test/api/owner/test_update_owner_history.py @@ -0,0 +1,128 @@ +import time +import test.bootstrap +import ifcopenshell.api + + +class TestUpdateOwnerHistory(test.bootstrap.IFC4): + def test_creating_an_owner_history_when_there_is_no_existing_history(self): + get_person = ifcopenshell.api.owner.settings.get_person + get_organisation = ifcopenshell.api.owner.settings.get_organisation + get_application = ifcopenshell.api.owner.settings.get_application + + person = self.file.createIfcPerson() + organisation = self.file.createIfcOrganization() + application = self.file.createIfcApplication() + user = self.file.createIfcPersonAndOrganization() + user.ThePerson = person + user.TheOrganization = organisation + ifcopenshell.api.owner.settings.get_person = lambda x : person + ifcopenshell.api.owner.settings.get_organisation = lambda x : organisation + ifcopenshell.api.owner.settings.get_application = lambda x : application + + element = self.file.createIfcWall() + history = ifcopenshell.api.run("owner.update_owner_history", self.file, element=element) + assert history.is_a("IfcOwnerHistory") + assert element.OwnerHistory == history + assert history.ChangeAction == "ADDED" + assert abs(history.LastModifiedDate - time.time()) < 5 + assert history.LastModifyingApplication == application + assert history.LastModifyingUser == user + + ifcopenshell.api.owner.settings.get_person = get_person + ifcopenshell.api.owner.settings.get_organisation = get_organisation + ifcopenshell.api.owner.settings.get_application = get_application + ifcopenshell.api.owner.settings.users = {} + + def test_updating_an_existing_history(self): + get_person = ifcopenshell.api.owner.settings.get_person + get_organisation = ifcopenshell.api.owner.settings.get_organisation + get_application = ifcopenshell.api.owner.settings.get_application + + person = self.file.createIfcPerson() + organisation = self.file.createIfcOrganization() + application = self.file.createIfcApplication() + user = self.file.createIfcPersonAndOrganization() + user.ThePerson = person + user.TheOrganization = organisation + ifcopenshell.api.owner.settings.get_person = lambda x : person + ifcopenshell.api.owner.settings.get_organisation = lambda x : organisation + ifcopenshell.api.owner.settings.get_application = lambda x : application + + element = self.file.createIfcWall() + old_history = ifcopenshell.api.run("owner.create_owner_history", self.file) + element.OwnerHistory = old_history + + new_history = ifcopenshell.api.run("owner.update_owner_history", self.file, element=element) + assert new_history == old_history + assert element.OwnerHistory == new_history + assert new_history.ChangeAction == "MODIFIED" + assert abs(new_history.LastModifiedDate - time.time()) < 5 + assert new_history.LastModifyingApplication == application + assert new_history.LastModifyingUser == user + + ifcopenshell.api.owner.settings.get_person = get_person + ifcopenshell.api.owner.settings.get_organisation = get_organisation + ifcopenshell.api.owner.settings.get_application = get_application + ifcopenshell.api.owner.settings.users = {} + + def test_updating_an_existing_history_shared_by_multiple_elements(self): + get_person = ifcopenshell.api.owner.settings.get_person + get_organisation = ifcopenshell.api.owner.settings.get_organisation + get_application = ifcopenshell.api.owner.settings.get_application + + person = self.file.createIfcPerson() + organisation = self.file.createIfcOrganization() + application = self.file.createIfcApplication() + user = self.file.createIfcPersonAndOrganization() + user.ThePerson = person + user.TheOrganization = organisation + ifcopenshell.api.owner.settings.get_person = lambda x : person + ifcopenshell.api.owner.settings.get_organisation = lambda x : organisation + ifcopenshell.api.owner.settings.get_application = lambda x : application + + element = self.file.createIfcWall() + element2 = self.file.createIfcWall() + old_history = ifcopenshell.api.run("owner.create_owner_history", self.file) + element.OwnerHistory = old_history + element2.OwnerHistory = old_history + + new_history = ifcopenshell.api.run("owner.update_owner_history", self.file, element=element) + assert new_history != old_history + assert element.OwnerHistory == new_history + assert new_history.ChangeAction == "MODIFIED" + assert abs(new_history.LastModifiedDate - time.time()) < 5 + assert new_history.LastModifyingApplication == application + assert new_history.LastModifyingUser == user + + ifcopenshell.api.owner.settings.get_person = get_person + ifcopenshell.api.owner.settings.get_organisation = get_organisation + ifcopenshell.api.owner.settings.get_application = get_application + ifcopenshell.api.owner.settings.users = {} + + def test_doing_nothing_if_no_history_can_be_updated(self): + person = self.file.createIfcPerson() + assert ifcopenshell.api.run("owner.update_owner_history", self.file, element=person) == None + + def test_creating_a_user_if_one_does_not_exist(self): + get_person = ifcopenshell.api.owner.settings.get_person + get_organisation = ifcopenshell.api.owner.settings.get_organisation + get_application = ifcopenshell.api.owner.settings.get_application + + person = self.file.createIfcPerson() + organisation = self.file.createIfcOrganization() + application = self.file.createIfcApplication() + ifcopenshell.api.owner.settings.get_person = lambda x : person + ifcopenshell.api.owner.settings.get_organisation = lambda x : organisation + ifcopenshell.api.owner.settings.get_application = lambda x : application + + element = self.file.createIfcWall() + element.OwnerHistory = ifcopenshell.api.run("owner.create_owner_history", self.file) + + history = ifcopenshell.api.run("owner.update_owner_history", self.file, element=element) + assert history.LastModifyingUser.ThePerson == person + assert history.LastModifyingUser.TheOrganization == organisation + + ifcopenshell.api.owner.settings.get_person = get_person + ifcopenshell.api.owner.settings.get_organisation = get_organisation + ifcopenshell.api.owner.settings.get_application = get_application + ifcopenshell.api.owner.settings.users = {} diff --git a/src/ifcopenshell-python/test/api/pset/__init__.py b/src/ifcopenshell-python/test/api/pset/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/ifcopenshell-python/test/api/pset/test_edit_pset.py b/src/ifcopenshell-python/test/api/pset/test_edit_pset.py new file mode 100644 index 0000000000..d00d5db5aa --- /dev/null +++ b/src/ifcopenshell-python/test/api/pset/test_edit_pset.py @@ -0,0 +1,172 @@ +import test.bootstrap +import ifcopenshell.api + + +class TestEditPset(test.bootstrap.IFC4): + def test_editing_a_blank_buildingsmart_templated_pset(self): + element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + pset = ifcopenshell.api.run("pset.add_pset", self.file, product=element, name="Pset_WallCommon") + ifcopenshell.api.run( + "pset.edit_pset", + self.file, + pset=pset, + properties={"Reference": "reference", "Status": "NEW", "Combustible": True, "ThermalTransmittance": 42}, + ) + pset = element.IsDefinedBy[0].RelatingPropertyDefinition + + assert pset.HasProperties[0].Name == "Reference" + assert pset.HasProperties[0].NominalValue.is_a("IfcIdentifier") + assert pset.HasProperties[0].NominalValue.wrappedValue == "reference" + + assert pset.HasProperties[1].Name == "Status" + assert pset.HasProperties[1].NominalValue.is_a("IfcLabel") + assert pset.HasProperties[1].NominalValue.wrappedValue == "NEW" + + assert pset.HasProperties[2].Name == "Combustible" + assert pset.HasProperties[2].NominalValue.is_a("IfcBoolean") + assert pset.HasProperties[2].NominalValue.wrappedValue == True + + assert pset.HasProperties[3].Name == "ThermalTransmittance" + assert pset.HasProperties[3].NominalValue.is_a("IfcThermalTransmittanceMeasure") + assert pset.HasProperties[3].NominalValue.wrappedValue == 42 + + def test_editing_an_existing_buildingsmart_templated_pset(self): + element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + pset = ifcopenshell.api.run("pset.add_pset", self.file, product=element, name="Pset_WallCommon") + ifcopenshell.api.run( + "pset.edit_pset", + self.file, + pset=pset, + properties={"Reference": "foo", "Status": "NEW", "Combustible": True, "ThermalTransmittance": 42}, + ) + ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"Reference": "bar", "Status": None}) + pset = element.IsDefinedBy[0].RelatingPropertyDefinition + + assert pset.HasProperties[0].Name == "Reference" + assert pset.HasProperties[0].NominalValue.is_a("IfcIdentifier") + assert pset.HasProperties[0].NominalValue.wrappedValue == "bar" + + assert pset.HasProperties[1].Name == "Status" + assert pset.HasProperties[1].NominalValue is None + + def test_not_adding_a_property_if_it_is_none(self): + element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + pset = ifcopenshell.api.run("pset.add_pset", self.file, product=element, name="Pset_WallCommon") + ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"Reference": None}) + pset = element.IsDefinedBy[0].RelatingPropertyDefinition + assert len(pset.HasProperties) == 0 + + def test_editing_a_pset_name(self): + element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + pset = ifcopenshell.api.run("pset.add_pset", self.file, product=element, name="foo") + ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, name="bar") + pset = element.IsDefinedBy[0].RelatingPropertyDefinition + assert pset.Name == "bar" + + def test_adding_properties_without_a_template_with_autodetected_and_manual_data_types(self): + element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + pset = ifcopenshell.api.run("pset.add_pset", self.file, product=element, name="Foo_Bar") + ifcopenshell.api.run( + "pset.edit_pset", + self.file, + pset=pset, + properties={ + "MyLabel": "foobar", + "MyBool": True, + "MyInteger": 42, + "MyFloat": 42.0, + "MyCustom": self.file.createIfcContextDependentMeasure(123), + }, + ) + pset = element.IsDefinedBy[0].RelatingPropertyDefinition + + assert pset.HasProperties[0].Name == "MyLabel" + assert pset.HasProperties[0].NominalValue.is_a("IfcLabel") + assert pset.HasProperties[0].NominalValue.wrappedValue == "foobar" + + assert pset.HasProperties[1].Name == "MyBool" + assert pset.HasProperties[1].NominalValue.is_a("IfcBoolean") + assert pset.HasProperties[1].NominalValue.wrappedValue == True + + assert pset.HasProperties[2].Name == "MyInteger" + assert pset.HasProperties[2].NominalValue.is_a("IfcInteger") + assert pset.HasProperties[2].NominalValue.wrappedValue == 42 + + assert pset.HasProperties[3].Name == "MyFloat" + assert pset.HasProperties[3].NominalValue.is_a("IfcReal") + assert pset.HasProperties[3].NominalValue.wrappedValue == 42.0 + + assert pset.HasProperties[4].Name == "MyCustom" + assert pset.HasProperties[4].NominalValue.is_a("IfcContextDependentMeasure") + assert pset.HasProperties[4].NominalValue.wrappedValue == 123.0 + + def test_editing_properties_with_autodetected_existing_types(self): + element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + pset = ifcopenshell.api.run("pset.add_pset", self.file, product=element, name="Foo_Bar") + ifcopenshell.api.run( + "pset.edit_pset", + self.file, + pset=pset, + properties={ + "MyCustom": self.file.createIfcContextDependentMeasure(12), + }, + ) + ifcopenshell.api.run( + "pset.edit_pset", + self.file, + pset=pset, + properties={ + "MyCustom": 34, + }, + ) + pset = element.IsDefinedBy[0].RelatingPropertyDefinition + assert pset.HasProperties[0].Name == "MyCustom" + assert pset.HasProperties[0].NominalValue.is_a("IfcContextDependentMeasure") + assert pset.HasProperties[0].NominalValue.wrappedValue == 34 + + def test_editing_properties_of_non_rooted_elements(self): + element = self.file.createIfcMaterial() + pset = ifcopenshell.api.run("pset.add_pset", self.file, product=element, name="Foo_Bar") + ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"foo": "bar"}) + + assert element.HasProperties[0] == pset + assert pset.Properties[0].Name == "foo" + assert pset.Properties[0].NominalValue.is_a("IfcLabel") + assert pset.Properties[0].NominalValue.wrappedValue == "bar" + + def test_editing_a_custom_templated_pset(self): + template = self.file.create_entity( + "IfcPropertySetTemplate", + **{ + "GlobalId": ifcopenshell.guid.new(), + "Name": "Foo_Bar", + "TemplateType": "PSET_TYPEDRIVENOVERRIDE", + "ApplicableEntity": "IfcWall", + "HasPropertyTemplates": [ + self.file.create_entity( + "IfcSimplePropertyTemplate", + **{ + "GlobalId": ifcopenshell.guid.new(), + "Name": "foo", + "TemplateType": "P_SINGLEVALUE", + "PrimaryMeasureType": "IfcContextDependentMeasure", + } + ) + ], + } + ) + element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + pset = ifcopenshell.api.run("pset.add_pset", self.file, product=element, name="Foo_Bar") + ifcopenshell.api.run( + "pset.edit_pset", + self.file, + pset=pset, + pset_template=template, + properties={ + "foo": 12, + }, + ) + pset = element.IsDefinedBy[0].RelatingPropertyDefinition + assert pset.HasProperties[0].Name == "foo" + assert pset.HasProperties[0].NominalValue.is_a("IfcContextDependentMeasure") + assert pset.HasProperties[0].NominalValue.wrappedValue == 12 diff --git a/src/ifcopenshell-python/test/api/resource/__init__.py b/src/ifcopenshell-python/test/api/resource/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/ifcopenshell-python/test/api/resource/test_calculate_resource_work.py b/src/ifcopenshell-python/test/api/resource/test_calculate_resource_work.py new file mode 100644 index 0000000000..f9a8a59a25 --- /dev/null +++ b/src/ifcopenshell-python/test/api/resource/test_calculate_resource_work.py @@ -0,0 +1,94 @@ +import test.bootstrap +import ifcopenshell.api + + +class TestCalculateResourceWork(test.bootstrap.IFC4): + def test_calculating_resource_work_based_on_a_daily_productivity_rate(self): + ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcProject") + + resource = ifcopenshell.api.run("resource.add_resource", self.file, ifc_class="IfcLaborResource") + pset = ifcopenshell.api.run("pset.add_pset", self.file, product=resource, name="EPset_Productivity") + ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={ + "BaseQuantityConsumed": "P0.5D", + "BaseQuantityProducedName": "GrossVolume", + "BaseQuantityProducedValue": 5, + }) + + slab = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcSlab") + qto = ifcopenshell.api.run("pset.add_qto", self.file, product=slab, name="Qto_SlabBaseQuantities") + ifcopenshell.api.run("pset.edit_qto", self.file, qto=qto, properties={"GrossVolume": 20}) + + task = ifcopenshell.api.run("sequence.add_task", self.file) + ifcopenshell.api.run("sequence.assign_product", self.file, relating_product=slab, related_object=task) + ifcopenshell.api.run("sequence.assign_process", self.file, relating_process=task, related_object=resource) + ifcopenshell.api.run("resource.calculate_resource_work", self.file, resource=resource) + assert resource.Usage.ScheduleWork == "P2.0D" + + + def test_calculating_resource_work_based_on_an_hourly_productivity_rate(self): + ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcProject") + + resource = ifcopenshell.api.run("resource.add_resource", self.file, ifc_class="IfcLaborResource") + pset = ifcopenshell.api.run("pset.add_pset", self.file, product=resource, name="EPset_Productivity") + ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={ + "BaseQuantityConsumed": "PT1H", + "BaseQuantityProducedName": "GrossVolume", + "BaseQuantityProducedValue": 5, + }) + + slab = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcSlab") + qto = ifcopenshell.api.run("pset.add_qto", self.file, product=slab, name="Qto_SlabBaseQuantities") + ifcopenshell.api.run("pset.edit_qto", self.file, qto=qto, properties={"GrossVolume": 20}) + + task = ifcopenshell.api.run("sequence.add_task", self.file) + ifcopenshell.api.run("sequence.assign_product", self.file, relating_product=slab, related_object=task) + ifcopenshell.api.run("sequence.assign_process", self.file, relating_process=task, related_object=resource) + ifcopenshell.api.run("resource.calculate_resource_work", self.file, resource=resource) + assert resource.Usage.ScheduleWork == "PT4.0H" + + def test_no_calculation_if_no_productivity_data_available(self): + ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcProject") + + resource = ifcopenshell.api.run("resource.add_resource", self.file, ifc_class="IfcLaborResource") + + slab = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcSlab") + qto = ifcopenshell.api.run("pset.add_qto", self.file, product=slab, name="Qto_SlabBaseQuantities") + ifcopenshell.api.run("pset.edit_qto", self.file, qto=qto, properties={"GrossVolume": 20}) + + task = ifcopenshell.api.run("sequence.add_task", self.file) + ifcopenshell.api.run("sequence.assign_product", self.file, relating_product=slab, related_object=task) + ifcopenshell.api.run("sequence.assign_process", self.file, relating_process=task, related_object=resource) + ifcopenshell.api.run("resource.calculate_resource_work", self.file, resource=resource) + + assert resource.Usage is None + + pset = ifcopenshell.api.run("pset.add_pset", self.file, product=resource, name="EPset_Productivity") + ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"BaseQuantityProducedName": "foo"}) + + ifcopenshell.api.run("resource.calculate_resource_work", self.file, resource=resource) + + assert resource.Usage is None + + def test_calculating_resource_work_based_on_a_counted_quantity(self): + ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcProject") + + resource = ifcopenshell.api.run("resource.add_resource", self.file, ifc_class="IfcLaborResource") + pset = ifcopenshell.api.run("pset.add_pset", self.file, product=resource, name="EPset_Productivity") + ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={ + "BaseQuantityConsumed": "PT1H", + "BaseQuantityProducedName": "Count", + "BaseQuantityProducedValue": 2, + }) + + slab = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcSlab") + qto = ifcopenshell.api.run("pset.add_qto", self.file, product=slab, name="Qto_SlabBaseQuantities") + ifcopenshell.api.run("pset.edit_qto", self.file, qto=qto, properties={"GrossVolume": 20}) + + ifcopenshell.api.run("sequence.assign_product", self.file, relating_product=slab, related_object=resource) + + schedule = ifcopenshell.api.run("sequence.add_work_schedule", self.file) + task = ifcopenshell.api.run("sequence.add_task", self.file, work_schedule=schedule) + ifcopenshell.api.run("sequence.assign_product", self.file, relating_product=slab, related_object=task) + ifcopenshell.api.run("sequence.assign_process", self.file, relating_process=task, related_object=resource) + ifcopenshell.api.run("resource.calculate_resource_work", self.file, resource=resource) + assert resource.Usage.ScheduleWork == "PT0.5H" diff --git a/src/ifcopenshell-python/test/api/root/__init__.py b/src/ifcopenshell-python/test/api/root/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/ifcopenshell-python/test/api/root/test_copy_class.py b/src/ifcopenshell-python/test/api/root/test_copy_class.py new file mode 100644 index 0000000000..19a02821b0 --- /dev/null +++ b/src/ifcopenshell-python/test/api/root/test_copy_class.py @@ -0,0 +1,72 @@ +import test.bootstrap +import ifcopenshell.api + + +class TestCopyClass(test.bootstrap.IFC4): + def test_copying_a_simple_element(self): + element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + new = ifcopenshell.api.run("root.copy_class", self.file, product=element) + assert new != element + assert new.GlobalId != element.GlobalId + assert new.is_a("IfcWall") + + def test_copying_an_element_with_properties(self): + element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + pset = ifcopenshell.api.run("pset.add_pset", self.file, product=element, name="Foobar") + ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"foo": "bar"}) + new = ifcopenshell.api.run("root.copy_class", self.file, product=element) + pset = element.IsDefinedBy[0].RelatingPropertyDefinition + new_pset = new.IsDefinedBy[0].RelatingPropertyDefinition + assert element.IsDefinedBy[0] != new.IsDefinedBy[0] + assert pset != new_pset + assert pset.Name == new_pset.Name + assert pset.HasProperties[0] != new_pset.HasProperties[0] + assert pset.HasProperties[0].Name == new_pset.HasProperties[0].Name + assert pset.HasProperties[0].NominalValue.wrappedValue == new_pset.HasProperties[0].NominalValue.wrappedValue + + def test_copying_an_aggregate_only_and_not_its_decomposition(self): + element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcElementAssembly") + subelement = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcBeam") + ifcopenshell.api.run("aggregate.assign_object", self.file, product=subelement, relating_object=element) + new = ifcopenshell.api.run("root.copy_class", self.file, product=element) + assert element.IsDecomposedBy + assert not new.IsDecomposedBy + + def test_copying_an_aggregate_decomposition(self): + element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcElementAssembly") + subelement = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcBeam") + ifcopenshell.api.run("aggregate.assign_object", self.file, product=subelement, relating_object=element) + new = ifcopenshell.api.run("root.copy_class", self.file, product=subelement) + assert new.Decomposes[0].RelatingObject == element + + def test_not_copying_any_representations_because_life_is_hard(self): + element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + element.Representation = self.file.createIfcProductDefinitionShape() + new = ifcopenshell.api.run("root.copy_class", self.file, product=element) + assert new.Representation is None + element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWallType") + element.RepresentationMaps = [self.file.createIfcRepresentationMap()] + new = ifcopenshell.api.run("root.copy_class", self.file, product=element) + assert new.RepresentationMaps is None + + def test_copying_an_opening_voiding_an_element(self): + wall = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + opening = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcOpeningElement") + ifcopenshell.api.run("void.add_opening", self.file, opening=opening, element=wall) + new = ifcopenshell.api.run("root.copy_class", self.file, product=opening) + assert opening.VoidsElements[0] != new.VoidsElements[0] + assert new.VoidsElements[0].RelatingBuildingElement == wall + + def test_copying_an_opening_with_a_filling(self): + door = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcDoor") + opening = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcOpeningElement") + ifcopenshell.api.run("void.add_filling", self.file, opening=opening, element=door) + new = ifcopenshell.api.run("root.copy_class", self.file, product=opening) + assert not new.HasFillings + + def test_copying_a_filling(self): + door = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcDoor") + opening = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcOpeningElement") + ifcopenshell.api.run("void.add_filling", self.file, opening=opening, element=door) + new = ifcopenshell.api.run("root.copy_class", self.file, product=door) + assert not new.FillsVoids diff --git a/src/ifcopenshell-python/test/api/sequence/__init__.py b/src/ifcopenshell-python/test/api/sequence/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/ifcopenshell-python/test/api/sequence/test_calculate_task_duration.py b/src/ifcopenshell-python/test/api/sequence/test_calculate_task_duration.py new file mode 100644 index 0000000000..7718af4025 --- /dev/null +++ b/src/ifcopenshell-python/test/api/sequence/test_calculate_task_duration.py @@ -0,0 +1,70 @@ +import test.bootstrap +import ifcopenshell.api + + +class TestCalculateTaskDuration(test.bootstrap.IFC4): + def test_calculating_the_duration_based_on_a_labour_resource_with_work_hours(self): + ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcProject") + schedule = ifcopenshell.api.run("sequence.add_work_schedule", self.file) + task = ifcopenshell.api.run("sequence.add_task", self.file, work_schedule=schedule) + resource = ifcopenshell.api.run("resource.add_resource", self.file, ifc_class="IfcLaborResource") + resource_time = ifcopenshell.api.run("resource.add_resource_time", self.file, resource=resource) + resource_time.ScheduleWork = "PT48H" + ifcopenshell.api.run("sequence.assign_process", self.file, relating_process=task, related_object=resource) + ifcopenshell.api.run("sequence.calculate_task_duration", self.file, task=task) + assert task.TaskTime.ScheduleDuration == "P6D" + + def test_calculating_the_duration_based_on_a_labour_resource_with_work_days(self): + ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcProject") + schedule = ifcopenshell.api.run("sequence.add_work_schedule", self.file) + task = ifcopenshell.api.run("sequence.add_task", self.file, work_schedule=schedule) + resource = ifcopenshell.api.run("resource.add_resource", self.file, ifc_class="IfcLaborResource") + resource_time = ifcopenshell.api.run("resource.add_resource_time", self.file, resource=resource) + resource_time.ScheduleWork = "P3.5D" + ifcopenshell.api.run("sequence.assign_process", self.file, relating_process=task, related_object=resource) + ifcopenshell.api.run("sequence.calculate_task_duration", self.file, task=task) + assert task.TaskTime.ScheduleDuration == "P4D" + + def test_calculating_a_task_duration_without_a_work_schedule_defining_workday_duration(self): + ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcProject") + task = ifcopenshell.api.run("sequence.add_task", self.file) + resource = ifcopenshell.api.run("resource.add_resource", self.file, ifc_class="IfcLaborResource") + resource_time = ifcopenshell.api.run("resource.add_resource_time", self.file, resource=resource) + resource_time.ScheduleWork = "P2D" + ifcopenshell.api.run("sequence.assign_process", self.file, relating_process=task, related_object=resource) + ifcopenshell.api.run("sequence.calculate_task_duration", self.file, task=task) + assert task.TaskTime.ScheduleDuration == "P2D" + + def test_calculating_a_task_duration_with_a_custom_workday_duration(self): + ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcProject") + schedule = ifcopenshell.api.run("sequence.add_work_schedule", self.file) + pset = ifcopenshell.api.run("pset.add_pset", self.file, product=schedule, name="Pset_WorkControlCommon") + ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"WorkDayDuration": "PT2H"}) + task = ifcopenshell.api.run("sequence.add_task", self.file, work_schedule=schedule) + resource = ifcopenshell.api.run("resource.add_resource", self.file, ifc_class="IfcLaborResource") + resource_time = ifcopenshell.api.run("resource.add_resource_time", self.file, resource=resource) + resource_time.ScheduleWork = "PT48H" + ifcopenshell.api.run("sequence.assign_process", self.file, relating_process=task, related_object=resource) + ifcopenshell.api.run("sequence.calculate_task_duration", self.file, task=task) + assert task.TaskTime.ScheduleDuration == "P24D" + + def test_failing_to_calculate_if_no_schedule_work_usage(self): + ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcProject") + schedule = ifcopenshell.api.run("sequence.add_work_schedule", self.file) + task = ifcopenshell.api.run("sequence.add_task", self.file, work_schedule=schedule) + resource = ifcopenshell.api.run("resource.add_resource", self.file, ifc_class="IfcLaborResource") + ifcopenshell.api.run("sequence.assign_process", self.file, relating_process=task, related_object=resource) + ifcopenshell.api.run("sequence.calculate_task_duration", self.file, task=task) + assert task.TaskTime is None + + def test_calculating_a_nested_task_duration_based_on_a_labour_resource_with_work_hours(self): + ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcProject") + schedule = ifcopenshell.api.run("sequence.add_work_schedule", self.file) + task = ifcopenshell.api.run("sequence.add_task", self.file, work_schedule=schedule) + subtask = ifcopenshell.api.run("sequence.add_task", self.file, parent_task=task) + resource = ifcopenshell.api.run("resource.add_resource", self.file, ifc_class="IfcLaborResource") + resource_time = ifcopenshell.api.run("resource.add_resource_time", self.file, resource=resource) + resource_time.ScheduleWork = "PT48H" + ifcopenshell.api.run("sequence.assign_process", self.file, relating_process=subtask, related_object=resource) + ifcopenshell.api.run("sequence.calculate_task_duration", self.file, task=subtask) + assert subtask.TaskTime.ScheduleDuration == "P6D" diff --git a/src/ifcopenshell-python/test/api/spatial/__init__.py b/src/ifcopenshell-python/test/api/spatial/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/ifcopenshell-python/test/api/spatial/test_assign_container.py b/src/ifcopenshell-python/test/api/spatial/test_assign_container.py new file mode 100644 index 0000000000..a91857a32c --- /dev/null +++ b/src/ifcopenshell-python/test/api/spatial/test_assign_container.py @@ -0,0 +1,79 @@ +import numpy +import pytest +import test.bootstrap +import ifcopenshell.api +import ifcopenshell.util.element +import ifcopenshell.util.placement + + +class TestEditObjectPlacement(test.bootstrap.IFC4): + def test_assigning_a_container(self): + element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcBuilding") + subelement = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + ifcopenshell.api.run("spatial.assign_container", self.file, product=subelement, relating_structure=element) + assert ifcopenshell.util.element.get_container(subelement) == element + + def test_doing_nothing_if_the_container_is_already_assigned(self): + element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcBuilding") + subelement = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + ifcopenshell.api.run("spatial.assign_container", self.file, product=subelement, relating_structure=element) + total_elements = len([e for e in self.file]) + ifcopenshell.api.run("spatial.assign_container", self.file, product=subelement, relating_structure=element) + assert len([e for e in self.file]) == total_elements + + def test_that_old_containment_relationships_are_updated_if_they_still_contain_elements(self): + element1 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcBuilding") + element2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcBuilding") + subelement1 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + subelement2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + ifcopenshell.api.run("spatial.assign_container", self.file, product=subelement1, relating_structure=element1) + ifcopenshell.api.run("spatial.assign_container", self.file, product=subelement2, relating_structure=element1) + rel = subelement1.ContainedInStructure[0] + assert len(rel.RelatedElements) == 2 + ifcopenshell.api.run("spatial.assign_container", self.file, product=subelement1, relating_structure=element2) + assert len(rel.RelatedElements) == 1 + + def test_that_old_containment_relationships_are_purged_if_no_more_elements_are_contained(self): + element1 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcBuilding") + element2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcBuilding") + subelement1 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + ifcopenshell.api.run("spatial.assign_container", self.file, product=subelement1, relating_structure=element1) + rel_id = subelement1.ContainedInStructure[0].id() + ifcopenshell.api.run("spatial.assign_container", self.file, product=subelement1, relating_structure=element2) + with pytest.raises(RuntimeError): + self.file.by_id(rel_id) + + def test_assigning_a_container_does_not_shift_object_placements(self): + ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcProject") + ifcopenshell.api.run("unit.assign_unit", self.file) + element1 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcBuilding") + element2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcBuilding") + subelement = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + ifcopenshell.api.run("spatial.assign_container", self.file, product=subelement, relating_structure=element1) + matrix1 = numpy.array( + ( + (1.0, 0.0, 0.0, 1.0), + (0.0, 1.0, 0.0, 1.0), + (0.0, 0.0, 1.0, 1.0), + (0.0, 0.0, 0.0, 1.0), + ) + ) + matrix2 = numpy.array( + ( + (1.0, 0.0, 0.0, 2.0), + (0.0, 1.0, 0.0, 2.0), + (0.0, 0.0, 1.0, 2.0), + (0.0, 0.0, 0.0, 1.0), + ) + ) + ifcopenshell.api.run( + "geometry.edit_object_placement", self.file, product=element1, matrix=matrix1.copy(), is_si=False + ) + ifcopenshell.api.run( + "geometry.edit_object_placement", self.file, product=element2, matrix=matrix2.copy(), is_si=False + ) + ifcopenshell.api.run( + "geometry.edit_object_placement", self.file, product=subelement, matrix=matrix1.copy(), is_si=False + ) + ifcopenshell.api.run("spatial.assign_container", self.file, product=subelement, relating_structure=element2) + assert numpy.array_equal(ifcopenshell.util.placement.get_local_placement(subelement.ObjectPlacement), matrix1) diff --git a/src/ifcopenshell-python/test/api/style/test_edit_surface_style.py b/src/ifcopenshell-python/test/api/style/test_edit_surface_style.py new file mode 100644 index 0000000000..eb090e710d --- /dev/null +++ b/src/ifcopenshell-python/test/api/style/test_edit_surface_style.py @@ -0,0 +1,87 @@ +import pytest +import test.bootstrap +import ifcopenshell.api + + +class TestEditSurfaceStyle(test.bootstrap.IFC4): + def test_editing_a_shading_style(self): + colour = self.file.createIfcColourRgb(None, 0, 0, 0) + style = self.file.createIfcSurfaceStyleShading(colour) + ifcopenshell.api.run( + "style.edit_surface_style", + self.file, + style=style, + attributes={"SurfaceColour": [1, 1, 1], "Transparency": 0.5}, + ) + assert style.SurfaceColour == colour + assert list(colour) == [None, 1, 1, 1] + assert style.Transparency == 0.5 + + def test_editing_an_empty_colour_or_factor(self): + for attribute in [ + "DiffuseColour", + "TransmissionColour", + "DiffuseTransmissionColour", + "ReflectionColour", + "SpecularColour", + ]: + style = self.file.createIfcSurfaceStyleRendering(self.file.createIfcColourRgb(None, 0, 0, 0)) + ifcopenshell.api.run("style.edit_surface_style", self.file, style=style, attributes={attribute: [1, 1, 1]}) + assert list(getattr(style, attribute)) == [None, 1, 1, 1] + + def test_editing_an_existing_colour_to_another_colour(self): + for attribute in [ + "DiffuseColour", + "TransmissionColour", + "DiffuseTransmissionColour", + "ReflectionColour", + "SpecularColour", + ]: + colour = self.file.createIfcColourRgb(None, 0, 0, 0) + style = self.file.createIfcSurfaceStyleRendering(self.file.createIfcColourRgb(None, 0, 0, 0)) + setattr(style, attribute, colour) + ifcopenshell.api.run("style.edit_surface_style", self.file, style=style, attributes={attribute: [1, 1, 1]}) + assert list(colour) == [None, 1, 1, 1] + + def test_editing_an_existing_colour_to_a_factor(self): + for attribute in [ + "DiffuseColour", + "TransmissionColour", + "DiffuseTransmissionColour", + "ReflectionColour", + "SpecularColour", + ]: + colour = self.file.createIfcColourRgb(None, 0, 0, 0) + colour_id = colour.id() + style = self.file.createIfcSurfaceStyleRendering(self.file.createIfcColourRgb(None, 0, 0, 0)) + setattr(style, attribute, colour) + ifcopenshell.api.run("style.edit_surface_style", self.file, style=style, attributes={attribute: 0.5}) + with pytest.raises(RuntimeError): + self.file.by_id(colour_id) + assert getattr(style, attribute).wrappedValue == 0.5 + + def test_editing_an_existing_factor_to_another_factor(self): + for attribute in [ + "DiffuseColour", + "TransmissionColour", + "DiffuseTransmissionColour", + "ReflectionColour", + "SpecularColour", + ]: + style = self.file.createIfcSurfaceStyleRendering(self.file.createIfcColourRgb(None, 0, 0, 0)) + setattr(style, attribute, self.file.createIfcNormalisedRatioMeasure(0.5)) + ifcopenshell.api.run("style.edit_surface_style", self.file, style=style, attributes={attribute: 0.4}) + assert getattr(style, attribute).wrappedValue == 0.4 + + def test_editing_an_existing_factor_to_a_colour(self): + for attribute in [ + "DiffuseColour", + "TransmissionColour", + "DiffuseTransmissionColour", + "ReflectionColour", + "SpecularColour", + ]: + style = self.file.createIfcSurfaceStyleRendering(self.file.createIfcColourRgb(None, 0, 0, 0)) + setattr(style, attribute, self.file.createIfcNormalisedRatioMeasure(0.5)) + ifcopenshell.api.run("style.edit_surface_style", self.file, style=style, attributes={attribute: [1, 1, 1]}) + assert list(getattr(style, attribute)) == [None, 1, 1, 1] diff --git a/src/ifcopenshell-python/test/api/void/__init__.py b/src/ifcopenshell-python/test/api/void/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/ifcopenshell-python/test/api/void/test_add_filling.py b/src/ifcopenshell-python/test/api/void/test_add_filling.py new file mode 100644 index 0000000000..b3d5bbf0cb --- /dev/null +++ b/src/ifcopenshell-python/test/api/void/test_add_filling.py @@ -0,0 +1,27 @@ +import test.bootstrap +import ifcopenshell.api + + +class TestAddFilling(test.bootstrap.IFC4): + def test_adding_a_filling(self): + opening = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcOpeningElement") + door = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcDoor") + ifcopenshell.api.run("void.add_filling", self.file, opening=opening, element=door) + assert door.FillsVoids[0].RelatingOpeningElement == opening + + def test_adding_a_filling_twice(self): + opening = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcOpeningElement") + door = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcDoor") + ifcopenshell.api.run("void.add_filling", self.file, opening=opening, element=door) + ifcopenshell.api.run("void.add_filling", self.file, opening=opening, element=door) + assert door.FillsVoids[0].RelatingOpeningElement == opening + assert len(opening.HasFillings) == 1 + + def test_adding_a_filling_which_is_already_filling_another_opening(self): + door = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcDoor") + opening1 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcOpeningElement") + opening2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcOpeningElement") + ifcopenshell.api.run("void.add_filling", self.file, opening=opening1, element=door) + ifcopenshell.api.run("void.add_filling", self.file, opening=opening2, element=door) + assert not opening1.HasFillings + assert opening2.HasFillings[0].RelatedBuildingElement == door diff --git a/src/ifcopenshell-python/test/api/void/test_add_opening.py b/src/ifcopenshell-python/test/api/void/test_add_opening.py new file mode 100644 index 0000000000..69013ee552 --- /dev/null +++ b/src/ifcopenshell-python/test/api/void/test_add_opening.py @@ -0,0 +1,27 @@ +import test.bootstrap +import ifcopenshell.api + + +class TestAddOpening(test.bootstrap.IFC4): + def test_adding_an_opening(self): + wall = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + opening = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcOpeningElement") + ifcopenshell.api.run("void.add_opening", self.file, opening=opening, element=wall) + assert wall.HasOpenings[0].RelatedOpeningElement == opening + + def test_adding_an_opening_twice(self): + wall = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + opening = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcOpeningElement") + ifcopenshell.api.run("void.add_opening", self.file, opening=opening, element=wall) + ifcopenshell.api.run("void.add_opening", self.file, opening=opening, element=wall) + assert wall.HasOpenings[0].RelatedOpeningElement == opening + assert len(wall.HasOpenings) == 1 + + def test_adding_an_opening_which_is_already_voiding_another_element(self): + slab = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcSlab") + wall = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + opening = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcOpeningElement") + ifcopenshell.api.run("void.add_opening", self.file, opening=opening, element=slab) + ifcopenshell.api.run("void.add_opening", self.file, opening=opening, element=wall) + assert not slab.HasOpenings + assert wall.HasOpenings[0].RelatedOpeningElement == opening diff --git a/src/ifcopenshell-python/test/api/void/test_remove_opening.py b/src/ifcopenshell-python/test/api/void/test_remove_opening.py new file mode 100644 index 0000000000..eaf6c37dbb --- /dev/null +++ b/src/ifcopenshell-python/test/api/void/test_remove_opening.py @@ -0,0 +1,31 @@ +import test.bootstrap +import ifcopenshell.api + + +class TestRemoveOpening(test.bootstrap.IFC4): + def test_removing_a_simple_opening(self): + opening = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcOpeningElement") + ifcopenshell.api.run("void.remove_opening", self.file, opening=opening) + assert len(list(self.file)) == 0 + + def test_removing_an_opening_voiding_a_wall(self): + wall = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + opening = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcOpeningElement") + ifcopenshell.api.run("void.add_opening", self.file, opening=opening, element=wall) + ifcopenshell.api.run("void.remove_opening", self.file, opening=opening) + assert len(self.file.by_type("IfcOpeningElement")) == 0 + assert len(self.file.by_type("IfcRelVoidsElement")) == 0 + assert wall + + def test_removing_an_opening_voiding_a_wall_with_a_filling(self): + wall = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + opening = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcOpeningElement") + door = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcDoor") + ifcopenshell.api.run("void.add_opening", self.file, opening=opening, element=wall) + ifcopenshell.api.run("void.add_filling", self.file, opening=opening, element=door) + ifcopenshell.api.run("void.remove_opening", self.file, opening=opening) + assert len(self.file.by_type("IfcOpeningElement")) == 0 + assert len(self.file.by_type("IfcRelVoidsElement")) == 0 + assert len(self.file.by_type("IfcRelFillsElement")) == 0 + assert wall + assert door diff --git a/src/ifcopenshell-python/test/bootstrap.py b/src/ifcopenshell-python/test/bootstrap.py new file mode 100644 index 0000000000..eb47e8d573 --- /dev/null +++ b/src/ifcopenshell-python/test/bootstrap.py @@ -0,0 +1,18 @@ +import pytest +import ifcopenshell +import ifcopenshell.api + + +class IFC4: + @pytest.fixture(autouse=True) + def setup(self): + self.file = ifcopenshell.api.run("project.create_file") + + +class IFC2X3: + @pytest.fixture(autouse=True) + def setup(self): + self.file = ifcopenshell.api.run("project.create_file", version="IFC2X3") + ifcopenshell.api.owner.settings.get_person = lambda ifc: ifc.createIfcPerson() + ifcopenshell.api.owner.settings.get_organisation = lambda ifc: ifc.createIfcOrganization() + ifcopenshell.api.owner.settings.get_application = lambda ifc: ifc.createIfcApplication() diff --git a/src/ifcopenshell-python/test/test_file.py b/src/ifcopenshell-python/test/test_file.py new file mode 100644 index 0000000000..7cf713b733 --- /dev/null +++ b/src/ifcopenshell-python/test/test_file.py @@ -0,0 +1,211 @@ +import pytest +import test.bootstrap +import ifcopenshell +import ifcopenshell.api +import ifcopenshell.util.element + + +class TestTransaction(test.bootstrap.IFC4): + def test_that_nothing_happens_without_a_transaction(self): + wall = self.file.createIfcWall() + self.file.undo() + assert wall + + def test_that_you_can_undo_and_redo_creation(self): + unchanged = self.file.createIfcWall() + self.file.begin_transaction() + wall = self.file.createIfcWall() + self.file.end_transaction() + self.file.undo() + assert unchanged + with pytest.raises(RuntimeError): + self.file.by_id(2) + self.file.redo() + self.file.by_id(2) + + def test_that_you_can_undo_and_redo_editing(self): + element = self.file.createIfcWall(Name="foo") + self.file.begin_transaction() + element.Name = "bar" + self.file.end_transaction() + self.file.undo() + assert element.Name == "foo" + self.file.redo() + assert element.Name == "bar" + + def test_that_you_can_undo_and_redo_deletion(self): + element = self.file.createIfcWall(GlobalId="id") + self.file.begin_transaction() + self.file.remove(element) + self.file.end_transaction() + self.file.undo() + assert self.file.by_id(1) + self.file.redo() + with pytest.raises(RuntimeError): + self.file.by_id(1) + + def test_that_you_can_undo_and_redo_deletion_with_inverse_relationships(self): + element = self.file.createIfcWall(GlobalId="id") + rel = self.file.createIfcRelAggregates() + rel.RelatingObject = element + self.file.begin_transaction() + self.file.remove(element) + self.file.end_transaction() + self.file.undo() + assert rel.RelatingObject == self.file.by_id(1) + self.file.redo() + assert rel.RelatingObject is None + + def test_that_you_can_undo_and_redo_batched_deletion_with_inverse_relationships(self): + element = self.file.createIfcWall(GlobalId="id") + rel = self.file.createIfcRelAggregates() + rel.RelatingObject = element + self.file.begin_transaction() + self.file.batch() + self.file.remove(element) + self.file.unbatch() + self.file.end_transaction() + self.file.undo() + assert rel.RelatingObject == self.file.by_id(1) + self.file.redo() + assert rel.RelatingObject is None + + def test_that_you_can_undo_and_redo_deletion_with_aggregated_inverse_relationships(self): + element = self.file.createIfcWall(GlobalId="id") + rel = self.file.createIfcRelAggregates() + rel.RelatedObjects = [element] + self.file.begin_transaction() + self.file.remove(element) + self.file.end_transaction() + self.file.undo() + assert rel.RelatedObjects == (self.file.by_id(1),) + self.file.redo() + assert len(rel.RelatedObjects) == 0 + + def test_the_editing_of_invalid_default_values(self): + element = self.file.createIfcWall() # This element is invalid, as GlobalId is None + self.file.begin_transaction() + element.GlobalId = "id" + self.file.end_transaction() + self.file.undo() + + def test_setting_the_history_size(self): + self.file.set_history_size(2) + self.file.begin_transaction() + self.file.end_transaction() + self.file.begin_transaction() + self.file.end_transaction() + self.file.begin_transaction() + self.file.end_transaction() + assert len(self.file.history) == 2 + self.file.set_history_size(1) + assert len(self.file.history) == 1 + + def test_discarding_the_active_transaction(self): + self.file.begin_transaction() + self.file.discard_transaction() + self.file.end_transaction() + assert len(self.file.history) == 0 + + def test_redoing_without_anything_in_the_redo_stack(self): + self.file.redo() + + def test_that_you_can_undo_and_redo_added_elements(self): + g = ifcopenshell.file() + element = g.createIfcWall() + self.file.begin_transaction() + self.file.add(element) + self.file.end_transaction() + self.file.undo() + assert len(list(self.file)) == 0 + self.file.redo() + assert len(list(self.file)) == 1 + + def test_that_you_can_undo_and_redo_added_subelements(self): + g = ifcopenshell.file() + owner = g.createIfcOwnerHistory() + element = g.createIfcWall(OwnerHistory=owner) + self.file.begin_transaction() + self.file.add(element) + self.file.end_transaction() + self.file.undo() + assert len(list(self.file)) == 0 + self.file.redo() + assert len(list(self.file)) == 2 + + +class TestFile(test.bootstrap.IFC4): + def test_creating_a_new_file(self): + f = ifcopenshell.file(schema="IFC4") + assert f.schema == "IFC4" + + def test_creating_an_entity(self): + element = self.file.create_entity("IfcPerson") + assert element.is_a("IfcPerson") + element = self.file.create_entity("IfcPerson", "identification") + assert element.Identification == "identification" + element = self.file.create_entity("IfcPerson", Identification="identification") + assert element.Identification == "identification" + element = self.file.create_entity("IfcPerson", Identification="identification", id=42) + assert element.id() == 42 + element = self.file.createIfcPerson() + assert element.is_a("IfcPerson") + + def test_getting_an_element_by_id(self): + element = self.file.createIfcWall("id") + assert self.file.by_id(1) == element + assert self.file.by_id("id") == element + + def test_getting_an_element_by_guid(self): + element = self.file.createIfcWall("id") + assert self.file.by_guid(1) == element + assert self.file.by_guid("id") == element + + def test_adding_an_element(self): + g = ifcopenshell.file() + element = g.createIfcWall() + result = self.file.add(element) + assert result.is_a() == element.is_a() + + def test_getting_elements_by_type(self): + wall = self.file.createIfcWall() + slab = self.file.createIfcSlab() + assert self.file.by_type("IfcWall") == [wall] + + def test_getting_elements_by_exact_type(self): + wall = self.file.createIfcWall() + assert self.file.by_type("IfcElement") == [wall] + assert len(self.file.by_type("IfcElement", include_subtypes=False)) == 0 + + def test_traversing_direct_attributes_of_an_element(self): + owner = self.file.createIfcOwnerHistory() + element = self.file.createIfcWall(OwnerHistory=owner) + assert self.file.traverse(element) == [element, owner] + + def test_traversing_direct_attributes_of_an_element_to_a_limited_level(self): + app = self.file.createIfcApplication() + owner = self.file.createIfcOwnerHistory(OwningApplication=app) + element = self.file.createIfcWall(OwnerHistory=owner) + assert self.file.traverse(element, max_levels=1) == [element, owner] + + def test_getting_inverse_references_of_an_element(self): + owner = self.file.createIfcOwnerHistory() + element = self.file.createIfcWall(OwnerHistory=owner) + assert self.file.get_inverse(owner) == [element] + + def test_removing_an_element(self): + element = self.file.createIfcWall(GlobalId="global_id") + self.file.remove(element) + assert len(list(self.file)) == 0 + + def test_batched_removing_an_element(self): + element = self.file.createIfcWall(GlobalId="global_id") + self.file.batch() + self.file.remove(element) + self.file.unbatch() + assert len(list(self.file)) == 0 + + def test_creating_ifc_data_from_a_string(self): + element = self.file.createIfcWall() + g = ifcopenshell.file.from_string(self.file.wrapped_data.to_string()) + assert g.by_id(1).is_a("IfcWall") diff --git a/src/ifcopenshell-python/test/test_ids.py b/src/ifcopenshell-python/test/test_ids.py new file mode 100644 index 0000000000..a0c181c049 --- /dev/null +++ b/src/ifcopenshell-python/test/test_ids.py @@ -0,0 +1,328 @@ +import os +import logging +import unittest +import tempfile +import requests +import ifcopenshell +from bcf import bcfxml +from ifcopenshell import ids + + +def read_web_file(URL): + return requests.get(URL).text + + +class TestIdsParsing(unittest.TestCase): + + """Parsing basic IDS files""" + + def test_parse_basic_ids(self): + IDS_URL = "https://raw.githubusercontent.com/atomczak/Sample-BIM-Files/main/IDS/IDS_Wall_needs_all_fields.xml" + ids_file = ids.ids.open(read_web_file(IDS_URL)) + self.assertEqual(type(ids_file).__name__, "ids") + + def test_parse_entity_facet(self): + IDS_URL = "https://raw.githubusercontent.com/atomczak/Sample-BIM-Files/main/IDS/IDS_Wall_needs_entity.xml" + ids_file = ids.ids.open(read_web_file(IDS_URL)) + self.assertEqual(ids_file.specifications[0].requirements.terms[0].node["name"]["simpleValue"], "IfcWall") + + def test_parse_predefinedtype_facet(self): + IDS_URL = ( + "https://raw.githubusercontent.com/atomczak/Sample-BIM-Files/main/IDS/IDS_Wall_needs_predefinedtype.xml" + ) + ids_file = ids.ids.open(read_web_file(IDS_URL)) + self.assertEqual( + ids_file.specifications[0].requirements.terms[0].node["predefinedtype"]["simpleValue"], "CLADDING" + ) + + def test_parse_property_facet(self): + IDS_URL = "https://raw.githubusercontent.com/atomczak/Sample-BIM-Files/main/IDS/IDS_Wall_needs_property.xml" + ids_file = ids.ids.open(read_web_file(IDS_URL)) + self.assertEqual( + ids_file.specifications[0].requirements.terms[0].node["propertyset"]["simpleValue"], "Test_PropertySet" + ) + self.assertEqual(ids_file.specifications[0].requirements.terms[0].node["name"]["simpleValue"], "Test_Parameter") + self.assertEqual(ids_file.specifications[0].requirements.terms[0].node["value"]["simpleValue"], "Test_Value") + + def test_parse_material_facet(self): + IDS_URL = "https://raw.githubusercontent.com/atomczak/Sample-BIM-Files/main/IDS/IDS_Wall_needs_material.xml" + ids_file = ids.ids.open(read_web_file(IDS_URL)) + self.assertEqual(ids_file.specifications[0].requirements.terms[0].node["value"]["simpleValue"], "Test_Material") + + def test_parse_classification_facet(self): + IDS_URL = ( + "https://raw.githubusercontent.com/atomczak/Sample-BIM-Files/main/IDS/IDS_Wall_needs_classification.xml" + ) + ids_file = ids.ids.open(read_web_file(IDS_URL)) + self.assertEqual( + ids_file.specifications[0].requirements.terms[0].node["value"]["simpleValue"], "Test_Classification" + ) + self.assertEqual(ids_file.specifications[0].requirements.terms[0].node["system"]["simpleValue"], "Test_System") + + """ Parsing invalid IDS.xml """ + # TODO + # def test_invalid_classification_facet(self): + # IDS_URL = "https://raw.githubusercontent.com/atomczak/Sample-BIM-Files/main/IDS/Invalid_IDS_Wall_needs_classification.xml" + # self.assertRaises( XMLSchemaChildrenValidationError, ids.open(read_web_file(IDS_URL)) ) + + """ Saving parsed IDS to IDS.xml """ + + def test_parsed_ids_to_xml(self): + IDS_URL = "https://raw.githubusercontent.com/atomczak/Sample-BIM-Files/main/IDS/IDS_Wall_needs_all_fields.xml" + ids_file = ids.ids.open(read_web_file(IDS_URL)) + fn = "TEST_FILE.xml" + result = ids_file.to_xml(fn) + os.remove(fn) + self.assertTrue(result) + + """ Parsing IDS files with restrictions """ + + def test_parse_restrictions_enumeration(self): + IDS_URL = "https://raw.githubusercontent.com/atomczak/Sample-BIM-Files/main/IDS/IDS_Wall_needs_property_with_restriction_enumeration.xml" + ids_file = ids.ids.open(read_web_file(IDS_URL)) + self.assertEqual(ids_file.specifications[0].requirements.terms[0].node["name"]["simpleValue"], "Test_Parameter") + self.assertEqual( + [ + x["@value"] + for x in ids_file.specifications[0].requirements.terms[0].node["value"]["restriction"][0]["enumeration"] + ], + ["testA", "testB"], + ) + + def test_parse_restrictions_bounds(self): + IDS_URL = "https://raw.githubusercontent.com/atomczak/Sample-BIM-Files/main/IDS/IDS_Wall_needs_property_with_restriction_bounds.xml" + ids_file = ids.ids.open(read_web_file(IDS_URL)) + self.assertEqual(ids_file.specifications[0].requirements.terms[0].node["name"]["simpleValue"], "Test_Parameter") + self.assertEqual( + ids_file.specifications[0].requirements.terms[0].node["value"]["restriction"][0]["minInclusive"]["@value"], + "0", + ) + + def test_parse_restrictions_pattern_simple(self): + IDS_URL = "https://raw.githubusercontent.com/atomczak/Sample-BIM-Files/main/IDS/IDS_Wall_needs_property_with_restriction_pattern.xml" + ids_file = ids.ids.open(read_web_file(IDS_URL)) + self.assertEqual(ids_file.specifications[0].requirements.terms[0].node["name"]["simpleValue"], "Test_Parameter") + self.assertEqual( + ids_file.specifications[0].requirements.terms[0].node["value"]["restriction"][0]["pattern"]["@value"], + "[A-Z]{2,4}", + ) + + +class TestIdsAuthoring(unittest.TestCase): + + """Creating basic IDS""" + + def test_entity_create(self): + e = ids.entity.create(name="Test_Name", predefinedtype="Test_PredefinedType") + self.assertEqual(e.name, "Test_Name") + self.assertEqual(e.predefinedtype, "Test_PredefinedType") + + def test_classification_create(self): + c = ids.classification.create(location="any", value="Test_Value", system="Test_System") + self.assertEqual(c.location, "any") + self.assertEqual(c.value, "Test_Value") + self.assertEqual(c.system, "Test_System") + + def test_property_create(self): + p = ids.property.create( + location="any", propertyset="Test_PropertySet", name="Test_Parameter", value="Test_Value" + ) + self.assertEqual(p.location, "any") + self.assertEqual(p.propertyset, "Test_PropertySet") + self.assertEqual(p.name, "Test_Parameter") + self.assertEqual(p.value, "Test_Value") + + def test_material_create(self): + m = ids.material.create(location="any", value="Test_Value") + self.assertEqual(m.location, "any") + self.assertEqual(m.value, "Test_Value") + + def test_specification_create(self): + s = ids.specification(name="Test_Specification") + self.assertEqual(s.name, "Test_Specification") + + def test_ids_add_content(self): + i = ids.ids() + i.specifications.append(ids.specification(name="Test_Specification")) + self.assertEqual(i.specifications[0].name, "Test_Specification") + m = ids.material.create(location="any", value="Test_Value") + i.specifications[0].add_applicability(m) + self.assertEqual(i.specifications[0].applicability.terms[0].value, "Test_Value") + i.specifications[0].add_applicability(m) + self.assertEqual(i.specifications[0].applicability.terms[1].value, "Test_Value") + i.specifications[0].add_requirement(m) + self.assertEqual(i.specifications[0].requirements.terms[0].value, "Test_Value") + i.specifications[0].add_requirement(m) + self.assertEqual(i.specifications[0].requirements.terms[1].value, "Test_Value") + + """ Creating IDS with restrictions """ + + def test_create_restrictions_enumeration(self): + i = ids.ids() + i.specifications.append(ids.specification(name="Test_Specification")) + i.specifications[0].add_applicability(ids.entity.create(name="Test_Name")) + r = ids.restriction.create(options=["testA", "testB"], type="enumeration", base="string") + m = ids.material.create(location="any", value=r) + i.specifications[0].add_requirement(m) + self.assertEqual(i.specifications[0].requirements.terms[0].value, "testA") + self.assertEqual(i.specifications[0].requirements.terms[0].value, "testB") + self.assertNotEqual(i.specifications[0].requirements.terms[0].value, "testC") + + def test_create_restrictions_bounds(self): + i = ids.ids() + i.specifications.append(ids.specification(name="Test_Specification")) + i.specifications[0].add_applicability(ids.entity.create(name="Test_Name")) + r = ids.restriction.create(options={"minInclusive": 0, "maxExclusive": 10}, type="bounds", base="integer") + p = ids.property.create(location="any", propertyset="Test", name="Test", value=r) + i.specifications[0].add_requirement(p) + self.assertEqual(i.specifications[0].requirements.terms[0].value, 0) + self.assertEqual(i.specifications[0].requirements.terms[0].value, 5) + self.assertNotEqual(i.specifications[0].requirements.terms[0].value, -1) + self.assertNotEqual(i.specifications[0].requirements.terms[0].value, 10) + + def test_create_restrictions_pattern_simple(self): + i = ids.ids() + i.specifications.append(ids.specification(name="Test_Specification")) + i.specifications[0].add_applicability(ids.entity.create(name="Test_Name")) + r = ids.restriction.create(options="[A-Z]{2,4}", type="pattern", base="string") + p = ids.property.create(location="any", propertyset="Test", name="Test", value=r) + i.specifications[0].add_requirement(p) + self.assertEqual(i.specifications[0].requirements.terms[0].value, "XYZ") + self.assertNotEqual(i.specifications[0].requirements.terms[0].value, "abc") + self.assertNotEqual(i.specifications[0].requirements.terms[0].value, "ABCDE") + self.assertNotEqual(i.specifications[0].requirements.terms[0].value, "A") + + def test_create_restrictions_pattern_advanced(self): + i = ids.ids() + i.specifications.append(ids.specification(name="Test_Specification")) + i.specifications[0].add_applicability(ids.entity.create(name="Test_Name")) + # r = ids.restriction.create(options="^(Wanddurchbruch.*|Deckendurchbruch.*)", type="pattern", base="string") + r = ids.restriction.create(options="(Wanddurchbruch|Deckendurchbruch).*", type="pattern", base="string") + p = ids.property.create(location="any", propertyset="Test", name="Test", value=r) + i.specifications[0].add_requirement(p) + self.assertEqual(i.specifications[0].requirements.terms[0].value, "Wanddurchbruch") + self.assertEqual(i.specifications[0].requirements.terms[0].value, "Deckendurchbruch") + self.assertNotEqual(i.specifications[0].requirements.terms[0].value, "Deeckendurchbruch") + + """ Saving created IDS to IDS.xml """ + + def test_created_ids_to_xml(self): + i = ids.ids() + i.specifications.append(ids.specification(name="Test_Specification")) + e = ids.entity.create(name="Test_Name", predefinedtype="Test_PredefinedType") + c = ids.classification.create(location="any", value="Test_Value", system="Test_System") + m = ids.material.create(location="any", value="Test_Value") + re = ids.restriction.create(options=["testA", "testB"], type="enumeration", base="string") + rb = ids.restriction.create(options={"minInclusive": 0, "maxExclusive": 10}, type="bounds", base="integer") + rp = ids.restriction.create(options="[A-Z]{2,4}", type="pattern", base="string") + p1 = ids.property.create(location="any", propertyset="Test_PropertySet", name="Test_Parameter", value=re) + p2 = ids.property.create(location="any", propertyset="Test_PropertySet", name="Test_Parameter", value=rb) + p3 = ids.property.create(location="any", propertyset="Test_PropertySet", name="Test_Parameter", value=rp) + p4 = ids.property.create( + location="any", propertyset="Test_PropertySet", name="Test_Parameter", value=[re, rb, rp] + ) + i.specifications[0].add_applicability(e) + i.specifications[0].add_applicability(m) + i.specifications[0].add_requirement(c) + i.specifications[0].add_requirement(p1) + i.specifications[0].add_requirement(p2) + i.specifications[0].add_requirement(p3) + i.specifications[0].add_requirement(p4) + fn = "TEST_FILE.xml" + result = i.to_xml(fn) + os.remove(fn) + self.assertTrue(result) + + """ IDS information """ + + def test_create_full_information(self): + i = ids.ids( + ifcversion="2.3.0.1", + description="test", + author="test@test.com", + copyright="test", + version=1.23, + creation_date="2021-01-01", + purpose="test", + milestone="test", + ) + self.assertEqual(i.info["version"], 1.23) + + +class TestIfcValidation(unittest.TestCase): + def test_validate_simple(self): + # TODO + pass + + def test_validate_all_facets(self): + # TODO + pass + + """ Validating IDS files with restrictions """ + + # def test_validate_restrictions_enumeration(self): + # IDS_URL = "https://raw.githubusercontent.com/atomczak/Sample-BIM-Files/main/IDS/IDS_Wall_needs_property_with_restriction_enumeration.xml" + # ids_file = ids.ids.open(read_web_file(IDS_URL)) + # self.assertEqual(ids_file.specifications[0].requirements.terms[0].node["name"]['simpleValue'], "Test_Parameter") + # self.assertEqual( [x['@value'] for x in ids_file.specifications[0].requirements.terms[0].node["value"]['restriction'][0]['enumeration'] ], ['testA', 'testB']) + # # TODO actual test of validation result + # # self.assertTrue( ) + + # def test_validate_restrictions_boundsInclusive(self): + # IDS_URL = "https://raw.githubusercontent.com/atomczak/Sample-BIM-Files/main/IDS/IDS_Wall_needs_property_with_restriction_bounds.xml" + # ids_file = ids.ids.open(read_web_file(IDS_URL)) + # self.assertEqual(ids_file.specifications[0].requirements.terms[0].node["name"]['simpleValue'], "Test_Parameter") + # self.assertEqual(ids_file.specifications[0].requirements.terms[0].node["value"]['restriction'][0]['minInclusive']['@value'], '0') + # # TODO actual test of validation result + # # self.assertTrue( ) + + # def test_validate_restrictions_boundsExclusive(self): + # #TODO + # pass + + # def test_validate_restrictions_pattern_simple(self): + # IDS_URL = "https://raw.githubusercontent.com/atomczak/Sample-BIM-Files/main/IDS/IDS_Wall_needs_property_with_restriction_pattern.xml" + # ids_file = ids.ids.open(read_web_file(IDS_URL)) + # self.assertEqual(ids_file.specifications[0].requirements.terms[0].node["name"]['simpleValue'], "Test_Parameter") + # self.assertEqual(ids_file.specifications[0].requirements.terms[0].node["value"]['restriction'][0]['pattern']['@value'], '[A-Z]{2,4}') + # # TODO actual test of validation result + # # self.assertTrue( ) + + +class TestIdsReporting(unittest.TestCase): + + TEST_PATH = os.getcwd() + IFC_URL = "https://raw.githubusercontent.com/atomczak/Sample-BIM-Files/main/IFC/IFC4_Wall_3_with_properties.ifc" + IDS_URL = "https://raw.githubusercontent.com/atomczak/Sample-BIM-Files/main/IDS/IDS_Wall_needs_all_fields.xml" + + logger = logging.getLogger("IDS_Logger") + # logging.basicConfig(level=logging.INFO, format="%(message)s") + # logging.basicConfig(filename=TEST_PATH+r"\log.txt", level=logging.INFO, format="%(message)s") + + content = read_web_file(IFC_URL) + file = open(TEST_PATH + r"\test.ifc", "w") + file.write(content) + file.close() + ifc_file = ifcopenshell.open(TEST_PATH + r"\test.ifc") + os.remove(TEST_PATH + r"\test.ifc") + + def test_simple_report(self): + ids_file = ids.ids.open(read_web_file(self.IDS_URL)) + report = ids.SimpleHandler() + self.logger.addHandler(report) + ids_file.validate(self.ifc_file, self.logger) + self.assertEqual(len(report.statements), 5) + + def test_bcf_report(self): + ids_file = ids.ids.open(read_web_file(self.IDS_URL)) + fn = os.path.join(tempfile.gettempdir(), "test.bcf") + bcf_handler = ids.BcfHandler(project_name="Default IDS Project", author="your@email.com", filepath=fn) + self.logger.addHandler(bcf_handler) + ids_file.validate(self.ifc_file, self.logger) + my_bcfxml = bcfxml.load(fn) + topics = my_bcfxml.get_topics() + self.assertEqual(len(topics), 5) + + +if __name__ == "__main__": + unittest.main() diff --git a/src/ifcopenshell-python/test/util/__init__.py b/src/ifcopenshell-python/test/util/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/ifcopenshell-python/test/util/test_element.py b/src/ifcopenshell-python/test/util/test_element.py new file mode 100644 index 0000000000..ec0a40984b --- /dev/null +++ b/src/ifcopenshell-python/test/util/test_element.py @@ -0,0 +1,271 @@ +import pytest +import test.bootstrap +import ifcopenshell.api +import ifcopenshell.util.element + + +class TestGetPsetsIFC4(test.bootstrap.IFC4): + def test_getting_the_psets_of_a_product_as_a_dictionary(self): + element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + assert ifcopenshell.util.element.get_psets(element) == {} + pset = ifcopenshell.api.run("pset.add_pset", self.file, product=element, name="name") + ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"a": "b"}) + assert ifcopenshell.util.element.get_psets(element) == {"name": {"a": "b"}} + + def test_getting_the_psets_of_a_product_type_as_a_dictionary(self): + type_element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWallType") + assert ifcopenshell.util.element.get_psets(type_element) == {} + pset = ifcopenshell.api.run("pset.add_pset", self.file, product=type_element, name="name") + ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"x": "y"}) + assert ifcopenshell.util.element.get_psets(type_element) == {"name": {"x": "y"}} + + def test_getting_psets_from_an_element_which_cannot_have_psets(self): + assert ifcopenshell.util.element.get_psets(self.file.create_entity("IfcPerson")) == {} + + +class TestGetPropertyDefinitionIFC4(test.bootstrap.IFC4): + def test_getting_the_properties_of_a_pset(self): + element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + pset = ifcopenshell.api.run("pset.add_pset", self.file, product=element, name="name") + ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"a": "b"}) + assert ifcopenshell.util.element.get_property_definition(pset) == {"a": "b"} + + def test_getting_the_properties_of_a_qto(self): + element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + qto = ifcopenshell.api.run("pset.add_qto", self.file, product=element, name="name") + ifcopenshell.api.run("pset.edit_qto", self.file, qto=qto, properties={"x": 42}) + assert ifcopenshell.util.element.get_property_definition(qto) == {"x": 42} + + def test_getting_the_properties_of_a_predefined_pset(self): + pset = self.file.create_entity("IfcDoorLiningProperties", ifcopenshell.guid.new()) + pset.LiningDepth = 42 + assert ifcopenshell.util.element.get_property_definition(pset) == {"LiningDepth": 42} + + +class TestGetQuantitiesIFC4(test.bootstrap.IFC4): + def test_getting_quantities_from_a_qto(self): + element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + qto = ifcopenshell.api.run("pset.add_qto", self.file, product=element, name="name") + ifcopenshell.api.run("pset.edit_qto", self.file, qto=qto, properties={"x": 42}) + assert ifcopenshell.util.element.get_quantities(qto.Quantities) == {"x": 42} + + +class TestGetPropertiesIFC4(test.bootstrap.IFC4): + def test_getting_no_properties_when_none_are_available(self): + element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + pset = ifcopenshell.api.run("pset.add_pset", self.file, product=element, name="name") + assert ifcopenshell.util.element.get_properties(pset.HasProperties) == {} + + def test_getting_single_properties_from_a_list_of_properties(self): + element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + pset = ifcopenshell.api.run("pset.add_pset", self.file, product=element, name="name") + ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"a": "b"}) + assert ifcopenshell.util.element.get_properties(pset.HasProperties) == {"a": "b"} + + def test_getting_complex_properties_from_a_list_of_properties(self): + element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + pset = ifcopenshell.api.run("pset.add_pset", self.file, product=element, name="pset") + complex_property = self.file.createIfcComplexProperty(Name="prop", UsageName="usage_name") + ifcopenshell.api.run("pset.edit_pset", self.file, pset=complex_property, properties={"a": "b"}) + pset.HasProperties = [complex_property] + assert ifcopenshell.util.element.get_properties(pset.HasProperties) == { + "prop": { + "UsageName": "usage_name", + "id": 4, + "type": "IfcComplexProperty", + "properties": {"a": "b"}, + } + } + + +class TestGetTypeIFC4(test.bootstrap.IFC4): + def test_getting_the_type_of_a_product(self): + element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + element_type = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWallType") + ifcopenshell.api.run("type.assign_type", self.file, related_object=element, relating_type=element_type) + assert ifcopenshell.util.element.get_type(element) == element_type + assert ifcopenshell.util.element.get_type(element_type) == element_type + + +class TestGetTypeIFC2X3(test.bootstrap.IFC2X3): + def test_getting_the_type_of_a_product(self): + element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + element_type = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWallType") + ifcopenshell.api.run("type.assign_type", self.file, related_object=element, relating_type=element_type) + assert ifcopenshell.util.element.get_type(element) == element_type + assert ifcopenshell.util.element.get_type(element_type) == element_type + + +class TestGetMaterial(test.bootstrap.IFC4): + def test_getting_the_material_of_a_product(self): + element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + material = ifcopenshell.api.run("material.add_material", self.file) + ifcopenshell.api.run("material.assign_material", self.file, product=element, material=material) + assert ifcopenshell.util.element.get_material(element) == material + + def test_getting_a_material_list_of_a_product(self): + element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + material = ifcopenshell.api.run("material.add_material", self.file) + rel = ifcopenshell.api.run( + "material.assign_material", self.file, product=element, type="IfcMaterialList", material=material + ) + assert ifcopenshell.util.element.get_material(element) == rel.RelatingMaterial + + def test_getting_a_material_layer_set_of_a_product(self): + element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + rel = ifcopenshell.api.run("material.assign_material", self.file, product=element, type="IfcMaterialLayerSet") + assert ifcopenshell.util.element.get_material(element) == rel.RelatingMaterial + + def test_getting_a_material_profile_set_of_a_product(self): + element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + rel = ifcopenshell.api.run("material.assign_material", self.file, product=element, type="IfcMaterialProfileSet") + assert ifcopenshell.util.element.get_material(element) == rel.RelatingMaterial + + def test_getting_a_material_layer_set_usage_of_a_product(self): + element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + rel = ifcopenshell.api.run( + "material.assign_material", self.file, product=element, type="IfcMaterialLayerSetUsage" + ) + assert ifcopenshell.util.element.get_material(element) == rel.RelatingMaterial + + def test_getting_a_material_profile_set_usage_of_a_product(self): + element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + rel = ifcopenshell.api.run( + "material.assign_material", self.file, product=element, type="IfcMaterialProfileSetUsage" + ) + assert ifcopenshell.util.element.get_material(element) == rel.RelatingMaterial + + def test_getting_a_material_layer_set_indirectly_from_an_assigned_usage(self): + element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + rel = ifcopenshell.api.run( + "material.assign_material", self.file, product=element, type="IfcMaterialLayerSetUsage" + ) + assert ( + ifcopenshell.util.element.get_material(element, should_skip_usage=True) == rel.RelatingMaterial.ForLayerSet + ) + + def test_getting_a_material_profile_set_indirectly_from_an_assigned_usage(self): + element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + rel = ifcopenshell.api.run( + "material.assign_material", self.file, product=element, type="IfcMaterialProfileSetUsage" + ) + assert ( + ifcopenshell.util.element.get_material(element, should_skip_usage=True) + == rel.RelatingMaterial.ForProfileSet + ) + + def test_getting_an_inherited_material_from_the_elements_type(self): + element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + element_type = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWallType") + ifcopenshell.api.run("type.assign_type", self.file, related_object=element, relating_type=element_type) + material = ifcopenshell.api.run("material.add_material", self.file) + ifcopenshell.api.run("material.assign_material", self.file, product=element_type, material=material) + assert ifcopenshell.util.element.get_material(element) == material + + +class TestGetContainerIFC4(test.bootstrap.IFC4): + def test_getting_the_spatial_container_of_an_element(self): + element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + building = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcBuilding") + ifcopenshell.api.run("spatial.assign_container", self.file, product=element, relating_structure=building) + assert ifcopenshell.util.element.get_container(element) == building + + def test_getting_an_indirect_spatial_container_of_an_element(self): + subelement = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcElementAssembly") + building = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcBuilding") + ifcopenshell.api.run("spatial.assign_container", self.file, product=element, relating_structure=building) + ifcopenshell.api.run("aggregate.assign_object", self.file, product=subelement, relating_object=element) + assert ifcopenshell.util.element.get_container(subelement) == building + + +class TestGetDecompositionIFC4(test.bootstrap.IFC4): + def test_getting_decomposed_subelements_of_an_element(self): + element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcElementAssembly") + subelement = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcBeam") + building = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcBuilding") + ifcopenshell.api.run("spatial.assign_container", self.file, product=element, relating_structure=building) + ifcopenshell.api.run("aggregate.assign_object", self.file, product=subelement, relating_object=element) + results = ifcopenshell.util.element.get_decomposition(building) + assert element in results + assert subelement in results + + +class TestGetAggregateIFC4(test.bootstrap.IFC4): + def test_getting_the_containing_aggregate_of_a_subelement(self): + element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + subelement = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcCovering") + ifcopenshell.api.run("aggregate.assign_object", self.file, product=subelement, relating_object=element) + assert ifcopenshell.util.element.get_aggregate(subelement) == element + + +class TestReplaceAttributeIFC4(test.bootstrap.IFC4): + def test_replacing_an_elements_attribute(self): + element = self.file.createIfcWall("foo") + ifcopenshell.util.element.replace_attribute(element, "foo", "bar") + assert element.GlobalId == "bar" + + def test_replacing_a_value_in_a_list(self): + old = self.file.createIfcWall() + new = self.file.createIfcWall() + rel = self.file.createIfcRelAggregates() + rel.RelatedObjects = [old] + ifcopenshell.util.element.replace_attribute(rel, old, new) + assert rel.RelatedObjects == (new,) + + +class TestHasElementReferenceIFC4(test.bootstrap.IFC4): + def test_if_a_element_attribute_references_another_element(self): + old = self.file.createIfcWall() + new = self.file.createIfcWall() + rel = self.file.createIfcRelAggregates() + rel.RelatedObjects = [old] + assert ifcopenshell.util.element.has_element_reference(rel.RelatedObjects, old) is True + assert ifcopenshell.util.element.has_element_reference(rel.RelatedObjects, new) is False + + +class TestRemoveDeepIFC4(test.bootstrap.IFC4): + def test_removing_an_element_along_with_all_direct_attributes_recursively(self): + owner = self.file.createIfcOwnerHistory() + element = self.file.createIfcWall(GlobalId="id", OwnerHistory=owner) + ifcopenshell.util.element.remove_deep(self.file, element) + with pytest.raises(RuntimeError): + self.file.by_id(1) + self.file.by_id(2) + + def test_removing_an_element_recursively_except_if_an_element_is_referenced_elsewhere(self): + owner = self.file.createIfcOwnerHistory() + element = self.file.createIfcWall(GlobalId="id1", OwnerHistory=owner) + element2 = self.file.createIfcWall(GlobalId="id2", OwnerHistory=owner) + ifcopenshell.util.element.remove_deep(self.file, element) + with pytest.raises(RuntimeError): + self.file.by_guid("id1") + assert self.file.by_id(1) + assert self.file.by_guid("id2") + + +class TestCopyIFC4(test.bootstrap.IFC4): + def test_copying_an_element(self): + element = self.file.createIfcWall(GlobalId="id", Name="name") + element2 = ifcopenshell.util.element.copy(self.file, element) + assert element.is_a() == element2.is_a() + assert element.GlobalId != element2.GlobalId + assert element.Name == element2.Name + + +class TestCopyDeepIFC4(test.bootstrap.IFC4): + def test_copying_an_element_recursively(self): + owner = self.file.createIfcOwnerHistory() + owner.State = "READWRITE" + element = self.file.createIfcWall(GlobalId="id", Name="name", OwnerHistory=owner) + element2 = ifcopenshell.util.element.copy_deep(self.file, element) + assert element.OwnerHistory != element2.OwnerHistory + assert element.OwnerHistory.State == element2.OwnerHistory.State + + def test_copying_an_element_recursively_even_if_references_are_aggregated(self): + element = self.file.createIfcWall(Name="name") + rel = self.file.createIfcRelAggregates() + rel.RelatedObjects = [element] + rel2 = ifcopenshell.util.element.copy_deep(self.file, rel) + assert rel.RelatedObjects != rel2.RelatedObjects + assert rel.RelatedObjects[0].Name == rel2.RelatedObjects[0].Name diff --git a/src/ifcopenshell-python/ifcopenshell/util/test_pset.py b/src/ifcopenshell-python/test/util/test_pset.py similarity index 100% rename from src/ifcopenshell-python/ifcopenshell/util/test_pset.py rename to src/ifcopenshell-python/test/util/test_pset.py diff --git a/src/ifcparse/IfcFile.h b/src/ifcparse/IfcFile.h index 34ee0750e2..f4377e62db 100644 --- a/src/ifcparse/IfcFile.h +++ b/src/ifcparse/IfcFile.h @@ -268,6 +268,8 @@ public: unsigned int FreshId() { return ++MaxId; } + unsigned int getMaxId() { return MaxId; } + void recalculate_id_counter(); IfcUtil::IfcBaseClass* addEntity(IfcUtil::IfcBaseClass* entity, int id=-1); diff --git a/src/ifcparse/IfcParse.cpp b/src/ifcparse/IfcParse.cpp index ca8ed3fa9b..85416335cc 100644 --- a/src/ifcparse/IfcParse.cpp +++ b/src/ifcparse/IfcParse.cpp @@ -1844,6 +1844,18 @@ IfcUtil::IfcBaseClass* IfcFile::addEntity(IfcUtil::IfcBaseClass* entity, int id) (*it) *= conversion_factor; } + IfcWrite::IfcWriteArgument* copy = new IfcWrite::IfcWriteArgument(); + copy->set(v); + we->setArgument(i, copy); + } else if (attr_type == IfcUtil::Argument_AGGREGATE_OF_AGGREGATE_OF_DOUBLE) { + std::vector> v = *attr; + for (std::vector>::iterator it = v.begin(); it != v.end(); ++it) { + std::vector& v2 = (*it); + for (std::vector::iterator jt = v2.begin(); jt != v2.end(); ++jt) { + (*jt) *= conversion_factor; + } + } + IfcWrite::IfcWriteArgument* copy = new IfcWrite::IfcWriteArgument(); copy->set(v); we->setArgument(i, copy); @@ -2368,7 +2380,13 @@ void IfcFile::setDefaultHeaderValues() { std::pair IfcFile::getUnit(const std::string& unit_type) { std::pair return_value(0, 1.); + aggregate_of_instance::ptr projects = instances_by_type(schema()->declaration_by_name("IfcProject")); + if (!projects || projects->size() == 0) { + try { + projects = instances_by_type(schema()->declaration_by_name("IfcContext")); + } catch ( IfcException& e ) {} + } if (projects && projects->size() == 1) { IfcUtil::IfcBaseClass* project = *projects->begin(); diff --git a/src/ifcparse/IfcSchema.h b/src/ifcparse/IfcSchema.h index 067c11ddaf..3e32f5d1d7 100644 --- a/src/ifcparse/IfcSchema.h +++ b/src/ifcparse/IfcSchema.h @@ -383,12 +383,12 @@ namespace IfcParse { virtual const entity* as_entity() const { return this; } }; - class instance_factory { + class IFC_PARSE_API instance_factory { public: virtual IfcUtil::IfcBaseClass* operator()(IfcEntityInstanceData* data) const = 0; }; - class schema_definition { + class IFC_PARSE_API schema_definition { private: std::string name_; diff --git a/src/ifcwrap/IfcParseWrapper.i b/src/ifcwrap/IfcParseWrapper.i index 1aeb470dbe..57cc1d2fa7 100644 --- a/src/ifcwrap/IfcParseWrapper.i +++ b/src/ifcwrap/IfcParseWrapper.i @@ -775,17 +775,34 @@ static IfcUtil::ArgumentType helper_fn_attribute_type(const IfcUtil::IfcBaseClas %inline %{ PyObject* get_info_cpp(IfcUtil::IfcBaseClass* v) { PyObject *d = PyDict_New(); - const std::vector attrs = v->declaration().as_entity()->all_attributes(); - std::vector::const_iterator it = attrs.begin(); - for (; it != attrs.end(); ++it) { - const std::string& name_cpp = (*it)->name(); + + if (v->declaration().as_entity()) { + const std::vector attrs = v->declaration().as_entity()->all_attributes(); + std::vector::const_iterator it = attrs.begin(); + auto dit = v->declaration().as_entity()->derived().begin(); + for (; it != attrs.end(); ++it, ++dit) { + const std::string& name_cpp = (*it)->name(); + auto name_py = pythonize(name_cpp); + auto attr_type = *dit + ? IfcUtil::Argument_DERIVED + : IfcUtil::from_parameter_type((*it)->type_of_attribute()); + auto value_cpp = v->data().getArgument(std::distance(attrs.begin(), it)); + auto value_py = convert_cpp_attribute_to_python(attr_type, *value_cpp); + PyDict_SetItem(d, name_py, value_py); + } + + const std::string& id_cpp = "id"; + auto id_py = pythonize(id_cpp); + auto id_v_py = pythonize(v->data().id()); + PyDict_SetItem(d, id_py, id_v_py); + } else { + const std::string& name_cpp = "wrappedValue"; auto name_py = pythonize(name_cpp); - auto attr_type = IfcUtil::from_parameter_type((*it)->type_of_attribute()); - auto value_cpp = v->data().getArgument(std::distance(attrs.begin(), it)); - auto value_py = convert_cpp_attribute_to_python(attr_type, *value_cpp); + auto value_cpp = v->data().getArgument(0); + auto value_py = convert_cpp_attribute_to_python(value_cpp->type(), *value_cpp); PyDict_SetItem(d, name_py, value_py); } - + // @todo type and id can be static? const std::string& type_cpp = "type"; auto type_py = pythonize(type_cpp); @@ -793,11 +810,6 @@ static IfcUtil::ArgumentType helper_fn_attribute_type(const IfcUtil::IfcBaseClas auto type_v_py = pythonize(type_v_cpp); PyDict_SetItem(d, type_py, type_v_py); - const std::string& id_cpp = "id"; - auto id_py = pythonize(id_cpp); - auto id_v_py = pythonize(v->data().id()); - PyDict_SetItem(d, id_py, id_v_py); - return d; } %} diff --git a/src/foundationserver/README.md b/src/opencdeserver/README.md similarity index 100% rename from src/foundationserver/README.md rename to src/opencdeserver/README.md diff --git a/src/foundationserver/bcfserver/bcf/project.json b/src/opencdeserver/opencdeserver/bcf/project.json similarity index 100% rename from src/foundationserver/bcfserver/bcf/project.json rename to src/opencdeserver/opencdeserver/bcf/project.json diff --git a/src/foundationserver/bcfserver/bcf/routes.py b/src/opencdeserver/opencdeserver/bcf/routes.py similarity index 97% rename from src/foundationserver/bcfserver/bcf/routes.py rename to src/opencdeserver/opencdeserver/bcf/routes.py index d9a1450bc7..fb0779dbc5 100644 --- a/src/foundationserver/bcfserver/bcf/routes.py +++ b/src/opencdeserver/opencdeserver/bcf/routes.py @@ -1,3 +1,22 @@ + +# OpenCDE - OpenCDE Python implementation +# Copyright (C) 2021 Prabhat Singh +# +# This file is part of OpenCDE. +# +# OpenCDE is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# OpenCDE is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with OpenCDE. If not, see . + from flask import jsonify, url_for, redirect, render_template, request, session, send_file from flask_login import login_user, logout_user, login_required, current_user from flask.blueprints import Blueprint diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Action/comment_actions.json b/src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/Action/comment_actions.json similarity index 100% rename from src/foundationserver/bcfserver/bcf/schemas/Collaboration/Action/comment_actions.json rename to src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/Action/comment_actions.json diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Action/project_actions.json b/src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/Action/project_actions.json similarity index 100% rename from src/foundationserver/bcfserver/bcf/schemas/Collaboration/Action/project_actions.json rename to src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/Action/project_actions.json diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Action/topic_actions.json b/src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/Action/topic_actions.json similarity index 100% rename from src/foundationserver/bcfserver/bcf/schemas/Collaboration/Action/topic_actions.json rename to src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/Action/topic_actions.json diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Action/viewpoint_actions.json b/src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/Action/viewpoint_actions.json similarity index 100% rename from src/foundationserver/bcfserver/bcf/schemas/Collaboration/Action/viewpoint_actions.json rename to src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/Action/viewpoint_actions.json diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Comment/comment_GET.json b/src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/Comment/comment_GET.json similarity index 100% rename from src/foundationserver/bcfserver/bcf/schemas/Collaboration/Comment/comment_GET.json rename to src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/Comment/comment_GET.json diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Comment/comment_POST.json b/src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/Comment/comment_POST.json similarity index 100% rename from src/foundationserver/bcfserver/bcf/schemas/Collaboration/Comment/comment_POST.json rename to src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/Comment/comment_POST.json diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Comment/comment_PUT.json b/src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/Comment/comment_PUT.json similarity index 100% rename from src/foundationserver/bcfserver/bcf/schemas/Collaboration/Comment/comment_PUT.json rename to src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/Comment/comment_PUT.json diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Document/document_GET.json b/src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/Document/document_GET.json similarity index 100% rename from src/foundationserver/bcfserver/bcf/schemas/Collaboration/Document/document_GET.json rename to src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/Document/document_GET.json diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/DocumentReference/document_reference_GET.json b/src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/DocumentReference/document_reference_GET.json similarity index 100% rename from src/foundationserver/bcfserver/bcf/schemas/Collaboration/DocumentReference/document_reference_GET.json rename to src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/DocumentReference/document_reference_GET.json diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/DocumentReference/document_reference_POST.json b/src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/DocumentReference/document_reference_POST.json similarity index 100% rename from src/foundationserver/bcfserver/bcf/schemas/Collaboration/DocumentReference/document_reference_POST.json rename to src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/DocumentReference/document_reference_POST.json diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/DocumentReference/document_reference_PUT.json b/src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/DocumentReference/document_reference_PUT.json similarity index 100% rename from src/foundationserver/bcfserver/bcf/schemas/Collaboration/DocumentReference/document_reference_PUT.json rename to src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/DocumentReference/document_reference_PUT.json diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Events/comment_event_GET.json b/src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/Events/comment_event_GET.json similarity index 100% rename from src/foundationserver/bcfserver/bcf/schemas/Collaboration/Events/comment_event_GET.json rename to src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/Events/comment_event_GET.json diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Events/event_action.json b/src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/Events/event_action.json similarity index 100% rename from src/foundationserver/bcfserver/bcf/schemas/Collaboration/Events/event_action.json rename to src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/Events/event_action.json diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Events/topic_event_GET.json b/src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/Events/topic_event_GET.json similarity index 100% rename from src/foundationserver/bcfserver/bcf/schemas/Collaboration/Events/topic_event_GET.json rename to src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/Events/topic_event_GET.json diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/File/file_GET.json b/src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/File/file_GET.json similarity index 100% rename from src/foundationserver/bcfserver/bcf/schemas/Collaboration/File/file_GET.json rename to src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/File/file_GET.json diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/File/file_PUT.json b/src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/File/file_PUT.json similarity index 100% rename from src/foundationserver/bcfserver/bcf/schemas/Collaboration/File/file_PUT.json rename to src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/File/file_PUT.json diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/File/project_file_display_information.json b/src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/File/project_file_display_information.json similarity index 100% rename from src/foundationserver/bcfserver/bcf/schemas/Collaboration/File/project_file_display_information.json rename to src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/File/project_file_display_information.json diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/File/project_file_information.json b/src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/File/project_file_information.json similarity index 100% rename from src/foundationserver/bcfserver/bcf/schemas/Collaboration/File/project_file_information.json rename to src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/File/project_file_information.json diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/File/project_files_information_GET.json b/src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/File/project_files_information_GET.json similarity index 100% rename from src/foundationserver/bcfserver/bcf/schemas/Collaboration/File/project_files_information_GET.json rename to src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/File/project_files_information_GET.json diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/RelatedTopic/related_topic_GET.json b/src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/RelatedTopic/related_topic_GET.json similarity index 100% rename from src/foundationserver/bcfserver/bcf/schemas/Collaboration/RelatedTopic/related_topic_GET.json rename to src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/RelatedTopic/related_topic_GET.json diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/RelatedTopic/related_topic_PUT.json b/src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/RelatedTopic/related_topic_PUT.json similarity index 100% rename from src/foundationserver/bcfserver/bcf/schemas/Collaboration/RelatedTopic/related_topic_PUT.json rename to src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/RelatedTopic/related_topic_PUT.json diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Topic/bim_snippet.json b/src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/Topic/bim_snippet.json similarity index 100% rename from src/foundationserver/bcfserver/bcf/schemas/Collaboration/Topic/bim_snippet.json rename to src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/Topic/bim_snippet.json diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Topic/topic_GET.json b/src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/Topic/topic_GET.json similarity index 100% rename from src/foundationserver/bcfserver/bcf/schemas/Collaboration/Topic/topic_GET.json rename to src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/Topic/topic_GET.json diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Topic/topic_POST.json b/src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/Topic/topic_POST.json similarity index 100% rename from src/foundationserver/bcfserver/bcf/schemas/Collaboration/Topic/topic_POST.json rename to src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/Topic/topic_POST.json diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Topic/topic_PUT.json b/src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/Topic/topic_PUT.json similarity index 100% rename from src/foundationserver/bcfserver/bcf/schemas/Collaboration/Topic/topic_PUT.json rename to src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/Topic/topic_PUT.json diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/bitmap_GET.json b/src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/Viewpoint/bitmap_GET.json similarity index 100% rename from src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/bitmap_GET.json rename to src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/Viewpoint/bitmap_GET.json diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/bitmap_POST.json b/src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/Viewpoint/bitmap_POST.json similarity index 100% rename from src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/bitmap_POST.json rename to src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/Viewpoint/bitmap_POST.json diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/clipping_plane.json b/src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/Viewpoint/clipping_plane.json similarity index 100% rename from src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/clipping_plane.json rename to src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/Viewpoint/clipping_plane.json diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/coloring.json b/src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/Viewpoint/coloring.json similarity index 100% rename from src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/coloring.json rename to src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/Viewpoint/coloring.json diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/coloring_GET.json b/src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/Viewpoint/coloring_GET.json similarity index 100% rename from src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/coloring_GET.json rename to src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/Viewpoint/coloring_GET.json diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/component.json b/src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/Viewpoint/component.json similarity index 100% rename from src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/component.json rename to src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/Viewpoint/component.json diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/component_list.json b/src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/Viewpoint/component_list.json similarity index 100% rename from src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/component_list.json rename to src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/Viewpoint/component_list.json diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/components.json b/src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/Viewpoint/components.json similarity index 100% rename from src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/components.json rename to src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/Viewpoint/components.json diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/direction.json b/src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/Viewpoint/direction.json similarity index 100% rename from src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/direction.json rename to src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/Viewpoint/direction.json diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/line.json b/src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/Viewpoint/line.json similarity index 100% rename from src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/line.json rename to src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/Viewpoint/line.json diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/location.json b/src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/Viewpoint/location.json similarity index 100% rename from src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/location.json rename to src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/Viewpoint/location.json diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/orthogonal_camera.json b/src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/Viewpoint/orthogonal_camera.json similarity index 100% rename from src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/orthogonal_camera.json rename to src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/Viewpoint/orthogonal_camera.json diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/perspective_camera.json b/src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/Viewpoint/perspective_camera.json similarity index 100% rename from src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/perspective_camera.json rename to src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/Viewpoint/perspective_camera.json diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/point.json b/src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/Viewpoint/point.json similarity index 100% rename from src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/point.json rename to src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/Viewpoint/point.json diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/selection_GET.json b/src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/Viewpoint/selection_GET.json similarity index 100% rename from src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/selection_GET.json rename to src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/Viewpoint/selection_GET.json diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/snapshot_GET.json b/src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/Viewpoint/snapshot_GET.json similarity index 100% rename from src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/snapshot_GET.json rename to src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/Viewpoint/snapshot_GET.json diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/snapshot_POST.json b/src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/Viewpoint/snapshot_POST.json similarity index 100% rename from src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/snapshot_POST.json rename to src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/Viewpoint/snapshot_POST.json diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/view_setup_hints.json b/src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/Viewpoint/view_setup_hints.json similarity index 100% rename from src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/view_setup_hints.json rename to src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/Viewpoint/view_setup_hints.json diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/viewpoint_GET.json b/src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/Viewpoint/viewpoint_GET.json similarity index 100% rename from src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/viewpoint_GET.json rename to src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/Viewpoint/viewpoint_GET.json diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/viewpoint_POST.json b/src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/Viewpoint/viewpoint_POST.json similarity index 100% rename from src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/viewpoint_POST.json rename to src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/Viewpoint/viewpoint_POST.json diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/visibility.json b/src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/Viewpoint/visibility.json similarity index 100% rename from src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/visibility.json rename to src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/Viewpoint/visibility.json diff --git a/src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/visibility_GET.json b/src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/Viewpoint/visibility_GET.json similarity index 100% rename from src/foundationserver/bcfserver/bcf/schemas/Collaboration/Viewpoint/visibility_GET.json rename to src/opencdeserver/opencdeserver/bcf/schemas/Collaboration/Viewpoint/visibility_GET.json diff --git a/src/foundationserver/bcfserver/bcf/schemas/Project/extensions_GET.json b/src/opencdeserver/opencdeserver/bcf/schemas/Project/extensions_GET.json similarity index 100% rename from src/foundationserver/bcfserver/bcf/schemas/Project/extensions_GET.json rename to src/opencdeserver/opencdeserver/bcf/schemas/Project/extensions_GET.json diff --git a/src/foundationserver/bcfserver/bcf/schemas/Project/project_GET.json b/src/opencdeserver/opencdeserver/bcf/schemas/Project/project_GET.json similarity index 100% rename from src/foundationserver/bcfserver/bcf/schemas/Project/project_GET.json rename to src/opencdeserver/opencdeserver/bcf/schemas/Project/project_GET.json diff --git a/src/foundationserver/bcfserver/bcf/schemas/Project/project_PUT.json b/src/opencdeserver/opencdeserver/bcf/schemas/Project/project_PUT.json similarity index 100% rename from src/foundationserver/bcfserver/bcf/schemas/Project/project_PUT.json rename to src/opencdeserver/opencdeserver/bcf/schemas/Project/project_PUT.json diff --git a/src/foundationserver/bcfserver/bcf/schemas/User/user_GET.json b/src/opencdeserver/opencdeserver/bcf/schemas/User/user_GET.json similarity index 100% rename from src/foundationserver/bcfserver/bcf/schemas/User/user_GET.json rename to src/opencdeserver/opencdeserver/bcf/schemas/User/user_GET.json diff --git a/src/foundationserver/bcfserver/bcf/schemas/error.json b/src/opencdeserver/opencdeserver/bcf/schemas/error.json similarity index 100% rename from src/foundationserver/bcfserver/bcf/schemas/error.json rename to src/opencdeserver/opencdeserver/bcf/schemas/error.json diff --git a/src/foundationserver/bcfserver/bcf/success.txt b/src/opencdeserver/opencdeserver/bcf/success.txt similarity index 100% rename from src/foundationserver/bcfserver/bcf/success.txt rename to src/opencdeserver/opencdeserver/bcf/success.txt diff --git a/src/opencdeserver/opencdeserver/bcf/templates/base.html b/src/opencdeserver/opencdeserver/bcf/templates/base.html new file mode 100644 index 0000000000..59ae9b37da --- /dev/null +++ b/src/opencdeserver/opencdeserver/bcf/templates/base.html @@ -0,0 +1,19 @@ + diff --git a/src/foundationserver/bcfserver/foundation/forms.py b/src/opencdeserver/opencdeserver/foundation/forms.py similarity index 74% rename from src/foundationserver/bcfserver/foundation/forms.py rename to src/opencdeserver/opencdeserver/foundation/forms.py index d326108a6e..2f86536b8d 100644 --- a/src/foundationserver/bcfserver/foundation/forms.py +++ b/src/opencdeserver/opencdeserver/foundation/forms.py @@ -1,3 +1,22 @@ + +# OpenCDE - OpenCDE Python implementation +# Copyright (C) 2021 Prabhat Singh +# +# This file is part of OpenCDE. +# +# OpenCDE is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# OpenCDE is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with OpenCDE. If not, see . + from flask_wtf import FlaskForm from wtforms import StringField, PasswordField, BooleanField, SubmitField from wtforms.validators import DataRequired, Length, Email, Regexp, EqualTo diff --git a/src/foundationserver/bcfserver/foundation/models.py b/src/opencdeserver/opencdeserver/foundation/models.py similarity index 73% rename from src/foundationserver/bcfserver/foundation/models.py rename to src/opencdeserver/opencdeserver/foundation/models.py index 4eef828f05..db451e4022 100644 --- a/src/foundationserver/bcfserver/foundation/models.py +++ b/src/opencdeserver/opencdeserver/foundation/models.py @@ -1,3 +1,22 @@ + +# OpenCDE - OpenCDE Python implementation +# Copyright (C) 2021 Prabhat Singh +# +# This file is part of OpenCDE. +# +# OpenCDE is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# OpenCDE is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with OpenCDE. If not, see . + from run import db, bcrypt, login_manager import time from authlib.integrations.sqla_oauth2 import ( diff --git a/src/foundationserver/bcfserver/foundation/oauth2.py b/src/opencdeserver/opencdeserver/foundation/oauth2.py similarity index 81% rename from src/foundationserver/bcfserver/foundation/oauth2.py rename to src/opencdeserver/opencdeserver/foundation/oauth2.py index b0d9e24b9f..bc69b28c0e 100644 --- a/src/foundationserver/bcfserver/foundation/oauth2.py +++ b/src/opencdeserver/opencdeserver/foundation/oauth2.py @@ -1,3 +1,22 @@ + +# OpenCDE - OpenCDE Python implementation +# Copyright (C) 2021 Prabhat Singh +# +# This file is part of OpenCDE. +# +# OpenCDE is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# OpenCDE is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with OpenCDE. If not, see . + from authlib.integrations.flask_oauth2 import ( AuthorizationServer, ResourceProtector, diff --git a/src/foundationserver/bcfserver/foundation/routes.py b/src/opencdeserver/opencdeserver/foundation/routes.py similarity index 92% rename from src/foundationserver/bcfserver/foundation/routes.py rename to src/opencdeserver/opencdeserver/foundation/routes.py index 9fdb7957bb..4f817b3d70 100644 --- a/src/foundationserver/bcfserver/foundation/routes.py +++ b/src/opencdeserver/opencdeserver/foundation/routes.py @@ -1,3 +1,22 @@ + +# OpenCDE - OpenCDE Python implementation +# Copyright (C) 2021 Prabhat Singh +# +# This file is part of OpenCDE. +# +# OpenCDE is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# OpenCDE is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with OpenCDE. If not, see . + from flask.blueprints import Blueprint from flask import render_template, redirect, url_for, flash, request from werkzeug import datastructures diff --git a/src/foundationserver/bcfserver/foundation/templates/base.html b/src/opencdeserver/opencdeserver/foundation/templates/base.html similarity index 82% rename from src/foundationserver/bcfserver/foundation/templates/base.html rename to src/opencdeserver/opencdeserver/foundation/templates/base.html index 534b7f5c43..70cb6009c5 100644 --- a/src/foundationserver/bcfserver/foundation/templates/base.html +++ b/src/opencdeserver/opencdeserver/foundation/templates/base.html @@ -1,3 +1,22 @@ + diff --git a/src/opencdeserver/opencdeserver/foundation/templates/clientdata.html b/src/opencdeserver/opencdeserver/foundation/templates/clientdata.html new file mode 100644 index 0000000000..c78d37c989 --- /dev/null +++ b/src/opencdeserver/opencdeserver/foundation/templates/clientdata.html @@ -0,0 +1,36 @@ + +{%extends 'base.html'%} {%block content%} {% if user %} + +
Logged in as {{user}}
+ +{% for client in clients %} +
+{{ client.client_info|tojson }}
+{{ client.client_metadata|tojson }}
+
+
+{% endfor %} {% else %} +
Not logged in
+{% endif %} {% endblock %} diff --git a/src/foundationserver/bcfserver/foundation/templates/createclient.html b/src/opencdeserver/opencdeserver/foundation/templates/createclient.html similarity index 58% rename from src/foundationserver/bcfserver/foundation/templates/createclient.html rename to src/opencdeserver/opencdeserver/foundation/templates/createclient.html index 51883fb79a..b423982721 100644 --- a/src/foundationserver/bcfserver/foundation/templates/createclient.html +++ b/src/opencdeserver/opencdeserver/foundation/templates/createclient.html @@ -1,3 +1,22 @@ + {%extends 'base.html' %} {%block title %} {% endblock%} {%block content%}
diff --git a/src/opencdeserver/opencdeserver/foundation/templates/index.html b/src/opencdeserver/opencdeserver/foundation/templates/index.html new file mode 100644 index 0000000000..b32168adf5 --- /dev/null +++ b/src/opencdeserver/opencdeserver/foundation/templates/index.html @@ -0,0 +1,35 @@ + + + + + + + + + + Document + + + Home Page + + diff --git a/src/foundationserver/bcfserver/foundation/templates/login.html b/src/opencdeserver/opencdeserver/foundation/templates/login.html similarity index 51% rename from src/foundationserver/bcfserver/foundation/templates/login.html rename to src/opencdeserver/opencdeserver/foundation/templates/login.html index 057494d54a..896fff4b07 100644 --- a/src/foundationserver/bcfserver/foundation/templates/login.html +++ b/src/opencdeserver/opencdeserver/foundation/templates/login.html @@ -1,3 +1,22 @@ + {% extends 'base.html' %} {% block title %}{% endblock %} {% block content %}
diff --git a/src/opencdeserver/opencdeserver/foundation/templates/oauth.html b/src/opencdeserver/opencdeserver/foundation/templates/oauth.html new file mode 100644 index 0000000000..544542e04b --- /dev/null +++ b/src/opencdeserver/opencdeserver/foundation/templates/oauth.html @@ -0,0 +1,20 @@ + +{%extends "base.html"%} {%block content%} this is Oauth page {%endblock%} diff --git a/src/foundationserver/bcfserver/foundation/templates/ologin.html b/src/opencdeserver/opencdeserver/foundation/templates/ologin.html similarity index 52% rename from src/foundationserver/bcfserver/foundation/templates/ologin.html rename to src/opencdeserver/opencdeserver/foundation/templates/ologin.html index 277f795cff..6f06cf5871 100644 --- a/src/foundationserver/bcfserver/foundation/templates/ologin.html +++ b/src/opencdeserver/opencdeserver/foundation/templates/ologin.html @@ -1,3 +1,22 @@ + {% extends 'base.html' %} {% block title %}{% endblock %} {% block content %}
diff --git a/src/foundationserver/bcfserver/foundation/templates/register.html b/src/opencdeserver/opencdeserver/foundation/templates/register.html similarity index 58% rename from src/foundationserver/bcfserver/foundation/templates/register.html rename to src/opencdeserver/opencdeserver/foundation/templates/register.html index 2aa75c7c2e..426d5c4318 100644 --- a/src/foundationserver/bcfserver/foundation/templates/register.html +++ b/src/opencdeserver/opencdeserver/foundation/templates/register.html @@ -1,3 +1,22 @@ + {%extends 'base.html' %} {%block title %} {% endblock%} {%block content%}
diff --git a/src/opencdeserver/opencdeserver/run.py b/src/opencdeserver/opencdeserver/run.py new file mode 100644 index 0000000000..a4cc214606 --- /dev/null +++ b/src/opencdeserver/opencdeserver/run.py @@ -0,0 +1,44 @@ + +# OpenCDE - OpenCDE Python implementation +# Copyright (C) 2021 Prabhat Singh +# +# This file is part of OpenCDE. +# +# OpenCDE is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# OpenCDE is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with OpenCDE. If not, see . + +from flask import Flask +from flask_sqlalchemy import SQLAlchemy +from flask_login import LoginManager +from flask_bcrypt import Bcrypt + + +app = Flask(__name__) +db = SQLAlchemy(app) +login_manager = LoginManager(app) +bcrypt = Bcrypt(app) +app.config["SECRET_KEY"] = "f613729206685405cde0e388" +app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///sqlite.db" +login_manager.login_view = "foundation_obj.login_page" +login_manager.login_message_category = "info" + +from foundation.routes import foundation_obj +from bcf.routes import bcf + + +app.register_blueprint(foundation_obj) +app.register_blueprint(bcf) + + +if __name__ == "__main__": + app.run(host="0.0.0.0", port=5000, debug=True) diff --git a/src/foundationserver/requirements.txt b/src/opencdeserver/requirements.txt similarity index 100% rename from src/foundationserver/requirements.txt rename to src/opencdeserver/requirements.txt diff --git a/src/serializers/SvgSerializer.cpp b/src/serializers/SvgSerializer.cpp index 5f2e534ad5..2fa3ebbca6 100644 --- a/src/serializers/SvgSerializer.cpp +++ b/src/serializers/SvgSerializer.cpp @@ -552,13 +552,13 @@ void SvgSerializer::write(const IfcGeom::BRepElement* brep_obj) { } else if (elevation_ref_guid_) { is_elevation = *elevation_ref_guid_ == brep_obj->guid(); } + + BRepBuilderAPI_Transform make_transform_global(compound_local, trsf, true); + make_transform_global.Build(); + // (When determinant < 0, copy is implied and the input is not mutated.) + auto compound_unmirrored = make_transform_global.Shape(); if (is_section || is_elevation) { - BRepBuilderAPI_Transform make_transform_global(compound_local, trsf, true); - make_transform_global.Build(); - // (When determinant < 0, copy is implied and the input is not mutated.) - auto compound_unmirrored = make_transform_global.Shape(); - boost::optional scale; boost::optional> size; @@ -675,6 +675,10 @@ void SvgSerializer::write(const IfcGeom::BRepElement* brep_obj) { element_buffer_.push_back(data); } + // Augment bnd_ regardless of whether emitting storeys as we depend + // on the global bounds also for the storey height annotations. + BRepBndLib::Add(compound_unmirrored, bnd_); + if (emit_building_storeys_) { write(data); } @@ -705,6 +709,19 @@ namespace { }; } +namespace { + int infront_or_behind(const gp_Pln& pln, const gp_Pnt& p) { + auto d = (p.XYZ() - pln.Location().XYZ()).Dot(pln.Axis().Direction().XYZ()); + int state; + if (std::abs(d) < 1.e-5) { + state = 0; + } else { + state = d < 0. ? -1 : 1; + } + return state; + } +} + void SvgSerializer::write(const geometry_data& data) { std::vector section_heights_storage; const std::vector* section_heights_used = §ion_heights_storage; @@ -736,10 +753,6 @@ void SvgSerializer::write(const geometry_data& data) { } #endif - if (is_floor_plan_) { - BRepBndLib::Add(compound_unmirrored, bnd_); - } - // SVG has a coordinate system with the origin in the *upper*-left corner // therefore we mirror the shape along the XZ-plane. gp_Trsf trsf_mirror; @@ -902,13 +915,7 @@ void SvgSerializer::write(const geometry_data& data) { // See if any of the vertices is in the negative Z-axis of the projection plane for (int i = 0; i < 8; ++i) { gp_Pnt p(xs[(i & 1) == 1], ys[(i & 2) == 2], zs[(i & 4) == 4]); - auto d = (p.XYZ() - projection_plane.Location().XYZ()).Dot(projection_plane.Axis().Direction().XYZ()); - int state; - if (std::abs(d) < 1.e-5) { - state = 0; - } else { - state = d < 0. ? -1 : 1; - } + int state = infront_or_behind(projection_plane, p); if (state == -1) { any_in_front = true; } else if (state == +1) { @@ -947,13 +954,7 @@ void SvgSerializer::write(const geometry_data& data) { TopExp_Explorer exp2(face, TopAbs_VERTEX); for (; exp2.More(); exp2.Next()) { gp_Pnt p = BRep_Tool::Pnt(TopoDS::Vertex(exp2.Current())); - auto d = (p.XYZ() - projection_plane.Location().XYZ()).Dot(projection_plane.Axis().Direction().XYZ()); - int state; - if (std::abs(d) < 1.e-5) { - state = 0; - } else { - state = d < 0. ? -1 : 1; - } + int state = infront_or_behind(projection_plane, p); if (state == -1) { any_in_front_face = true; } else if (state == +1) { @@ -1064,15 +1065,17 @@ void SvgSerializer::write(const geometry_data& data) { object_type.erase(std::remove_if(object_type.begin(), object_type.end(), [](char c) { return !std::isalnum(c); }), object_type.end()); } - auto z_local = gp::DZ().Transformed(data.trsf.Inverted()); + auto z_global = gp::DZ().Transformed(data.trsf); + auto xyz_global = gp_Pnt().Transformed(data.trsf); + int state = infront_or_behind(projection_plane, xyz_global); if (data.product->declaration().is("IfcAnnotation") && // is an Annotation (proj.Magnitude() > 1.e-5) && // when projected onto the view has a length - is_floor_plan_ + (is_floor_plan_ ? (zmin >= range.first && zmin < (range.second - 1.e-5)) // the Z-coords are within the range of the building storey, // this excludes the upper bound with a small tolerance - : (projection_direction.Dot(z_local) < -0.99) // For elevations only include annotations that are "facing" the view direction - ) + : (projection_direction.Dot(z_global) > 0.99 && state == -1) // For elevations only include annotations that are "facing" the view direction + )) { auto svg_name = data.svg_name;