Merge branch 'IfcOpenShell:v0.7.0' into v0.7.0

This commit is contained in:
Carlos Dias
2023-09-03 12:33:20 -03:00
committed by GitHub
80 changed files with 2676 additions and 1507 deletions
+2 -2
View File
@@ -48,10 +48,10 @@ jobs:
run: |
pip install build
cd src/bcf &&
python -m build
make dist
- name: Publish a Python distribution to PyPI
uses: ortega2247/pypi-upload-action@master
with:
user: __token__
password: ${{ secrets.PYPI_API_TOKEN }}
packages_dir: src/bcf/dist
packages_dir: src/bcf/dist
+1
View File
@@ -88,6 +88,7 @@ jobs:
sudo /usr/bin/python -m pip install networkx
sudo /usr/bin/python -m pip install tabulate
sudo /usr/bin/python -m pip install python-dateutil
sudo /usr/bin/python -m pip install mathutils
- name: Test
run: |
+1
View File
@@ -114,6 +114,7 @@ jobs:
sudo /usr/bin/python -m pip install https://github.com/Andrej730/aud/archive/refs/heads/master-reduced-size.zip
sudo /usr/bin/python -m pip install tabulate
sudo /usr/bin/python -m pip install python-dateutil
sudo /usr/bin/python -m pip install mathutils
- name: Test
run: |
+4 -1
View File
@@ -96,5 +96,8 @@ src/ifcopenshell-python/ifcopenshell/ifcopenshell_wrapper.py
# apple
.DS_Store
# clangd cache
.cache
# Brickschema
src/blenderbim/blenderbim/bim/schema/Brick.ttl
src/blenderbim/blenderbim/bim/schema/Brick.ttl
+626 -567
View File
File diff suppressed because it is too large Load Diff
+43
View File
@@ -0,0 +1,43 @@
################################################################################
# #
# This file is part of IfcOpenShell. #
# #
# IfcOpenShell is free software: you can redistribute it and/or modify #
# it under the terms of the Lesser GNU General Public License as published by #
# the Free Software Foundation, either version 3.0 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 #
# Lesser GNU General Public License for more details. #
# #
# You should have received a copy of the Lesser GNU General Public License #
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
# #
################################################################################
if(NOT EXISTS "@CMAKE_BINARY_DIR@/install_manifest.txt")
message(FATAL_ERROR "Cannot find install manifest: @CMAKE_BINARY_DIR@/install_manifest.txt")
endif()
file(READ "@CMAKE_BINARY_DIR@/install_manifest.txt" files)
string(REGEX REPLACE "\n" ";" files "${files}")
foreach(file ${files})
message(STATUS "Uninstalling $ENV{DESTDIR}${file}")
if(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}")
exec_program(
"@CMAKE_COMMAND@" ARGS "-E remove \"$ENV{DESTDIR}${file}\""
OUTPUT_VARIABLE rm_out
RETURN_VALUE rm_retval
)
if(NOT "${rm_retval}" STREQUAL 0)
message(FATAL_ERROR "Problem when removing $ENV{DESTDIR}${file}")
endif()
else(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}")
message(STATUS "File $ENV{DESTDIR}${file} does not exist.")
endif()
endforeach()
+14 -1
View File
@@ -1,3 +1,9 @@
VERSION:=`date '+%y%m%d'`
SED:=sed -i
ifeq ($(UNAME_S),Darwin)
SED:=sed -i '' -e
endif
.PHONY: ci
ci:
tox
@@ -12,6 +18,13 @@ models:
cd src && xsdata generate -p bcf.v2.model --unnest-classes --kw-only --slots -ds Google bcf/v2/xsd
cd src && xsdata generate -p bcf.v3.model --unnest-classes --kw-only --slots -ds Google bcf/v3/xsd
.PHONY: dist
dist:
rm -rf dist
$(SED) "s/999999/$(VERSION)/" pyproject.toml
python -m build
$(SED) "s/$(VERSION)/999999/" pyproject.toml
# .PHONY
# api:
# openapi-python-client generate --url https://api.swaggerhub.com/apis/buildingSMART/BCF/3.0
# openapi-python-client generate --url https://api.swaggerhub.com/apis/buildingSMART/BCF/3.0
+3 -3
View File
@@ -17,7 +17,7 @@ dependencies = [
"numpy",
"ifcopenshell",
]
version = "0.0.1"
version = "0.0.999999"
classifiers = [
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
"Operating System :: OS Independent",
@@ -76,7 +76,7 @@ deps =
black
isort
pylint
commands =
commands =
black {posargs:.}
isort {posargs:.}
pylint {posargs:.} --output-format=colorized
@@ -138,4 +138,4 @@ max-attributes = 10
[tool.pylint.format]
expected-line-ending-format = "LF"
max-line-length = 120
max-line-length = 120
+11 -12
View File
@@ -57,7 +57,6 @@ ifeq ($(PYVERSION), py39)
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/2.3.4/download/linux-64/hpp-fcl-2.3.4-py39h40a70d0_0.conda
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/3.1.0/download/linux-64/eigenpy-3.1.0-py39hdfdd6bb_0.conda
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/linux-64/boost-1.78.0-py39h7c9e3ff_4.tar.bz2
QHULL_URL:=https://anaconda.org/conda-forge/qhull/2020.2/download/linux-64/qhull-2020.2-h4bd325d_2.tar.bz2
LXML_URL:=https://files.pythonhosted.org/packages/19/d9/a69c6aff5673554df48120565a14a50eaa41d29ae03b02faa0b023666318/lxml-4.6.3-cp39-cp39-manylinux2014_x86_64.whl
SHAPELY_URL:=https://files.pythonhosted.org/packages/2d/f2/8ec281d357e8bb7d08dc8d727f0e4c8ef3dae7d3fa75c69c8e452bb82d50/shapely-2.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
PILLOW_URL:=https://files.pythonhosted.org/packages/01/61/3ff85fb4bb596ce3d223c8fcf93c8df5c12bc8899dfb4fb3cb1c5b20dd5f/Pillow-9.2.0-cp39-cp39-manylinux_2_28_x86_64.whl
@@ -66,12 +65,12 @@ ifeq ($(PYVERSION), py310)
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/2.3.4/download/linux-64/hpp-fcl-2.3.4-py310h995690b_0.conda
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/3.1.0/download/linux-64/eigenpy-3.1.0-py310hf02b7e0_0.conda
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/linux-64/boost-1.78.0-py310hc4a4660_4.tar.bz2
QHULL_URL:=https://anaconda.org/conda-forge/qhull/2020.2/download/linux-64/qhull-2020.2-h4bd325d_2.tar.bz2
LXML_URL:=https://files.pythonhosted.org/packages/25/1e/19b46d8e8881fe0df2e20945d51919eeb1817836d62a90efa8506530e45c/lxml-4.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl
SHAPELY_URL:=https://files.pythonhosted.org/packages/a8/a5/403728b5614b28083f6424dfdefec5fcf58068495fb03bb08532671c642f/shapely-2.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
PILLOW_URL:=https://files.pythonhosted.org/packages/f6/51/320986ebd6d46a0e95c2240468ced73153b691ce07617078bcdf30c609ec/Pillow-9.2.0-cp310-cp310-manylinux_2_28_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
QHULL_URL:=https://anaconda.org/conda-forge/qhull/2020.2/download/linux-64/qhull-2020.2-h4bd325d_2.tar.bz2
OCTOMAP_URL:=https://anaconda.org/conda-forge/octomap/1.9.8/download/linux-64/octomap-1.9.8-h924138e_0.tar.bz2
BOOSTCPP_URL:=https://anaconda.org/conda-forge/boost-cpp/1.78.0/download/linux-64/boost-cpp-1.78.0-h6582d0a_3.conda
ZLIB_URL:=https://anaconda.org/conda-forge/zlib/1.2.11/download/linux-64/zlib-1.2.11-h516909a_1010.tar.bz2
@@ -83,7 +82,6 @@ ifeq ($(PYVERSION), py39)
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/2.3.4/download/osx-64/hpp-fcl-2.3.4-py39hd85b194_0.conda
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/3.1.0/download/osx-64/eigenpy-3.1.0-py39hc4d6e28_0.conda
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/osx-64/boost-1.78.0-py39h953a6b8_4.tar.bz2
QHULL_URL:=https://anaconda.org/conda-forge/qhull/2020.2/download/osx-64/qhull-2020.2-h940c156_2.tar.bz2
LXML_URL:=https://files.pythonhosted.org/packages/b8/74/a71f7ad72e8db54ce899efab84507b801660750cbbfa6a39e6717557d36a/lxml-4.6.3-cp39-cp39-macosx_10_9_x86_64.whl
SHAPELY_URL:=https://files.pythonhosted.org/packages/36/a4/7e542a209f862f967d7cb8e939eff155f4294a27d17e16441fb8bdd51a2c/shapely-2.0.1-cp39-cp39-macosx_10_9_x86_64.whl
PILLOW_URL:=https://files.pythonhosted.org/packages/88/7a/ddfe28b485b623361457d4783007c1f9ba83a87f93e7fec32f64793efb6c/Pillow-9.2.0-cp39-cp39-macosx_10_10_x86_64.whl
@@ -92,12 +90,12 @@ ifeq ($(PYVERSION), py310)
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/2.3.4/download/osx-64/hpp-fcl-2.3.4-py310h1db6f5f_0.conda
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/3.1.0/download/osx-64/eigenpy-3.1.0-py310h43da829_0.conda
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/osx-64/boost-1.78.0-py310h3e792ce_4.tar.bz2
QHULL_URL:=https://anaconda.org/conda-forge/qhull/2020.2/download/osx-64/qhull-2020.2-h940c156_2.tar.bz2
LXML_URL:=https://files.pythonhosted.org/packages/a1/44/17b7dac7a18807d30e2fe10c3328c152808f5464565e230bfd0e77f178c6/lxml-4.8.0-cp310-cp310-macosx_10_15_x86_64.whl
SHAPELY_URL:=https://files.pythonhosted.org/packages/1f/2a/dc3353c2431cf53e8d04bb8fba27e584410ca3435c9c85f76d71bf0c0e80/shapely-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl
PILLOW_URL:=https://files.pythonhosted.org/packages/d8/60/b13c00d403f34110e96c1b5c0afa73ce461efe3fe960c3a7e3e7fe190d82/Pillow-9.2.0-cp310-cp310-macosx_10_10_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
QHULL_URL:=https://anaconda.org/conda-forge/qhull/2020.2/download/osx-64/qhull-2020.2-h940c156_2.tar.bz2
OCTOMAP_URL:=https://anaconda.org/conda-forge/octomap/1.9.8/download/osx-64/octomap-1.9.8-hb8565cd_0.tar.bz2
BOOSTCPP_URL:=https://anaconda.org/conda-forge/boost-cpp/1.78.0/download/osx-64/boost-cpp-1.78.0-hf5ba120_3.conda
ZLIB_URL:=https://anaconda.org/conda-forge/zlib/1.2.11/download/osx-64/zlib-1.2.11-h7795811_1010.tar.bz2
@@ -109,7 +107,6 @@ ifeq ($(PYVERSION), py39)
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/2.3.4/download/osx-arm64/hpp-fcl-2.3.4-py39hc34188a_0.conda
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/3.1.0/download/osx-arm64/eigenpy-3.1.0-py39h13cfc01_0.conda
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/osx-arm64/boost-1.78.0-py39h99de9ae_4.tar.bz2
QHULL_URL:=https://anaconda.org/conda-forge/qhull/2020.2/download/osx-arm64/qhull-2020.2-hc021e02_2.tar.bz2
LXML_URL:=https://anaconda.org/conda-forge/lxml/4.9.1/download/osx-arm64/lxml-4.9.1-py39h9eb174b_0.tar.bz2
SHAPELY_URL:=https://files.pythonhosted.org/packages/ea/aa/45fbd031edf3149cb767d8b9f9db45d5faf0324d743c6b8fb0298cc022d0/shapely-2.0.1-cp39-cp39-macosx_11_0_arm64.whl
PILLOW_URL:=https://files.pythonhosted.org/packages/aa/bc/21097cd891dd2fa02f2b3d767e02e883e026482e59d29975d1bc30024aa3/Pillow-9.2.0-cp39-cp39-macosx_11_0_arm64.whl
@@ -118,12 +115,12 @@ ifeq ($(PYVERSION), py310)
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/2.3.4/download/osx-arm64/hpp-fcl-2.3.4-py310h46fc4cd_0.conda
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/3.1.0/download/osx-arm64/eigenpy-3.1.0-py310ha2643af_0.conda
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/osx-arm64/boost-1.78.0-py310h629746b_4.tar.bz2
QHULL_URL:=https://anaconda.org/conda-forge/qhull/2020.2/download/osx-arm64/qhull-2020.2-hc021e02_2.tar.bz2
LXML_URL:=https://anaconda.org/conda-forge/lxml/4.9.1/download/osx-arm64/lxml-4.9.1-py310h02f21da_0.tar.bz2
SHAPELY_URL:=https://files.pythonhosted.org/packages/ec/41/d59208743e737184e1b403e95a937aebb022b8459e99efbcd5208fc8be46/shapely-2.0.1-cp310-cp310-macosx_11_0_arm64.whl
PILLOW_URL:=https://files.pythonhosted.org/packages/0c/5f/117b653cad585f3aedfe0de996c292e67d4b020ed77f652e5a6c8c24f908/Pillow-9.2.0-cp310-cp310-macosx_11_0_arm64.whl
endif
ASSIMP_URL:=https://anaconda.org/conda-forge/assimp/5.0.1/download/osx-arm64/assimp-5.0.1-h0f81e16_7.tar.bz2
QHULL_URL:=https://anaconda.org/conda-forge/qhull/2020.2/download/osx-arm64/qhull-2020.2-hc021e02_2.tar.bz2
OCTOMAP_URL:=https://anaconda.org/conda-forge/octomap/1.9.8/download/osx-arm64/octomap-1.9.8-hffc8910_0.tar.bz2
BOOSTCPP_URL:=https://anaconda.org/conda-forge/boost-cpp/1.78.0/download/osx-arm64/boost-cpp-1.78.0-h9ed8d21_3.conda
ZLIB_URL:=https://anaconda.org/conda-forge/zlib/1.2.11/download/osx-arm64/zlib-1.2.11-h90dfc92_1014.tar.bz2
@@ -147,7 +144,8 @@ LXML_URL:=https://files.pythonhosted.org/packages/f6/71/65c80a4caa1617a4c6e8fe15
SHAPELY_URL:=https://files.pythonhosted.org/packages/81/8a/7ac076a86b2632f1872284c5e60ed5f2fc26094875a85b35d9fa17b52504/shapely-2.0.1-cp310-cp310-win_amd64.whl
PILLOW_URL:=https://files.pythonhosted.org/packages/02/55/67a3c17b9e7d972ed8c246f104da99ca4f3ea42fba566697e479011b84b6/Pillow-9.2.0-cp310-cp310-win_amd64.whl
endif
ASSIMP_URL:=https://anaconda.org/conda-forge/assimp/5.0.1/download/win-64/assimp-5.0.1-hc2aa0de_6.tar.bz2
ASSIMP_URL:=https://anaconda.org/conda-forge/assimp/5.2.5/download/win-64/assimp-5.2.5-h4dcb625_0.tar.bz2
QHULL_URL:=https://anaconda.org/conda-forge/qhull/2020.2/download/win-64/qhull-2020.2-h70d2c02_2.tar.bz2
OCTOMAP_URL:=https://anaconda.org/conda-forge/octomap/1.9.8/download/win-64/octomap-1.9.8-h91493d7_0.tar.bz2
BOOSTCPP_URL:=https://anaconda.org/conda-forge/boost-cpp/1.78.0/download/win-64/boost-cpp-1.78.0-h9f4b32c_3.conda
ZLIB_URL:=https://anaconda.org/conda-forge/zlib/1.2.11/download/win-64/zlib-1.2.11-h62dcd97_1010.tar.bz2
@@ -171,7 +169,7 @@ endif
cp -r blenderbim/* dist/blenderbim/
# Provides IfcOpenShell Python functionality
cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-$(PYNUMBER)-v0.7.0-dcc9d0e-$(PLATFORM)64.zip
cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-$(PYNUMBER)-v0.7.0-9cc1f5f-$(PLATFORM)64.zip
cd dist/working && unzip ifcopenshell-python*
cp -r dist/working/ifcopenshell dist/blenderbim/libs/site/packages/
@@ -437,8 +435,7 @@ ifeq ($(PLATFORM), win)
endif
rm -rf dist/working
# Required by hpp-fcl except on Windows
ifneq ($(PLATFORM), win)
# Required by hpp-fcl
mkdir dist/working
cd dist/working && wget $(QHULL_URL)
cd dist/working && tar -xf qhull*
@@ -451,8 +448,10 @@ endif
ifeq ($(PLATFORM), macosm1)
cp -r dist/working/lib/*.dylib dist/blenderbim/libs/
endif
rm -rf dist/working
ifeq ($(PLATFORM), win)
cp -r dist/working/Library/bin/*.dll dist/blenderbim/libs/site/packages/hppfcl/
endif
rm -rf dist/working
# Required by hpp-fcl
mkdir dist/working
@@ -487,7 +486,7 @@ ifeq ($(PLATFORM), macosm1)
cp -r dist/working/lib/*.dylib dist/blenderbim/libs/
endif
ifeq ($(PLATFORM), win)
# Uh, do nothing, apparently? No DLLs are shipped.
cp -r dist/working/Library/bin/*.dll dist/blenderbim/libs/site/packages/hppfcl/
endif
rm -rf dist/working
@@ -44,17 +44,22 @@
<circle r="5" fill="white" stroke="black" style="stroke-width: 0.25;" />
<line x1="-5" y1="0" x2="5" y2="0" style="stroke: black; stroke-width: 0.25;" />
</g>
<g id="SURVEY">
<g id="dot">
<circle r="1" fill="black" stroke="black" style="stroke-width: 0.25;" />
</g>
<g id="SURVEY-CONTROLPOINT">
<path style="fill: white; stroke: black; stroke-width: 0.25;" d="M 2.286165,1.4798666 H -1.0000316e-7 -2.2861651 L -1.1430826,-0.5000101 -1.0000316e-7,-2.479888 1.1430827,-0.5000101 Z" />
<g id="setout-point">
<circle fill="white" stroke="black" r="2.5" style="stroke-width: 0.5"/>
<path d="M 0 0 L -2.5 0 A 2.5 2.5 0 0 1 0 -2.5 Z" fill="black"/>
<path d="M 0 0 L 2.5 0 A 2.5 2.5 0 0 1 0 2.5 Z" fill="black"/>
</g>
<g id="control-point">
<path style="fill: white; stroke: black; stroke-width: 0.5;" d="M 2.286165,1.4798666 H -1.0000316e-7 -2.2861651 L -1.1430826,-0.5000101 -1.0000316e-7,-2.479888 1.1430827,-0.5000101 Z" />
<circle r="0.5" fill="black" stroke="black" style="stroke-width: 0.25;" />
</g>
<g id="SURVEY-TRAVERSEPOINT">
<g id="traverse-point">
<path style="fill: white; stroke: black; stroke-width: 0.5;" d="M -2,-2 2,2 M -2,2 2,-2" />
</g>
<g id="SURVEY-SPOTELEVATION">
<g id="spot-elevation">
<path style="fill: white; stroke: black; stroke-width: 0.5;" d="M 0,-2 0,2 M -2,0 2,0" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

@@ -1,13 +1,13 @@
ISO-10303-21;
HEADER;
FILE_DESCRIPTION(('ViewDefinition[DesignTransferView]'),'2;1');
FILE_NAME('/dev/null','2023-07-30T21:07:33+10:00',(),(),'IfcOpenShell v0.7.0-8f41ae0c1','IfcOpenShell v0.7.0-8f41ae0c1','Nobody');
FILE_NAME('/dev/null','2023-09-02T16:09:02+10:00',(),(),'IfcOpenShell v0.7.0-fbd8ea1ed','IfcOpenShell v0.7.0-fbd8ea1ed','Nobody');
FILE_SCHEMA(('IFC4'));
ENDSEC;
DATA;
#1=IFCPROJECT('0TszJmTqLCyxcP10DtK_MP',$,'BlenderBIM Demo',$,$,$,$,(#12,#16),#7);
#2=IFCPROJECTLIBRARY('0Y_CJXSsbCgBlSGFw0mbLa',$,'BlenderBIM Demo Library',$,$,$,$,$,$);
#3=IFCRELDECLARES('3IxQaOPH5Fx90CGmGm1gtQ',$,$,$,#1,(#2));
#1=IFCPROJECT('1UlLTVwDzDvR$QNEAxFx6F',$,'BlenderBIM Demo',$,$,$,$,(#12,#16),#7);
#2=IFCPROJECTLIBRARY('3QN02XEh1FQh0fii364VEi',$,'BlenderBIM Demo Library',$,$,$,$,$,$);
#3=IFCRELDECLARES('0aiEyU5lL7mAdB5xwdXbUN',$,$,$,#1,(#2));
#4=IFCSIUNIT(*,.LENGTHUNIT.,$,.METRE.);
#5=IFCSIUNIT(*,.AREAUNIT.,$,.SQUARE_METRE.);
#6=IFCSIUNIT(*,.VOLUMEUNIT.,$,.CUBIC_METRE.);
@@ -25,82 +25,82 @@ DATA;
#18=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Body','Plan',*,*,*,*,#16,$,.PLAN_VIEW.,$);
#19=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Annotation','Plan',*,*,*,*,#16,$,.PLAN_VIEW.,$);
#20=IFCMATERIAL('Unknown',$,$);
#21=IFCWALLTYPE('3U0gAUglf9WBlcfpB15EK0',$,'WAL50',$,$,$,$,$,$,.NOTDEFINED.);
#21=IFCWALLTYPE('0Ir6HewafB2hnPRigxrUnM',$,'WAL50',$,$,$,$,$,$,.NOTDEFINED.);
#22=IFCMATERIALLAYERSET((#24),$,$);
#23=IFCRELASSOCIATESMATERIAL('3J36H22Gz1ovVzdiKgApaM',$,$,$,(#21),#22);
#23=IFCRELASSOCIATESMATERIAL('3S$YbWQCnDCA2tmoikSnrh',$,$,$,(#21),#22);
#24=IFCMATERIALLAYER(#20,0.05,$,$,$,$,$);
#25=IFCRELDECLARES('28ApgMJzH10h$Ng$fmCpYf',$,$,$,#2,(#63,#21,#54,#87,#153,#34,#67,#210,#38,#82,#77,#42,#26,#48,#72,#59,#92,#30,#96));
#26=IFCWALLTYPE('1wGfdUdC10TuASJo4jnO99',$,'WAL100',$,$,$,$,$,$,.NOTDEFINED.);
#25=IFCRELDECLARES('1eLCJqssP2kvQIdmPlRK19',$,$,$,#2,(#38,#82,#77,#42,#26,#48,#72,#59,#92,#30,#96,#63,#54,#87,#153,#34,#67,#21,#210));
#26=IFCWALLTYPE('2oUFJayonDlBTcbJtxYFSv',$,'WAL100',$,$,$,$,$,$,.NOTDEFINED.);
#27=IFCMATERIALLAYERSET((#29),$,$);
#28=IFCRELASSOCIATESMATERIAL('0NRoxSQPzCgQHfsu5ru91z',$,$,$,(#26),#27);
#28=IFCRELASSOCIATESMATERIAL('0n3vPKxufDgO48f4yfGtUd',$,$,$,(#26),#27);
#29=IFCMATERIALLAYER(#20,0.1,$,$,$,$,$);
#30=IFCWALLTYPE('1lt5MuGLLDfwmZd0tNruF7',$,'WAL200',$,$,$,$,$,$,.NOTDEFINED.);
#30=IFCWALLTYPE('0UDhWxnIXDMOxZ2yPDCUMP',$,'WAL200',$,$,$,$,$,$,.NOTDEFINED.);
#31=IFCMATERIALLAYERSET((#33),$,$);
#32=IFCRELASSOCIATESMATERIAL('21rGVxPODC$xmWYFaDy1T6',$,$,$,(#30),#31);
#32=IFCRELASSOCIATESMATERIAL('2Boxz3uaP0LOy6luKau2R0',$,$,$,(#30),#31);
#33=IFCMATERIALLAYER(#20,0.2,$,$,$,$,$);
#34=IFCWALLTYPE('0NZm1D8h12xAxyW7fL5nc1',$,'WAL300',$,$,$,$,$,$,.NOTDEFINED.);
#34=IFCWALLTYPE('20fPutWQrFyeMIiKxc$MkU',$,'WAL300',$,$,$,$,$,$,.NOTDEFINED.);
#35=IFCMATERIALLAYERSET((#37),$,$);
#36=IFCRELASSOCIATESMATERIAL('2yvmN42crCJevtirp5cMeV',$,$,$,(#34),#35);
#36=IFCRELASSOCIATESMATERIAL('2qAjlTTxjBkvdp80A9XdMZ',$,$,$,(#34),#35);
#37=IFCMATERIALLAYER(#20,0.3,$,$,$,$,$);
#38=IFCCOVERINGTYPE('1eb4wZ_5n6xRGrs3rlKMiM',$,'COV10',$,$,$,$,$,$,.NOTDEFINED.);
#38=IFCCOVERINGTYPE('100hdtcxzBdQATomAi0tv5',$,'COV10',$,$,$,$,$,$,.NOTDEFINED.);
#39=IFCMATERIALLAYERSET((#41),$,$);
#40=IFCRELASSOCIATESMATERIAL('3HKIKf06T6xRqM6Lp69$OM',$,$,$,(#38),#39);
#40=IFCRELASSOCIATESMATERIAL('3tO1xRn9b06utMa1TZC$WW',$,$,$,(#38),#39);
#41=IFCMATERIALLAYER(#20,0.01,$,$,$,$,$);
#42=IFCCOVERINGTYPE('2E89aYmtrEfxhX_jsNqviN',$,'COV20',$,$,(#46),$,$,$,.NOTDEFINED.);
#42=IFCCOVERINGTYPE('1sO51oul95ABWezHr7P7Dk',$,'COV20',$,$,(#46),$,$,$,.NOTDEFINED.);
#43=IFCMATERIALLAYERSET((#45),$,$);
#44=IFCRELASSOCIATESMATERIAL('2115lEhhP4Z9G1Yhd_DqSC',$,$,$,(#42),#43);
#44=IFCRELASSOCIATESMATERIAL('0w6lexCg19TuiqqTxo91zc',$,$,$,(#42),#43);
#45=IFCMATERIALLAYER(#20,0.02,$,$,$,$,$);
#46=IFCPROPERTYSET('0vtU63YmjAgg8DeHlTgNx7',$,'EPset_Parametric',$,(#47));
#46=IFCPROPERTYSET('3ytgFxH0571uBCG4mBNXI9',$,'EPset_Parametric',$,(#47));
#47=IFCPROPERTYSINGLEVALUE('LayerSetDirection',$,IFCLABEL('AXIS2'),$);
#48=IFCCOVERINGTYPE('0XiSBI57L9Uf8f2NaQ_ksi',$,'COV30',$,$,(#52),$,$,$,.NOTDEFINED.);
#48=IFCCOVERINGTYPE('09GKC1aO56qwEPF9halcxJ',$,'COV30',$,$,(#52),$,$,$,.NOTDEFINED.);
#49=IFCMATERIALLAYERSET((#51),$,$);
#50=IFCRELASSOCIATESMATERIAL('3U3JdGAmbABA4mB$Fgpq1l',$,$,$,(#48),#49);
#50=IFCRELASSOCIATESMATERIAL('0rDrnpiX58DgQhcURj_WlB',$,$,$,(#48),#49);
#51=IFCMATERIALLAYER(#20,0.03,$,$,$,$,$);
#52=IFCPROPERTYSET('17nhQ5I4b9ZvhDu4gRJLKO',$,'EPset_Parametric',$,(#53));
#52=IFCPROPERTYSET('14u6OUyPL5y8O$QkEnB6Rr',$,'EPset_Parametric',$,(#53));
#53=IFCPROPERTYSINGLEVALUE('LayerSetDirection',$,IFCLABEL('AXIS3'),$);
#54=IFCRAMPTYPE('1BTHjVDP19tgb3MDCSkWCJ',$,'RAM200',$,$,$,$,$,$,.NOTDEFINED.);
#54=IFCRAMPTYPE('2G4JrZ99j2B9SkIwWDk0sC',$,'RAM200',$,$,$,$,$,$,.NOTDEFINED.);
#55=IFCMATERIALLAYERSET((#57),$,$);
#56=IFCRELASSOCIATESMATERIAL('3HN21bHtL16vvHA2GnnxH1',$,$,$,(#54),#55);
#56=IFCRELASSOCIATESMATERIAL('2nNlFzNPj0_h6_NwdVJvW$',$,$,$,(#54),#55);
#57=IFCMATERIALLAYER(#20,0.2,$,$,$,$,$);
#58=IFCCIRCLEPROFILEDEF(.AREA.,$,$,0.3);
#59=IFCPILETYPE('04_2WZSqH8TgT7ET6DCCpd',$,'P1',$,$,$,$,$,$,.NOTDEFINED.);
#59=IFCPILETYPE('3uIc5iHjP5Lel3X6KXyN8W',$,'P1',$,$,$,$,$,$,.NOTDEFINED.);
#60=IFCMATERIALPROFILESET($,$,(#62),$);
#61=IFCRELASSOCIATESMATERIAL('1qTZZRXsPF9hmYwT$tgFgp',$,$,$,(#59),#60);
#61=IFCRELASSOCIATESMATERIAL('13buLvs1r5vAyhgbabb2g1',$,$,$,(#59),#60);
#62=IFCMATERIALPROFILE($,$,#20,#58,$,$);
#63=IFCSLABTYPE('3_OV9$Sd52KRhIW2TWIxbW',$,'FLR150',$,$,$,$,$,$,.NOTDEFINED.);
#63=IFCSLABTYPE('2CbahfxvnFpPoHyMBpGTQm',$,'FLR150',$,$,$,$,$,$,.NOTDEFINED.);
#64=IFCMATERIALLAYERSET((#66),$,$);
#65=IFCRELASSOCIATESMATERIAL('1agyf45$X1NhR127pUL0Iw',$,$,$,(#63),#64);
#65=IFCRELASSOCIATESMATERIAL('2Irb7$9xL8YOXW99sF0SOp',$,$,$,(#63),#64);
#66=IFCMATERIALLAYER(#20,0.2,$,$,$,$,$);
#67=IFCSLABTYPE('0LvmpIGf9AVPcG$Kt06TEk',$,'FLR250',$,$,$,$,$,$,.NOTDEFINED.);
#67=IFCSLABTYPE('04dx2Vg4vEp9ZOKGI1ifWb',$,'FLR250',$,$,$,$,$,$,.NOTDEFINED.);
#68=IFCMATERIALLAYERSET((#70),$,$);
#69=IFCRELASSOCIATESMATERIAL('0EKQPSL656secN86SgeutH',$,$,$,(#67),#68);
#69=IFCRELASSOCIATESMATERIAL('04tl3Me6T6xOb$WEvv_dL0',$,$,$,(#67),#68);
#70=IFCMATERIALLAYER(#20,0.3,$,$,$,$,$);
#71=IFCRECTANGLEPROFILEDEF(.AREA.,'500x600',$,0.5,0.6);
#72=IFCCOLUMNTYPE('1SI8m9m813tAXfSnM1fCbm',$,'C1',$,$,$,$,$,$,.NOTDEFINED.);
#72=IFCCOLUMNTYPE('13rk_6wmDApeVQWfrArKbl',$,'C1',$,$,$,$,$,$,.NOTDEFINED.);
#73=IFCMATERIALPROFILESET($,$,(#75),$);
#74=IFCRELASSOCIATESMATERIAL('1EhwSTRtPBQgBt2x6dhuT$',$,$,$,(#72),#73);
#74=IFCRELASSOCIATESMATERIAL('0X3OUScT9E1BjOvJWiUrv3',$,$,$,(#72),#73);
#75=IFCMATERIALPROFILE($,$,#20,#71,$,$);
#76=IFCCIRCLEHOLLOWPROFILEDEF(.AREA.,'500.0x5.0 CHS',$,0.25,0.005);
#77=IFCCOLUMNTYPE('25GZwy2zLFcgi57r5S8iY3',$,'C2',$,$,$,$,$,$,.NOTDEFINED.);
#77=IFCCOLUMNTYPE('0IaS5T1Uv6DQu$BJ5I3bks',$,'C2',$,$,$,$,$,$,.NOTDEFINED.);
#78=IFCMATERIALPROFILESET($,$,(#80),$);
#79=IFCRELASSOCIATESMATERIAL('2VURAOhz93ZQqT9YJUA1DO',$,$,$,(#77),#78);
#79=IFCRELASSOCIATESMATERIAL('1HWcDe7a17WwPeKCHnckid',$,$,$,(#77),#78);
#80=IFCMATERIALPROFILE($,$,#20,#76,$,$);
#81=IFCRECTANGLEHOLLOWPROFILEDEF(.AREA.,'150x75x2.0 RHS',$,0.075,0.15,0.002,0.005,0.005);
#82=IFCCOLUMNTYPE('1Wg$I84s9BTh$iDuZnmSkH',$,'C3',$,$,$,$,$,$,.NOTDEFINED.);
#82=IFCCOLUMNTYPE('13p6$J2Jv84RFI5QAp6FTQ',$,'C3',$,$,$,$,$,$,.NOTDEFINED.);
#83=IFCMATERIALPROFILESET($,$,(#85),$);
#84=IFCRELASSOCIATESMATERIAL('3fIIwEe7DAle7x_zISvMUD',$,$,$,(#82),#83);
#84=IFCRELASSOCIATESMATERIAL('1GWCzHY657$9kUJrD0AEir',$,$,$,(#82),#83);
#85=IFCMATERIALPROFILE($,$,#20,#81,$,$);
#86=IFCISHAPEPROFILEDEF(.AREA.,'DEMO-I',$,0.1,0.2,0.005,0.01,0.005,$,$);
#87=IFCBEAMTYPE('2yg2Irs7L26gFsD_AvvFUu',$,'B1',$,$,$,$,$,$,.NOTDEFINED.);
#87=IFCBEAMTYPE('1Y_PtOyJP9YfZkU59r$vs1',$,'B1',$,$,$,$,$,$,.NOTDEFINED.);
#88=IFCMATERIALPROFILESET($,$,(#90),$);
#89=IFCRELASSOCIATESMATERIAL('1PU_gbkifF9hYpi9DvXTAU',$,$,$,(#87),#88);
#89=IFCRELASSOCIATESMATERIAL('2CfmfKJ0X45xijxsDUHZ85',$,$,$,(#87),#88);
#90=IFCMATERIALPROFILE($,$,#20,#86,$,$);
#91=IFCCSHAPEPROFILEDEF(.AREA.,'DEMO-C',$,0.2,0.1,0.0015,0.03,0.005);
#92=IFCBEAMTYPE('2Nf2L5qpz8K8KhUNFBg_fT',$,'B2',$,$,$,$,$,$,.NOTDEFINED.);
#92=IFCBEAMTYPE('2qogXGNrb9EANilnZjnGjq',$,'B2',$,$,$,$,$,$,.NOTDEFINED.);
#93=IFCMATERIALPROFILESET($,$,(#95),$);
#94=IFCRELASSOCIATESMATERIAL('1SgarGNCzEQQpfs29NMq0y',$,$,$,(#92),#93);
#94=IFCRELASSOCIATESMATERIAL('3$1pGIrVLA2RTOmJIsbelu',$,$,$,(#92),#93);
#95=IFCMATERIALPROFILE($,$,#20,#91,$,$);
#96=IFCWINDOWTYPE('2zRUFP8Ef81xDOR2iJx1zK',$,'WT01',$,$,$,(#135,#152),$,$,.NOTDEFINED.,.NOTDEFINED.,$,$);
#96=IFCWINDOWTYPE('1Dc9pAExL3fxL9jScEo2EF',$,'WT01',$,$,$,(#135,#152),$,$,.NOTDEFINED.,.NOTDEFINED.,$,$);
#97=IFCINDEXEDPOLYGONALFACE((13,17,18,14));
#98=IFCINDEXEDPOLYGONALFACE((5,6,3,4));
#99=IFCINDEXEDPOLYGONALFACE((7,8,2,1));
@@ -157,7 +157,7 @@ DATA;
#150=IFCDIRECTION((0.,0.,1.));
#151=IFCAXIS2PLACEMENT3D(#148,#150,#149);
#152=IFCREPRESENTATIONMAP(#151,#147);
#153=IFCDOORTYPE('0oekOIA$z8lxWiQs9_tFWU',$,'DT01',$,$,$,(#196,#209),$,$,.NOTDEFINED.,.NOTDEFINED.,$,$);
#153=IFCDOORTYPE('05Z1DhJjj11wczPRkvXSPE',$,'DT01',$,$,$,(#196,#209),$,$,.NOTDEFINED.,.NOTDEFINED.,$,$);
#154=IFCINDEXEDPOLYGONALFACE((17,16,15,14));
#155=IFCINDEXEDPOLYGONALFACE((2,3,29,28));
#156=IFCINDEXEDPOLYGONALFACE((27,28,29,30,32,31));
@@ -214,7 +214,7 @@ DATA;
#207=IFCDIRECTION((0.,0.,1.));
#208=IFCAXIS2PLACEMENT3D(#205,#207,#206);
#209=IFCREPRESENTATIONMAP(#208,#204);
#210=IFCFURNITURETYPE('0I2mc$m3T60hLXZYg9Y1wu',$,'BUN01',$,$,$,(#931,#952),$,$,.NOTDEFINED.,.NOTDEFINED.);
#210=IFCFURNITURETYPE('3ZMmSB_rfBnhrBckPTFKvP',$,'BUN01',$,$,$,(#931,#952),$,$,.NOTDEFINED.,.NOTDEFINED.);
#211=IFCINDEXEDPOLYGONALFACE((187,278,44));
#212=IFCINDEXEDPOLYGONALFACE((21,52,60));
#213=IFCINDEXEDPOLYGONALFACE((91,100,31));
@@ -957,131 +957,140 @@ DATA;
#950=IFCDIRECTION((0.,0.,1.));
#951=IFCAXIS2PLACEMENT3D(#948,#950,#949);
#952=IFCREPRESENTATIONMAP(#951,#947);
#953=IFCTYPEPRODUCT('2be705eyD4DPBr6j4K7ZEx',$,'DASHED',$,'IfcAnnotation/LINEWORK',(#954),$,$);
#954=IFCPROPERTYSET('0uEfutIxDDIxMIBUJOZpSM',$,'EPset_Annotation',$,(#955));
#955=IFCPROPERTYSINGLEVALUE('Classes',$,IFCLABEL('dashed'),$);
#956=IFCTYPEPRODUCT('1M7mm9wUHFoxdM0MdAMyqM',$,'FINE',$,'IfcAnnotation/LINEWORK',(#957),$,$);
#957=IFCPROPERTYSET('3AZ8m1Bdn1gfbvagzKr2c3',$,'EPset_Annotation',$,(#958));
#958=IFCPROPERTYSINGLEVALUE('Classes',$,IFCLABEL('fine'),$);
#959=IFCTYPEPRODUCT('3LdrNIFOvAkBzrNnqWAZfA',$,'THIN',$,'IfcAnnotation/LINEWORK',(#960),$,$);
#960=IFCPROPERTYSET('0$9iwgSID7xBul4Tb9r2du',$,'EPset_Annotation',$,(#961));
#961=IFCPROPERTYSINGLEVALUE('Classes',$,IFCLABEL('thin'),$);
#962=IFCTYPEPRODUCT('3fFNO0t1TB$wYRaIkKwUCH',$,'MEDIUM',$,'IfcAnnotation/LINEWORK',(#963),$,$);
#963=IFCPROPERTYSET('33Fm$9zgb1cgsCOy859RlR',$,'EPset_Annotation',$,(#964));
#964=IFCPROPERTYSINGLEVALUE('Classes',$,IFCLABEL('medium'),$);
#965=IFCTYPEPRODUCT('1CpMBhemj5VQC43bU9J7zG',$,'THICK',$,'IfcAnnotation/LINEWORK',(#966),$,$);
#966=IFCPROPERTYSET('1OLDsFSDz2TPz5ifkQvWo8',$,'EPset_Annotation',$,(#967));
#967=IFCPROPERTYSINGLEVALUE('Classes',$,IFCLABEL('thick'),$);
#968=IFCTYPEPRODUCT('0NITtya3b16hoA3sh17SL4',$,'STRONG',$,'IfcAnnotation/LINEWORK',(#969),$,$);
#969=IFCPROPERTYSET('1_kfqWVDn0puI29Ah6eitX',$,'EPset_Annotation',$,(#970));
#970=IFCPROPERTYSINGLEVALUE('Classes',$,IFCLABEL('strong'),$);
#971=IFCTYPEPRODUCT('0rRN9EsvL5AQ3PorxrZSKM',$,'DOOR-TAG',$,'IfcAnnotation/TEXT',(#972),(#991),$);
#972=IFCPROPERTYSET('3lRZ$d2VH8cxZYh$mm45iq',$,'EPset_Annotation',$,(#973));
#973=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('door-tag'),$);
#974=IFCCARTESIANPOINT((0.,0.,0.));
#975=IFCDIRECTION((0.,0.,1.));
#976=IFCDIRECTION((1.,0.,0.));
#977=IFCAXIS2PLACEMENT3D(#974,#975,#976);
#978=IFCPLANAREXTENT(1000.,1000.);
#979=IFCTEXTLITERALWITHEXTENT('{{type.Name}}',#977,.RIGHT.,#978,'center');
#980=IFCCARTESIANPOINT((0.,0.,0.));
#981=IFCDIRECTION((0.,0.,1.));
#982=IFCDIRECTION((1.,0.,0.));
#983=IFCAXIS2PLACEMENT3D(#980,#981,#982);
#984=IFCPLANAREXTENT(1000.,1000.);
#985=IFCTEXTLITERALWITHEXTENT('{{Name}}',#983,.RIGHT.,#984,'center');
#986=IFCSHAPEREPRESENTATION(#19,'Annotation','Annotation2D',(#979,#985));
#987=IFCCARTESIANPOINT((0.,0.,0.));
#988=IFCDIRECTION((1.,0.,0.));
#989=IFCDIRECTION((0.,0.,1.));
#990=IFCAXIS2PLACEMENT3D(#987,#989,#988);
#991=IFCREPRESENTATIONMAP(#990,#986);
#992=IFCTYPEPRODUCT('0bFZUWHjbA2e03hRHNinTt',$,'WINDOW-TAG',$,'IfcAnnotation/TEXT',(#993),(#1006),$);
#993=IFCPROPERTYSET('3TJOWv5NDBXBNCshYFjsFV',$,'EPset_Annotation',$,(#994));
#994=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('window-tag'),$);
#995=IFCCARTESIANPOINT((0.,0.,0.));
#996=IFCDIRECTION((0.,0.,1.));
#953=IFCTYPEPRODUCT('1hmWolVQb9buX2GhHwaVFB',$,'SETOUT-POINT',$,'IfcAnnotation/SYMBOL',(#954),$,$);
#954=IFCPROPERTYSET('2YNCFr62b8vhuhHGspQl4$',$,'EPset_Annotation',$,(#955));
#955=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('setout-point'),$);
#956=IFCTYPEPRODUCT('3H4oE1a_9AwQE8OFsCjdfh',$,'CONTROL-POINT',$,'IfcAnnotation/SYMBOL',(#957),$,$);
#957=IFCPROPERTYSET('0ARWSOH$z3kQDRkpEPHWCt',$,'EPset_Annotation',$,(#958));
#958=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('control-point'),$);
#959=IFCTYPEPRODUCT('3YDPnyrWn2wRWgRzb0V7Kx',$,'TRAVERSE-POINT',$,'IfcAnnotation/SYMBOL',(#960),$,$);
#960=IFCPROPERTYSET('1v$MzHU1b82hDqHdtz4e5y',$,'EPset_Annotation',$,(#961));
#961=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('traverse-point'),$);
#962=IFCTYPEPRODUCT('31Em8$VNL0Mh$9EZ3pXjpL',$,'DASHED',$,'IfcAnnotation/LINEWORK',(#963),$,$);
#963=IFCPROPERTYSET('0K84$piRb31PmkAwZfP5kd',$,'EPset_Annotation',$,(#964));
#964=IFCPROPERTYSINGLEVALUE('Classes',$,IFCLABEL('dashed'),$);
#965=IFCTYPEPRODUCT('3P0K0gDxP4NgwnuLGqlyW6',$,'FINE',$,'IfcAnnotation/LINEWORK',(#966),$,$);
#966=IFCPROPERTYSET('179vwKcVv9uet4_kI4NT9G',$,'EPset_Annotation',$,(#967));
#967=IFCPROPERTYSINGLEVALUE('Classes',$,IFCLABEL('fine'),$);
#968=IFCTYPEPRODUCT('0Rh30iszn41xBCgQJl2w2c',$,'THIN',$,'IfcAnnotation/LINEWORK',(#969),$,$);
#969=IFCPROPERTYSET('0bZV_Tby121uj5O3n08l1l',$,'EPset_Annotation',$,(#970));
#970=IFCPROPERTYSINGLEVALUE('Classes',$,IFCLABEL('thin'),$);
#971=IFCTYPEPRODUCT('3ti_6xztr7qPp9dASz9vP4',$,'MEDIUM',$,'IfcAnnotation/LINEWORK',(#972),$,$);
#972=IFCPROPERTYSET('19ZhF3bxL07evL8gi27Kei',$,'EPset_Annotation',$,(#973));
#973=IFCPROPERTYSINGLEVALUE('Classes',$,IFCLABEL('medium'),$);
#974=IFCTYPEPRODUCT('2hlcxjzxv8zf0gh1md23xY',$,'THICK',$,'IfcAnnotation/LINEWORK',(#975),$,$);
#975=IFCPROPERTYSET('0wRtpbInzE2AkqX8RIreQr',$,'EPset_Annotation',$,(#976));
#976=IFCPROPERTYSINGLEVALUE('Classes',$,IFCLABEL('thick'),$);
#977=IFCTYPEPRODUCT('1pefU$PA1ENw7kZG9T3mjr',$,'STRONG',$,'IfcAnnotation/LINEWORK',(#978),$,$);
#978=IFCPROPERTYSET('1RpJZBysn8shqevvAquPQ8',$,'EPset_Annotation',$,(#979));
#979=IFCPROPERTYSINGLEVALUE('Classes',$,IFCLABEL('strong'),$);
#980=IFCTYPEPRODUCT('1HKC8zCIv5_ef3dEfa$L5N',$,'DOOR-TAG',$,'IfcAnnotation/TEXT',(#981),(#1000),$);
#981=IFCPROPERTYSET('0aC8JrqFX6HhKUhjdLw4mN',$,'EPset_Annotation',$,(#982));
#982=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('door-tag'),$);
#983=IFCCARTESIANPOINT((0.,0.,0.));
#984=IFCDIRECTION((0.,0.,1.));
#985=IFCDIRECTION((1.,0.,0.));
#986=IFCAXIS2PLACEMENT3D(#983,#984,#985);
#987=IFCPLANAREXTENT(1000.,1000.);
#988=IFCTEXTLITERALWITHEXTENT('{{type.Name}}',#986,.RIGHT.,#987,'center');
#989=IFCCARTESIANPOINT((0.,0.,0.));
#990=IFCDIRECTION((0.,0.,1.));
#991=IFCDIRECTION((1.,0.,0.));
#992=IFCAXIS2PLACEMENT3D(#989,#990,#991);
#993=IFCPLANAREXTENT(1000.,1000.);
#994=IFCTEXTLITERALWITHEXTENT('{{Name}}',#992,.RIGHT.,#993,'center');
#995=IFCSHAPEREPRESENTATION(#19,'Annotation','Annotation2D',(#988,#994));
#996=IFCCARTESIANPOINT((0.,0.,0.));
#997=IFCDIRECTION((1.,0.,0.));
#998=IFCAXIS2PLACEMENT3D(#995,#996,#997);
#999=IFCPLANAREXTENT(1000.,1000.);
#1000=IFCTEXTLITERALWITHEXTENT('{{Name}}',#998,.RIGHT.,#999,'center');
#1001=IFCSHAPEREPRESENTATION(#19,'Annotation','Annotation2D',(#1000));
#1002=IFCCARTESIANPOINT((0.,0.,0.));
#1003=IFCDIRECTION((1.,0.,0.));
#1004=IFCDIRECTION((0.,0.,1.));
#1005=IFCAXIS2PLACEMENT3D(#1002,#1004,#1003);
#1006=IFCREPRESENTATIONMAP(#1005,#1001);
#1007=IFCTYPEPRODUCT('2DlmDdasX05un_uMP4O23U',$,'SPACE-TAG',$,'IfcAnnotation/TEXT',(#1008),(#1033),$);
#1008=IFCPROPERTYSET('1$OAHURHr3GON7ZzAJnv72',$,'EPset_Annotation',$,(#1009));
#1009=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('space-tag'),$);
#1010=IFCCARTESIANPOINT((0.,0.,0.));
#1011=IFCDIRECTION((0.,0.,1.));
#998=IFCDIRECTION((0.,0.,1.));
#999=IFCAXIS2PLACEMENT3D(#996,#998,#997);
#1000=IFCREPRESENTATIONMAP(#999,#995);
#1001=IFCTYPEPRODUCT('2Jy_wl$FX8h9mgYT1$Y9oc',$,'WINDOW-TAG',$,'IfcAnnotation/TEXT',(#1002),(#1015),$);
#1002=IFCPROPERTYSET('2ncCyO$LPCzv7kqXZK_Omh',$,'EPset_Annotation',$,(#1003));
#1003=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('window-tag'),$);
#1004=IFCCARTESIANPOINT((0.,0.,0.));
#1005=IFCDIRECTION((0.,0.,1.));
#1006=IFCDIRECTION((1.,0.,0.));
#1007=IFCAXIS2PLACEMENT3D(#1004,#1005,#1006);
#1008=IFCPLANAREXTENT(1000.,1000.);
#1009=IFCTEXTLITERALWITHEXTENT('{{Name}}',#1007,.RIGHT.,#1008,'center');
#1010=IFCSHAPEREPRESENTATION(#19,'Annotation','Annotation2D',(#1009));
#1011=IFCCARTESIANPOINT((0.,0.,0.));
#1012=IFCDIRECTION((1.,0.,0.));
#1013=IFCAXIS2PLACEMENT3D(#1010,#1011,#1012);
#1014=IFCPLANAREXTENT(1000.,1000.);
#1015=IFCTEXTLITERALWITHEXTENT('{{Name}}',#1013,.RIGHT.,#1014,'center');
#1016=IFCCARTESIANPOINT((0.,0.,0.));
#1017=IFCDIRECTION((0.,0.,1.));
#1018=IFCDIRECTION((1.,0.,0.));
#1019=IFCAXIS2PLACEMENT3D(#1016,#1017,#1018);
#1020=IFCPLANAREXTENT(1000.,1000.);
#1021=IFCTEXTLITERALWITHEXTENT('{{Description}}',#1019,.RIGHT.,#1020,'center');
#1022=IFCCARTESIANPOINT((0.,0.,0.));
#1023=IFCDIRECTION((0.,0.,1.));
#1024=IFCDIRECTION((1.,0.,0.));
#1025=IFCAXIS2PLACEMENT3D(#1022,#1023,#1024);
#1026=IFCPLANAREXTENT(1000.,1000.);
#1027=IFCTEXTLITERALWITHEXTENT('``round({{Qto_SpaceBaseQuantities.NetFloorArea}} or 0., 2)``',#1025,.RIGHT.,#1026,'center');
#1028=IFCSHAPEREPRESENTATION(#19,'Annotation','Annotation2D',(#1015,#1021,#1027));
#1029=IFCCARTESIANPOINT((0.,0.,0.));
#1030=IFCDIRECTION((1.,0.,0.));
#1031=IFCDIRECTION((0.,0.,1.));
#1032=IFCAXIS2PLACEMENT3D(#1029,#1031,#1030);
#1033=IFCREPRESENTATIONMAP(#1032,#1028);
#1034=IFCTYPEPRODUCT('2vNx3CIlrFs8bLFlmGvJF3',$,'MATERIAL-TAG',$,'IfcAnnotation/TEXT',(#1035),(#1048),$);
#1035=IFCPROPERTYSET('1l$T4lUqnBb93zcgfbSPvx',$,'EPset_Annotation',$,(#1036));
#1036=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('rectangle-tag'),$);
#1037=IFCCARTESIANPOINT((0.,0.,0.));
#1038=IFCDIRECTION((0.,0.,1.));
#1013=IFCDIRECTION((0.,0.,1.));
#1014=IFCAXIS2PLACEMENT3D(#1011,#1013,#1012);
#1015=IFCREPRESENTATIONMAP(#1014,#1010);
#1016=IFCTYPEPRODUCT('1LTLrGzxH4qhuWGNeTlTRi',$,'SPACE-TAG',$,'IfcAnnotation/TEXT',(#1017),(#1042),$);
#1017=IFCPROPERTYSET('2JpTIsSZf3zQUWK48RLIcL',$,'EPset_Annotation',$,(#1018));
#1018=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('space-tag'),$);
#1019=IFCCARTESIANPOINT((0.,0.,0.));
#1020=IFCDIRECTION((0.,0.,1.));
#1021=IFCDIRECTION((1.,0.,0.));
#1022=IFCAXIS2PLACEMENT3D(#1019,#1020,#1021);
#1023=IFCPLANAREXTENT(1000.,1000.);
#1024=IFCTEXTLITERALWITHEXTENT('{{Name}}',#1022,.RIGHT.,#1023,'center');
#1025=IFCCARTESIANPOINT((0.,0.,0.));
#1026=IFCDIRECTION((0.,0.,1.));
#1027=IFCDIRECTION((1.,0.,0.));
#1028=IFCAXIS2PLACEMENT3D(#1025,#1026,#1027);
#1029=IFCPLANAREXTENT(1000.,1000.);
#1030=IFCTEXTLITERALWITHEXTENT('{{Description}}',#1028,.RIGHT.,#1029,'center');
#1031=IFCCARTESIANPOINT((0.,0.,0.));
#1032=IFCDIRECTION((0.,0.,1.));
#1033=IFCDIRECTION((1.,0.,0.));
#1034=IFCAXIS2PLACEMENT3D(#1031,#1032,#1033);
#1035=IFCPLANAREXTENT(1000.,1000.);
#1036=IFCTEXTLITERALWITHEXTENT('``round({{Qto_SpaceBaseQuantities.NetFloorArea}} or 0., 2)``',#1034,.RIGHT.,#1035,'center');
#1037=IFCSHAPEREPRESENTATION(#19,'Annotation','Annotation2D',(#1024,#1030,#1036));
#1038=IFCCARTESIANPOINT((0.,0.,0.));
#1039=IFCDIRECTION((1.,0.,0.));
#1040=IFCAXIS2PLACEMENT3D(#1037,#1038,#1039);
#1041=IFCPLANAREXTENT(1000.,1000.);
#1042=IFCTEXTLITERALWITHEXTENT('{{material.Name}}',#1040,.RIGHT.,#1041,'center');
#1043=IFCSHAPEREPRESENTATION(#19,'Annotation','Annotation2D',(#1042));
#1044=IFCCARTESIANPOINT((0.,0.,0.));
#1045=IFCDIRECTION((1.,0.,0.));
#1046=IFCDIRECTION((0.,0.,1.));
#1047=IFCAXIS2PLACEMENT3D(#1044,#1046,#1045);
#1048=IFCREPRESENTATIONMAP(#1047,#1043);
#1049=IFCTYPEPRODUCT('3hqzFtbSb56RjhAZNaB4Vh',$,'TYPE-TAG',$,'IfcAnnotation/TEXT',(#1050),(#1063),$);
#1050=IFCPROPERTYSET('3U7kaKt_nBUvD$U2ryif_H',$,'EPset_Annotation',$,(#1051));
#1051=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('capsule-tag'),$);
#1052=IFCCARTESIANPOINT((0.,0.,0.));
#1053=IFCDIRECTION((0.,0.,1.));
#1040=IFCDIRECTION((0.,0.,1.));
#1041=IFCAXIS2PLACEMENT3D(#1038,#1040,#1039);
#1042=IFCREPRESENTATIONMAP(#1041,#1037);
#1043=IFCTYPEPRODUCT('3Or$DhaVf6ygGBUpRb2PMs',$,'MATERIAL-TAG',$,'IfcAnnotation/TEXT',(#1044),(#1057),$);
#1044=IFCPROPERTYSET('2YfvIc6dPDdwWjERHZZlof',$,'EPset_Annotation',$,(#1045));
#1045=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('rectangle-tag'),$);
#1046=IFCCARTESIANPOINT((0.,0.,0.));
#1047=IFCDIRECTION((0.,0.,1.));
#1048=IFCDIRECTION((1.,0.,0.));
#1049=IFCAXIS2PLACEMENT3D(#1046,#1047,#1048);
#1050=IFCPLANAREXTENT(1000.,1000.);
#1051=IFCTEXTLITERALWITHEXTENT('{{material.Name}}',#1049,.RIGHT.,#1050,'center');
#1052=IFCSHAPEREPRESENTATION(#19,'Annotation','Annotation2D',(#1051));
#1053=IFCCARTESIANPOINT((0.,0.,0.));
#1054=IFCDIRECTION((1.,0.,0.));
#1055=IFCAXIS2PLACEMENT3D(#1052,#1053,#1054);
#1056=IFCPLANAREXTENT(1000.,1000.);
#1057=IFCTEXTLITERALWITHEXTENT('{{type.Name}}',#1055,.RIGHT.,#1056,'center');
#1058=IFCSHAPEREPRESENTATION(#19,'Annotation','Annotation2D',(#1057));
#1059=IFCCARTESIANPOINT((0.,0.,0.));
#1060=IFCDIRECTION((1.,0.,0.));
#1061=IFCDIRECTION((0.,0.,1.));
#1062=IFCAXIS2PLACEMENT3D(#1059,#1061,#1060);
#1063=IFCREPRESENTATIONMAP(#1062,#1058);
#1064=IFCTYPEPRODUCT('2SnSxJPiD9sh$NDx92OCSS',$,'NAME-TAG',$,'IfcAnnotation/TEXT',(#1065),(#1078),$);
#1065=IFCPROPERTYSET('3Lu86QVuv5nfjkuPLvdQSH',$,'EPset_Annotation',$,(#1066));
#1066=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('capsule-tag'),$);
#1067=IFCCARTESIANPOINT((0.,0.,0.));
#1068=IFCDIRECTION((0.,0.,1.));
#1055=IFCDIRECTION((0.,0.,1.));
#1056=IFCAXIS2PLACEMENT3D(#1053,#1055,#1054);
#1057=IFCREPRESENTATIONMAP(#1056,#1052);
#1058=IFCTYPEPRODUCT('01Hhk_DbjDROhyr91Hp$aV',$,'TYPE-TAG',$,'IfcAnnotation/TEXT',(#1059),(#1072),$);
#1059=IFCPROPERTYSET('0LyRD7lHr17uJo7XNzCvZq',$,'EPset_Annotation',$,(#1060));
#1060=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('capsule-tag'),$);
#1061=IFCCARTESIANPOINT((0.,0.,0.));
#1062=IFCDIRECTION((0.,0.,1.));
#1063=IFCDIRECTION((1.,0.,0.));
#1064=IFCAXIS2PLACEMENT3D(#1061,#1062,#1063);
#1065=IFCPLANAREXTENT(1000.,1000.);
#1066=IFCTEXTLITERALWITHEXTENT('{{type.Name}}',#1064,.RIGHT.,#1065,'center');
#1067=IFCSHAPEREPRESENTATION(#19,'Annotation','Annotation2D',(#1066));
#1068=IFCCARTESIANPOINT((0.,0.,0.));
#1069=IFCDIRECTION((1.,0.,0.));
#1070=IFCAXIS2PLACEMENT3D(#1067,#1068,#1069);
#1071=IFCPLANAREXTENT(1000.,1000.);
#1072=IFCTEXTLITERALWITHEXTENT('{{Name}}',#1070,.RIGHT.,#1071,'center');
#1073=IFCSHAPEREPRESENTATION(#19,'Annotation','Annotation2D',(#1072));
#1074=IFCCARTESIANPOINT((0.,0.,0.));
#1075=IFCDIRECTION((1.,0.,0.));
#1076=IFCDIRECTION((0.,0.,1.));
#1077=IFCAXIS2PLACEMENT3D(#1074,#1076,#1075);
#1078=IFCREPRESENTATIONMAP(#1077,#1073);
#1070=IFCDIRECTION((0.,0.,1.));
#1071=IFCAXIS2PLACEMENT3D(#1068,#1070,#1069);
#1072=IFCREPRESENTATIONMAP(#1071,#1067);
#1073=IFCTYPEPRODUCT('0a8MHfTUzA5AV8H6p1bqC9',$,'NAME-TAG',$,'IfcAnnotation/TEXT',(#1074),(#1087),$);
#1074=IFCPROPERTYSET('2EboL9u1j68Q_Mp8dBa7ux',$,'EPset_Annotation',$,(#1075));
#1075=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('capsule-tag'),$);
#1076=IFCCARTESIANPOINT((0.,0.,0.));
#1077=IFCDIRECTION((0.,0.,1.));
#1078=IFCDIRECTION((1.,0.,0.));
#1079=IFCAXIS2PLACEMENT3D(#1076,#1077,#1078);
#1080=IFCPLANAREXTENT(1000.,1000.);
#1081=IFCTEXTLITERALWITHEXTENT('{{Name}}',#1079,.RIGHT.,#1080,'center');
#1082=IFCSHAPEREPRESENTATION(#19,'Annotation','Annotation2D',(#1081));
#1083=IFCCARTESIANPOINT((0.,0.,0.));
#1084=IFCDIRECTION((1.,0.,0.));
#1085=IFCDIRECTION((0.,0.,1.));
#1086=IFCAXIS2PLACEMENT3D(#1083,#1085,#1084);
#1087=IFCREPRESENTATIONMAP(#1086,#1082);
ENDSEC;
END-ISO-10303-21;
@@ -93,6 +93,8 @@ class IfcExporter:
try:
if isinstance(obj, bpy.types.Material):
continue
if obj.library:
continue
tool.Collector.sync(obj)
result = self.sync_object_placement(obj)
if result:
+12 -2
View File
@@ -574,7 +574,13 @@ class IfcImporter:
return
if not self.does_element_likely_have_geometry_far_away(element):
continue
shape = ifcopenshell.geom.create_shape(self.settings, element)
try:
shape = ifcopenshell.geom.create_shape(self.settings, element)
except:
try:
shape = ifcopenshell.geom.create_shape(self.settings_body_2d, element)
except:
continue
m = shape.transformation.matrix.data
mat = np.array(
([m[0], m[3], m[6], m[9]], [m[1], m[4], m[7], m[10]], [m[2], m[5], m[8], m[11]], [0, 0, 0, 1])
@@ -974,10 +980,14 @@ class IfcImporter:
placement_matrix = self.get_element_matrix(product)
vertex_list = []
for item in representation.Items:
if item.is_a("IfcCartesianPointList"):
if item.is_a("IfcCartesianPointList3D"):
vertex_list.extend(
mathutils.Vector(list(coordinates)) * self.unit_scale for coordinates in item.CoordList
)
elif item.is_a("IfcCartesianPointList2D"):
vertex_list.extend(
mathutils.Vector(list(coordinates)).to_3d() * self.unit_scale for coordinates in item.CoordList
)
elif item.is_a("IfcCartesianPoint"):
vertex_list.append(mathutils.Vector(list(item.Coordinates)) * self.unit_scale)
@@ -101,6 +101,7 @@ class ImportCsvAttributes(bpy.types.Operator):
new.sort = attribute["sort"]
new.group = attribute["group"]
new.summary = attribute["summary"]
new.formatting = attribute["formatting"]
return {"FINISHED"}
def invoke(self, context, event):
@@ -123,7 +124,14 @@ class ExportCsvAttributes(bpy.types.Operator):
data = {
"query": tool.Search.export_filter_query(props.filter_groups),
"attributes": [
{"name": a.name, "header": a.header, "sort": a.sort, "group": a.group, "summary": a.summary}
{
"name": a.name,
"header": a.header,
"sort": a.sort,
"group": a.group,
"summary": a.summary,
"formatting": a.formatting,
}
for a in props.csv_attributes
],
}
@@ -173,6 +181,7 @@ class ExportIfcCsv(bpy.types.Operator):
sort = []
groups = []
summaries = []
formatting = []
for attribute in props.csv_attributes:
if attribute.sort != "NONE":
sort.append({"name": attribute.name, "order": attribute.sort})
@@ -181,6 +190,9 @@ class ExportIfcCsv(bpy.types.Operator):
if attribute.summary != "NONE":
summaries.append({"name": attribute.name, "type": attribute.summary})
if attribute.formatting != "{{value}}" and "{{value}}" in attribute.formatting:
formatting.append({"name": attribute.name, "format": attribute.formatting})
sep = props.csv_custom_delimiter if props.csv_delimiter == "CUSTOM" else props.csv_delimiter
ifc_csv.export(
ifc_file,
@@ -198,6 +210,7 @@ class ExportIfcCsv(bpy.types.Operator):
sort=sort,
groups=groups,
summaries=summaries,
formatting=formatting,
)
return {"FINISHED"}
@@ -58,6 +58,7 @@ class CsvAttribute(PropertyGroup):
("MAX", "Max", "Gets the maximum value of all rows"),
]
)
formatting: StringProperty(default="{{value}}", name="Formatting")
class CsvProperties(PropertyGroup):
@@ -94,4 +95,5 @@ class CsvProperties(PropertyGroup):
should_show_sort: BoolProperty(default=False, name="Show Sorting")
should_show_group: BoolProperty(default=False, name="Show Grouping")
should_show_summary: BoolProperty(default=False, name="Show Summary")
should_show_formatting: BoolProperty(default=False, name="Show Formatting")
should_load_from_memory: BoolProperty(default=False, name="Load from Memory")
@@ -23,7 +23,7 @@ from blenderbim.bim.module.search.data import SearchData
class BIM_PT_ifccsv(Panel):
bl_label = "CSV Import/Export"
bl_label = "Spreadsheet Import/Export"
bl_idname = "BIM_PT_ifccsv"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
@@ -86,6 +86,7 @@ class BIM_PT_ifccsv(Panel):
row.prop(props, "should_show_sort", icon="SORTSIZE", text="")
row.prop(props, "should_show_group", icon="OUTLINER_COLLECTION", text="")
row.prop(props, "should_show_summary", icon="SYNTAX_ON", text="")
row.prop(props, "should_show_formatting", icon="CON_TRANSLIKE", text="")
total = len(props.csv_attributes)
for index, attribute in enumerate(props.csv_attributes):
@@ -100,6 +101,8 @@ class BIM_PT_ifccsv(Panel):
row.prop(attribute, "varies_value", text="")
if props.should_show_summary:
row.prop(attribute, "summary", text="")
if props.should_show_formatting:
row.prop(attribute, "formatting", text="")
if total > 1:
if index != 0:
op = row.operator(f"bim.reorder_csv_attribute", icon="TRIA_UP", text="")
@@ -16,11 +16,13 @@
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
import os
import blenderbim.tool as tool
from mathutils import Vector
import bpy
import math
import bmesh
import blenderbim.tool as tool
import ifcopenshell.util.element
from mathutils import Vector, Matrix
class Annotator:
@@ -90,6 +92,15 @@ class Annotator:
return obj
@staticmethod
def add_vertex_to_annotation(obj):
verts_world_space = Annotator.get_placeholder_coords()
vert_local = obj.matrix_world.inverted() @ verts_world_space[0]
bm = tool.Blender.get_bmesh_for_mesh(obj.data, clean=True)
bm.verts.new(vert_local)
tool.Blender.apply_bmesh(obj.data, bm, obj)
return obj
@staticmethod
def add_plane_to_annotation(obj, remove_face=False):
# default order = bot left, top left, bot right, top right
@@ -111,7 +122,7 @@ class Annotator:
def get_annotation_obj(drawing, object_type, data_type):
camera = tool.Ifc.get_object(drawing)
co1, _, _, _ = Annotator.get_placeholder_coords(camera)
matrix_world = camera.matrix_world.copy()
matrix_world = tool.Drawing.get_camera_matrix(camera)
matrix_world.translation = co1
collection = camera.BIMObjectProperties.collection
@@ -142,6 +153,8 @@ class Annotator:
data = bpy.data.curves.new(object_type, type="CURVE")
data.dimensions = "3D"
data.resolution_u = 2
elif data_type == "empty":
data = None
obj = bpy.data.objects.new(object_type, data)
obj.matrix_world = matrix_world
@@ -154,6 +167,13 @@ class Annotator:
camera = bpy.context.scene.camera
z_offset = camera.matrix_world.to_quaternion() @ Vector((0, 0, -1))
if (
ifcopenshell.util.element.get_pset(tool.Ifc.get_entity(camera), "EPset_Drawing", "TargetView")
== "REFLECTED_PLAN_VIEW"
):
z_offset *= -1
y = camera.data.ortho_scale / 4
res_x = bpy.context.scene.render.resolution_x
@@ -520,7 +520,9 @@ class BaseDecorator:
return matrix.inverted()[i].to_3d().normalized()
text_dir_world_x_axis = get_basis_vector(obj.matrix_world)
camera_matrix = camera.matrix_world.normalized()
camera_matrix = tool.Drawing.get_camera_matrix(camera)
text_dir = (camera_matrix.inverted().to_quaternion() @ text_dir_world_x_axis).to_2d().normalized()
pos = location_3d_to_region_2d(region, region3d, text_world_position)
@@ -70,15 +70,6 @@ class profile:
print(self.task, timer() - self.start)
def open_with_user_command(user_command, path):
if user_command:
commands = eval(user_command)
for command in commands:
subprocess.Popen(command)
else:
webbrowser.open("file://" + path)
class Operator:
def execute(self, context):
IfcStore.execute_ifc_operator(self, context)
@@ -269,7 +260,7 @@ class CreateDrawing(bpy.types.Operator):
with profile("Combine SVG layers"):
svg_path = self.combine_svgs(context, underlay_svg, linework_svg, annotation_svg)
open_with_user_command(context.preferences.addons["blenderbim"].preferences.svg_command, svg_path)
tool.Drawing.open_with_user_command(context.preferences.addons["blenderbim"].preferences.svg_command, svg_path)
if self.print_all:
bpy.ops.bim.activate_drawing(drawing=original_drawing_id, camera_view_point=False)
@@ -555,7 +546,7 @@ class CreateDrawing(bpy.types.Operator):
if self.camera.data.BIMCameraProperties.calculate_shapely_surfaces:
# shapely variant
group = root.findall(".//{http://www.w3.org/2000/svg}g")[0]
group = root.find("{http://www.w3.org/2000/svg}g")
nm = group.attrib["{http://www.ifcopenshell.org/ns}name"]
m4 = np.array(json.loads(group.attrib["{http://www.ifcopenshell.org/ns}plane"]))
m3 = np.array(json.loads(group.attrib["{http://www.ifcopenshell.org/ns}matrix3"]))
@@ -566,10 +557,21 @@ class CreateDrawing(bpy.types.Operator):
m44[1][3] = m3[1][2]
m44 = np.linalg.inv(m44)
projections = group.findall('.//{http://www.w3.org/2000/svg}g[@class="projection"]') or []
elements_with_faces = set()
for element in drawing_elements.copy():
obj = tool.Ifc.get_object(element)
if obj and obj.type == "MESH" and len(obj.data.polygons):
elements_with_faces.add(element.GlobalId)
projections = root.xpath(
".//svg:g[contains(@class, 'projection')]", namespaces={"svg": "http://www.w3.org/2000/svg"}
)
boundary_lines = []
for projection in projections:
boundary_lines = []
global_id = projection.attrib["{http://www.ifcopenshell.org/ns}guid"]
if global_id not in elements_with_faces:
continue
for path in projection.findall("./{http://www.w3.org/2000/svg}path"):
# Rounding is necessary to ensure coincident points are coincident
start, end = [[round(float(o), 1) for o in co[1:].split(",")] for co in path.attrib["d"].split()]
@@ -578,50 +580,55 @@ class CreateDrawing(bpy.types.Operator):
# Extension by 0.5mm is necessary to ensure lines overlap with other diagonal lines
start, end = tool.Drawing.extend_line(start, end, 0.5)
boundary_lines.append(shapely.LineString([start, end]))
unioned_boundaries = shapely.union_all(shapely.GeometryCollection(boundary_lines))
closed_polygons = shapely.polygonize(unioned_boundaries.geoms)
for polygon in closed_polygons.geoms:
# Less than 1mm2 is not worth styling on sheet
if polygon.area < 1:
continue
centroid = polygon.centroid
internal_point = centroid if polygon.contains(centroid) else polygon.representative_point()
if internal_point:
internal_point = [internal_point.x, internal_point.y]
a, b = self.drawing_to_model_co(m44, m4, internal_point, 0.0), self.drawing_to_model_co(
m44, m4, internal_point, -100.0
)
inside_elements = [e for e in tree.select(self.pythonize(a)) if not e.is_a("IfcAnnotation")]
if not inside_elements:
elements = [
e
for e in tree.select_ray(self.pythonize(a), self.pythonize(b - a))
if not e.instance.is_a("IfcAnnotation")
and tool.Cad.is_point_on_edge(
Vector(list(e.position)), (Vector(self.pythonize(a)), Vector(self.pythonize(b)))
)
]
if elements:
path = etree.Element("path")
d = (
"M"
+ " L".join(
[",".join([str(o) for o in co]) for co in polygon.exterior.coords[0:-1]]
)
unioned_boundaries = shapely.union_all(shapely.GeometryCollection(boundary_lines))
closed_polygons = shapely.polygonize(unioned_boundaries.geoms)
for polygon in closed_polygons.geoms:
# Less than 1mm2 is not worth styling on sheet
if polygon.area < 1:
continue
centroid = polygon.centroid
internal_point = centroid if polygon.contains(centroid) else polygon.representative_point()
if internal_point:
internal_point = [internal_point.x, internal_point.y]
a, b = self.drawing_to_model_co(m44, m4, internal_point, 0.0), self.drawing_to_model_co(
m44, m4, internal_point, -100.0
)
inside_elements = [e for e in tree.select(self.pythonize(a)) if not e.is_a("IfcAnnotation")]
if not inside_elements:
elements = [
e
for e in tree.select_ray(self.pythonize(a), self.pythonize(b - a))
if not e.instance.is_a("IfcAnnotation")
and tool.Cad.is_point_on_edge(
Vector(list(e.position)), (Vector(self.pythonize(a)), Vector(self.pythonize(b)))
)
]
if elements:
path = etree.Element("path")
d = (
"M"
+ " L".join([",".join([str(o) for o in co]) for co in polygon.exterior.coords[0:-1]])
+ " Z"
)
for interior in polygon.interiors:
d += (
" M"
+ " L".join([",".join([str(o) for o in co]) for co in interior.coords[0:-1]])
+ " Z"
)
for interior in polygon.interiors:
d += (
" M"
+ " L".join([",".join([str(o) for o in co]) for co in interior.coords[0:-1]])
+ " Z"
)
path.attrib["d"] = d
classes = self.get_svg_classes(ifc.by_id(elements[0].instance.id()))
classes.append("surface")
path.set("class", " ".join(list(classes)))
group.insert(0, path)
path.attrib["d"] = d
classes = self.get_svg_classes(ifc.by_id(elements[0].instance.id()))
classes.append(f"intpoint-{internal_point}")
classes.append(f"ab-{a}, {b}")
for i, ray_result in enumerate(elements):
classes.append(f"el{i}-{ray_result.instance.id()}")
classes.append(f"el{i}-pos-{list(ray_result.position)}")
classes.append(f"el{i}-dst-{ray_result.distance}")
classes.append("surface")
path.set("class", " ".join(list(classes)))
group.insert(0, path)
if self.camera.data.BIMCameraProperties.calculate_svgfill_surfaces:
results = etree.tostring(root).decode("utf8")
@@ -636,7 +643,7 @@ class CreateDrawing(bpy.types.Operator):
dom1 = parseString(svg_data_1)
svg1 = dom1.childNodes[0]
groups1 = [g for g in yield_groups(svg1) if g.getAttribute("class") == "projection"]
groups1 = [g for g in yield_groups(svg1) if "projection" in g.getAttribute("class")]
ls_groups = ifcopenshell.ifcopenshell_wrapper.svg_to_line_segments(results, "projection")
@@ -840,7 +847,7 @@ class CreateDrawing(bpy.types.Operator):
self.serialiser.setPolygonal(True)
self.serialiser.setUseHlrPoly(True)
# Objects with more than these edges are rendered as wireframe instead of HLR for optimisation
self.serialiser.setProfileThreshold(1000)
self.serialiser.setProfileThreshold(10000)
self.serialiser.setUseNamespace(True)
self.serialiser.setAlwaysProject(True)
self.serialiser.setAutoElevation(False)
@@ -853,6 +860,8 @@ class CreateDrawing(bpy.types.Operator):
self.serialiser.setScale(self.scale)
self.serialiser.setSubtractionSettings(ifcopenshell.ifcopenshell_wrapper.ALWAYS)
self.serialiser.setUsePrefiltering(True) # See #3359
self.serialiser.setUnifyInputs(True)
self.serialiser.setSegmentProjection(True)
if target_view == "REFLECTED_PLAN_VIEW":
self.serialiser.setMirrorY(True)
# tree = ifcopenshell.geom.tree()
@@ -912,7 +921,7 @@ class CreateDrawing(bpy.types.Operator):
# Drawing convention states that same objects classes with the same material are merged when cut.
join_criteria = ["class", "material.Name", 'r"Pset.*Common"."Status"']
group = root.findall(".//{http://www.w3.org/2000/svg}g")[0]
group = root.find("{http://www.w3.org/2000/svg}g")
joined_paths = {}
self.is_manifold_cache = {}
@@ -920,9 +929,15 @@ class CreateDrawing(bpy.types.Operator):
for el in root.findall(".//{http://www.w3.org/2000/svg}g[@{http://www.ifcopenshell.org/ns}guid]"):
element = ifc.by_guid(el.get("{http://www.ifcopenshell.org/ns}guid"))
classes = self.get_svg_classes(element)
classes.append("cut")
el.set("class", " ".join(classes))
if "projection" in el.get("class", "").split():
classes = self.get_svg_classes(element)
classes.append("projection")
el.set("class", " ".join(classes))
continue
else:
classes = self.get_svg_classes(element)
classes.append("cut")
el.set("class", " ".join(classes))
obj = tool.Ifc.get_object(element)
if not self.is_manifold(obj):
@@ -1044,9 +1059,12 @@ class CreateDrawing(bpy.types.Operator):
# IfcConvert puts the projection afterwards which is not correct since
# projection should be drawn underneath the cut.
group = root.find("{http://www.w3.org/2000/svg}g")
projection = group.find("{http://www.w3.org/2000/svg}g[@class='projection']")
projection.getparent().remove(projection)
group.insert(0, projection)
projections = root.xpath(
".//svg:g[contains(@class, 'projection')]", namespaces={"svg": "http://www.w3.org/2000/svg"}
)
for projection in projections:
projection.getparent().remove(projection)
group.insert(0, projection)
def generate_annotation(self, context):
if not ifcopenshell.util.element.get_pset(self.drawing, "EPset_Drawing", "HasAnnotation"):
@@ -1260,11 +1278,15 @@ class CreateSheets(bpy.types.Operator, Operator):
# These variables will be made available to the evaluated commands
svg = references["SHEET"]
basename = os.path.basename(svg)
path = os.path.dirname(svg)
pdf = os.path.splitext(svg)[0] + ".pdf"
eps = os.path.splitext(svg)[0] + ".eps"
dxf = os.path.splitext(svg)[0] + ".dxf"
replacements = {
"svg": svg,
"basename": os.path.basename(svg),
"path": os.path.dirname(svg),
"pdf": pdf,
"eps": os.path.splitext(svg)[0] + ".eps",
"dxf": os.path.splitext(svg)[0] + ".dxf",
}
has_sheet_reference = False
for reference in tool.Drawing.get_document_references(sheet):
@@ -1297,22 +1319,23 @@ class CreateSheets(bpy.types.Operator, Operator):
if svg2pdf_command:
# With great power comes great responsibility. Example:
# [['inkscape', svg, '-o', pdf]]
commands = eval(svg2pdf_command)
# [["inkscape", "svg", "-o", "pdf"]]
commands = json.loads(svg2pdf_command)
for command in commands:
subprocess.run(command)
subprocess.run([replacements.get(c, c) for c in command])
if svg2dxf_command:
# With great power comes great responsibility. Example:
# [['inkscape', svg, '-o', eps], ['pstoedit', '-dt', '-f', 'dxf:-polyaslines -mm', eps, dxf, '-psarg', '-dNOSAFER']]
commands = eval(svg2dxf_command)
# [["inkscape", "svg", "-o", "eps"], ["pstoedit", "-dt", "-f", "dxf:-polyaslines -mm", "eps", "dxf", "-psarg", "-dNOSAFER"]]
commands = json.loads(svg2dxf_command)
for command in commands:
subprocess.run(command)
command[0] = shutil.which(command[0]) or command[0]
subprocess.run([replacements.get(c, c) for c in command])
if svg2pdf_command:
open_with_user_command(context.preferences.addons["blenderbim"].preferences.pdf_command, pdf)
tool.Drawing.open_with_user_command(context.preferences.addons["blenderbim"].preferences.pdf_command, pdf)
else:
open_with_user_command(context.preferences.addons["blenderbim"].preferences.svg_command, svg)
tool.Drawing.open_with_user_command(context.preferences.addons["blenderbim"].preferences.svg_command, svg)
class SelectAllDrawings(bpy.types.Operator):
@@ -1379,7 +1402,9 @@ class OpenDrawing(bpy.types.Operator):
return {"CANCELLED"}
for drawing_uri in drawing_uris:
open_with_user_command(context.preferences.addons["blenderbim"].preferences.svg_command, drawing_uri)
tool.Drawing.open_with_user_command(
context.preferences.addons["blenderbim"].preferences.svg_command, drawing_uri
)
return {"FINISHED"}
@@ -1415,6 +1440,7 @@ class ActivateModel(bpy.types.Operator):
if view3d_context:
bpy.ops.object.hide_view_clear(view3d_context)
bpy.ops.bim.activate_status_filters()
subcontext = ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model", "Body", "MODEL_VIEW")
@@ -1544,6 +1570,9 @@ class RemoveDrawing(bpy.types.Operator, Operator):
removed_drawings = [drawing.id() for drawing in drawings]
for drawing in drawings:
sheet_references = tool.Drawing.get_sheet_references(drawing)
for reference in sheet_references:
bpy.ops.bim.remove_drawing_from_sheet(reference=reference.id())
core.remove_drawing(tool.Ifc, tool.Drawing, drawing=drawing)
@@ -502,6 +502,8 @@ ANNOTATION_TYPES_DATA = {
"PLAN_LEVEL": ("Level (Plan)", "", "SORTBYEXT", "curve"),
"SECTION_LEVEL": ("Level (Section)", "", "TRIA_DOWN", "curve"),
"BREAKLINE": ("Breakline", "", "FCURVE", "mesh"),
"SYMBOL": ("Symbol", "", "KEYFRAME", "empty"),
"MULTI_SYMBOL": ("Multi-Symbol", "", "OUTLINER_DATA_POINTCLOUD", "mesh"),
"LINEWORK": ("Line", "", "SNAP_MIDPOINT", "mesh"),
"BATTING": ("Batting", "Add batting annotation.\nThickness could be changed through Thickness property of BBIM_Batting property set", "FORCE_FORCE", "mesh"),
"REVISION_CLOUD":("Revision Cloud", "Add revision cloud", "VOLUME_DATA", "mesh"),
@@ -174,6 +174,8 @@ class SheetBuilder:
ET.register_namespace("", "http://www.w3.org/2000/svg")
layout_path = tool.Drawing.get_document_uri(sheet, "LAYOUT")
if not os.path.exists(layout_path):
return
layout_tree = ET.parse(layout_path)
layout_root = layout_tree.getroot()
@@ -326,11 +326,12 @@ class SvgWriter:
# We have to decide whether this should come from Blender or from IFC.
# For the moment, for convenience of experimenting with ideas, it comes
# from Blender. In the future, it should probably come from IFC.
if not isinstance(obj.data, bpy.types.Mesh):
return
classes = self.get_attribute_classes(obj)
if len(obj.data.vertices) and not len(obj.data.edges):
if obj.data is None:
return self.draw_empty_annotation(obj, classes)
elif not isinstance(obj.data, bpy.types.Mesh):
return
elif len(obj.data.vertices) and not len(obj.data.edges):
return self.draw_point_annotation(obj, classes)
elif len(obj.data.polygons) == 0:
return self.draw_edge_annotation(obj, classes)
@@ -774,11 +775,14 @@ class SvgWriter:
"text-anchor": text_anchor,
}
def add_fill_bg(self, element):
element = element.copy()
def add_fill_bg(self, element, copy=True):
if copy:
element = element.copy()
if hasattr(element, "xml"):
attrib = element.xml.attrib
else:
elif isinstance(element, ET.Element):
attrib = element.attrib
else: # assuming it's svgwrite.base.BaseElement
attrib = element.attribs
attrib["filter"] = "url(#fill-background)"
return element
@@ -802,7 +806,7 @@ class SvgWriter:
text_dir_world_x_axis = get_basis_vector(text_obj.matrix_world)
# RCP cameras may be scaled, so reset scales.
camera_matrix = self.camera.matrix_world.normalized()
camera_matrix = tool.Drawing.get_camera_matrix(self.camera)
text_dir = (camera_matrix.inverted().to_quaternion() @ text_dir_world_x_axis).to_2d().normalized()
angle = math.degrees(-text_dir.angle_signed(Vector((1, 0))))
@@ -831,7 +835,12 @@ class SvgWriter:
field.attrib["class"] = classes_str
if fill_bg:
self.svg.add(self.add_fill_bg(symbol_svg))
symbol_copied = symbol_svg.copy()
for text_tag in symbol_copied.xml.findall("text"):
self.add_fill_bg(text_tag, copy=False)
# NOTE: in case we'll later need to add fill-bg for the entire symbol:
# self.add_fill_bg(symbol_svg, copy=False)
self.svg.add(symbol_copied)
self.svg.add(symbol_svg)
return None
@@ -854,6 +863,27 @@ class SvgWriter:
self.svg.add(tag)
line_number += len(tag.elements)
def draw_empty_annotation(self, obj, classes):
x_offset = self.raw_width / 2
y_offset = self.raw_height / 2
point = self.project_point_onto_camera(obj.matrix_world.translation)
element = tool.Ifc.get_entity(obj)
svg_id = ifcopenshell.util.element.get_pset(element, "EPset_Annotation", "Symbol")
if not svg_id:
# EPset_AnnotationSurveyArea is not standard! See bSI-4.3 proposal #660.
svg_id = ifcopenshell.util.element.get_pset(element, "EPset_AnnotationSurveyArea", "PointType")
if not svg_id:
svg_id = str(ifcopenshell.util.element.get_predefined_type(element))
if not svg_id:
return
point = Vector(((x_offset + point.x), (y_offset - point.y)))
symbol_position_svg = point * self.svg_scale
self.svg.add(self.svg.use(f"#{svg_id}", insert=symbol_position_svg))
def draw_point_annotation(self, obj, classes):
x_offset = self.raw_width / 2
y_offset = self.raw_height / 2
@@ -862,12 +892,14 @@ class SvgWriter:
projected_points = [self.project_point_onto_camera(matrix_world @ v.co) for v in obj.data.vertices]
element = tool.Ifc.get_entity(obj)
svg_id = str(ifcopenshell.util.element.get_predefined_type(element))
# EPset_AnnotationSurveyArea is not standard! See bSI-4.3 proposal #660.
point_type = ifcopenshell.util.element.get_pset(element, "EPset_AnnotationSurveyArea", "PointType")
if point_type:
svg_id += f"-{point_type}"
svg_id = ifcopenshell.util.element.get_pset(element, "EPset_Annotation", "Symbol")
if not svg_id:
# EPset_AnnotationSurveyArea is not standard! See bSI-4.3 proposal #660.
svg_id = ifcopenshell.util.element.get_pset(element, "EPset_AnnotationSurveyArea", "PointType")
if not svg_id:
svg_id = str(ifcopenshell.util.element.get_predefined_type(element))
if not svg_id:
return
for symbol_position in projected_points:
symbol_position = Vector(((x_offset + symbol_position.x), (y_offset - symbol_position.y)))
@@ -76,7 +76,7 @@ class SetIfcGridNorth(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.set_ifc_grid_north"
bl_label = "Set IFC Grid North"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Set IFC grid north"
bl_description = "Set IFC grid north based on current Blender North Offset"
def _execute(self, context):
core.set_ifc_grid_north(tool.Georeference)
@@ -86,7 +86,7 @@ class SetBlenderGridNorth(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.set_blender_grid_north"
bl_label = "Set Blender Grid North"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Set Blender grid north"
bl_description = "Set Blender North Offset based on current IFC grid north"
def _execute(self, context):
core.set_blender_grid_north(tool.Georeference)
@@ -125,7 +125,7 @@ class SetIfcTrueNorth(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.set_ifc_true_north"
bl_label = "Set IFC True North"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Set IFC True north"
bl_description = "Set IFC true north based on current Blender North Offset"
def _execute(self, context):
core.set_ifc_true_north(tool.Georeference)
@@ -135,7 +135,7 @@ class SetBlenderTrueNorth(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.set_blender_true_north"
bl_label = "Set Blender True North"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Set Blender true north"
bl_description = "Set Blender North Offset based on current IFC true north"
def _execute(self, context):
core.set_blender_true_north(tool.Georeference)
@@ -58,6 +58,8 @@ class BIM_PT_materials(Panel):
row.alignment = "RIGHT"
if self.props.material_type == "IfcMaterial":
if not self.props.active_material_id:
row.operator("bim.add_material", text="", icon="ADD")
if self.props.materials and self.props.active_material_index < len(self.props.materials):
material = self.props.materials[self.props.active_material_index]
if material.ifc_definition_id:
@@ -66,13 +68,10 @@ class BIM_PT_materials(Panel):
row.operator("bim.disable_editing_material", text="", icon="CANCEL").material = material.ifc_definition_id
self.draw_editable_material_attributes_ui()
else:
row.operator("bim.add_material", text="", icon="ADD")
op = row.operator("bim.select_by_material", text="", icon="RESTRICT_SELECT_OFF")
op.material = material.ifc_definition_id
row.operator("bim.enable_editing_material", text="", icon="GREASEPENCIL").material = material.ifc_definition_id
row.operator("bim.remove_material", text="", icon="X").material = material.ifc_definition_id
else:
row.operator("bim.add_material", text="", icon="ADD")
else:
row.operator("bim.add_material_set", text="", icon="ADD").set_type = self.props.material_type
if self.props.materials and self.props.active_material_index < len(self.props.materials):
@@ -402,15 +402,23 @@ class MEPGenerator:
return len(segments_data) == 0
def pack_return_data(fitting_type, ports, segments_data):
packed_data = {"fitting_type": fitting_type}
if predefined_type == "OBSTRUCTION":
return packed_data
for port in ports:
port_local_position = V(*port.ObjectPlacement.RelativePlacement.Location.Coordinates)
if tool.Cad.is_x(port_local_position.length, 0.0):
start_port = port
break
connected_port = tool.System.get_connected_port(start_port)
connected_element = tool.System.get_port_relating_element(connected_port)
element_type = ifcopenshell.util.element.get_type(connected_element)
return {"fitting_type": fitting_type, "start_port_match": element_type == segments_data[0][0]}
packed_data["start_port_match"] = element_type == segments_data[0][0]
return packed_data
fitting_types = tool.Ifc.get().by_type(self.get_mep_element_class_name(segments[0], "FittingType"))
for fitting_type in fitting_types:
@@ -156,15 +156,14 @@ class BIM_PT_authoring(Panel):
class BIM_PT_Grids(Panel):
bl_label = "Grids"
bl_idname = "BIM_PT_Grids"
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
bl_options = {"DEFAULT_CLOSED"}
bl_category = "BlenderBIM"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_project_setup"
def draw(self, context):
self.animation_props = context.scene.BIMAnimationProperties
row = self.layout.row()
row.operator("mesh.add_grid", icon="ADD", text="Add Grids")
self.layout.row().operator("mesh.add_grid", icon="ADD", text="Add Grids")
class BIM_PT_array(bpy.types.Panel):
@@ -568,7 +568,8 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator):
if not bpy.context.selected_objects:
return
if self.active_material_usage == "LAYER2":
bpy.ops.bim.align_wall(align_type="CENTERLINE")
if bpy.ops.bim.align_wall.poll():
bpy.ops.bim.align_wall(align_type="CENTERLINE")
else:
bpy.ops.bim.align_product(align_type="CENTERLINE")
@@ -47,6 +47,7 @@ classes = (
operator.UnlinkIfc,
operator.UnloadLink,
operator.UnloadProject,
operator.ReloadLink,
prop.LibraryElement,
prop.FilterCategory,
prop.Link,
@@ -850,7 +850,7 @@ class UnloadLink(bpy.types.Operator):
for scene in bpy.data.scenes:
if scene.library and scene.library.filepath == filepath:
bpy.data.scenes.remove(scene)
link = context.scene.BIMProjectProperties.links.get(filepath)
link = context.scene.BIMProjectProperties.links.get(self.filepath)
link.is_loaded = False
return {"FINISHED"}
@@ -866,7 +866,7 @@ class LoadLink(bpy.types.Operator):
filepath = self.filepath
if not os.path.isabs(filepath):
filepath = os.path.abspath(os.path.join(bpy.path.abspath("//"), filepath))
with bpy.data.libraries.load(self.filepath, link=True) as (data_from, data_to):
with bpy.data.libraries.load(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 != filepath:
@@ -875,11 +875,31 @@ class LoadLink(bpy.types.Operator):
if "IfcProject" not in child.name:
continue
bpy.data.scenes[0].collection.children.link(child)
link = context.scene.BIMProjectProperties.links.get(filepath)
link = context.scene.BIMProjectProperties.links.get(self.filepath)
link.is_loaded = True
return {"FINISHED"}
class ReloadLink(bpy.types.Operator):
bl_idname = "bim.reload_link"
bl_label = "Reload Link"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Reload the selected file"
filepath: bpy.props.StringProperty()
def execute(self, context):
def get_linked_ifcs():
selected_filename = os.path.basename(self.filepath)
return [
c.library
for c in bpy.data.collections
if "IfcProject" in c.name and c.library and os.path.basename(c.library.filepath) == selected_filename
]
for library in get_linked_ifcs() or []:
library.reload()
return {"FINISHED"}
class ToggleLinkSelectability(bpy.types.Operator):
bl_idname = "bim.toggle_link_selectability"
bl_label = "Toggle Link Selectability"
@@ -890,6 +910,9 @@ class ToggleLinkSelectability(bpy.types.Operator):
def execute(self, context):
props = context.scene.BIMProjectProperties
link = props.links.get(self.link)
self.filepath = self.link
if not os.path.isabs(self.filepath):
self.filepath = os.path.abspath(os.path.join(bpy.path.abspath("//"), self.filepath))
for collection in self.get_linked_collections():
collection.hide_select = not collection.hide_select
link.is_selectable = not collection.hide_select
@@ -897,7 +920,7 @@ class ToggleLinkSelectability(bpy.types.Operator):
def get_linked_collections(self):
return [
c for c in bpy.data.collections if "IfcProject" in c.name and c.library and c.library.filepath == self.link
c for c in bpy.data.collections if "IfcProject" in c.name and c.library and c.library.filepath == self.filepath
]
@@ -912,6 +935,9 @@ class ToggleLinkVisibility(bpy.types.Operator):
def execute(self, context):
props = context.scene.BIMProjectProperties
link = props.links.get(self.link)
self.filepath = self.link
if not os.path.isabs(self.filepath):
self.filepath = os.path.abspath(os.path.join(bpy.path.abspath("//"), self.filepath))
if self.mode == "WIREFRAME":
self.toggle_wireframe(link)
elif self.mode == "VISIBLE":
@@ -948,7 +974,7 @@ class ToggleLinkVisibility(bpy.types.Operator):
def get_linked_collections(self):
return [
c for c in bpy.data.collections if "IfcProject" in c.name and c.library and c.library.filepath == self.link
c for c in bpy.data.collections if "IfcProject" in c.name and c.library and c.library.filepath == self.filepath
]
@@ -1037,6 +1063,7 @@ class ExportIFC(bpy.types.Operator):
settings.json_compact = self.json_compact
ifc_exporter = export_ifc.IfcExporter(settings)
print("Starting export")
settings.logger.info("Starting export")
ifc_exporter.export()
settings.logger.info("Export finished in {:.2f} seconds".format(time.time() - start))
@@ -375,6 +375,8 @@ class BIM_UL_links(UIList):
op.mode = "VISIBLE"
op = row.operator("bim.unload_link", text="", icon="UNLINKED")
op.filepath = item.name
op = row.operator("bim.reload_link", text="", icon="FILE_REFRESH")
op.filepath = item.name
else:
row.prop(item, "name", text="")
op = row.operator("bim.load_link", text="", icon="LINKED")
@@ -49,8 +49,8 @@ class IfcClassData:
@classmethod
def ifc_products(cls):
products = [
"IfcElement",
"IfcElementType",
"IfcElement",
"IfcSpatialElement",
"IfcSpatialElementType",
"IfcGroup",
@@ -62,8 +62,8 @@ class IfcClassData:
version = tool.Ifc.get_schema()
if version == "IFC2X3":
products = [
"IfcElement",
"IfcElementType",
"IfcElement",
"IfcSpatialStructureElement",
"IfcGroup",
"IfcStructuralItem",
@@ -104,7 +104,7 @@ class SelectSimilarData:
psets = ifcopenshell.util.element.get_psets(element, psets_only=True)
for pset, properties in psets.items():
if pset.endswith("Common"):
keys.extend([f'r".*Common"."{name}"' for name in properties.keys() if name != "id"])
keys.extend([f'/.*Common/."{name}"' for name in properties.keys() if name != "id"])
else:
keys.extend([f"{pset}.{name}" for name in properties.keys() if name != "id"])
return [(k, k, "") for k in keys]
@@ -0,0 +1,45 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2023 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BlenderBIM Add-on.
#
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BlenderBIM Add-on is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
import blenderbim.tool as tool
def refresh():
TesterData.is_loaded = False
class TesterData:
data = {}
is_loaded = False
@classmethod
def load(cls):
cls.data = {"has_report": cls.has_report(), "specification": cls.specification()}
cls.is_loaded = True
@classmethod
def has_report(cls):
return tool.Tester.report
@classmethod
def specification(cls):
if not tool.Tester.report:
return {}
props = bpy.context.scene.IfcTesterProperties
return tool.Tester.report[props.active_specification_index]
@@ -21,15 +21,16 @@ import bpy
import time
import tempfile
import webbrowser
import json
import ifctester
import ifctester.ids
import ifctester.reporter
import ifcopenshell
import blenderbim.tool as tool
import blenderbim.bim.handler
from blenderbim.bim.module.tester.data import TesterData
class ExecuteIfcTester(bpy.types.Operator):
class ExecuteIfcTester(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.execute_ifc_tester"
bl_label = "Execute IfcTester"
@@ -54,28 +55,28 @@ class ExecuteIfcTester(bpy.types.Operator):
print("Finished loading:", time.time() - start)
start = time.time()
specs.validate(ifc)
print("Finished validating:", time.time() - start)
start = time.time()
if props.generate_html_report:
engine = ifctester.reporter.Html(specs)
engine.report()
engine.report()
engine.to_file(output)
webbrowser.open("file://" + output)
report = None
report = ifctester.reporter.Json(specs).report()['specifications']
report = ifctester.reporter.Json(specs).report()["specifications"]
if report:
props.has_report = True
props.report = json.dumps(report)
tool.Tester.specs = specs
tool.Tester.report = report
props.specifications.clear()
c=0
for spec in report:
new_spec = props.specifications.add()
new_spec.name = spec['name']
new_spec.status = spec['status']
new_spec.name = spec["name"]
new_spec.status = spec["status"]
blenderbim.bim.handler.refresh_ui_data()
return {"FINISHED"}
@@ -118,23 +119,24 @@ class SelectRequirement(bpy.types.Operator):
bl_label = "Select Specification"
bl_options = {"REGISTER", "UNDO"}
spec_index: bpy.props.IntProperty()
req_index: bpy.props.IntProperty()
req_index: bpy.props.IntProperty()
def execute(self, context):
props = context.scene.IfcTesterProperties
report = json.loads(props.report)
report = tool.Tester.report
props.old_index = self.spec_index
failed_entities = report[self.spec_index]['requirements'] [self.req_index]['failed_entities']
failed_entities = report[self.spec_index]["requirements"][self.req_index]["failed_entities"]
props.n_entities = len(failed_entities)
props.has_entities = True if props.n_entities > 0 else False
props.failed_entities.clear()
for e in failed_entities:
new_entity = props.failed_entities.add()
new_entity.element = e['element']
new_entity.reason = e['reason']
new_entity = props.failed_entities.add()
new_entity.element = e["element"]
new_entity.reason = e["reason"]
return {"FINISHED"}
class SelectEntity(bpy.types.Operator):
bl_idname = "bim.select_entity"
bl_label = "Select Entity"
@@ -142,33 +144,28 @@ class SelectEntity(bpy.types.Operator):
ifc_id: bpy.props.IntProperty()
def execute(self, context):
bpy.ops.object.select_all(action='DESELECT')
bpy.ops.object.select_all(action="DESELECT")
for obj in context.scene.objects:
if obj.BIMObjectProperties.ifc_definition_id == self.ifc_id:
obj.select_set(True)
bpy.context.view_layer.objects.active = obj
return {"FINISHED"}
class ExportBcf(bpy.types.Operator):
bl_idname = "bim.export_bcf"
bl_label = "Export BCF"
bl_options = {"REGISTER", "UNDO"}
filter_glob: bpy.props.StringProperty(default="*.bcf", options={"HIDDEN"})
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def execute(self, context):
props = context.scene.IfcTesterProperties
if tool.Ifc.get():
ifc = tool.Ifc.get()
else:
ifc = ifcopenshell.open(props.ifc_file)
with tempfile.TemporaryDirectory() as dirpath:
output = os.path.join(dirpath, "{}.bcf".format(props.specs))
specs = ifctester.ids.open(props.specs)
specs.validate(ifc)
bcf_reporter = ifctester.reporter.Bcf(specs)
bcf_reporter.report()
bcf_reporter.to_file(output)
print("Finished exporting!")
self.report({"INFO"}, 'Finished exporting!')
bcf_reporter = ifctester.reporter.Bcf(tool.Tester.specs)
bcf_reporter.report()
bcf_reporter.to_file(self.filepath)
self.report({"INFO"}, "Finished exporting!")
return {"FINISHED"}
def invoke(self, context, event):
context.window_manager.fileselect_add(self)
return {"RUNNING_MODAL"}
@@ -16,6 +16,7 @@
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
from blenderbim.bim.module.tester.data import TesterData
from blenderbim.bim.prop import StrProperty
from bpy.types import PropertyGroup
from bpy.props import (
@@ -34,8 +35,8 @@ def purge():
pass
def get_failure_entities():
return
def update_active_specification_index(self, context):
TesterData.load()
class Specification(PropertyGroup):
@@ -53,13 +54,10 @@ class IfcTesterProperties(PropertyGroup):
ifc_file: StringProperty(default="", name="IFC File")
should_load_from_memory: BoolProperty(default=False, name="Load from Memory")
generate_html_report: BoolProperty(default=False, name="Generate HTML report")
active_specification_index: IntProperty(name="Active Specification Index")
active_requirement_index: IntProperty(name="Active Requirement Index")
active_specification_index: IntProperty(name="Active Specification Index", update=update_active_specification_index)
old_index: IntProperty(name="", default=0)
active_failed_entity_index: IntProperty(name="Active Failed Entity Index")
report: StringProperty(default="", name="JSON report")
specifications: CollectionProperty(name="Specifications", type=Specification)
failed_entities: CollectionProperty(name="FailedEntities", type=FailedEntities)
has_report: BoolProperty(default=False, name="")
has_entities: BoolProperty(default=False, name="")
n_entities: IntProperty(name="", default=0)
@@ -18,7 +18,7 @@
import blenderbim.tool as tool
from bpy.types import Panel, UIList
import json
from blenderbim.bim.module.tester.data import TesterData
class BIM_PT_tester(Panel):
@@ -31,6 +31,9 @@ class BIM_PT_tester(Panel):
bl_parent_id = "BIM_PT_tab_quality_control"
def draw(self, context):
if not TesterData.is_loaded:
TesterData.load()
self.layout.use_property_split = True
props = context.scene.IfcTesterProperties
@@ -53,7 +56,7 @@ class BIM_PT_tester(Panel):
row = self.layout.row()
row.operator("bim.execute_ifc_tester")
if props.has_report:
if TesterData.data["has_report"]:
self.layout.template_list(
"BIM_UL_tester_specifications",
"",
@@ -69,33 +72,26 @@ class BIM_PT_tester(Panel):
def draw_editable_ui(self, context):
props = context.scene.IfcTesterProperties
i = props.active_specification_index
dic_report = json.loads(props.report)
specification = TesterData.data["specification"]
total_successes = dic_report[i]["total_successes"]
total = dic_report[i]["total"]
percentage = dic_report[i]["percentage"]
n_requirements = len(dic_report[i]["requirements"])
n_requirements = len(specification["requirements"])
row = self.layout.row()
row.label(text=f"Passed: {total_successes}/{total} ({percentage}%)")
row.label(
text=f'Passed: {specification["total_checks_pass"]}/{specification["total_checks"]} ({specification["percent_checks_pass"]}%)'
)
row = self.layout.row()
row.label(text=f"Requirements ({n_requirements}):")
c = 0
box = self.layout.box()
for req in dic_report[i]["requirements"]:
for i, requirement in enumerate(specification["requirements"]):
row = box.row(align=True)
row.label(text=f" {c+1}. {req['description']}")
if req["status"]:
row.label(text="PASS", icon="CHECKMARK")
else:
row.label(text="FAIL", icon="CANCEL")
row.label(text=requirement["description"], icon="CHECKMARK" if requirement["status"] else "CANCEL")
if not requirement["status"]:
op = row.operator("bim.select_requirement", text="", icon="LONGDISPLAY")
op.spec_index = i
op.req_index = c
c += 1
op.spec_index = props.active_specification_index
op.req_index = i
if props.old_index == i and props.n_entities > 0:
if props.old_index == props.active_specification_index and props.n_entities > 0:
row = self.layout.row()
row.label(text=f"Failed entities [{props.n_entities}]:")
self.layout.template_list(
@@ -112,11 +108,7 @@ class BIM_UL_tester_specifications(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, icon="WORDWRAP_ON")
if item.status:
row.label(text="PASS")
else:
row.label(text="FAIL")
row.label(text=item.name, icon="CHECKMARK" if item.status else "CANCEL")
class BIM_UL_tester_failed_entities(UIList):
@@ -445,7 +445,7 @@ class AddType(bpy.types.Operator, tool.Ifc.Operator):
should_add_representation=True,
context=body,
)
tool.Blender.select_and_activate_single_object(obj)
tool.Blender.select_and_activate_single_object(context, obj)
bpy.ops.bim.add_roof()
bpy.ops.bim.load_type_thumbnails(ifc_class=ifc_class)
+5 -5
View File
@@ -126,14 +126,14 @@ class BIM_UL_topics(bpy.types.UIList):
class BIM_ADDON_preferences(bpy.types.AddonPreferences):
bl_idname = "blenderbim"
svg2pdf_command: StringProperty(name="SVG to PDF Command", description="E.g. [['inkscape', svg, '-o', pdf]]")
svg2pdf_command: StringProperty(name="SVG to PDF Command", description='E.g. [["inkscape", "svg", "-o", pdf]]')
svg2dxf_command: StringProperty(
name="SVG to DXF Command",
description="E.g. [['inkscape', svg, '-o', eps], ['pstoedit', '-dt', '-f', 'dxf:-polyaslines -mm', eps, dxf, '-psarg', '-dNOSAFER']]",
description='E.g. [["inkscape", "svg", "-o", "eps"], ["pstoedit", "-dt", "-f", "dxf:-polyaslines -mm", "eps", "dxf", "-psarg", "-dNOSAFER"]]',
)
svg_command: StringProperty(name="SVG Command", description="E.g. [['firefox', path]]")
pdf_command: StringProperty(name="PDF Command", description="E.g. [['firefox', path]]")
spreadsheet_command: StringProperty(name="Spreadsheet Command", description="E.g. [['libreoffice', path]]")
svg_command: StringProperty(name="SVG Command", description='E.g. [["firefox", "path"]]')
pdf_command: StringProperty(name="PDF Command", description='E.g. [["firefox", "path"]]')
spreadsheet_command: StringProperty(name="Spreadsheet Command", description='E.g. [["libreoffice", "path"]]')
openlca_port: IntProperty(name="OpenLCA IPC Port", default=8080)
should_hide_empty_props: BoolProperty(name="Should Hide Empty Properties", default=True)
should_setup_workspace: BoolProperty(name="Should Setup Workspace Layout for BIM", default=True)
@@ -51,6 +51,7 @@ from blenderbim.tool.structural import Structural
from blenderbim.tool.style import Style
from blenderbim.tool.surveyor import Surveyor
from blenderbim.tool.system import System
from blenderbim.tool.tester import Tester
from blenderbim.tool.type import Type
from blenderbim.tool.unit import Unit
from blenderbim.tool.search import Search
+31 -2
View File
@@ -20,6 +20,7 @@ import os
import re
import bpy
import math
import json
import lark
import bmesh
import shutil
@@ -70,6 +71,8 @@ class Drawing(blenderbim.core.tool.Drawing):
obj = annotation.Annotator.add_plane_to_annotation(obj)
elif object_type == "REVISION_CLOUD":
obj = annotation.Annotator.add_plane_to_annotation(obj, remove_face=True)
elif object_type == "MULTI_SYMBOL":
obj = annotation.Annotator.add_vertex_to_annotation(obj)
elif object_type == "TEXT_LEADER":
co1, _, co2, _ = annotation.Annotator.get_placeholder_coords()
obj = annotation.Annotator.add_line_to_annotation(obj, co2, co1)
@@ -835,9 +838,11 @@ class Drawing(blenderbim.core.tool.Drawing):
@classmethod
def open_with_user_command(cls, user_command, path):
if user_command:
commands = eval(user_command)
commands = json.loads(user_command)
replacements = {"path": path}
for command in commands:
subprocess.Popen(command)
command[0] = shutil.which(command[0]) or command[0]
subprocess.Popen([replacements.get(c, c) for c in command])
else:
webbrowser.open("file://" + path)
@@ -1835,3 +1840,27 @@ class Drawing(blenderbim.core.tool.Drawing):
direction = end - start
offset = distance * (direction / np.linalg.norm(direction))
return (start - offset).tolist(), (end + offset).tolist()
@classmethod
def get_sheet_references(cls, drawing):
sheet_references = []
drawing_reference = cls.get_drawing_document(drawing)
for sheet in tool.Ifc.get().by_type("IfcDocumentInformation"):
if not sheet.Scope == "SHEET":
continue
references = cls.get_document_references(sheet)
for reference in references:
if reference.Location == drawing_reference.Location:
sheet_references.append(reference)
break
return sheet_references
@classmethod
def get_camera_matrix(cls, camera):
matrix_world = camera.matrix_world.copy().normalized()
location, rotation, scale = matrix_world.decompose()
if scale.x < 0 or scale.y < 0 or scale.z < 0:
# RCPs may be inversely scaled. We discard the scale and rotate the Z to compensate.
rotate180z = mathutils.Matrix.Rotation(math.radians(180.0), 4, "Z")
return mathutils.Matrix.Translation(location) @ rotation.to_matrix().to_4x4() @ rotate180z
return mathutils.Matrix.Translation(location) @ rotation.to_matrix().to_4x4()
+3 -1
View File
@@ -29,7 +29,7 @@ import blenderbim.core.spatial
import blenderbim.tool as tool
import blenderbim.bim.import_ifc
from math import radians
from mathutils import Vector
from mathutils import Vector, Matrix
from blenderbim.bim.ifc import IfcStore
@@ -59,6 +59,8 @@ class Geometry(blenderbim.core.tool.Geometry):
# Note that clearing scale has no impact on cameras.
if (obj.scale - Vector((1.0, 1.0, 1.0))).length > 1e-4:
if not obj.data:
location, rotation, _ = obj.matrix_world.decompose()
obj.matrix_world = Matrix.Translation(location) @ rotation.to_matrix().to_4x4()
obj.matrix_world.normalize()
elif obj.data.users == 1:
context_override = {}
+1 -1
View File
@@ -220,7 +220,7 @@ class IfcGit:
bpy.data.orphans_purge(do_recursive=True)
bpy.ops.bim.load_project(filepath=path_ifc)
bpy.ops.bim.load_project(filepath=path_ifc, should_start_fresh_session=False)
bpy.ops.object.select_all(action="DESELECT")
@classmethod
+22
View File
@@ -0,0 +1,22 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BlenderBIM Add-on.
#
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BlenderBIM Add-on is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
class Tester:
specs = None
report = {}
@@ -60,3 +60,11 @@ download.
See also blocks should be used to reference `further reading
<https://blenderbim.org>`__ links.
Tables can be very annoying to format. You can use a CSV table instead.
.. csv-table::
:header: "Foo", "Bar", "Baz"
"ABC", "01", "02"
"DEF", "03", "04"
@@ -146,6 +146,9 @@ class LibraryGenerator:
self.create_type("IfcDoorType", "DT01", {"model_body": "Door", "plan_body": "Door-Plan"})
self.create_type("IfcFurnitureType", "BUN01", {"model_body": "Bunny", "plan_body": "Bunny-Plan"})
self.create_symbol_type("SETOUT-POINT", "setout-point")
self.create_symbol_type("CONTROL-POINT", "control-point")
self.create_symbol_type("TRAVERSE-POINT", "traverse-point")
self.create_line_type("DASHED", "dashed")
self.create_line_type("FINE", "fine")
self.create_line_type("THIN", "thin")
@@ -161,6 +164,12 @@ class LibraryGenerator:
self.file.write("IFC4 Demo Library.ifc")
def create_symbol_type(self, name, symbol):
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcTypeProduct", name=name)
element.ApplicableOccurrence = "IfcAnnotation/SYMBOL"
pset = ifcopenshell.api.run("pset.add_pset", self.file, product=element, name="EPset_Annotation")
ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"Symbol": symbol})
def create_line_type(self, name, classes):
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcTypeProduct", name=name)
element.ApplicableOccurrence = "IfcAnnotation/LINEWORK"
@@ -1877,4 +1877,4 @@ class LibraryGenerator:
if __name__ == "__main__":
path = Path(__file__).parents[1] / "blenderbim/bim/data/libraries"
LibraryGenerator().generate(output_filename=str(path / "IFC4 AU Library.ifc"))
LibraryGenerator().generate(output_filename=str(path / "IFC4 Furniture Library.ifc"))
+3
View File
@@ -416,6 +416,8 @@ int main(int argc, char** argv) {
"Uses the polygonal algorithm for hidden line rendering")
("svg-prefilter",
"Prefilter faces and shapes before feeding to HLR algorithm")
("svg-segment-projection",
"Segment result of projection wrt original products")
("svg-write-poly",
"Approximate every curve as polygonal in SVG output")
("svg-project",
@@ -1085,6 +1087,7 @@ int main(int argc, char** argv) {
static_cast<SvgSerializer*>(serializer.get())->setUseNamespace(vmap.count("svg-xmlns") > 0);
static_cast<SvgSerializer*>(serializer.get())->setUseHlrPoly(vmap.count("svg-poly") > 0);
static_cast<SvgSerializer*>(serializer.get())->setUsePrefiltering(vmap.count("svg-prefilter") > 0);
static_cast<SvgSerializer*>(serializer.get())->setSegmentProjection(vmap.count("svg-segment-projection") > 0);
static_cast<SvgSerializer*>(serializer.get())->setPolygonal(vmap.count("svg-write-poly") > 0);
static_cast<SvgSerializer*>(serializer.get())->setAlwaysProject(vmap.count("svg-project") > 0);
static_cast<SvgSerializer*>(serializer.get())->setWithoutStoreys(vmap.count("svg-without-storeys") > 0);
+30 -33
View File
@@ -75,6 +75,7 @@ class IfcCsv:
sort=None,
groups=None,
summaries=None,
formatting=None,
):
self.ifc_file = ifc_file
self.results = []
@@ -90,10 +91,6 @@ class IfcCsv:
for element in elements:
result = []
for index, attribute in enumerate(attributes):
if "*" in attribute:
attributes.extend(self.get_wildcard_attributes(attribute))
del attributes[index]
for attribute in attributes:
value = ifcopenshell.util.selector.get_element_value(element, attribute)
@@ -116,6 +113,7 @@ class IfcCsv:
self.group_results(groups, attributes)
self.summarise_results(summaries, attributes)
self.sort_results(sort, attributes, include_global_id)
self.format_results(formatting, attributes, null)
if format == "csv":
self.export_csv(output, delimiter=delimiter)
@@ -225,7 +223,27 @@ class IfcCsv:
self.summaries[si] = max(summary_values[si])
self.summaries[si] = summary_type.title() + ": " + str(self.summaries[si])
def format_results(self, formatting, attributes, null):
if not formatting:
return
formatting_indices = {}
for data in formatting:
index = attributes.index(data["name"])
formatting_indices[index] = data["format"]
for row in self.results:
for index, format_query in formatting_indices.items():
if row[index] == null:
continue
if not isinstance(row[index], str):
row[index] = '"' + str(row[index]) + '"'
row[index] = ifcopenshell.util.selector.format(format_query.replace("{{value}}", row[index]))
def sort_results(self, sort, attributes, include_global_id):
if not self.results:
return
if sort:
def natural_sort(value):
if isinstance(value, str):
@@ -240,7 +258,10 @@ class IfcCsv:
reverse = sort_data["order"] == "DESC"
self.results = sorted(self.results, key=lambda x: natural_sort(x[i]), reverse=reverse)
else:
self.results = sorted(self.results, key=lambda x: x[1 if include_global_id else 0])
if include_global_id and len(list(self.results)[0]) > 1:
self.results = sorted(self.results, key=lambda x: x[1])
elif not include_global_id:
self.results = sorted(self.results, key=lambda x: x[0])
def export_csv(self, output, delimiter=None):
with open(output, "w", newline="", encoding="utf-8") as f:
@@ -372,36 +393,12 @@ class IfcCsv:
self.process_row(ifc_file, row, headers, attributes, null, bool_true, bool_false)
def import_xlsx(self, ifc_file, table, attributes, null, bool_true, bool_false):
workbook = openpyxl.load_workbook(filename=table, read_only=True)
worksheet = workbook.active # Assuming data is on the first sheet
headers = None
for row in worksheet.iter_rows(values_only=True):
if not headers:
headers = list(row)
if not attributes:
attributes = [None] * len(headers)
elif len(attributes) == len(headers) - 1:
attributes.insert(0, "") # The GlobalId column
continue
self.process_row(ifc_file, row, headers, attributes, null, bool_true, bool_false)
df = pd.read_excel(table)
self.import_pd(ifc_file, df, attributes, null, bool_true, bool_false)
def import_ods(self, ifc_file, table, attributes, null, bool_true, bool_false):
doc = load(table)
first_sheet = doc.spreadsheet.getElementsByType(Table)[0]
rows = first_sheet.getElementsByType(TableRow)
headers = None
for row in rows:
values = [cell.getElementsByType(P)[0].childNodes[0].data for cell in row.getElementsByType(TableCell)]
if not headers:
headers = values
if not attributes:
attributes = [None] * len(headers)
elif len(attributes) == len(headers) - 1:
attributes.insert(0, "") # The GlobalId column
continue
self.process_row(ifc_file, values, headers, attributes, null, bool_true, bool_false)
df = pd.read_excel(table, engine="odf")
self.import_pd(ifc_file, df, attributes, null, bool_true, bool_false)
def import_pd(self, ifc_file, df, attributes=None, null="-", bool_true="YES", bool_false="NO"):
headers = df.columns.tolist()
+1 -1
View File
@@ -103,7 +103,7 @@ endif
mkdir -p dist/ifcopenshell
cp -r ifcopenshell/* dist/ifcopenshell/
cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-$(PYNUMBER)-v0.7.0-dcc9d0e-$(PLATFORM)64.zip
cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-$(PYNUMBER)-v0.7.0-9cc1f5f-$(PLATFORM)64.zip
cd dist/working && unzip ifcopenshell-python*
cp -r dist/working/ifcopenshell/ifcopenshell_wrapper.py dist/ifcopenshell/
ifeq ($(PLATFORM), win)
@@ -20,11 +20,11 @@ Pre-built packages
| build-linux64_ | build-win32_ | build-win64_ | build-macos64_ | build-macosm164_ |
+----------------+----------------+----------------+----------------+------------------+
.. _build-linux64: https://s3.amazonaws.com/ifcopenshell-builds/IfcConvert-v0.7.0-dcc9d0e-linux64.zip
.. _build-win32: https://s3.amazonaws.com/ifcopenshell-builds/IfcConvert-v0.7.0-dcc9d0e-win32.zip
.. _build-win64: https://s3.amazonaws.com/ifcopenshell-builds/IfcConvert-v0.7.0-dcc9d0e-win64.zip
.. _build-macos64: https://s3.amazonaws.com/ifcopenshell-builds/IfcConvert-v0.7.0-dcc9d0e-macos64.zip
.. _build-macosm164: https://s3.amazonaws.com/ifcopenshell-builds/IfcConvert-v0.7.0-dcc9d0e-macosm164.zip
.. _build-linux64: https://s3.amazonaws.com/ifcopenshell-builds/IfcConvert-v0.7.0-9cc1f5f-linux64.zip
.. _build-win32: https://s3.amazonaws.com/ifcopenshell-builds/IfcConvert-v0.7.0-9cc1f5f-win32.zip
.. _build-win64: https://s3.amazonaws.com/ifcopenshell-builds/IfcConvert-v0.7.0-9cc1f5f-win64.zip
.. _build-macos64: https://s3.amazonaws.com/ifcopenshell-builds/IfcConvert-v0.7.0-9cc1f5f-macos64.zip
.. _build-macosm164: https://s3.amazonaws.com/ifcopenshell-builds/IfcConvert-v0.7.0-9cc1f5f-macosm164.zip
2. Unzip the downloaded file and run IfcConvert using the command line.
@@ -15,4 +15,5 @@ system, as well as high level analysis and authoring functions.
ifcopenshell-python/geometry_processing
ifcopenshell-python/geometry_creation
ifcopenshell-python/geometry_tree
ifcopenshell-python/selector_syntax
ifcopenshell-python/developer_guide
@@ -40,34 +40,34 @@ changes in the IfcOpenShell C++ core.
| Python 3.11 | py311-linux64_ | py311-win32_ | py311-win64_ | N/A | py311-macosm164_ |
+-------------+----------------+----------------+----------------+----------------+------------------+
.. _py36-linux64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-36-v0.7.0-dcc9d0e-linux64.zip
.. _py37-linux64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-37-v0.7.0-dcc9d0e-linux64.zip
.. _py38-linux64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-38-v0.7.0-dcc9d0e-linux64.zip
.. _py39-linux64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-39-v0.7.0-dcc9d0e-linux64.zip
.. _py310-linux64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-310-v0.7.0-dcc9d0e-linux64.zip
.. _py311-linux64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-311-v0.7.0-dcc9d0e-linux64.zip
.. _py36-win32: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-36-v0.7.0-dcc9d0e-win32.zip
.. _py37-win32: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-37-v0.7.0-dcc9d0e-win32.zip
.. _py38-win32: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-38-v0.7.0-dcc9d0e-win32.zip
.. _py39-win32: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-39-v0.7.0-dcc9d0e-win32.zip
.. _py310-win32: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-310-v0.7.0-dcc9d0e-win32.zip
.. _py311-win32: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-311-v0.7.0-dcc9d0e-win32.zip
.. _py36-win64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-36-v0.7.0-dcc9d0e-win64.zip
.. _py37-win64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-37-v0.7.0-dcc9d0e-win64.zip
.. _py38-win64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-38-v0.7.0-dcc9d0e-win64.zip
.. _py39-win64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-39-v0.7.0-dcc9d0e-win64.zip
.. _py310-win64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-310-v0.7.0-dcc9d0e-win64.zip
.. _py311-win64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-311-v0.7.0-dcc9d0e-win64.zip
.. _py36-macos64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-36-v0.7.0-dcc9d0e-macos64.zip
.. _py37-macos64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-37-v0.7.0-dcc9d0e-macos64.zip
.. _py38-macos64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-38-v0.7.0-dcc9d0e-macos64.zip
.. _py39-macos64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-39-v0.7.0-dcc9d0e-macos64.zip
.. _py310-macos64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-310-v0.7.0-dcc9d0e-macos64.zip
.. _py37-macosm164: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-37-v0.7.0-dcc9d0e-macosm164.zip
.. _py38-macosm164: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-38-v0.7.0-dcc9d0e-macosm164.zip
.. _py39-macosm164: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-39-v0.7.0-dcc9d0e-macosm164.zip
.. _py310-macosm164: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-310-v0.7.0-dcc9d0e-macosm164.zip
.. _py311-macosm164: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-311-v0.7.0-dcc9d0e-macosm164.zip
.. _py36-linux64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-36-v0.7.0-9cc1f5f-linux64.zip
.. _py37-linux64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-37-v0.7.0-9cc1f5f-linux64.zip
.. _py38-linux64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-38-v0.7.0-9cc1f5f-linux64.zip
.. _py39-linux64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-39-v0.7.0-9cc1f5f-linux64.zip
.. _py310-linux64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-310-v0.7.0-9cc1f5f-linux64.zip
.. _py311-linux64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-311-v0.7.0-9cc1f5f-linux64.zip
.. _py36-win32: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-36-v0.7.0-9cc1f5f-win32.zip
.. _py37-win32: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-37-v0.7.0-9cc1f5f-win32.zip
.. _py38-win32: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-38-v0.7.0-9cc1f5f-win32.zip
.. _py39-win32: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-39-v0.7.0-9cc1f5f-win32.zip
.. _py310-win32: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-310-v0.7.0-9cc1f5f-win32.zip
.. _py311-win32: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-311-v0.7.0-9cc1f5f-win32.zip
.. _py36-win64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-36-v0.7.0-9cc1f5f-win64.zip
.. _py37-win64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-37-v0.7.0-9cc1f5f-win64.zip
.. _py38-win64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-38-v0.7.0-9cc1f5f-win64.zip
.. _py39-win64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-39-v0.7.0-9cc1f5f-win64.zip
.. _py310-win64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-310-v0.7.0-9cc1f5f-win64.zip
.. _py311-win64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-311-v0.7.0-9cc1f5f-win64.zip
.. _py36-macos64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-36-v0.7.0-9cc1f5f-macos64.zip
.. _py37-macos64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-37-v0.7.0-9cc1f5f-macos64.zip
.. _py38-macos64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-38-v0.7.0-9cc1f5f-macos64.zip
.. _py39-macos64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-39-v0.7.0-9cc1f5f-macos64.zip
.. _py310-macos64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-310-v0.7.0-9cc1f5f-macos64.zip
.. _py37-macosm164: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-37-v0.7.0-9cc1f5f-macosm164.zip
.. _py38-macosm164: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-38-v0.7.0-9cc1f5f-macosm164.zip
.. _py39-macosm164: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-39-v0.7.0-9cc1f5f-macosm164.zip
.. _py310-macosm164: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-310-v0.7.0-9cc1f5f-macosm164.zip
.. _py311-macosm164: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-311-v0.7.0-9cc1f5f-macosm164.zip
2. Unzip the downloaded file and copy the ``ifcopenshell`` directory into your
Python path. If you're not sure where your Python path is, run the following
@@ -0,0 +1,230 @@
Selector syntax
===============
A common task in querying IFC models is to filter or search for elements which
match particular criteria. For example, you might want to find all plasterboard
walls with a 2 hour fire rating on level 3.
Alternatively, you might want to fetch some data about a single element. For
example, you might want to fetch the fire rating property of an element, or the
type description of an element, or the net volume of a list of elements.
Once you've retreived your data, you might want to format it in some way. You
might want to ensure that all names are always uppercase. Or you might want to
take length values defined in feet, and apply imperial formatting such that it
shows both feet and inches including fractions.
These three usecases of filtering, getting a value, and formatting that value
are common and used in many utilities, such as in the BlenderBIM Add-on,
IfcCSV, IfcDiff, IfcClash, IfcPatch, and IfcFM.
IfcOpenShell provides a custom syntax to consistently and concisely describe
filters, value queries, and formatting rules.
Filtering elements
------------------
Filtering is typically used to select any IFC element or type.
.. code-block:: python
import ifcopenshell
import ifcopenshell.util.selector
model = ifcopenshell.open("model.ifc")
# Get all concrete walls and slabs.
ifcopenshell.util.selector.filter_elements(model, "IfcWall, IfcSlab, material=concrete")
.. csv-table::
:header: "Example Query", "Description"
"``IfcElement``", "All physical IfcElements including subclasses like walls, doors, windows, etc. Yep, that's it! Nothing else. Literally just ``IfcElement``."
"``IfcWall, IfcSlab``", "All walls and slabs. Technically, this is either a wall or a slab, but it's easier to describe it as all walls and slabs"
"``IfcWall, IfcSlab, material=concrete``", "All walls made out of concrete and slabs made out of concrete. The material checks any assigned IfcMaterial with a matching name or category attribute."
"``325Q7Fhnf67OZC$$r43uzK``", "A single element. Yep, just the GlobalId, nothing else! Easy."
"``325Q7Fhnf67OZC$$r43uzK, 2VlJ7nbF5AFfQQuRvSWexT``", "A bunch of arbitrary elements."
"``IfcWall, ! 325Q7Fhnf67OZC$$r43uzK``", "All walls except that one element."
"``IfcElement, ! IfcWall``", "All elements except for walls."
"``IfcDoor, Name=D01``", "Any doors named D01, notice how attributes match the IFC Attribute naming exactly"
"``IfcDoor, Name=/D[0-9]{2}/``", "Any doors with the naming scheme of D followed by two numbers:"
"``IfcWall, Pset_WallCommon.FireRating=2HR``", "Any 2 hour fire rated wall"
"``IfcWall, IfcColumn, IfcBeam, IfcFooting, /Pset_.*Common/.LoadBearing=TRUE``", "Any load bearing structure"
"``IfcElement, /Pset_.*Common/.FireRating != NULL``", "Any element with a fire rating property"
"``IfcWall, type=WT01, location=""Level 3""``", "Any walls of wall type WT01 on level 3 (we quote Level 3 since it has a space)"
"``IfcElement, classification=/Pr_.*/``", "Any maintainable product according to Uniclass tables"
"``IfcWall, IfcSlab, ! 325Q7Fhnf67OZC$$r43uzK, material=concrete, /Pset_.*Common/.FireRating=2HR``", "Notice how there are intuitive rules that class and instance filters are OR whereas other filters are AND So here is any wall or slab except that one element that has a material of concrete and has a 2 hour fire rating"
"``IfcSlab, material=concrete + IfcDoor``", "Finally, you can union facet lists together. So here is all concrete slabs, as well as all doors (regardless of concrete)"
"``IfcDoor, IfcWindow + IfcWall, IfcSlab, material=concrete + 325Q7Fhnf67OZC$$r43uzK``", "Here's another example of unioning facet groups. All doors and window, and all concrete walls and slabs, plus that one random element"
"``IfcPump, location=""Level 3""``", "Locations bubble up the hierarchy. So if a pump is in a space and that space is on Level 3, then you can say ""all pumps on level 3"" which will include that pump in the space."
The filter elements syntax works by specifying one or more groups of filters
separated by a ``+`` character. Each filter group will return a set of filtered
elements, and these are unioned together.
.. code-block::
filter_group[ + filter_group]*
A filter group consists of one or more filters separated by a ``,`` character.
The filters are chained and apply from left to right.
.. code-block::
filter[, filter]*
There are nine types of filters to choose from. Some of these filters will add
new elements to your filter group, and some will filter previously added
elements in your filter group based on their criteria.
.. csv-table::
:header: "Filter", "Type", "Usage", "Example"
"Class", "Add", "``[!] {{ifc_class_name}}``", "``IfcWall`` adds all IfcWall elements and their subclasses. ``! IfcWall`` subtracts all non-IfcWall elements from the filter group."
"GlobalId", "Add", "``[!] {{global_id}}``", "``325Q7Fhnf67OZC$$r43uzK`` adds the single element with that GlobalId attribute. ``! 325Q7Fhnf67OZC$$r43uzK`` subtracts that single element."
"Attribute", "Filter", "``{{name}}{{=}}{{value}}``", "``Name=Foo`` specifies the criteria that elements must have a ``Name`` attribute with a value of ``Foo``. Attribute names must be spelled exactly the same as in IFC, which means that they must start with an uppercase character."
"Property", "Filter", "``{{pset}}.{{prop}}{{=}}{{value}}``", "``Pset_WallCommon.FireRating=2HR`` specifies the criteria that elements must have a ``Pset_WallCommon`` property set, with a ``FireRating`` property within it with a value of ``2HR``. The property set name and the property name are separated by a ``.``."
"Type", "Filter", "``type{{=}}{{value}}``", "``type=Foo`` specifies the criteria that elements must have a type which has a ``Name`` attribute with a value of ``Foo``."
"Material", "Filter", "``material{{=}}{{value}}``", "``material=Foo`` specifies the criteria that elements must have a IfcMaterial assigned directly or indirectly (such as within a layer set). That IfcMaterial must have either a ``Name`` or ``Category`` attribute with a value of ``Foo``."
"Classification", "Filter", "``classification{{=}}{{value}}``", "``classification=Foo`` specifies the criteria that elements must have an IfcClassificationReference with an ``Identification`` attribute with a value of ``Foo``."
"Location", "Filter", "``location{{=}}{{value}}``", "``location=Foo`` specifies the criteria that elements must be contained directly or indirectly in a spatial element with a ``Name`` attribute with a value of ``Foo``."
"Query", "Filter", "``query:{{keys}}{{=}}{{value}}``", "``query:types.count=0`` specifies the criteria that elements must have zero type occurrences. The query keys corresponds to the syntax used in the `Getting element values`_ section"
When you specify a filter with a ``{{=}}`` check, you can choose from one of
the following comparison checks:
.. csv-table::
:header: "Comparison", "Description"
"``=``", "Must equal the value. The data type of the value is automatically converted to match."
"``!=``", "Must not equal the value."
"``>``", "Must be greater than the value."
"``>=``", "Must be greater than or equal to the value."
"``<``", "Must be less than the value."
"``<=``", "Must be less than or equal to the value."
When you specify a ``{{pset}}``, ``{{prop}}``, or ``{{value}}``, there are
three ways you can do so:
.. csv-table::
:header: "Value Type", "Example", "Description"
"Quoted string", "``""foo \""bar\"" baz""``", "The value must be in double quotes. The value may contain spaces, symbols, and other characters. If you need to use a double quote, you can escape it with a backslash. This is the safest, most general way to specify a value."
"Unquoted string", "``foobarbaz``", "For convenience, if you have a simple value which contains no spaces or special characters, you are free to specify it as an unquoted string."
"Regex string", "``/foo.*baz/``", "You may specify a Python-compatible regex pattern delimited by forward slashes."
Getting element values
----------------------
Given a single element, this syntax provides a simple way to extract a value
without needing to write complex code for it.
.. code-block:: python
import ifcopenshell
import ifcopenshell.util.selector
# Get the Name attribute of the wall's type.
ifcopenshell.util.selector.get_element_value(wall, "type.Name")
.. csv-table::
:header: "Example Query", "Description"
"``class``", "Get the IFC class of the element."
"``Name``", "Get the ``Name`` attribute."
"``Pset_WallCommon.Status``", "Get the value of the ``Status`` property in the ``Pset_WallCommon`` property set."
"``/Pset_.*Common/.Status``", "Get the value of the ``Status`` property in the any common property set."
"``type.Name``", "Get the ``Name`` attribute of the element's relating type."
"``types.count``", "Count the number of occurrences of a type."
"``storey.Name``", "Get the ``Name`` attribute of the storey that the element is contained in."
"``materials.count``", "Count the number of materials assigned to an element."
"``material.Name``", "Get the name of the assigned material."
"``material.item.0.Name``", "Get the name of the first item in a material set (e.g. the first material layer)"
The element value syntax works by specifying one or more query keys separated
by a ``.`` character. Each query key returns data based of the results of the
previous key.
.. code-block::
key[.key]*
Valid keys are:
.. csv-table::
:header: "Key", "Description"
"``id``", "Gets the IFC ID (equivalent to ``.id()``)"
"``class``", "Gets the IFC class (equivalent to ``.is_a()``)"
"``predefined_type``", "Gets the predefined type of the element, taking into account inheritance."
"``{{attribute}}``", "Gets the value of the attribute you specify. Attributes always start with an uppercase letter."
"``{{pset}}``", "This gets the property set with the same name specified in ``{{pset}}``. Note that this can be ambiguous with ``{{attribute}}``. If there is an ambiguity, ``{{attribute}}`` takes priority."
"``{{prop}}``", "If the previous key returns a property set, ``{{prop}}`` gets the value of a property with the same name specified in ``{{prop}}``. For this reason, often you specify both keys together, like this: ``{{pset}}.{{prop}}``."
"``type``", "Gets the relating type of an element occurrence."
"``types`` or ``occurrences``", "Gets the related objects of an element type."
"``container``", "Gets the immediate spatial element that an element is contained in."
"``space``", "Gets the first IfcSpace spatial element that an element is contained in."
"``storey``", "Gets the first IfcBuildingStorey spatial element that an element is contained in."
"``building``", "Gets the first IfcBuilding spatial element that an element is contained in."
"``site``", "Gets the first IccSite spatial element that an element is contained in."
"``material`` or ``mat``", "Gets the assigned material, which may be a material set."
"``item`` or ``i``", "If the previous key returns a material set, gets the relevant material set items"
"``materials`` or ``mats``", "Gets a list of IfcMaterials assigned directly or indirectly (such as via a material set) to the element"
"``count``", "If the previous key returns multiple things, count that list. Otherwise, return 1."
"``{{number}}``", "If the previous key returns multiple things, fetch the ``{{number}}`` index (e.g. 0, 1, 2, 3, etc) item in that list."
When you specify a ``{{pset}}`` or ``{{prop}}``, there are three ways you can
do so:
.. csv-table::
:header: "Value Type", "Example", "Description"
"Quoted string", "``""foo \""bar\"" baz""``", "The value must be in double quotes. The value may contain spaces, symbols, and other characters. If you need to use a double quote, you can escape it with a backslash. This is the safest, most general way to specify a value."
"Unquoted string", "``foobarbaz``", "For convenience, if you have a simple value which contains no spaces or special characters, you are free to specify it as an unquoted string."
"Regex string", "``/foo.*baz/``", "You may specify a Python-compatible regex pattern delimited by forward slashes."
Formatting
----------
Given a value, this syntax allows a simple way to specify a set of formatting
rules. This is useful for configuring outputs of how data should be presented.
.. code-block:: python
import ifcopenshell
import ifcopenshell.util.selector
# Get the Name attribute of the wall's type.
value = ifcopenshell.util.selector.get_element_value(wall, "type.Name")
# Always display names in uppercase.
ifcopenshell.util.selector.format(f'upper("{value}")')
Formatting queries are written similar to how you'd write functions or formulas
in spreadsheets. For example ``upper("foo")`` will produce ``FOO``. You may
nest formulas, for example ``concat(title("foo"), lower("Bar"))`` will produce
``Foobar``. Strings must be double quoted.
.. csv-table::
:header: "Function", "Example", "Result", "Description"
"``upper({{value}})``", "``upper(""Foo"")``", "``FOO``", "Uppercases a string."
"``lower({{value}})``", "``lower(""Foo"")``", "``foo``", "Lowercases a string."
"``title({{value}})``", "``title(""foo"")``", "``Foo``", "Titlecases a string."
"``concat({{value}}[, {{value2}}]*)``", "``concat(""foo"", ""bar"")``", "``foobar``", "Concatenates two or more strings."
"``round({{value}}, {{precision}})``", "``round(3.123, 0.1)``", "``3.1``", "Rounds ``{{value}}`` to the nearest ``{{precision}}``."
"``metric_length({{value}}, {{precision}}, {{decimals}})``", "``metric_length(3.123, 0.1, 2)``", "``3.10``", "Rounds ``{{value}}`` to the nearest ``{{precision}}`` then displays using a certain amount of decimal places."
"``imperial_length({{value}}, {{precision}}, {{unit}})``", "``imperial_length(3.22, 4, ""foot"")``", "``3' - 3 3/4""``", "Rounds ``{{value}}`` to the nearest ``1/{{precision}}`` inch then displays using fractional feet and inches. The ``{{value}}`` may be specified either as ``foot`` or ``inch`` depending on ``{{unit}}``."
@@ -136,13 +136,13 @@ operating systems. GCC (4.7 or newer) or Clang (any version) is required.
-DOCC_INCLUDE_DIR=/usr/include/ \
# Optional Collada support
-DCOLLADA_SUPPORT=On
-DCOLLADA_SUPPORT=On \
-DOPENCOLLADA_INCLUDE_DIR="/usr/local/include/opencollada" \
-DOPENCOLLADA_LIBRARY_DIR="/usr/local/lib/opencollada" \
-DPCRE_LIBRARY_DIR=/usr/lib/x86_64-linux-gnu/ \
# Optional HDF5 support
-DHDF5_SUPPORT=On
-DHDF5_SUPPORT=On \
-DHDF5_LIBRARIES="/usr/local/hdf5/lib/libhdf5_cpp.so;/usr/local/hdf5/lib/libhdf5.so;/usr/lib64/libz.so;/usr/lib64/libsz.so;/usr/lib64/libaec.so" \
-DHDF5_INCLUDE_DIR="/usr/local/hdf5/include" \
@@ -190,7 +190,7 @@ GCC (4.7 or newer) or Clang (any version) is required.
$ mkdir build && cd build
# set library flags
$ export LDFLAGS="$LDFLAGS -Wl,-flat_namespace,-undefined,suppress"
$ cmake ../cmake
$ cmake ../cmake \
-DPYTHON_EXECUTABLE=/opt/homebrew/bin/python3.10 \
-DPYTHON_LIBRARY=/opt/homebrew/opt/python@3.10/Frameworks/Python.framework/Versions/3.10/lib/libpython3.10.dylib \
-DPYTHON_INCLUDE_DIR=/opt/homebrew/opt/python@3.10/Frameworks/Python.framework/Versions/3.10/include/python3.10/ \
@@ -198,10 +198,10 @@ GCC (4.7 or newer) or Clang (any version) is required.
-DOCC_INCLUDE_DIR=/opt/homebrew/include/opencascade/ \
-DCGAL_INCLUDE_DIR=/opt/homebrew/include/ \
-DGMP_LIBRARY_DIR=/opt/homebrew/lib/ \
-DMPFR_LIBRARY_DIR=/opt/homebrew/lib/
-DMPFR_LIBRARY_DIR=/opt/homebrew/lib/ \
-DHDF5_LIBRARY_DIR=/opt/homebrew/lib/ \
-DHDF5_INCLUDE_DIR=/opt/homebrew/include/ \
-DCOLLADA_SUPPORT=0 \
-DCOLLADA_SUPPORT=0
# `sysctl -n hw.ncpu` returns the number of cpu cores on macOS
$ make -j$(sysctl -n hw.ncpu)
@@ -823,6 +823,8 @@ class Usecase:
def create_annotation2d_representation(self):
if isinstance(self.settings["geometry"], bpy.types.Mesh) and len(self.settings["geometry"].polygons):
items = self.create_annotation_fill_areas(is_2d=True)
elif isinstance(self.settings["geometry"], bpy.types.Mesh) and not len(self.settings["geometry"].edges):
return self.create_point_cloud_representation(is_2d=True)
else:
items = [self.file.createIfcGeometricCurveSet(self.create_curves(is_2d=True))]
return self.file.createIfcShapeRepresentation(
@@ -420,6 +420,15 @@ def process_expression(context):
exclude=[context.rel_op_extended],
)
else:
if len(context.simple_expression.branches()) == 2 and str(context.rel_op_extended) == 'in':
# IfcBlobTexture
try:
is_literal_str_list = set(map(type, ast.literal_eval(str(context.simple_expression.branches()[1])))) == {str}
except:
is_literal_str_list = False
if is_literal_str_list:
a, b = map(str, context.simple_expression.branches())
return f"{a}.lower() {str(context.rel_op_extended)} {b}"
return concat(context.rel_op_extended, context.simple_expression)
elif context.multiplication_like_op:
if str(context.multiplication_like_op.branches()[0]) == "||":
@@ -217,7 +217,14 @@ def run(f, logger):
check(value[0], S.declaration_by_name(value.is_a()), instance=inst)
for inst in f:
values = list(inst)
try:
values = list(inst)
except Exception as e:
if hasattr(logger, "set_state"):
logger.error(str(e))
else:
logger.error("For instance:\n %s\n%s", inst, e)
continue
entity = S.declaration_by_name(inst.is_a())
attrs = entity.all_attributes()
for i, (attr, val, is_derived) in enumerate(
@@ -20,6 +20,7 @@ import re
import lark
import ifcopenshell.util
import ifcopenshell.util.fm
import ifcopenshell.util.unit
import ifcopenshell.util.element
import ifcopenshell.util.classification
@@ -51,8 +52,8 @@ filter_elements_grammar = lark.Lark(
value: special | quoted_string | regex_string | unquoted_string
unquoted_string: /[^.=\\s]+/
quoted_string: ESCAPED_STRING
regex_string: "/" /[^\\/]+/ "/"
quoted_string: ESCAPED_STRING
special: null | true | false
@@ -93,30 +94,151 @@ filter_elements_grammar = lark.Lark(
)
get_element_grammar = lark.Lark(
"""start: WORD | ESCAPED_STRING | keys_regex | keys_quoted | keys_simple
keys_regex: "r" ESCAPED_STRING ("." ESCAPED_STRING)*
keys_quoted: ESCAPED_STRING ("." ESCAPED_STRING)*
keys_simple: /[^\\W][^.=<>!%*\\]]*/ ("." /[^\\W][^.=<>!%*\\]]*/)*
"""start: keys
keys: key ("." key)*
key: quoted_string | regex_string | unquoted_string
unquoted_string: /[^.=\\/\\s]+/
regex_string: "/" /[^\\/]+/ "/"
quoted_string: ESCAPED_STRING
// Embed common.lark for packaging
_STRING_INNER: /.*?/
_STRING_ESC_INNER: _STRING_INNER /(?<!\\\\)(\\\\\\\\)*?/
ESCAPED_STRING : "\\"" _STRING_ESC_INNER "\\""
WS: /[ \\t\\f\\r\\n]/+
%ignore WS // Disregard spaces in text
"""
)
format_grammar = lark.Lark(
"""start: function
function: round | format_length | lower | upper | title | concat | ESCAPED_STRING | NUMBER
round: "round(" function "," NUMBER ")"
format_length: metric_length | imperial_length
metric_length: "metric_length(" function "," NUMBER "," NUMBER ")"
imperial_length: "imperial_length(" function "," NUMBER ["," ESCAPED_STRING] ")"
lower: "lower(" function ")"
upper: "upper(" function ")"
title: "title(" function ")"
concat: "concat(" function ("," function)* ")"
// Embed common.lark for packaging
DIGIT: "0".."9"
HEXDIGIT: "a".."f"|"A".."F"|DIGIT
INT: DIGIT+
SIGNED_INT: ["+"|"-"] INT
DECIMAL: INT "." INT? | "." INT
_EXP: ("e"|"E") SIGNED_INT
FLOAT: INT _EXP | DECIMAL _EXP?
SIGNED_FLOAT: ["+"|"-"] FLOAT
NUMBER: FLOAT | INT
SIGNED_NUMBER: ["+"|"-"] NUMBER
_STRING_INNER: /.*?/
_STRING_ESC_INNER: _STRING_INNER /(?<!\\\\)(\\\\\\\\)*?/
ESCAPED_STRING : "\\"" _STRING_ESC_INNER "\\""
LCASE_LETTER: "a".."z"
UCASE_LETTER: "A".."Z"
LETTER: UCASE_LETTER | LCASE_LETTER
WORD: LETTER+
CNAME: ("_"|LETTER) ("_"|LETTER|DIGIT)*
WS_INLINE: (" "|/\\t/)+
WS: /[ \\t\\f\\r\\n]/+
CR : /\\r/
LF : /\\n/
NEWLINE: (CR? LF)+
%ignore WS // Disregard spaces in text
"""
"""
)
class FormatTransformer(lark.Transformer):
def start(self, args):
return args[0]
def function(self, args):
return args[0]
def ESCAPED_STRING(self, args):
return args[1:-1].replace("\\", "")
def NUMBER(self, args):
return str(args)
def lower(self, args):
return str(args[0]).lower()
def upper(self, args):
return str(args[0]).upper()
def title(self, args):
return str(args[0]).title()
def concat(self, args):
return "".join(args)
def round(self, args):
return str(round(float(args[0]) / float(args[1])) * float(args[1]))
def format_length(self, args):
return args[0]
def metric_length(self, args):
value, precision, decimal_places = args
return ifcopenshell.util.unit.format_length(
float(value), float(precision), int(decimal_places), unit_system="metric"
)
def imperial_length(self, args):
if len(args) == 2:
imperial_unit = "foot"
value, precision = args
else:
value, precision, imperial_unit = args
if imperial_unit == "inch":
imperial_unit = "inch"
else:
imperial_unit = "foot"
return ifcopenshell.util.unit.format_length(
float(value), int(precision), unit_system="imperial", imperial_unit=imperial_unit
)
class GetElementTransformer(lark.Transformer):
def start(self, args):
return args[0]
def keys(self, args):
return args
def key(self, args):
return args[0]
def quoted_string(self, args):
return str(args[0])
def regex_string(self, args):
return re.compile(args[0])
def unquoted_string(self, args):
return str(args[0])
def ESCAPED_STRING(self, args):
return args[1:-1].replace("\\", "")
def format(query):
return FormatTransformer().transform(format_grammar.parse(query))
def get_element_value(element, query):
start = get_element_grammar.parse(query)
filter_query = Selector.parse_filter_query(start.children[0])
return Selector.get_element_value(element, filter_query["keys"], filter_query["is_regex"])
keys = GetElementTransformer().transform(get_element_grammar.parse(query))
return Selector.get_element_value(element, keys)
def filter_elements(ifc_file, query, elements=None, edit_in_place=False):
@@ -129,13 +251,14 @@ def filter_elements(ifc_file, query, elements=None, edit_in_place=False):
def set_element_value(ifc_file, element, query, value):
start = get_element_grammar.parse(query)
filter_query = Selector.parse_filter_query(start.children[0])
keys = filter_query["keys"]
is_regex = filter_query["is_regex"]
if isinstance(query, (list, tuple)):
keys = query
else:
keys = GetElementTransformer().transform(get_element_grammar.parse(query))
for i, key in enumerate(keys):
key = key.strip()
if isinstance(key, str):
key = key.strip()
if element is None:
return
if key == "type":
@@ -164,7 +287,7 @@ def set_element_value(ifc_file, element, query, value):
elif key == "site":
element = ifcopenshell.util.element.get_container(element, ifc_class="IfcSite")
elif key == "class":
if element.is_a() != value:
if element.is_a().lower() != value.lower():
return ifcopenshell.util.schema.reassign_class(ifc_file, element, value)
elif key == "id":
return
@@ -172,42 +295,49 @@ def set_element_value(ifc_file, element, query, value):
if key == "Name" and element.is_a("IfcMaterialLayerSet"):
key = "LayerSetName" # This oddity in the IFC spec is annoying so we account for it.
if hasattr(element, key):
if isinstance(key, str) and hasattr(element, key):
if getattr(element, key) != value:
return setattr(element, key, value)
else:
# Try to extract pset
if is_regex:
if isinstance(key, re.Pattern):
psets = ifcopenshell.util.element.get_psets(element)
matching_psets = []
for pset_name, pset in psets.items():
if re.match(key, pset_name):
if key.match(pset_name):
matching_psets.append(pset)
result = matching_psets or None
if result and len(result) == 1:
result = result[0]
else:
result = ifcopenshell.util.element.get_pset(element, key)
if value and not result and len(keys) == i + 2: # The next key is the prop name
if "qto" in key.lower() or "quantity" in key.lower() or "quantities" in key.lower():
pset = ifcopenshell.api.run("pset.add_qto", ifc_file, product=element, name=key)
else:
pset = ifcopenshell.api.run("pset.add_pset", ifc_file, product=element, name=key)
result = {"id": pset.id()}
if value and not result and len(keys) == i + 2: # The next key is the prop name
if "qto" in key.lower() or "quantity" in key.lower() or "quantities" in key.lower():
pset = ifcopenshell.api.run("pset.add_qto", ifc_file, product=element, name=key)
else:
pset = ifcopenshell.api.run("pset.add_pset", ifc_file, product=element, name=key)
result = {"id": pset.id()}
element = result
elif isinstance(element, dict): # Such as from the result of a prior get_pset
pset = ifc_file.by_id(element["id"])
if is_regex:
if isinstance(key, re.Pattern):
for prop, prop_value in element.items():
if re.match(key, prop):
if key.match(prop):
if pset.is_a("IfcPropertySet") and prop_value != value:
ifcopenshell.api.run("pset.edit_pset", ifc_file, pset=pset, properties={prop: value})
elif pset.is_a("IfcElementQuantity") and prop_value != float(value):
ifcopenshell.api.run("pset.edit_qto", ifc_file, qto=pset, properties={prop: float(value)})
elif pset.is_a("IfcPropertySet") and element[key] != value:
elif pset.is_a("IfcPropertySet") and element.get(key, None) != value:
ifcopenshell.api.run("pset.edit_pset", ifc_file, pset=pset, properties={key: value})
elif pset.is_a("IfcElementQuantity") and element[key] != float(value):
ifcopenshell.api.run("pset.edit_qto", ifc_file, qto=pset, properties={key: float(value)})
elif pset.is_a("IfcElementQuantity"):
try:
value = float(value)
if element.get(key, None) != value:
ifcopenshell.api.run("pset.edit_qto", ifc_file, qto=pset, properties={key: value})
except:
pass
return
elif isinstance(element, (list, tuple)): # If we use regex
if key.isnumeric():
@@ -218,7 +348,7 @@ def set_element_value(ifc_file, element, query, value):
else:
results = []
for v in element:
cls.set_element_value(v, keys[i + 1 :], is_regex=is_regex)
cls.set_element_value(ifc_file, v, keys[i + 1 :], value)
return
@@ -612,7 +742,9 @@ class Selector:
elif token_type == "NULL":
value = None
for element in elements:
element_value = cls.get_element_value(element, filter_query["keys"], is_regex=filter_query["is_regex"])
if filter_query["is_regex"]:
filter_query["keys"] = [re.compile(k) for k in filter_query["keys"]]
element_value = cls.get_element_value(element, filter_query["keys"])
if element_value is None and value is not None and "not" not in comparison:
continue
if comparison and cls.filter_element(element, element_value, comparison, value, is_regex=is_regex):
@@ -637,10 +769,11 @@ class Selector:
return {"keys": keys, "is_regex": is_regex}
@classmethod
def get_element_value(cls, element, keys, is_regex=False):
def get_element_value(cls, element, keys):
value = element
for key in keys:
key = key.strip()
if isinstance(key, str):
key = key.strip()
if value is None:
return
if key == "type":
@@ -661,60 +794,73 @@ class Selector:
elif key == "container":
value = ifcopenshell.util.element.get_container(value)
elif key == "space":
value = ifcopenshell.util.element.get_container(element, ifc_class="IfcSpace")
value = ifcopenshell.util.element.get_container(value, ifc_class="IfcSpace")
elif key == "storey":
value = ifcopenshell.util.element.get_container(element, ifc_class="IfcBuildingStorey")
value = ifcopenshell.util.element.get_container(value, ifc_class="IfcBuildingStorey")
elif key == "building":
value = ifcopenshell.util.element.get_container(element, ifc_class="IfcBuilding")
value = ifcopenshell.util.element.get_container(value, ifc_class="IfcBuilding")
elif key == "site":
value = ifcopenshell.util.element.get_container(element, ifc_class="IfcSite")
elif key == "types":
value = ifcopenshell.util.element.get_types(element)
value = ifcopenshell.util.element.get_container(value, ifc_class="IfcSite")
elif key in ("types", "occurrences"):
value = ifcopenshell.util.element.get_types(value)
elif key == "count":
if isinstance(value, set):
value = len(list(value))
elif isinstance(value, (list, tuple)):
value = len(value)
value = 1
else:
value = 1
elif key == "class":
value = value.is_a()
elif key == "predefined_type":
value = ifcopenshell.util.element.get_predefined_type(value)
elif key == "id":
value = value.id()
elif isinstance(value, ifcopenshell.entity_instance):
if key == "Name" and value.is_a("IfcMaterialLayerSet"):
key = "LayerSetName" # This oddity in the IFC spec is annoying so we account for it.
attribute = getattr(value, key, None)
if isinstance(key, re.Pattern):
attribute = None # Should we support regex attributes? Probably not for now.
else:
attribute = getattr(value, key, None)
if attribute is not None:
value = attribute
else:
# Try to extract pset
if is_regex:
if isinstance(key, re.Pattern):
psets = ifcopenshell.util.element.get_psets(value)
matching_psets = []
for pset_name, pset in psets.items():
if re.match(key, pset_name):
if key.match(pset_name):
del pset["id"]
matching_psets.append(pset)
result = matching_psets or None
if result and len(result) == 1:
result = result[0]
else:
result = ifcopenshell.util.element.get_pset(value, key)
if result:
del result["id"]
value = result
elif isinstance(value, dict): # Such as from the result of a prior get_pset
if is_regex:
if isinstance(key, re.Pattern):
results = []
for prop_name, prop_value in value.items():
if re.match(key, prop_name):
if key.match(prop_name):
if isinstance(prop_value, (list, tuple)):
results.extend(prop_value)
else:
results.append(prop_value)
value = results
value = results or None
if value and len(value) == 1:
value = value[0]
else:
value = value.get(key, None)
elif isinstance(value, (list, tuple)): # If we use regex
if key.isnumeric():
if isinstance(key, str) and key.isnumeric():
try:
value = value[int(key)]
except IndexError:
@@ -722,7 +868,7 @@ class Selector:
else:
results = []
for v in value:
subvalue = cls.get_element_value(v, [key], is_regex=is_regex)
subvalue = cls.get_element_value(v, [key])
if isinstance(subvalue, list):
results.extend(subvalue)
else:
@@ -127,13 +127,16 @@ class ShapeBuilder:
def rectangle(self, size: Vector = Vector((1.0, 1.0)).freeze(), position: Vector = None):
"""
function supports both 2d and 3d rectangle sizes
Generate a rectangle polyline, method supports both 2d and 3d rectangle sizes.
:param size: rectangle size
:param type: Vector
:param size: rectangle position, default to `None`.
if `position` not specified zero-vector will be used
:param type: Vector, optional
returns IfcIndexedPolyCurve
:return: IfcIndexedPolyCurve
"""
# < IfcIndexedPolyCurve
return self.polyline(self.get_rectangle_coords(size, position), closed=True)
def circle(self, center: Vector = Vector((0.0, 0.0)).freeze(), radius=1.0):
@@ -572,9 +575,17 @@ class ShapeBuilder:
disk_solid = self.file.createIfcSweptDiskSolid(Directrix=path_curve, Radius=radius)
return disk_solid
def get_representation(self, context, items, representation_type=None):
# > items - could be a list or single curve/IfcExtrudedAreaSolid
# < IfcShapeRepresentation
def get_representation(self, context, items, representation_type:str = None):
"""Create IFC representation for the specified context and items.
:param context: IfcGeometricRepresentationSubContext
:param items: could be a list or single curve/IfcExtrudedAreaSolid
:param representation_type: Explicitly specified RepresentationType, defaults to `None`.
If not provided it will be guessed from the items types.
:type representation_type: str, optional
:return: IfcRepresentation
"""
if not isinstance(items, collections.abc.Iterable):
items = [items]
@@ -17,6 +17,7 @@
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
from math import pi
from fractions import Fraction
prefixes = {
"EXA": 1e18,
@@ -573,9 +574,10 @@ def format_length(
if imperial_unit == "foot":
feet = int(value)
inches = (value - feet) * 12
elif imperial_unit == "inch":
inches = value % 12
feet = int(round((value - inches) / 12))
inches = value * 12
# Round to the nearest 1/N
nearest = round(inches * precision)
@@ -585,14 +587,22 @@ def format_length(
# If fraction is a whole number, format it accordingly
if frac.denominator == 1:
if suppress_zero_inches and frac.numerator == 0:
return f"{feet}'"
return f"{feet}' - {frac.numerator}\""
if frac.numerator > frac.denominator:
if imperial_unit == "inch":
return f"{round(inches)}\""
if suppress_zero_inches:
if imperial_unit == "foot":
return f"{round(value)}'"
elif not suppress_zero_inches:
if imperial_unit == "foot":
return f"{round(value)}' - 0\""
if frac.numerator > frac.denominator and not frac.denominator == 0:
remainder = frac.numerator % frac.denominator
whole = int((frac.numerator - remainder) / frac.denominator)
return f"{feet}' - {whole} {remainder}/{frac.denominator}\""
return f"{feet}' - {frac.numerator}/{frac.denominator}\""
if imperial_unit == "foot":
return f"{feet}' - {whole} {remainder}/{frac.denominator}\""
elif imperial_unit == "inch":
return f"{whole} {remainder}/{frac.denominator}\""
elif unit_system == "metric":
rounded_val = round(value / precision) * precision
return f"{rounded_val:.{decimal_places}f}"
@@ -323,7 +323,8 @@ def validate(f, logger, express_rules=False):
f = ifcopenshell.open(f)
else:
raise e
logger.error(f'Unsupported schema: {schema_name}')
return
log_internal_cpp_errors(filename, logger)
@@ -406,7 +407,15 @@ def validate(f, logger, express_rules=False):
)
for attr in entity.all_inverse_attributes():
val = getattr(inst, attr.name())
try:
val = getattr(inst, attr.name())
except Exception as e:
if hasattr(logger, "set_state"):
logger.set_state('attribute', f"{entity.name()}.{attr.name()}")
logger.error(str(e))
else:
logger.error("For instance:\n %s\n%s", inst, e)
continue
try:
assert_valid_inverse(attr, val, schema)
except ValidationError as e:
@@ -22,6 +22,32 @@ import ifcopenshell.api
import ifcopenshell.util.selector as subject
class TestFormat():
def test_no_formatting(self):
assert subject.format("123") == "123"
assert subject.format('\"123\"') == "123"
assert subject.format('\"foo\"') == "foo"
def test_string_formatting(self):
assert subject.format('upper(\"fOo\")') == "FOO"
assert subject.format('lower(\"fOo\")') == "foo"
assert subject.format('title(\"fOo\")') == "Foo"
assert subject.format('concat(\"fOo\", \"bar\")') == "fOobar"
assert subject.format('upper(concat(\"fOo\", \"bar\"))') == "FOOBAR"
def test_number_formatting(self):
assert subject.format("round(123, 5)") == "125.0"
assert subject.format('round(\"123\", 5)') == "125.0"
assert subject.format('metric_length(123, 5, 2)') == "125.00"
assert subject.format('metric_length(123.123, 0.1, 2)') == "123.10"
assert subject.format('metric_length(\"123\", 5, 2)') == "125.00"
assert subject.format('imperial_length(1, 1)') == "1'"
assert subject.format('imperial_length(3.123, 1)') == "3' - 1\""
assert subject.format('imperial_length(3.123, 2)') == "3' - 1 1/2\""
assert subject.format('imperial_length(\"3.123\", 2)') == "3' - 1 1/2\""
assert subject.format('imperial_length(\"123.123\", 2, \"inch\")') == "10' - 3\""
class TestGetElementValue(test.bootstrap.IFC4):
def test_selecting_an_elements_class_or_id_using_a_query(self):
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
@@ -52,7 +78,7 @@ class TestGetElementValue(test.bootstrap.IFC4):
assert subject.get_element_value(element, "material.item.Name.0") == "L1"
assert subject.get_element_value(element, "material.item.Name.1") == "L2"
assert subject.get_element_value(element, '"material"."item"."Name"') == ["L1", "L2"]
assert subject.get_element_value(element, 'r"material"."item"."Name"') == ["L1", "L2"]
assert subject.get_element_value(element, 'material."item"."Name"') == ["L1", "L2"]
# Provide shortform for convenience
assert subject.get_element_value(element, "mat.i.Name") == ["L1", "L2"]
@@ -60,7 +86,7 @@ class TestGetElementValue(test.bootstrap.IFC4):
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
assert subject.get_element_value(element, "material.item.Name.0") is None
def test_selceting_a_list_item_that_fails_silently(self):
def test_selecting_a_list_item_that_fails_silently(self):
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
material = ifcopenshell.api.run("material.add_material", self.file, name="CON01")
material2 = ifcopenshell.api.run("material.add_material", self.file, name="CON02")
@@ -73,6 +99,24 @@ class TestGetElementValue(test.bootstrap.IFC4):
assert subject.get_element_value(element, "material.item.Name.0") == "L1"
assert subject.get_element_value(element, "material.item.Name.1") is None
def test_selecting_a_pset(self):
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
element2 = 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"})
assert subject.get_element_value(element, "Foobar.Foo") == "Bar"
assert subject.get_element_value(element, "Foobar./F.*/") == "Bar"
assert subject.get_element_value(element, "/Foo.*/./F.*/") == "Bar"
ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"Baz": 123})
assert subject.get_element_value(element, "/Foo.*/./B.*/") == 123
assert subject.get_element_value(element, "/Foo.*/./.*/") == ["Bar", 123]
ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"Bay": 123.3})
assert subject.get_element_value(element, "/Foo.*/./B.*/") == [123, 123.3]
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={"Status": ["New"]})
assert subject.get_element_value(element, "/Pset_.*Common/.Status") == ["New"]
assert subject.get_element_value(element, "/Pset_.*Common/.Status.0") == "New"
class TestFilterElements(test.bootstrap.IFC4):
def test_selecting_by_globalid(self):
+2
View File
@@ -168,6 +168,8 @@ class Specification:
def parse(self, ids_dict):
self.name = ids_dict.get("@name", "")
self.description = ids_dict.get("@description", "")
self.instructions = ids_dict.get("@instructions", "")
self.minOccurs = ids_dict["@minOccurs"]
self.maxOccurs = ids_dict["@maxOccurs"]
self.ifcVersion = ids_dict["@ifcVersion"]
+109 -21
View File
@@ -21,8 +21,11 @@ import sys
import math
import logging
import datetime
import numpy as np
import ifcopenshell
import ifcopenshell.util.unit
import ifcopenshell.util.element
import ifcopenshell.util.placement
cwd = os.path.dirname(os.path.realpath(__file__))
@@ -159,31 +162,85 @@ class Json(Reporter):
def report(self):
self.results["title"] = self.ids.info.get("title", "Untitled IDS")
self.results["date"] = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
total_specifications = 0
total_specifications_pass = 0
total_requirements = 0
total_requirements_pass = 0
total_checks = 0
total_checks_pass = 0
status = True
self.results["specifications"] = []
for specification in self.ids.specifications:
self.results["specifications"].append(self.report_specification(specification))
specification_report = self.report_specification(specification)
self.results["specifications"].append(specification_report)
total_specifications += 1
total_specifications_pass += 1 if specification_report["status"] else 0
total_requirements += len(specification_report["requirements"])
total_requirements_pass += len([r for r in specification_report["requirements"] if r["status"]])
total_checks += specification_report["total_checks"]
total_checks_pass += specification_report["total_checks_pass"]
if not specification_report["status"]:
status = False
self.results["status"] = status
self.results["total_specifications"] = total_specifications
self.results["total_specifications_pass"] = total_specifications_pass
self.results["total_specifications_fail"] = total_specifications - total_specifications_pass
self.results["percent_specifications_pass"] = (
math.floor((total_specifications_pass / total_specifications) * 100) if total_specifications else "N/A"
)
self.results["total_requirements"] = total_requirements
self.results["total_requirements_pass"] = total_requirements_pass
self.results["total_requirements_fail"] = total_requirements - total_requirements_pass
self.results["percent_requirements_pass"] = (
math.floor((total_requirements_pass / total_requirements) * 100) if total_requirements else "N/A"
)
self.results["total_checks"] = total_checks
self.results["total_checks_pass"] = total_checks_pass
self.results["total_checks_fail"] = total_checks - total_checks_pass
self.results["percent_checks_pass"] = (
math.floor((total_checks_pass / total_checks) * 100) if total_checks else "N/A"
)
return self.results
def report_specification(self, specification):
applicability = [a.to_string("applicability") for a in specification.applicability]
total_applicable = len(specification.applicable_entities)
total_checks = 0
total_checks_pass = 0
requirements = []
for requirement in specification.requirements:
total_fail = len(requirement.failed_entities)
total_checks += total_applicable
total_checks_pass += total_applicable - total_fail
requirements.append(
{
"description": requirement.to_string("requirement"),
"status": requirement.status,
"failed_entities": self.report_failed_entities(requirement),
"total_applicable": total_applicable,
"total_pass": total_applicable - total_fail,
"total_fail": total_fail,
}
)
total = len(specification.applicable_entities)
total_successes = total - len(specification.failed_entities)
percentage = math.floor((total_successes / total) * 100) if total else "N/A"
total_applicable_pass = total_applicable - len(specification.failed_entities)
percent_applicable_pass = (
math.floor((total_applicable_pass / total_applicable) * 100) if total_applicable else "N/A"
)
percent_checks_pass = math.floor((total_checks_pass / total_checks) * 100) if total_checks else "N/A"
return {
"name": specification.name,
"description": specification.description,
"instructions": specification.instructions,
"status": specification.status,
"total_successes": total_successes,
"total": total,
"percentage": percentage,
"total_applicable": total_applicable,
"total_applicable_pass": total_applicable_pass,
"total_applicable_fail": total_applicable - total_applicable_pass,
"percent_applicable_pass": percent_applicable_pass,
"total_checks": total_checks,
"total_checks_pass": total_checks_pass,
"total_checks_fail": total_checks - total_checks_pass,
"percent_checks_pass": percent_checks_pass,
"required": specification.minOccurs != 0,
"applicability": applicability,
"requirements": requirements,
@@ -223,12 +280,15 @@ class Html(Json):
self.results = {}
def report(self):
self.results["title"] = self.ids.info.get("title", "Untitled IDS")
self.results["time"] = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
self.results["specifications"] = []
for specification in self.ids.specifications:
self.results["specifications"].append(self.report_specification(specification))
return self.results
super().report()
entity_limit = 100
for spec in self.results["specifications"]:
for requirement in spec["requirements"]:
total = len(requirement["failed_entities"])
requirement["failed_entities"] = requirement["failed_entities"][0:entity_limit]
requirement["has_omitted"] = total > entity_limit
requirement["total_entities"] = total
requirement["total_omitted"] = total - entity_limit
def to_string(self):
import pystache
@@ -272,7 +332,7 @@ class Ods(Json):
table = Table(name=self.results["title"])
tr = TableRow()
for header in ["Specification", "Status", "Total Compliant", "Total Applicable", "Percentage Compliant"]:
for header in ["Specification", "Status", "Total Pass", "Total Checks", "Percentage Pass"]:
tc = TableCell(valuetype="string", stylename="h")
tc.addElement(P(text=header))
tr.addElement(tc)
@@ -284,9 +344,9 @@ class Ods(Json):
[
specification["name"],
"Pass" if specification["status"] else "Fail",
str(specification["total_successes"]),
str(specification["total"]),
str(specification["percentage"]),
str(specification["total_checks_pass"]),
str(specification["total_checks"]),
str(specification["percent_checks_pass"]),
]
)
@@ -309,7 +369,17 @@ class Ods(Json):
continue
table = Table(name=specification["name"])
tr = TableRow()
for header in ["Requirement", "Problem", "Element"]:
for header in [
"Requirement",
"Problem",
"Class",
"PredefinedType",
"Name",
"Description",
"GlobalId",
"Tag",
"Element",
]:
tc = TableCell(valuetype="string", stylename="h")
tc.addElement(P(text=header))
tr.addElement(tc)
@@ -321,6 +391,12 @@ class Ods(Json):
row = [
requirement["description"],
failure.get("reason", "No reason provided"),
failure["class"],
failure["predefined_type"],
failure["name"],
failure["description"],
failure["global_id"],
failure["tag"],
str(failure.get("element", "No element found")),
]
tr = TableRow()
@@ -347,6 +423,7 @@ class Bcf(Json):
def to_file(self, filepath):
from bcf.v2.bcfxml import BcfXml
unit_scale = None
bcfxml = BcfXml.create_new(self.results["title"])
for specification in self.results["specifications"]:
if specification["status"]:
@@ -356,11 +433,22 @@ class Bcf(Json):
continue
for failure in requirement["failed_entities"]:
element = failure["element"]
title = f"ID:[{element.id()}]/GUID:[{element.GlobalId}]/{element.is_a()}/"
title += getattr(element, "Name", None) or "Unnamed"
title += " - " + failure.get("reason", "No reason")
title_components = [
element.is_a(),
getattr(element, "Name", None) or "Unnamed",
failure.get("reason", "No reason"),
getattr(element, "GlobalId", ""),
getattr(element, "Tag", ""),
]
title = " - ".join(title_components)
description = f'{specification["name"]} - {requirement["description"]}'
topic = bcfxml.add_topic(title, description, "IfcTester")
if getattr(element, "ObjectPlacement", None):
placement = ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement)
if unit_scale is None:
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(element.wrapped_data.file)
location = [(o * unit_scale) + 5.0 for o in placement[:, 3][:3]]
viewpoint = topic.add_viewpoint_from_point_and_guids(np.array(location), element.GlobalId)
if element.is_a("IfcElement"):
topic.add_viewpoint(element)
bcfxml.save_project(filepath)
+118 -138
View File
@@ -26,173 +26,153 @@
<title>{{name}}</title>
<link href="https://fonts.googleapis.com/css?family=Comfortaa|Inconsolata|Open+Sans&display=swap" rel="stylesheet">
<style>
:root {
--green: #97cc64;
--light-green: #b6cca1;
--red: #fb5a3e;
--light-red: #fbb4a8;
}
body { font-family: 'Arial', sans-serif; padding: 10px 40px; }
section { padding: 15px; border-radius: 5px; border: 1px solid #eee; margin-bottom: 15px; }
section>h2 { margin-top: 0px; }
span.time { color: #999; font-style: italic; float: right; }
span.step-time { float: right; color: #555; font-size: 0.8em; font-style: italic; }
span.success { background-color: #97cc64; padding: 5px; border-radius: 5px; color: #FFF; font-weight: bold; }
span.failure { background-color: #fb5a3e; padding: 5px; border-radius: 5px; color: #FFF; font-weight: bold; }
p.failure { background-color: #fb5a3e; padding: 5px; border-radius: 5px; color: #fff; }
span.item { padding: 5px; border-radius: 5px; margin-right: 5px; border: 1px solid #eee; }
span.item.pass, span.item.fail { color: #FFF; font-weight: bold; border: 0px; }
p.fail { background-color: #fb5a3e; padding: 5px; border-radius: 5px; color: #fff; }
p.unspecified { background-color: #994f00; padding: 5px; border-radius: 5px; color: #fff; }
p.skipped { background-color: #8b8d8f; padding: 5px; border-radius: 5px; color: #fff; }
p.description { background-color: #eee; border-radius: 5px; padding: 20px; margin-left: auto; margin-right: auto; display: inline-block; font-weight: bold;}
li { padding: 10px; font-family: monospace; }
li.success { background-color: #b6cca1; color: #333; }
li.failure { background-color: #fbb4a8; color: #900; }
li.pass { background-color: var(--light-green); color: #333; }
li.fail { background-color: var(--light-red); color: #900; }
li.unspecified { background-color: #ffd37f; color: #a30; }
li.skipped { background-color: #f5f5f5; color: #333; }
li p { margin-bottom: 0px; }
footer { color: #999; font-size: 0.8em; }
header { text-align: center; }
hr { margin: 20px; margin-left: 0px; margin-right: 0px; border: none; border-top: 1px solid #ccc; }
details { user-select: none; }
details>summary span.icon { width: 24px; height: 24px; transition: all 0.3s; margin-left: auto; }
summary { display: flex; cursor: pointer; }
summary::-webkit-details-marker { display: none; }
* {box-sizing:border-box}
.container { width: 100%; background-color: #ddd; border-radius: 5px}
.completion { text-align: left; padding-top: 5px; padding-left: 5px; padding-bottom: 5px; color: white; }
.percent0 {width: 0%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent1 {width: 1%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent2 {width: 2%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent3 {width: 3%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent4 {width: 4%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent5 {width: 5%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent6 {width: 6%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent7 {width: 7%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent8 {width: 8%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent9 {width: 9%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent10 {width: 10%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent11 {width: 11%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent12 {width: 12%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent13 {width: 13%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent14 {width: 14%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent15 {width: 15%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent16 {width: 16%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent17 {width: 17%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent18 {width: 18%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent19 {width: 19%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent20 {width: 20%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent21 {width: 21%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent22 {width: 22%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent23 {width: 23%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent24 {width: 24%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent25 {width: 25%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent26 {width: 26%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent27 {width: 27%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent28 {width: 28%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent29 {width: 29%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent30 {width: 30%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent31 {width: 31%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent32 {width: 32%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent33 {width: 33%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent34 {width: 34%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent35 {width: 35%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent36 {width: 36%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent37 {width: 37%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent38 {width: 38%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent39 {width: 39%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent40 {width: 40%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent41 {width: 41%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent42 {width: 42%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent43 {width: 43%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent44 {width: 44%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent45 {width: 45%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent46 {width: 46%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent47 {width: 47%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent48 {width: 48%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent49 {width: 49%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent50 {width: 50%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent51 {width: 51%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent52 {width: 52%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent53 {width: 53%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent54 {width: 54%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent55 {width: 55%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent56 {width: 56%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent57 {width: 57%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent58 {width: 58%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent59 {width: 59%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent60 {width: 60%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent61 {width: 61%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent62 {width: 62%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent63 {width: 63%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent64 {width: 64%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent65 {width: 65%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent66 {width: 66%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent67 {width: 67%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent68 {width: 68%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent69 {width: 69%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent70 {width: 70%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent71 {width: 71%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent72 {width: 72%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent73 {width: 73%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent74 {width: 74%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent75 {width: 75%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent76 {width: 76%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent77 {width: 77%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent78 {width: 78%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent79 {width: 79%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent80 {width: 80%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent81 {width: 81%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent82 {width: 82%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent83 {width: 83%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent84 {width: 84%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent85 {width: 85%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent86 {width: 86%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent87 {width: 87%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent88 {width: 88%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent89 {width: 89%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent90 {width: 90%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent91 {width: 91%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent92 {width: 92%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent93 {width: 93%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent94 {width: 94%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent95 {width: 95%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent96 {width: 96%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent97 {width: 97%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent98 {width: 98%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent99 {width: 99%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent100 {width: 100%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
summary { display: flex; cursor: pointer; }
summary::-webkit-details-marker { display: none; }
* {box-sizing:border-box}
.container { width: 100%; background-color: #ddd; border-radius: 5px}
.percent { text-align: left; padding-top: 5px; padding-left: 5px; padding-bottom: 5px; color: white; border-radius: 5px; white-space: nowrap; }
.pass { background-color: var(--green); }
.fail { background-color: var(--red); }
table { width: 100%; border-bottom: 2px solid var(--red); border-spacing: 0; border-radius: 5px; margin-top: 10px; }
th, td { padding: 5px; }
thead>tr { background-color: var(--red); font-weight: bold; color: #fff; }
tbody tr { border-bottom: 1px solid var(--red); }
tbody tr:nth-child(odd) { background-color: rgba(1, 1, 1, 0.05); }
tbody tr:nth-child(even) { background-color: rgba(1, 1, 1, 0.1); }
tbody tr:hover { background-color: rgba(0, 0, 0, 0); }
</style>
</head>
<body>
<header>
<h1>{{title}}</h1>
<p><strong>{{time}}</strong></p>
<p><strong>{{date}}</strong></p>
</header>
<h2>Summary</h2>
<div class="container">
<div class="{{#status}}pass{{/status}}{{^status}}fail{{/status}} percent" style="width: {{percent_checks_pass}}%;">{{percent_checks_pass}}%</div>
</div>
<p>
<span class="item {{#status}}pass{{/status}}{{^status}}fail{{/status}}">{{#status}}Pass{{/status}}{{^status}}Fail{{/status}}</span>
<span class="item">
Specifications passed: <strong>{{total_specifications_pass}}</strong> / <strong>{{total_specifications}}</strong>
</span>
<span class="item">
Requirements passed: <strong>{{total_requirements_pass}}</strong> / <strong>{{total_requirements}}</strong>
</span>
<span class="item">
Checks passed: <strong>{{total_checks_pass}}</strong> / <strong>{{total_checks}}</strong>
</span>
</p>
<hr>
{{#specifications}}
<section>
<h2>{{name}}</h2>
{{#description}}
<p>{{description}}</p>
{{/description}}
{{#instructions}}
<p><em>{{instructions}}</em></p>
{{/instructions}}
<div class="container">
<div class="completion percent{{percentage}}">{{percentage}}%</div>
<div class="{{#status}}pass{{/status}}{{^status}}fail{{/status}} percent" style="width: {{percent_checks_pass}}%;">{{percent_checks_pass}}%</div>
</div>
<details>
<summary>
<p>
<span class="{{#status}}success{{/status}}{{^status}}failure{{/status}}">{{#status}}Pass{{/status}}{{^status}}Fail{{/status}}</span>
Passed: <strong>{{total_successes}} / {{total}}</strong> ({{percentage}}%) - click here to see details
</p>
</summary>
<ol>
{{#requirements}}
<li class="{{#status}}success{{/status}}{{^status}}failure{{/status}}">
{{description}}
{{^status}}{{#total}}
<p class="failure{{#is_unspecified}} unspecified{{/is_unspecified}}{{#is_skipped}} skipped{{/is_skipped}}">
{{#failed_entities}}
{{reason}} <em style="color: #fbb4a8;">{{element}}</em><br />
{{/failed_entities}}
</p>
{{/total}}{{/status}}
</li>
{{/requirements}}
</ol>
</details>
<p>
<span class="item {{#status}}pass{{/status}}{{^status}}fail{{/status}}">{{#status}}Pass{{/status}}{{^status}}Fail{{/status}}</span>
<span class="item">
Checks passed: <strong>{{total_checks_pass}}</strong> / <strong>{{total_checks}}</strong>
</span>
<span class="item">
Elements passed: <strong>{{total_applicable_pass}}</strong> / <strong>{{total_applicable}}</strong>
</span>
</p>
<p>
<strong>Applicability</strong>
</p>
<ul>
{{#applicability}}
<li>{{.}}</li>
{{/applicability}}
</ul>
<p>
<strong>Requirements</strong>
</p>
<ol>
{{#requirements}}
<li class="{{#status}}pass{{/status}}{{^status}}fail{{/status}}">
<details>
<summary>
{{description}}
</summary>
{{#total_fail}}
<table>
<thead>
<tr>
<th>Class</th>
<th>PredefinedType</th>
<th>Name</th>
<th>Description</th>
<th>Warning</th>
<th>GlobalId</th>
<th>Tag</th>
</tr>
</thead>
<tbody>
{{#failed_entities}}
<tr>
<td>{{class}}</td>
<td>{{predefined_type}}</td>
<td>{{name}}</td>
<td>{{description}}</td>
<td>{{reason}}</td>
<td>{{global_id}}</td>
<td>{{tag}}</td>
</tr>
{{/failed_entities}}
{{#has_omitted}}
<tr>
<td colspan="7"> ... {{total_omitted}} more elements not shown out of {{total_entities}} total ...</td>
</tr>
{{/has_omitted}}
</tbody>
</table>
{{/total_fail}}
</details>
</li>
{{/requirements}}
</ol>
</section>
{{/specifications}}
<hr>
<footer>
<p>
Report by <a href="https://blenderbim.org/">BlenderBIM</a> and <a href="http://ifcopenshell.org/">IfcOpenShell</a>.
Report by the <a href="https://blenderbim.org/">BlenderBIM Add-on</a> and <a href="http://ifcopenshell.org/">IfcOpenShell</a>.
</p>
</footer>
</body>
+39 -13
View File
@@ -20,22 +20,48 @@
cmake_minimum_required(VERSION 3.1.3)
message("Running CMakeLists.txt in /src/qtviewer")
message("Provide the path to Qt5 via CMAKE_PREFIX_PATH")
find_package(Qt5 COMPONENTS Core Gui OpenGL Widgets REQUIRED)
# Specify the Qt version and components to use
set(QT_VERSION 6 CACHE STRING "Qt version")
set(QT_COMPONENTS Core Gui OpenGL OpenGLWidgets Widgets CACHE STRING "Qt components")
add_executable(QtViewer
../../src/qtviewer/mainwindow.h
../../src/qtviewer/mainwindow.cpp
../../src/qtviewer/main.cpp
find_package(Qt${QT_VERSION} COMPONENTS ${QT_COMPONENTS} REQUIRED PATHS ${QT_DIR})
if(Qt${QT_VERSION}_FOUND)
message(STATUS "Found Qt Version: ${Qt${QT_VERSION}_VERSION}")
endif()
set(targetName "QtViewer")
add_executable(${targetName}
IfcViewerWidget.h
IfcViewerWidget.cpp
ParseIfcFile.h
ParseIfcFile.cpp
MainWindow.h
MainWindow.cpp
main.cpp
)
set_target_properties(QtViewer PROPERTIES
AUTOMOC On
set_target_properties(${targetName} PROPERTIES
AUTOMOC On
WIN32_EXECUTABLE ON
MACOSX_BUNDLE ON
)
target_link_libraries(QtViewer
${IFCLIBS}
Qt5::Core Qt5::Gui Qt5::OpenGL Qt5::Widgets
${OPENCASCADE_LIBRARIES}
)
target_link_libraries(${targetName}
${IFCOPENSHELL_LIBRARIES}
Qt${QT_VERSION}::Core
Qt${QT_VERSION}::Gui
Qt${QT_VERSION}::OpenGL
Qt${QT_VERSION}::OpenGLWidgets
Qt${QT_VERSION}::Widgets
${OPENCASCADE_LIBRARIES}
)
target_include_directories(${targetName} PUBLIC
${QT_DIR}/include
)
get_target_property(targetIncludeDirs ${targetName} INCLUDE_DIRECTORIES)
message(STATUS "target_include_directories: ${targetIncludeDirs}")
+27
View File
@@ -0,0 +1,27 @@
#include "IfcViewerWidget.h"
IfcViewerWidget::IfcViewerWidget(QWidget *parent)
: QOpenGLWidget(parent)
{}
void IfcViewerWidget::initializeGL()
{
// Set up the rendering context, load shaders and other resources, etc.:
QOpenGLFunctions *f = QOpenGLContext::currentContext()->functions();
f->glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
}
void IfcViewerWidget::resizeGL(int w, int h)
{
// Update projection matrix and other size related settings:
m_projection.setToIdentity();
m_projection.perspective(45.0f, w / float(h), 0.01f, 100.0f);
}
void IfcViewerWidget::paintGL()
{
// Render geometries from the parsed IFC file
// Draw the scene:
QOpenGLFunctions *f = QOpenGLContext::currentContext()->functions();
f->glClear(GL_COLOR_BUFFER_BIT);
}
+23
View File
@@ -0,0 +1,23 @@
#ifndef IFCVIEWERWIDGET_H
#define IFCVIEWERWIDGET_H
#include <QOpenGLWidget>
#include <QOpenGLFunctions>
#include <QOpenGLContext>
#include <QMatrix4x4>
class IfcViewerWidget : public QOpenGLWidget
{
public:
IfcViewerWidget(QWidget *parent = nullptr);
protected:
void initializeGL() override;
void resizeGL(int w, int h) override;
void paintGL() override;
private:
QMatrix4x4 m_projection;
};
#endif // IFCVIEWERWIDGET_H
+127
View File
@@ -0,0 +1,127 @@
#include <QCoreApplication>
#include <QMenuBar>
#include <QMenu>
#include <QAction>
#include <QFileDialog>
#include <QFileInfo>
#include <QFile>
#include <QMessageBox>
#include <QSplitter>
#include <sstream>
#include "MainWindow.h"
#include "IfcViewerWidget.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
this->setWindowTitle("IfcOpenShell Viewer");
this->resize(800, 600); //temporary reasonable initial size
m_glWidget = new IfcViewerWidget(this);
QSizePolicy glSizePolicy = m_glWidget->sizePolicy();
glSizePolicy.setVerticalStretch(3);
m_glWidget->setSizePolicy(glSizePolicy);
m_outputText = new QPlainTextEdit(this);
m_outputText->setReadOnly(true);
QSplitter* splitter = new QSplitter(Qt::Vertical, this);
splitter->addWidget(m_glWidget);
splitter->addWidget(m_outputText);
setCentralWidget(splitter);
createActions();
createMenus();
createConnections();
}
void MainWindow::createActions()
{
openAction = new QAction(tr("&Open"), this);
openAction->setShortcut(QKeySequence(tr("Ctrl+O")));
quitAction = new QAction(tr("E&xit"), this);
quitAction->setShortcut(QKeySequence::Quit);
m_backgroundAction = new QAction(tr("&Background"));
m_backgroundAction->setEnabled(false);
m_backgroundAction->setCheckable(true);
m_backgroundAction->setChecked(false);
m_outlineAction = new QAction(tr("&Outline"));
m_outlineAction->setEnabled(false);
m_outlineAction->setCheckable(true);
m_outlineAction->setChecked(false);
}
void MainWindow::createMenus()
{
QMenuBar *menuBar = new QMenuBar();
setMenuBar(menuBar);
fileMenu = new QMenu(tr("&File"), menuBar);
menuBar->addMenu(fileMenu);
fileMenu->addAction(openAction);
fileMenu->addAction(quitAction);
viewMenu = new QMenu(tr("&View"), menuBar);
menuBar->addMenu(viewMenu);
viewMenu->addAction(m_backgroundAction);
viewMenu->addAction(m_outlineAction);
}
void MainWindow::createConnections()
{
connect(openAction, SIGNAL(triggered()), this, SLOT(openFile()));
connect(quitAction, SIGNAL(triggered()), qApp, SLOT(quit()));
//connect(m_backgroundAction, SIGNAL(toggled(bool)), (QWidget*)m_v, SLOT(setViewBackground(bool)));
//connect(m_outlineAction, SIGNAL(toggled(bool)), (QWidget*)m_v, SLOT(setViewOutline(bool)));
connect(&m_parser, &ParseIfcFile::parsingInfo, this, &MainWindow::appendToOutputText);
}
void MainWindow::appendToOutputText(const QString& message)
{
m_outputText->appendPlainText(message);
}
void MainWindow::openFile()
{
QString filePath = QFileDialog::getOpenFileName(this, tr("Open IFC File"),
".", tr("IFC Files (*.ifc)"));
QFileInfo fileInfo(filePath);
QString fileName = fileInfo.fileName();
if (fileName.isEmpty())
return;
// Check if the file is a Qt resource file
if (fileName.startsWith(":/"))
return;
QFile file(filePath);
if (!file.exists()) {
QMessageBox::critical(this, tr("Open IFC"),
QString("Could not open '%1'.").arg(filePath));
m_outlineAction->setEnabled(false);
m_backgroundAction->setEnabled(false);
return;
}
QString message = tr("Opening file: %1\n").arg(filePath);
appendToOutputText(message);
m_parser.Parse(filePath.toStdString());
m_currentPath = filePath;
setWindowTitle(tr("%1 - IFCViewer").arg(fileName));
m_outlineAction->setEnabled(true);
m_backgroundAction->setEnabled(true);
}
+49
View File
@@ -0,0 +1,49 @@
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QObject>
#include <QMainWindow>
#include <QString>
#include <QAction>
#include <QOpenGLWidget>
#include <QPlainTextEdit>
#include "ParseIfcFile.h"
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
public slots:
void openFile();
private:
void createActions();
void createMenus();
void createConnections();
void appendToOutputText(const QString& message);
private:
QMenu *fileMenu;
QAction *openAction;
QAction *quitAction;
QMenu *viewMenu;
QAction *m_nativeAction;
QAction *m_glAction;
QAction *m_imageAction;
QAction *m_highQualityAntialiasingAction;
QAction *m_backgroundAction;
QAction *m_outlineAction;
QString m_currentPath;
QOpenGLWidget *m_glWidget;
QPlainTextEdit *m_outputText;
ParseIfcFile m_parser;
};
#endif // MAINWINDOW_H
+42
View File
@@ -0,0 +1,42 @@
#include "ParseIfcFile.h"
#include "../ifcgeom_schema_agnostic/IfcGeomIterator.h"
ParseIfcFile::ParseIfcFile() {}
ParseIfcFile::~ParseIfcFile() {}
void ParseIfcFile::outputMsg(const std::string& msg)
{
emit parsingInfo(QString::fromStdString(msg));
}
void ParseIfcFile::Parse(const std::string& filePath)
{
IfcParse::IfcFile file(filePath);
IfcGeom::IteratorSettings settings;
settings.set(IfcGeom::IteratorSettings::USE_WORLD_COORDS, true);
settings.set(IfcGeom::IteratorSettings::WELD_VERTICES, false);
IfcGeom::Iterator* it = new IfcGeom::Iterator(settings, &file);
if (!it->initialize()) {
outputMsg("Error: Iterator failed to initialize! Aborting.");
delete it;
return;
}
do {
//const IfcGeom::BRepElement* bRepElem = it->get_native();
const IfcGeom::TriangulationElement* triElem = static_cast<const IfcGeom::TriangulationElement*>(it->get());
outputMsg(triElem->type() + ": " + triElem->name());
const boost::shared_ptr<IfcGeom::Representation::Triangulation>& triElemGeom = triElem->geometry_pointer();
// materials
const std::vector<IfcGeom::Material>& elemMats = triElemGeom->materials();
for (auto mat : elemMats) {
outputMsg(" " + mat.original_name());
}
} while (it->next());
}
+25
View File
@@ -0,0 +1,25 @@
#ifndef PARSEIFCFILE_H
#define PARSEIFCFILE_H
#include <QObject>
#include <QString>
#include <string>
class ParseIfcFile : public QObject
{
Q_OBJECT
signals:
void parsingInfo(const QString& info);
private:
void outputMsg(const std::string& msg);
public:
ParseIfcFile();
~ParseIfcFile();
void Parse(const std::string& filePath);
};
#endif // PARSEIFCFILE_H
+5 -17
View File
@@ -1,24 +1,12 @@

#include "MainWindow.h"
#include <QApplication>
#include <QString>
#ifndef QT_NO_OPENGL
#include <QGLFormat>
#endif
#include "mainwindow.h"
int main(int argc, char **argv)
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
//QGLViewer viewer;
// Restore the previous viewer state.
//viewer.restoreStateFromFile();
MainWindow window;
//window.openFile(" ");
QApplication app(argc, argv);
MainWindow window;
window.show();
return app.exec();
-97
View File
@@ -1,97 +0,0 @@

#include "mainwindow.h"
#include <math.h>
#include <QtGui>
MainWindow::MainWindow()
: QMainWindow()
{
IfcGeomObjects::Settings(IfcGeomObjects::USE_WORLD_COORDS,true);
IfcGeomObjects::Settings(IfcGeomObjects::WELD_VERTICES,false);
IfcGeomObjects::Settings(IfcGeomObjects::SEW_SHELLS,true);
QMenu *fileMenu = new QMenu(tr("&File"), this);
QAction *openAction = fileMenu->addAction(tr("&Open..."));
openAction->setShortcut(QKeySequence(tr("Ctrl+O")));
QAction *quitAction = fileMenu->addAction(tr("E&xit"));
quitAction->setShortcuts(QKeySequence::Quit);
menuBar()->addMenu(fileMenu);
QMenu *viewMenu = new QMenu(tr("&View"), this);
m_backgroundAction = viewMenu->addAction(tr("&Background"));
m_backgroundAction->setEnabled(false);
m_backgroundAction->setCheckable(true);
m_backgroundAction->setChecked(false);
//connect(m_backgroundAction, SIGNAL(toggled(bool)), (QWidget*)m_v, SLOT(setViewBackground(bool)));
m_outlineAction = viewMenu->addAction(tr("&Outline"));
m_outlineAction->setEnabled(false);
m_outlineAction->setCheckable(true);
m_outlineAction->setChecked(true);
// connect(m_outlineAction, SIGNAL(toggled(bool)), (QWidget*)m_v, SLOT(setViewOutline(bool)));
menuBar()->addMenu(viewMenu);
connect(openAction, SIGNAL(triggered()), this, SLOT(openFile()));
connect(quitAction, SIGNAL(triggered()), qApp, SLOT(quit()));
//setCentralWidget((QWidget*)m_v);
setWindowTitle(tr("IfcOpenShell QT Viewer"));
}
void MainWindow::openFile(const QString &path)
{
QString fileName;
if (path.isNull())
fileName = QFileDialog::getOpenFileName(this, tr("Open IFC"),
m_currentPath, "IFC files (*.ifc)");
else
fileName = path;
if (!fileName.isEmpty()) {
QFile file(fileName);
if (!file.exists()) {
QMessageBox::critical(this, tr("Open IFC"),
QString("Could not open file '%1'.").arg(fileName));
m_outlineAction->setEnabled(false);
m_backgroundAction->setEnabled(false);
return;
}
std::stringstream ss;
if ( ! IfcGeomObjects::Init(fileName.toStdString(),&std::cout,&ss) ) {
QMessageBox::critical(this, tr("Open IFC"),
QString("[Error] unable to parse file '%1'. Or no geometrical entities found" ).arg(fileName));
return;
}
//connect((QWidget*)m_view, SIGNAL(drawNeeded()), this, SLOT(drawIfcObject()));
if (!fileName.startsWith(":/")) {
m_currentPath = fileName;
setWindowTitle(tr("%1 - IFCViewer").arg(m_currentPath));
}
m_outlineAction->setEnabled(true);
m_backgroundAction->setEnabled(true);
// resize(m_view->sizeHint() + QSize(80, 80 + menuBar()->height()));
}
}
void MainWindow::draw()
{
}
-47
View File
@@ -1,47 +0,0 @@

#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <qobject.h>
#include <QMainWindow>
#include <QString>
#include "../ifcgeom/IfcGeomObjects.h"
class ObjectsView;
class QGLViewer;
QT_BEGIN_NAMESPACE
class QAction;
class QGraphicsView;
class QGraphicsScene;
class QGraphicsRectItem;
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow();
public Q_SLOTS:
void draw();
public slots:
void openFile(const QString &path = QString());
private:
QAction *m_nativeAction;
QAction *m_glAction;
QAction *m_imageAction;
QAction *m_highQualityAntialiasingAction;
QAction *m_backgroundAction;
QAction *m_outlineAction;
QString m_currentPath;
};
#endif
+36 -9
View File
@@ -44,6 +44,9 @@ static const uint32_t PRIM_TRIANGLES = 4;
static const uint32_t PRIM_TRIANGLE_STRIP = 5;
static const uint32_t PRIM_TRIANGLE_FAN = 6;
static const uint32_t ELEMENT_ARRAY_BUFFER = 34963;
static const uint32_t ARRAY_BUFFER = 34962;
GltfSerializer::GltfSerializer(const std::string& filename, const SerializerSettings& settings)
: WriteOnlyGeometrySerializer(settings)
, filename_(filename)
@@ -52,6 +55,7 @@ GltfSerializer::GltfSerializer(const std::string& filename, const SerializerSett
, fstream_(IfcUtil::path::from_utf8(filename).c_str(), std::ios_base::binary)
, tmp_fstream1_(IfcUtil::path::from_utf8(tmp_filename1_).c_str(), std::ios_base::binary)
, tmp_fstream2_(IfcUtil::path::from_utf8(tmp_filename2_).c_str(), std::ios_base::binary)
, bufferViewId(0)
{}
GltfSerializer::~GltfSerializer() {
@@ -121,17 +125,25 @@ const uint32_t component_type<int>::value = CT_UNSIGNED_INT;
template <>
const uint32_t component_type<float>::value = CT_FLOAT;
template <size_t N, typename It>
size_t write_accessor(json& j, std::ofstream& ofs, It begin, It end) {
size_t write_accessor(json& j, std::ofstream& ofs, It begin, It end, int bufferViewId) {
auto num = std::distance(begin, end) / N;
json accessor = json::object();
accessor["bufferView"] = N == 1 ? 0 : 1;
accessor["byteOffset"] = (size_t)ofs.tellp();
accessor["bufferView"] = bufferViewId;
accessor["byteOffset"] = 0;
accessor["componentType"] = component_type<typename It::value_type>::value;
accessor["count"] = num;
if (N == 1) {
j["bufferViews"].push_back({ {"buffer", 0}, {"byteOffset", (size_t)ofs.tellp()}, { "byteLength", num * 4}, {"target", ELEMENT_ARRAY_BUFFER} });
} else {
j["bufferViews"].push_back({ {"buffer", 0}, {"byteStride", 12}, { "byteOffset", (size_t)ofs.tellp()}, { "byteLength", num * 12}, {"target", ARRAY_BUFFER}});
}
std::array<typename It::value_type, N> min, max;
min.fill(std::numeric_limits<typename It::value_type>::max());
max.fill(std::numeric_limits<typename It::value_type>::lowest());
@@ -233,16 +245,16 @@ void GltfSerializer::write(const IfcGeom::TriangulationElement* o) {
json primitive = json::object();
primitive["indices"] = write_accessor<1U>(json_, tmp_fstream1_, idx_transformed.begin(), idx_transformed.end());
primitive["indices"] = write_accessor<1U>(json_, tmp_fstream1_, idx_transformed.begin(), idx_transformed.end(), bufferViewId++);
auto vbegin = o->geometry().verts().begin();
std::vector<float> vf(vbegin + idx_begin * 3, vbegin + idx_end * 3);
primitive["attributes"]["POSITION"] = write_accessor<3U>(json_, tmp_fstream2_, vf.begin(), vf.end());
primitive["attributes"]["POSITION"] = write_accessor<3U>(json_, tmp_fstream2_, vf.begin(), vf.end(), bufferViewId++);
if (o->geometry().normals().size()) {
auto nbegin = o->geometry().normals().begin();
std::vector<float> nf(nbegin + idx_begin * 3, nbegin + idx_end * 3);
primitive["attributes"]["NORMAL"] = write_accessor<3U>(json_, tmp_fstream2_, nf.begin(), nf.end());
primitive["attributes"]["NORMAL"] = write_accessor<3U>(json_, tmp_fstream2_, nf.begin(), nf.end(), bufferViewId++);
}
primitive["material"] = writeMaterial(o->geometry().materials()[*mid0]);
@@ -325,23 +337,38 @@ void GltfSerializer::finalize() {
json scene_0;
scene_0["nodes"] = node_array_;
json_["scenes"].push_back(scene_0);
json_["bufferViews"].push_back({ {"buffer", 0}, { "byteLength", indices_length } });
json_["bufferViews"].push_back({ {"buffer", 0}, {"byteStride", 12}, { "byteOffset", indices_length }, { "byteLength", binary_length - indices_length } });
//The generated glb file will contain the indices buffer followed by the vertices buffer.
//Therefore once we know the size of the indices buffer, we update our vertices buffer
//to have an offset equal to the size of the indices buffer.
for (auto &n : json_["bufferViews"]) {
if (n.contains("byteStride")) {
n["byteOffset"] = (int)n["byteOffset"] + indices_length;
}
}
json_["buffers"].push_back({ {"byteLength", binary_length} });
std::string json_contents = json_.dump();
uint32_t json_length = (uint32_t) json_contents.size();
uint32_t header[] = { GLTF, 2U, 12 + 8 + json_length + padding_for(json_length) + 8 + binary_length + padding_for(binary_length) };
const int GLB_FILE_HEADER = 12;
const int GLB_JSON_HEADER = 8;
const int GLB_BINARY_CHUNK_HEADER = 8;
uint32_t header[] = { GLTF, 2U, GLB_FILE_HEADER + GLB_JSON_HEADER + json_length + padding_for(json_length) +
GLB_BINARY_CHUNK_HEADER + binary_length + padding_for(binary_length) };
fstream_.write((const char*)header, sizeof(header));
write_block<JSON>(fstream_, json_contents.begin(), json_contents.end());
write_header<BIN>(fstream_, binary_length);
{
//First, write the indices buffer into our glb file
std::ifstream ifs(IfcUtil::path::from_utf8(tmp_filename1_).c_str(), std::ios::binary);
fstream_ << ifs.rdbuf();
}
{
//Next, write the vertices buffer into our glb file
std::ifstream ifs(IfcUtil::path::from_utf8(tmp_filename2_).c_str(), std::ios::binary);
fstream_ << ifs.rdbuf();
}
+2
View File
@@ -36,6 +36,8 @@ private:
std::ofstream fstream_, tmp_fstream1_, tmp_fstream2_;
std::map<std::string, int> materials_, meshes_;
json json_, node_array_;
int bufferViewId;
int writeMaterial(const IfcGeom::Material& style);
public:
+65 -53
View File
@@ -1149,14 +1149,14 @@ void SvgSerializer::write(const geometry_data& data) {
if (storey) {
auto it = storey_hlr.find(storey);
if (it == storey_hlr.end()) {
it = storey_hlr.insert({ storey, hlr_t(use_prefiltering_, use_hlr_poly_, projection_plane) }).first;
it = storey_hlr.insert({ storey, hlr_t(use_prefiltering_, use_hlr_poly_, segment_projection_, projection_plane) }).first;
}
it->second.add(*compound_to_hlr);
it->second.add(*compound_to_hlr, data.product);
} else {
Logger::Warning("Unable to invoke HLR due to absence of storey containment", data.product);
}
} else if (hlr) {
hlr->add(*compound_to_hlr);
hlr->add(*compound_to_hlr, data.product);
}
}
}
@@ -1699,55 +1699,66 @@ std::array<std::array<double, 3>, 3> SvgSerializer::resize() {
}
void SvgSerializer::draw_hlr(const gp_Pln& pln, const drawing_key& drawing_name) {
TopoDS_Shape hlr_compound_unmirrored = (drawing_name.first ? this->storey_hlr.find(drawing_name.first)->second : *hlr).build();
auto hlr_items = (drawing_name.first ? this->storey_hlr.find(drawing_name.first)->second : *hlr).build();
if (!hlr_compound_unmirrored.IsNull()) {
// Compound 3D curves for mirroring to work
ShapeFix_Edge sfe;
TopExp_Explorer exp(hlr_compound_unmirrored, TopAbs_EDGE);
for (; exp.More(); exp.Next()) {
sfe.FixAddCurve3d(TopoDS::Edge(exp.Current()));
}
for (auto& p : hlr_items) {
const TopoDS_Shape& hlr_compound_unmirrored = p.second;
// Mirror to match SVG coord system.
// @todo this is very wasteful. We better do the Y-mirror in the SVG writing and
// not on the TopoDS_Shape input.
TopoDS_Shape hlr_compound;
if (drawing_name.first == nullptr) {
gp_Trsf trsf_mirror;
if (!mirror_y_) {
trsf_mirror.SetMirror(gp_Ax2(gp::Origin(), gp::DY()));
if (!hlr_compound_unmirrored.IsNull()) {
// Compound 3D curves for mirroring to work
ShapeFix_Edge sfe;
TopExp_Explorer exp(hlr_compound_unmirrored, TopAbs_EDGE);
for (; exp.More(); exp.Next()) {
sfe.FixAddCurve3d(TopoDS::Edge(exp.Current()));
}
if (mirror_x_) {
gp_Trsf mirror_x;
mirror_x.SetMirror(gp_Ax2(gp::Origin(), gp::DX()));
trsf_mirror.PreMultiply(mirror_x);
// Mirror to match SVG coord system.
// @todo this is very wasteful. We better do the Y-mirror in the SVG writing and
// not on the TopoDS_Shape input.
TopoDS_Shape hlr_compound;
if (drawing_name.first == nullptr) {
gp_Trsf trsf_mirror;
if (!mirror_y_) {
trsf_mirror.SetMirror(gp_Ax2(gp::Origin(), gp::DY()));
}
if (mirror_x_) {
gp_Trsf mirror_x;
mirror_x.SetMirror(gp_Ax2(gp::Origin(), gp::DX()));
trsf_mirror.PreMultiply(mirror_x);
}
BRepBuilderAPI_Transform make_transform_mirror(hlr_compound_unmirrored, trsf_mirror, true);
make_transform_mirror.Build();
hlr_compound = make_transform_mirror.Shape();
} else {
// In case of building storey-based floor plan the mirroring has already
// been taken into account before projection.
hlr_compound = hlr_compound_unmirrored;
}
BRepBuilderAPI_Transform make_transform_mirror(hlr_compound_unmirrored, trsf_mirror, true);
make_transform_mirror.Build();
hlr_compound = make_transform_mirror.Shape();
} else {
// In case of building storey-based floor plan the mirroring has already
// been taken into account before projection.
hlr_compound = hlr_compound_unmirrored;
}
exp.Init(hlr_compound, TopAbs_EDGE);
BRep_Builder B;
path_object* po;
if (drawing_name.first) {
po = &start_path(pln, drawing_name.first, "class=\"projection\"");
} else {
po = &start_path(pln, drawing_name.second, "class=\"projection\"");
}
for (; exp.More(); exp.Next()) {
TopoDS_Wire w;
B.MakeWire(w);
B.Add(w, exp.Current());
write(*po, w);
}
exp.Init(hlr_compound, TopAbs_EDGE);
BRep_Builder B;
path_object* po;
std::string name;
if (p.first) {
name = nameElement(p.first);
boost::replace_all(name, "class=\"", "class=\"projection ");
} else {
name = "class=\"projection\"";
}
if (drawing_name.first) {
po = &start_path(pln, drawing_name.first, name);
} else {
po = &start_path(pln, drawing_name.second, name);
}
for (; exp.More(); exp.Next()) {
TopoDS_Wire w;
B.MakeWire(w);
B.Add(w, exp.Current());
write(*po, w);
}
}
}
}
@@ -1812,18 +1823,19 @@ void SvgSerializer::addTextAnnotations(const drawing_key& k) {
v.Transform(trsf_view);
auto svg_name = nameElement(ann);
path_object* po;
if (k.first) {
po = &start_path(meta.pln_3d, k.first, svg_name);
} else {
po = &start_path(meta.pln_3d, k.second, svg_name);
}
if (object_type.size()) {
// postfix the object_type for CSS matching
boost::replace_all(svg_name, "class=\"IfcAnnotation\"", "class=\"IfcAnnotation " + object_type + "\"");
}
path_object* po;
if (k.first) {
po = &start_path(meta.pln_3d, k.first, svg_name);
} else {
po = &start_path(meta.pln_3d, k.second, svg_name);
}
boost::optional<double> font_size;
std::vector<std::string> tokens;
boost::split(tokens, name, boost::is_any_of("_"));
@@ -1987,7 +1999,7 @@ void SvgSerializer::finalize() {
// @todo do we have always have pln here?
if (use_hlr && pln) {
hlr = new hlr_t(use_prefiltering_, use_hlr_poly_, *pln);
hlr = new hlr_t(use_prefiltering_, use_hlr_poly_, segment_projection_, *pln);
}
section_data_ = std::vector<section_data>{ sd };
+56 -20
View File
@@ -205,31 +205,52 @@ namespace {
class hlr_calc {
private:
const HLRAlgo_Projector& projector_;
const std::list<std::pair<const IfcUtil::IfcBaseEntity*, TopoDS_Shape>>* product_shapes_ = nullptr;
public:
typedef TopoDS_Shape result_type;
typedef std::list<std::pair<const IfcUtil::IfcBaseEntity*, TopoDS_Shape>> result_type;
hlr_calc(const HLRAlgo_Projector& projector) : projector_(projector)
{}
TopoDS_Shape operator()(boost::blank&) const {
void set_product_shape(const std::list<std::pair<const IfcUtil::IfcBaseEntity*, TopoDS_Shape>>* product_shapes) {
product_shapes_ = product_shapes;
}
result_type operator()(boost::blank&) const {
throw std::runtime_error("");
}
TopoDS_Shape operator()(opencascade::handle<HLRBRep_Algo>& algo) {
result_type operator()(opencascade::handle<HLRBRep_Algo>& algo) {
algo->Projector(projector_);
algo->Update();
algo->Hide();
HLRBRep_HLRToShape hlr_shapes(algo);
return occt_join(hlr_shapes.OutLineVCompound(), hlr_shapes.VCompound());
if (product_shapes_) {
std::list<std::pair<const IfcUtil::IfcBaseEntity*, TopoDS_Shape>> r;
for (auto& p : *product_shapes_) {
r.push_back({ p.first, occt_join(hlr_shapes.OutLineVCompound(p.second), hlr_shapes.VCompound(p.second)) });
}
return r;
} else {
return { {nullptr, occt_join(hlr_shapes.OutLineVCompound(), hlr_shapes.VCompound())}};
}
}
TopoDS_Shape operator()(opencascade::handle<HLRBRep_PolyAlgo>& algo) {
result_type operator()(opencascade::handle<HLRBRep_PolyAlgo>& algo) {
algo->Projector(projector_);
algo->Update();
HLRBRep_PolyHLRToShape hlr_shapes;
hlr_shapes.Update(algo);
return occt_join(hlr_shapes.OutLineVCompound(), hlr_shapes.VCompound());
if (product_shapes_) {
std::list<std::pair<const IfcUtil::IfcBaseEntity*, TopoDS_Shape>> r;
for (auto& p : *product_shapes_) {
r.push_back({ p.first, occt_join(hlr_shapes.OutLineVCompound(p.second), hlr_shapes.VCompound(p.second)) });
}
return r;
} else {
return { {nullptr, occt_join(hlr_shapes.OutLineVCompound(), hlr_shapes.VCompound()) } };
}
}
};
@@ -240,13 +261,13 @@ namespace {
gp_XYZ dxyz, xdir, ydir;
public:
std::list<TopoDS_Shape>::const_iterator item;
TopoDS_Shape* item;
TopoDS_Face face;
bool is_convex;
// @note copying the BRepTopAdaptor_FClass2d didn't work so it's a pointer
BRepTopAdaptor_FClass2d* fclass;
face_info(std::list<TopoDS_Shape>::const_iterator it, const TopoDS_Face& fa)
face_info(TopoDS_Shape* it, const TopoDS_Face& fa)
: item(it)
, face(fa)
, fclass(nullptr)
@@ -334,17 +355,19 @@ namespace {
hlr_brep_or_poly_t engine_;
bool use_prefiltering_;
bool use_hlr_poly_;
bool segment_projection_;
gp_Ax1 view_direction_;
HLRAlgo_Projector projector_;
std::multimap<double, face_info> large_ortho_faces_;
std::list<TopoDS_Shape> items_;
std::list<std::pair<const IfcUtil::IfcBaseEntity*, TopoDS_Shape>> items_;
public:
prefiltered_hlr(bool use_prefiltering, bool use_hlr_poly, const gp_Pln& view_direction)
prefiltered_hlr(bool use_prefiltering, bool use_hlr_poly, bool segment_projection, const gp_Pln& view_direction)
: use_prefiltering_(use_prefiltering)
, use_hlr_poly_(use_hlr_poly)
, segment_projection_(segment_projection)
// @nb negative z in accordance with occt projector convention (and opengl)
, view_direction_(view_direction.Axis())
{
@@ -359,7 +382,7 @@ namespace {
projector_ = HLRAlgo_Projector(trsf, false, 1.);
}
bool is_obscured_(std::list<TopoDS_Shape>::const_iterator sit) {
bool is_obscured_(TopoDS_Shape* sit) {
const TopoDS_Shape& s = *sit;
double min_d = std::numeric_limits<double>::infinity();
@@ -393,9 +416,9 @@ namespace {
return false;
}
void add(const TopoDS_Shape& s) {
void add(const TopoDS_Shape& s, const IfcUtil::IfcBaseEntity* product) {
if (!use_prefiltering_) {
items_.insert(items_.end(), s);
items_.insert(items_.end(), {product, s});
return;
}
@@ -434,7 +457,7 @@ namespace {
Logger::Notice("Included " + std::to_string(n_faces_included) + " faces out of " + std::to_string(n_total) + " after prefiltering");
auto it = items_.insert(items_.end(), C);
auto it = items_.insert(items_.end(), { product, C });
{
TopExp_Explorer exp(C, TopAbs_FACE);
@@ -458,7 +481,7 @@ namespace {
auto d = -(pnt.XYZ() - view_direction_.Location().XYZ()).Dot(view_direction_.Direction().XYZ());
if (d > 1.e-5) {
large_ortho_faces_.insert({ d, face_info(it, face) });
large_ortho_faces_.insert({ d, face_info(&it->second, face) });
}
}
}
@@ -467,15 +490,15 @@ namespace {
}
}
} else {
items_.insert(items_.end(), s);
items_.insert(items_.end(), { product, s });
}
}
TopoDS_Shape build() {
std::list<std::pair<const IfcUtil::IfcBaseEntity*, TopoDS_Shape>> build() {
size_t n_included = 0;
for (auto it = items_.begin(); it != items_.end(); ++it) {
if (!use_prefiltering_ || !is_obscured_(it)) {
hlr_writer vis(*it);
if (!use_prefiltering_ || !is_obscured_(&it->second)) {
hlr_writer vis(it->second);
boost::apply_visitor(vis, engine_);
n_included++;
}
@@ -483,7 +506,11 @@ namespace {
if (use_prefiltering_) {
Logger::Notice("Included " + std::to_string(n_included) + " elements out of " + std::to_string(items_.size()) + " after prefiltering");
}
hlr_calc vis(projector_);
if (segment_projection_) {
vis.set_product_shape(&items_);
}
return boost::apply_visitor(vis, engine_);
}
};
@@ -517,7 +544,7 @@ protected:
storey_height_display_types storey_height_display_;
bool draw_door_arcs_, is_floor_plan_;
bool auto_section_, auto_elevation_;
bool use_namespace_, use_hlr_poly_, use_prefiltering_, always_project_, polygonal_;
bool use_namespace_, use_hlr_poly_, use_prefiltering_, segment_projection_, always_project_, polygonal_;
bool emit_building_storeys_;
bool no_css_;
bool unify_inputs_;
@@ -570,6 +597,7 @@ public:
, use_namespace_(false)
, use_hlr_poly_(false)
, use_prefiltering_(false)
, segment_projection_(false)
, always_project_(false)
, polygonal_(false)
, emit_building_storeys_(true)
@@ -657,6 +685,14 @@ public:
return use_prefiltering_;
}
void setSegmentProjection(bool b) {
segment_projection_ = b;
}
bool getSegmentProjection() const {
return segment_projection_;
}
void setPolygonal(bool b) {
polygonal_ = b;
}