mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-12 18:43:26 +00:00
Merge branch 'IfcOpenShell:v0.7.0' into v0.7.0
This commit is contained in:
@@ -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
|
||||
@@ -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: |
|
||||
|
||||
@@ -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
@@ -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
File diff suppressed because it is too large
Load Diff
@@ -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
@@ -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
|
||||
@@ -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
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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 = {}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"))
|
||||
|
||||
@@ -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
@@ -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()
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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}")
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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());
|
||||
}
|
||||
@@ -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
@@ -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();
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user