mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-06 07:51:47 +00:00
Compare commits
166 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 219b7d0c24 | |||
| 97dd37b345 | |||
| e4877e8246 | |||
| e216d345c1 | |||
| 14c1b70768 | |||
| e15f222a19 | |||
| 4ab2664529 | |||
| bd0cfa5366 | |||
| 2e4b0e83a2 | |||
| cf61111386 | |||
| 3aa5ddb070 | |||
| 25dacc8fed | |||
| 7cba63af9c | |||
| 3c1d6abc7e | |||
| 8da4b9f23f | |||
| 99fa77edee | |||
| 839c5ad932 | |||
| b68ce08811 | |||
| e662308665 | |||
| a2ccff67ca | |||
| 8e045d772c | |||
| 16fd614886 | |||
| f31df307c6 | |||
| 11873a7c51 | |||
| ccccbe23f4 | |||
| 2173a562be | |||
| a11f81f2ce | |||
| d3b553c4fb | |||
| 5eb11c3516 | |||
| c195799801 | |||
| ac5a44a962 | |||
| 86880e9e62 | |||
| f8ccdf1170 | |||
| e3d8473d47 | |||
| bd5d4cecbf | |||
| 2ede8a2206 | |||
| cb77f9bd26 | |||
| 0bb21fedae | |||
| 478f90c2d4 | |||
| a016f6e5c8 | |||
| 1b2fa4f994 | |||
| 2d8383efc6 | |||
| add5132dec | |||
| fe333051df | |||
| 8d1b533f91 | |||
| fb304e141a | |||
| bd094f930e | |||
| b61000d2fe | |||
| c72cadb234 | |||
| 10642177a9 | |||
| 0b8ea28463 | |||
| 9b24f54f0d | |||
| 289645dacc | |||
| c365acb517 | |||
| 785cf56621 | |||
| 60617eecce | |||
| 26c68c00b7 | |||
| ab0a602f9f | |||
| 066326ab3b | |||
| 02713dbd39 | |||
| d58dae5d2f | |||
| ca3685900f | |||
| 5e10e61aca | |||
| 92e25ce3cb | |||
| 804a670454 | |||
| efa8382fc5 | |||
| 92f9d5f506 | |||
| 8800c14105 | |||
| ec2d0bfa4a | |||
| d684d47dc8 | |||
| babf95785c | |||
| f6ebf80fab | |||
| ea2cff3311 | |||
| bf4aa8cbb1 | |||
| 8b751577f4 | |||
| 6fbe982623 | |||
| 0b93694097 | |||
| 2027c71d99 | |||
| 6da51c2a6e | |||
| 53def55dc9 | |||
| 8722350c0b | |||
| 34c92d4d9c | |||
| 725cb40ef2 | |||
| 0c81b41ac7 | |||
| a8a82450a5 | |||
| 1b5b9fdcf6 | |||
| af1e542064 | |||
| 78154d17bf | |||
| 9bc8012aa4 | |||
| 407962ec93 | |||
| 21fd09b424 | |||
| 2264e69a1e | |||
| e02d6c3f4b | |||
| ec0abb7b37 | |||
| 477becab0b | |||
| 60bf424461 | |||
| 923e8423f9 | |||
| e228095391 | |||
| 1492a660c8 | |||
| 7a931ea6f2 | |||
| 2d59bb9cb8 | |||
| 06f5ded038 | |||
| 91f371a46e | |||
| 573e53ebfd | |||
| a492be5971 | |||
| ceac85ca02 | |||
| bfa981709a | |||
| c64b477e53 | |||
| 4f7501e1e8 | |||
| a1f20ec7bb | |||
| e2ee6c6e56 | |||
| 6940a05f2b | |||
| ff8f4ecd3c | |||
| de2bd3ff1a | |||
| cfa21b3cbf | |||
| db7c3e3b01 | |||
| 0532ceb029 | |||
| 0fe869c169 | |||
| bcde68d851 | |||
| 960a90e36c | |||
| aea0395fd6 | |||
| 416d51f7c7 | |||
| a9b13fd056 | |||
| 080056806e | |||
| 7a33a9eb02 | |||
| 07ba483a13 | |||
| 4925741060 | |||
| 7c4505b2fd | |||
| f9ee838cd2 | |||
| cce0c39ab2 | |||
| cd57fd4b1d | |||
| 6b705f0448 | |||
| 9cff6c87f0 | |||
| cac41064e0 | |||
| 350cf0406d | |||
| 47fd52b7c1 | |||
| 8a33e2fea1 | |||
| d4c02a312a | |||
| c20294b993 | |||
| 7500631b81 | |||
| 7cc1a849a6 | |||
| 4df93af1fd | |||
| fc1c8f2071 | |||
| deedffa84e | |||
| e8d924426f | |||
| 5a3f809d03 | |||
| 58ffd8bf62 | |||
| b22452aaac | |||
| 6c05e2ea8c | |||
| a1773b6f46 | |||
| b3583d4191 | |||
| 50e8d9f67e | |||
| 0d4819d6e7 | |||
| ce91d296b6 | |||
| 21137b9268 | |||
| 0dc319e510 | |||
| 798beb0030 | |||
| a717b348e0 | |||
| 14ed4d1d08 | |||
| bd3250aff6 | |||
| 1ee5bb73fa | |||
| 9a6dc54f6c | |||
| ab59173135 | |||
| b6abb24858 | |||
| 2b24d5ebe5 | |||
| 10a99fff05 |
@@ -11,7 +11,7 @@ jobs:
|
||||
matrix:
|
||||
include:
|
||||
- os: macos
|
||||
runner: macos-13
|
||||
runner: macos-14
|
||||
arch: x64
|
||||
oldarch:
|
||||
- os: macos
|
||||
@@ -42,6 +42,7 @@ jobs:
|
||||
- name: Install Dependencies
|
||||
run: |
|
||||
brew update
|
||||
# preinstalled: xz, cmake
|
||||
brew install git bison autoconf automake libffi findutils
|
||||
echo "$(brew --prefix findutils)/libexec/gnubin" >> $GITHUB_PATH
|
||||
|
||||
@@ -55,16 +56,40 @@ jobs:
|
||||
[ -n "$install_root" ] && find "$install_root" -type f -name 'cache-*.tar.gz' -maxdepth 1 -exec tar -xzf {} -C "$install_root" \; || true
|
||||
|
||||
- name: ccache
|
||||
uses: hendrikmuhs/ccache-action@v1
|
||||
uses: hendrikmuhs/ccache-action@v1.2
|
||||
with:
|
||||
key: ${GITHUB_WORKFLOW}-${{ matrix.os }}
|
||||
key: mac-${{ matrix.arch }}
|
||||
|
||||
- name: Run Build Script
|
||||
shell: bash
|
||||
run: |
|
||||
if [ "${{ matrix.os }}" == "macos" ]; then
|
||||
DARWIN_C_SOURCE=-D_DARWIN_C_SOURCE
|
||||
fi
|
||||
CXXFLAGS="-O3" CFLAGS="-O3 ${DARWIN_C_SOURCE}" ADD_COMMIT_SHA=1 BUILD_CFG=Release python3 ./nix/build-all.py --diskcleanup
|
||||
if [ "${{ matrix.arch }}" == "x64" ]; then
|
||||
arch -x86_64 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
|
||||
MAC_INTEL=-mac-cross-compile-intel
|
||||
# We don't use gmpcxx, but it comes preinstalled on macos runner as arm64 bottle
|
||||
# and CGAL detects it and breaks cross compilation.
|
||||
brew uninstall --ignore-dependencies gmp
|
||||
# Required by Python.
|
||||
/usr/local/bin/brew install gettext openssl
|
||||
fi
|
||||
set -o pipefail
|
||||
CXXFLAGS="-O3" CFLAGS="-O3 ${DARWIN_C_SOURCE}" ADD_COMMIT_SHA=1 BUILD_CFG=Release \
|
||||
python3 ./nix/build-all.py -v --diskcleanup ${MAC_INTEL} \
|
||||
| tee build.log
|
||||
|
||||
- name: Upload Build Logs
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: build-logs-osx-${{ matrix.arch }}
|
||||
path: |
|
||||
build.log
|
||||
build/*/*/*/logs/*.log
|
||||
build/*/*/*/build/ifcopenshell/**/CMakeCache.txt
|
||||
retention-days: 30
|
||||
|
||||
- name: Pack Dependencies
|
||||
run: |
|
||||
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
bzip2 patch mesa-libGL-devel libffi-devel fontconfig-devel \
|
||||
sqlite-devel bzip2-devel zlib-devel openssl-devel xz-devel \
|
||||
readline-devel ncurses-devel libffi-devel libuuid-devel git-lfs \
|
||||
findutils xz
|
||||
findutils xz byacc
|
||||
python3 -m pip install typing_extensions
|
||||
git config --global --add safe.directory '*'
|
||||
|
||||
@@ -47,15 +47,27 @@ jobs:
|
||||
install_root=$(find ./build -maxdepth 4 -type d -name install 2>/dev/null | head -n 1 || true)
|
||||
[ -n "$install_root" ] && find "$install_root" -type f -name 'cache-*.tar.gz' -maxdepth 1 -exec tar -xzf {} -C "$install_root" \; || true
|
||||
|
||||
# Not supported on docker
|
||||
# - name: ccache
|
||||
# uses: hendrikmuhs/ccache-action@v1
|
||||
# with:
|
||||
# key: ${GITHUB_WORKFLOW}-rockylinux8-x64
|
||||
- name: ccache
|
||||
# Using fork to support Rocky.
|
||||
uses: Andrej730/ccache-action@main
|
||||
with:
|
||||
key: ubuntu-22.04-${{ runner.arch }}-rockylinux8
|
||||
|
||||
- name: Run Build Script
|
||||
shell: bash
|
||||
run: |
|
||||
CXXFLAGS="-O3" CFLAGS="-O3 ${DARWIN_C_SOURCE}" ADD_COMMIT_SHA=1 BUILD_CFG=Release python3 ./nix/build-all.py --diskcleanup
|
||||
set -o pipefail
|
||||
CXXFLAGS="-O3" CFLAGS="-O3 ${DARWIN_C_SOURCE}" ADD_COMMIT_SHA=1 BUILD_CFG=Release python3 ./nix/build-all.py -v --diskcleanup 2>&1 | tee build.log
|
||||
|
||||
- name: Upload Build Logs
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: build-logs-rocky
|
||||
path: |
|
||||
build.log
|
||||
build/*/*/logs/*.log
|
||||
retention-days: 30
|
||||
|
||||
- name: Pack Dependencies
|
||||
run: |
|
||||
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
bzip2 patch mesa-libGL-devel libffi-devel fontconfig-devel \
|
||||
sqlite-devel bzip2-devel zlib-devel openssl-devel xz-devel \
|
||||
readline-devel ncurses-devel libffi-devel libuuid-devel git-lfs \
|
||||
findutils xz
|
||||
findutils xz byacc
|
||||
python3 -m pip install typing_extensions
|
||||
git config --global --add safe.directory '*'
|
||||
|
||||
@@ -47,15 +47,27 @@ jobs:
|
||||
install_root=$(find ./build -maxdepth 4 -type d -name install 2>/dev/null | head -n 1 || true)
|
||||
[ -n "$install_root" ] && find "$install_root" -type f -name 'cache-*.tar.gz' -maxdepth 1 -exec tar -xzf {} -C "$install_root" \; || true
|
||||
|
||||
# Not supported on docker
|
||||
# - name: ccache
|
||||
# uses: hendrikmuhs/ccache-action@v1
|
||||
# with:
|
||||
# key: ${GITHUB_WORKFLOW}-rockylinux8-x64
|
||||
- name: ccache
|
||||
# Using fork to support Rocky.
|
||||
uses: Andrej730/ccache-action@main
|
||||
with:
|
||||
key: ubuntu-22.04-${{ runner.arch }}-rockylinux8
|
||||
|
||||
- name: Run Build Script
|
||||
shell: bash
|
||||
run: |
|
||||
CXXFLAGS="-O3" CFLAGS="-O3 ${DARWIN_C_SOURCE}" ADD_COMMIT_SHA=1 BUILD_CFG=Release python3 ./nix/build-all.py --diskcleanup
|
||||
set -o pipefail
|
||||
CXXFLAGS="-O3" CFLAGS="-O3 ${DARWIN_C_SOURCE}" ADD_COMMIT_SHA=1 BUILD_CFG=Release python3 ./nix/build-all.py -v --diskcleanup 2>&1 | tee build.log
|
||||
|
||||
- name: Upload Build Logs
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: build-logs-rocky-arm64
|
||||
path: |
|
||||
build.log
|
||||
build/*/*/logs/*.log
|
||||
retention-days: 30
|
||||
|
||||
- name: Pack Dependencies
|
||||
run: |
|
||||
|
||||
@@ -47,6 +47,8 @@ jobs:
|
||||
id: ruff
|
||||
run: |
|
||||
uvx ruff check
|
||||
# It's actually Python 3.6, but ruff only supports 3.7+, but it should do.
|
||||
uvx ruff check nix/build-all.py --target-version py37
|
||||
continue-on-error: true
|
||||
|
||||
- name: Final check
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
name: ci-ifctester-org
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
paths:
|
||||
- src/ifctester/**
|
||||
|
||||
jobs:
|
||||
publish_website:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Checkout ifctester_org_static_html
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: IfcOpenShell/ifctester_org_static_html
|
||||
token: ${{ secrets.IFCOPENBOT_TOKEN }}
|
||||
path: ifctester_org_static_html
|
||||
- name: Build webapp
|
||||
working-directory: ./src/ifctester
|
||||
run: |
|
||||
sudo apt update && sudo apt install -y nodejs
|
||||
git config --global user.name 'IfcOpenBot'
|
||||
git config --global user.email 'IfcOpenBot@users.noreply.github.com'
|
||||
make webapp-build
|
||||
- name: Commit and push
|
||||
run: |
|
||||
cp -r src/ifctester/webapp/dist/* ifctester_org_static_html/
|
||||
git -C ifctester_org_static_html add .
|
||||
git -C ifctester_org_static_html commit --allow-empty -m "$(git log --oneline -1)"
|
||||
git -C ifctester_org_static_html push
|
||||
@@ -49,7 +49,7 @@ jobs:
|
||||
python -m pip install --upgrade pip
|
||||
pip install xmlschema xsdata numpy lxml pytest isodate lark networkx tabulate python-dateutil shapely
|
||||
pip install src/bcf --no-deps
|
||||
pip install https://github.com/Andrej730/aud/archive/refs/heads/master-reduced-size.zip
|
||||
pip install git+https://github.com/zdhoward/aud
|
||||
|
||||
- name: Install C++ dependencies
|
||||
run: |
|
||||
@@ -69,9 +69,9 @@ jobs:
|
||||
libhdf5-dev libcgal-dev libeigen3-dev
|
||||
|
||||
- name: ccache
|
||||
uses: hendrikmuhs/ccache-action@v1
|
||||
uses: hendrikmuhs/ccache-action@v1.2
|
||||
with:
|
||||
key: ${GITHUB_WORKFLOW}
|
||||
key: ubuntu-22.04-${{ runner.arch }}
|
||||
|
||||
# rocksdb on debian distros misses RTTI?
|
||||
- name: build rocksdb
|
||||
@@ -79,6 +79,7 @@ jobs:
|
||||
git clone https://github.com/facebook/rocksdb --branch v9.11.2
|
||||
cd rocksdb
|
||||
mkdir build && cd build
|
||||
# rocksdb is using ccache automatically.
|
||||
cmake -DFAIL_ON_WARNINGS=Off \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_INSTALL_PREFIX=/usr/local \
|
||||
@@ -99,9 +100,9 @@ jobs:
|
||||
echo ${{ env.pythonLocation }}
|
||||
|
||||
mkdir build && cd build
|
||||
# Ubuntu 22.04's libocct-foundation-dev package doesn't have Config.cmake, so we provide OCC paths directly.
|
||||
# In later versions of Ubuntu, this can be simplified and the OCC paths can be removed.
|
||||
cmake \
|
||||
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
|
||||
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
|
||||
-DCMAKE_CXX_STANDARD=17 \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_PREFIX_PATH=/usr \
|
||||
@@ -114,14 +115,6 @@ jobs:
|
||||
-DCOLLADA_SUPPORT=Off \
|
||||
"-DSCHEMA_VERSIONS=2x3;4;4x3_add2" \
|
||||
-DGLTF_SUPPORT=On \
|
||||
-DJSON_INCLUDE_DIR=/usr/include \
|
||||
-DCGAL_INCLUDE_DIR=/usr/include \
|
||||
-DGMP_INCLUDE_DIR=/usr/include \
|
||||
-DMPFR_INCLUDE_DIR=/usr/include \
|
||||
-DGMP_LIBRARY_DIR=/usr/lib/x86_64-linux-gnu \
|
||||
-DMPFR_LIBRARY_DIR=/usr/lib/x86_64-linux-gnu \
|
||||
-DHDF5_INCLUDE_DIR=/usr/include/hdf5/serial \
|
||||
-DEIGEN_DIR=/usr/include/eigen3 \
|
||||
-DWITH_ROCKSDB=On \
|
||||
../cmake
|
||||
sudo make -j $(nproc)
|
||||
|
||||
+75
-35
@@ -30,10 +30,14 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
find_program(CCACHE_FOUND ccache)
|
||||
if(CCACHE_FOUND)
|
||||
set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ccache)
|
||||
message(STATUS "`ccache` is found, using it as a compiler launcher.")
|
||||
endif()
|
||||
|
||||
add_definitions(-D_DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR)
|
||||
|
||||
if(POLICY CMP0141) # 3.25+
|
||||
cmake_policy(SET CMP0141 NEW) # Support for `CMAKE_MSVC_DEBUG_INFORMATION_FORMAT`.
|
||||
endif()
|
||||
if(POLICY CMP0144) # 3.27
|
||||
cmake_policy(SET CMP0144 NEW) # find_package() uses upper-case <PACKAGENAME>_ROOT variables.
|
||||
endif()
|
||||
@@ -87,8 +91,8 @@ option(WITH_ROCKSDB "Support a RocksDB key-value store as a file backend in IfcO
|
||||
option(WITH_ZSTD "Use Zstd compression in RocksDB writes" OFF)
|
||||
|
||||
option(USERSPACE_PYTHON_PREFIX "Installs IfcPython for the current user only instead of system-wide." OFF)
|
||||
option(ADD_COMMIT_SHA "Add commit sha and branch in version number, warning results in many rebuilds, requires git" OFF)
|
||||
option(VERSION_OVERRIDE "Override the version defined in IfcParse.h with the file VERSION in the repository root" OFF)
|
||||
option(ADD_COMMIT_SHA "Add commit sha and branch in version number, requires git" OFF)
|
||||
option(VERSION_OVERRIDE "Override the version defined in buildinfo.cpp with the file VERSION in the repository root" OFF)
|
||||
|
||||
set(
|
||||
PYTHON_MODULE_INSTALL_DIR
|
||||
@@ -123,6 +127,26 @@ if((BUILD_CONVERT OR BUILD_GEOMSERVER OR BUILD_IFCPYTHON) AND(NOT BUILD_IFCGEOM)
|
||||
set(BUILD_IFCGEOM ON)
|
||||
endif()
|
||||
|
||||
if(CCACHE_FOUND)
|
||||
set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ccache)
|
||||
if(MSVC)
|
||||
# By default Visual Studio generators will use /Zi which is not compatible
|
||||
# with ccache, so tell Visual Studio to use /Z7 instead.
|
||||
set(CMAKE_MSVC_DEBUG_INFORMATION_FORMAT "$<$<CONFIG:Debug,RelWithDebInfo>:Embedded>")
|
||||
# Not needed for Ninja.
|
||||
if(CMAKE_GENERATOR MATCHES "Visual Studio")
|
||||
file(COPY_FILE
|
||||
${CCACHE_FOUND} ${CMAKE_BINARY_DIR}/cl.exe
|
||||
ONLY_IF_DIFFERENT)
|
||||
set(CMAKE_VS_GLOBALS
|
||||
"CLToolExe=cl.exe"
|
||||
"CLToolPath=${CMAKE_BINARY_DIR}"
|
||||
"UseMultiToolTask=true"
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(MSVC AND MSVC_PARALLEL_BUILD)
|
||||
add_definitions("/MP")
|
||||
endif()
|
||||
@@ -230,6 +254,13 @@ endmacro()
|
||||
|
||||
if(WITH_CGAL)
|
||||
if(NOT CGAL_INCLUDE_DIR)
|
||||
# CGAL is not respecting default Boost_USE_STATIC_LIBS value
|
||||
# and sometiems it's getting in the way.
|
||||
if(NOT DEFINED Boost_USE_STATIC_LIBS)
|
||||
set(CGAL_Boost_USE_STATIC_LIBS OFF)
|
||||
else()
|
||||
set(CGAL_Boost_USE_STATIC_LIBS "${Boost_USE_STATIC_LIBS}")
|
||||
endif()
|
||||
find_package(CGAL REQUIRED)
|
||||
if(NOT CGAL_DIR)
|
||||
message(
|
||||
@@ -335,10 +366,16 @@ endif(USD_SUPPORT)
|
||||
if (WITH_ROCKSDB)
|
||||
# Temporaily mess with CMAKE_FIND_PACKAGE_PREFER_CONFIG to help RocksDB
|
||||
# find it's zstd dependency on Windows.
|
||||
set(TEMP CMAKE_FIND_PACKAGE_PREFER_CONFIG)
|
||||
set(CMAKE_FIND_PACKAGE_PREFER_CONFIG TRUE)
|
||||
# Only do it on Windows, otherwise it might create problems as
|
||||
# findzstd and zstd-config target names do not match.
|
||||
if(WIN32)
|
||||
set(TEMP CMAKE_FIND_PACKAGE_PREFER_CONFIG)
|
||||
set(CMAKE_FIND_PACKAGE_PREFER_CONFIG TRUE)
|
||||
endif()
|
||||
find_package(RocksDB CONFIG REQUIRED)
|
||||
set(CMAKE_FIND_PACKAGE_PREFER_CONFIG ${TEMP})
|
||||
if(WIN32)
|
||||
set(CMAKE_FIND_PACKAGE_PREFER_CONFIG ${TEMP})
|
||||
endif()
|
||||
|
||||
message(STATUS "RocksDB: found at '${RocksDB_DIR}'.")
|
||||
add_definitions(-DIFOPSH_WITH_ROCKSDB)
|
||||
@@ -756,7 +793,7 @@ if(HDF5_SUPPORT)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(NOT HDF5_INCLUDE_DIR)
|
||||
if(NOT HDF5_INCLUDE_DIR OR NOT HDF5_LIBRARY_DIR)
|
||||
# First try to find it as a config.
|
||||
find_package(HDF5 CONFIG)
|
||||
if(HDF5_DIR)
|
||||
@@ -766,10 +803,11 @@ if(HDF5_SUPPORT)
|
||||
# If it failed, still try to find as a module.
|
||||
# E.g. on Ubuntu `libhdf5-dev` doesn't provie hdf5-config.cmake.
|
||||
# Will automatically fill HDF5_LIBRARIES and HDF5_INCLUDE_DIR.
|
||||
find_package(HDF5)
|
||||
find_package(HDF5 COMPONENTS CXX)
|
||||
if(NOT HDF5_INCLUDE_DIR)
|
||||
message(
|
||||
FATAL_ERROR
|
||||
"HDF5_INCLUDE_DIR is not provided (current value: '${HDF5_INCLUDE_DIR}'). "
|
||||
"HDF5_LIBRARY_DIR is not provided (current value: '${HDF5_LIBRARY_DIR}'). "
|
||||
"Also could not find HDF5 package (neither module or config)."
|
||||
)
|
||||
@@ -1008,7 +1046,7 @@ if(BUILD_IFCGEOM)
|
||||
if(WASM_BUILD)
|
||||
set(IFCOPENSHELL_LIBRARIES ${IFCOPENSHELL_LIBRARIES} IfcGeom ${IFCGEOM_SCHEMA_LIBRARIES})
|
||||
else()
|
||||
set(IFCOPENSHELL_LIBRARIES ${IFCOPENSHELL_LIBRARIES} IfcGeom ${IFCGEOM_SCHEMA_LIBRARIES} IfcGeom ${IFCGEOM_SCHEMA_LIBRARIES})
|
||||
set(IFCOPENSHELL_LIBRARIES ${IFCOPENSHELL_LIBRARIES} IfcGeom ${IFCGEOM_SCHEMA_LIBRARIES} IfcGeom ${IFCGEOM_SCHEMA_LIBRARIES} IfcGeom)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
@@ -1023,12 +1061,14 @@ if(BUILD_CONVERT OR BUILD_IFCPYTHON)
|
||||
set(GEOM_SERIALIZER_SCHEMA_LIBRARIES ${GEOM_SERIALIZER_SCHEMA_LIBRARIES} GeometrySerializers_ifc${schema})
|
||||
|
||||
add_library(geometry_serializer_ifc${schema} STATIC ../src/ifcgeom/Serialization/schema/Serialization.cpp)
|
||||
set_target_properties(geometry_serializer_ifc${schema} PROPERTIES COMPILE_FLAGS "-DIFC_GEOM_EXPORTS -DIfcSchema=Ifc${schema}")
|
||||
set_target_properties(geometry_serializer_ifc${schema} PROPERTIES COMPILE_FLAGS "-DIFC_GEOMSERIALIZATION_EXPORTS -DIfcSchema=Ifc${schema}")
|
||||
target_link_libraries(geometry_serializer_ifc${schema} ${OPENCASCADE_LIBRARIES})
|
||||
list(APPEND geometry_serializer_libraries geometry_serializer_ifc${schema})
|
||||
endforeach()
|
||||
|
||||
add_library(geometry_serializer STATIC ../src/ifcgeom/Serialization/Serialization.cpp)
|
||||
target_link_libraries(geometry_serializer ${geometry_serializer_libraries})
|
||||
add_library(geometry_serializer STATIC ../src/ifcgeom/Serialization/Serialization.h ../src/ifcgeom/Serialization/Serialization.cpp)
|
||||
set_target_properties(geometry_serializer PROPERTIES COMPILE_FLAGS "-DIFC_GEOMSERIALIZATION_EXPORTS")
|
||||
target_link_libraries(geometry_serializer ${geometry_serializer_libraries} IfcParse)
|
||||
set(IFCOPENSHELL_LIBRARIES ${IFCOPENSHELL_LIBRARIES} geometry_serializer ${geometry_serializer_libraries})
|
||||
endif()
|
||||
endif()
|
||||
@@ -1066,13 +1106,6 @@ endforeach()
|
||||
|
||||
set(IFCPARSE_FILES ${IFCPARSE_CPP_FILES} ${IFCPARSE_H_FILES})
|
||||
|
||||
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
||||
if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 9.0)
|
||||
message(STATUS "GCC < 9 detected, linking stdc++fs explicitly")
|
||||
set(STDCPPFS stdc++fs)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
add_library(IfcParse ${IFCPARSE_FILES})
|
||||
target_link_libraries(IfcParse ${STDCPPFS})
|
||||
set_target_properties(IfcParse PROPERTIES COMPILE_FLAGS -DIFC_PARSE_EXPORTS VERSION "${PROJECT_VERSION}" SOVERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}")
|
||||
@@ -1113,23 +1146,23 @@ if(BUILD_IFCGEOM)
|
||||
file(GLOB IFCGEOM_CPP_FILES ../src/ifcgeom/kernels/${kernel}/*.cpp)
|
||||
set(IFCGEOM_FILES ${IFCGEOM_CPP_FILES} ${IFCGEOM_H_FILES})
|
||||
|
||||
add_library(geometry_kernel_${kernel} ${IFCGEOM_FILES})
|
||||
set_property(TARGET geometry_kernel_${kernel} APPEND PROPERTY COMPILE_FLAGS "-DIFC_GEOM_EXPORTS")
|
||||
add_library(geometry_kernel_${kernel} ${IFCGEOM_FILES} ../src/ifcgeom/kernels/ifc_geomlibrary_api.h)
|
||||
set_property(TARGET geometry_kernel_${kernel} APPEND PROPERTY COMPILE_FLAGS "-DIFC_GEOMLIBRARY_EXPORTS")
|
||||
# needed?
|
||||
# if(NOT WASM_BUILD)
|
||||
# endif()
|
||||
target_link_libraries(geometry_kernel_${kernel} ${${KERNEL_UPPER}_LIBRARIES})
|
||||
target_link_libraries(geometry_kernel_${kernel} ${${KERNEL_UPPER}_LIBRARIES} IfcGeom IfcParse)
|
||||
list(APPEND kernel_libraries geometry_kernel_${kernel})
|
||||
|
||||
if(${kernel} STREQUAL "cgal")
|
||||
set_property(TARGET geometry_kernel_${kernel} APPEND_STRING PROPERTY COMPILE_FLAGS " -DCGAL_HAS_THREADS")
|
||||
|
||||
add_library(geometry_kernel_${kernel}_simple ${IFCGEOM_FILES})
|
||||
set_target_properties(geometry_kernel_${kernel}_simple PROPERTIES COMPILE_FLAGS "-DIFC_GEOM_EXPORTS -DIFOPSH_SIMPLE_KERNEL -DCGAL_HAS_THREADS")
|
||||
set_target_properties(geometry_kernel_${kernel}_simple PROPERTIES COMPILE_FLAGS "-DIFC_GEOMLIBRARY_EXPORTS -DIFOPSH_SIMPLE_KERNEL -DCGAL_HAS_THREADS")
|
||||
# needed?
|
||||
# if(NOT WASM_BUILD)
|
||||
# endif()
|
||||
target_link_libraries(geometry_kernel_${kernel}_simple ${${KERNEL_UPPER}_LIBRARIES})
|
||||
target_link_libraries(geometry_kernel_${kernel}_simple ${${KERNEL_UPPER}_LIBRARIES} IfcGeom IfcParse)
|
||||
list(APPEND kernel_libraries geometry_kernel_${kernel}_simple)
|
||||
endif()
|
||||
endforeach()
|
||||
@@ -1159,11 +1192,7 @@ if(BUILD_IFCGEOM)
|
||||
find_package(Threads)
|
||||
endif()
|
||||
|
||||
if(WASM_BUILD)
|
||||
target_link_libraries(IfcGeom ${kernel_libraries} ${mapping_libraries} ${CMAKE_THREAD_LIBS_INIT})
|
||||
else()
|
||||
target_link_libraries(IfcGeom IfcParse ${kernel_libraries} ${mapping_libraries} ${CMAKE_THREAD_LIBS_INIT})
|
||||
endif()
|
||||
target_link_libraries(IfcGeom IfcParse ${mapping_libraries} ${CMAKE_THREAD_LIBS_INIT})
|
||||
|
||||
endif(BUILD_IFCGEOM)
|
||||
|
||||
@@ -1178,7 +1207,7 @@ if(BUILD_CONVERT OR BUILD_IFCPYTHON)
|
||||
|
||||
foreach(schema ${SCHEMA_VERSIONS})
|
||||
add_library(Serializers_ifc${schema} STATIC ${SERIALIZERS_S_FILES})
|
||||
set_target_properties(Serializers_ifc${schema} PROPERTIES COMPILE_FLAGS "-DIFC_GEOM_EXPORTS -DIfcSchema=Ifc${schema}")
|
||||
set_target_properties(Serializers_ifc${schema} PROPERTIES COMPILE_FLAGS "-DSERIALIZERS_EXPORTS -DIfcSchema=Ifc${schema}")
|
||||
|
||||
if(WASM_BUILD)
|
||||
target_link_libraries(Serializers_ifc${schema} ${HDF5_LIBRARIES})
|
||||
@@ -1188,7 +1217,7 @@ if(BUILD_CONVERT OR BUILD_IFCPYTHON)
|
||||
endforeach()
|
||||
|
||||
add_library(Serializers ${SERIALIZERS_FILES})
|
||||
set_target_properties(Serializers PROPERTIES COMPILE_FLAGS "-DIFC_GEOM_EXPORTS" VERSION "${PROJECT_VERSION}" SOVERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}")
|
||||
set_target_properties(Serializers PROPERTIES COMPILE_FLAGS "-DSERIALIZERS_EXPORTS" VERSION "${PROJECT_VERSION}" SOVERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}")
|
||||
|
||||
if(WITH_PROJ)
|
||||
target_compile_definitions(Serializers PRIVATE "WITH_PROJ")
|
||||
@@ -1199,7 +1228,7 @@ if(BUILD_CONVERT OR BUILD_IFCPYTHON)
|
||||
target_link_libraries(Serializers ${PROJ_LIBRARIES})
|
||||
endif()
|
||||
|
||||
target_link_libraries(Serializers ${SERIALIZER_SCHEMA_LIBRARIES} ${OPENCOLLADA_LIBRARIES} ${USD_LIBRARIES})
|
||||
target_link_libraries(Serializers ${SERIALIZER_SCHEMA_LIBRARIES} ${OPENCOLLADA_LIBRARIES} ${USD_LIBRARIES} IfcGeom ${OPENCASCADE_LIBRARIES} ${kernel_libraries} IfcParse)
|
||||
|
||||
endif(BUILD_CONVERT OR BUILD_IFCPYTHON)
|
||||
|
||||
@@ -1246,7 +1275,7 @@ if(BUILD_CONVERT)
|
||||
set(IFCCONVERT_FILES ${IFCCONVERT_CPP_FILES} ${IFCCONVERT_H_FILES})
|
||||
add_executable(IfcConvert ${IFCCONVERT_FILES})
|
||||
|
||||
target_link_libraries(IfcConvert ${IFCOPENSHELL_LIBRARIES} ${OPENCASCADE_LIBRARIES} ${Boost_LIBRARIES} ${HDF5_LIBRARIES} ${USD_LIBRARIES})
|
||||
target_link_libraries(IfcConvert IfcGeom IfcParse Serializers ${kernel_libraries} ${OPENCASCADE_LIBRARIES} ${Boost_LIBRARIES} ${HDF5_LIBRARIES} ${USD_LIBRARIES})
|
||||
if (WITH_RELATIONSHIP_VALIDATION)
|
||||
set_property(TARGET IfcConvert APPEND_STRING PROPERTY COMPILE_FLAGS " -DWITH_RELATIONSHIP_VALIDATION")
|
||||
endif()
|
||||
@@ -1280,7 +1309,7 @@ if(BUILD_GEOMSERVER)
|
||||
file(GLOB H_FILES ../src/ifcgeomserver/*.h)
|
||||
set(SOURCE_FILES ${CPP_FILES} ${H_FILES})
|
||||
add_executable(IfcGeomServer ${SOURCE_FILES})
|
||||
target_link_libraries(IfcGeomServer ${IFCOPENSHELL_LIBRARIES} ${OPENCASCADE_LIBRARIES} ${Boost_LIBRARIES})
|
||||
target_link_libraries(IfcGeomServer IfcGeom IfcParse Serializers ${kernel_libraries} ${OPENCASCADE_LIBRARIES} ${Boost_LIBRARIES})
|
||||
|
||||
if((NOT WIN32) AND BUILD_SHARED_LIBS)
|
||||
SET_INSTALL_RPATHS(IfcGeomServer "${IFCOPENSHELL_LIBRARY_DIR};${OCC_LIBRARY_DIR};${Boost_LIBRARY_DIRS}")
|
||||
@@ -1296,6 +1325,10 @@ endif(BUILD_GEOMSERVER)
|
||||
if(ADD_COMMIT_SHA)
|
||||
find_package(Git)
|
||||
|
||||
if(NOT GIT_FOUND)
|
||||
message(FATAL_ERROR "Failed to find Git for ADD_COMMIT_SHA option.")
|
||||
endif()
|
||||
|
||||
if(GIT_FOUND)
|
||||
if (VERSION_OVERRIDE)
|
||||
set (git_branch ${RELEASE_VERSION})
|
||||
@@ -1333,8 +1366,10 @@ if(ADD_COMMIT_SHA)
|
||||
message(FATAL_ERROR "Unable to determine commit sha and/or branch")
|
||||
endif()
|
||||
|
||||
add_definitions(-DIFCOPENSHELL_BRANCH=${git_branch})
|
||||
add_definitions(-DIFCOPENSHELL_COMMIT=${git_sha})
|
||||
target_compile_definitions(IfcParse PRIVATE
|
||||
-DIFCOPENSHELL_BRANCH=${git_branch}
|
||||
-DIFCOPENSHELL_COMMIT=${git_sha}
|
||||
)
|
||||
endif()
|
||||
endif(ADD_COMMIT_SHA)
|
||||
|
||||
@@ -1391,6 +1426,11 @@ if(BUILD_IFCGEOM)
|
||||
DESTINATION ${INCLUDEDIR}/ifcgeom
|
||||
)
|
||||
|
||||
file(GLOB SERIALIZATION_H_FILES ../src/ifcgeom/serialization/*.h)
|
||||
install(FILES ${SERIALIZATION_H_FILES}
|
||||
DESTINATION ${INCLUDEDIR}/ifcgeom/serialization
|
||||
)
|
||||
|
||||
foreach(kernel ${GEOMETRY_KERNELS})
|
||||
file(GLOB IFCGEOM_H_FILES ../src/ifcgeom/kernels/${kernel}/*.h)
|
||||
install(FILES ${IFCGEOM_H_FILES}
|
||||
|
||||
+164
-25
@@ -31,6 +31,7 @@ Available arguments:
|
||||
``-py-313`` - build for specific Python version
|
||||
(building for all supported Python version by default).
|
||||
``-wasm`` - compile for wasm
|
||||
``-mac-cross-compile-intel`` - cross compile for Intel Mac on Apple Silicon host
|
||||
``-shared`` - build shared libraries. By default will build static.
|
||||
``-diskcleanup`` - clean up build directories after finishing building dependencies
|
||||
``-v`` - enable verbose logs
|
||||
@@ -49,6 +50,8 @@ Used environment variables:
|
||||
- ``IFCOS_SCHEMAS`` - schemas to be built; defaults to cmake default (IFC2X3; IFC4; IFC4X3_ADD2) - to be supplied as `2x3;4`
|
||||
- ``USE_OCCT`` - whether to use official Open CASCADE instead of Community Edition
|
||||
(`true` by default, any other value is considered `false`)
|
||||
- ``ADD_COMMIT_SHA`` - if defined with any non-empty value then
|
||||
`ADD_COMMIT_SHA` and `VERSION_OVERRIDE` will be set to `ON` while configuring IfcOpenShell
|
||||
|
||||
# This script builds IfcOpenShell and its dependencies #
|
||||
# #
|
||||
@@ -73,12 +76,12 @@ Used environment variables:
|
||||
# on debian 7.8 these can be obtained with: #
|
||||
# $ apt-get install git gcc g++ autoconf bison bzip2 cmake #
|
||||
# mesa-common-dev libffi-dev libfontconfig1-dev #
|
||||
# libssl-dev #
|
||||
# libssl-dev xz #
|
||||
# #
|
||||
# on ubuntu 14.04: #
|
||||
# $ apt-get install git gcc g++ autoconf bison make cmake #
|
||||
# mesa-common-dev libffi-dev libfontconfig1-dev #
|
||||
# libssl-dev #
|
||||
# libssl-dev xz-utils #
|
||||
# #
|
||||
# on OS X El Capitan with homebrew: #
|
||||
# $ brew install git bison autoconf automake libffi cmake #
|
||||
@@ -86,7 +89,7 @@ Used environment variables:
|
||||
# on RHEL-related distros: #
|
||||
# $ yum install git gcc gcc-c++ autoconf bison make cmake #
|
||||
# mesa-libGL-devel libffi-devel fontconfig-devel bzip2 #
|
||||
# automake patch #
|
||||
# automake patch byacc xz #
|
||||
|
||||
"""
|
||||
|
||||
@@ -130,12 +133,12 @@ PROJECT_NAME = "IfcOpenShell"
|
||||
USE_CURRENT_PYTHON_VERSION = os.getenv("USE_CURRENT_PYTHON_VERSION")
|
||||
ADD_COMMIT_SHA = os.getenv("ADD_COMMIT_SHA")
|
||||
|
||||
PYTHON_VERSIONS = ["3.9.11", "3.10.3", "3.11.8", "3.12.1", "3.13.0"]
|
||||
JSON_VERSION = "3.10.0"
|
||||
PYTHON_VERSIONS = ["3.9.11", "3.10.3", "3.11.8", "3.12.1", "3.13.6"]
|
||||
JSON_VERSION = "3.11.3"
|
||||
OCE_VERSION = "0.18.3"
|
||||
OCCT_VERSION = "7.8.1"
|
||||
BOOST_VERSION = "1.86.0"
|
||||
EIGEN_VERSION = "3.3.9"
|
||||
EIGEN_VERSION = "3.4.0"
|
||||
PCRE_VERSION = "8.41"
|
||||
PCRE2_VERSION = "10.32"
|
||||
LIBXML2_VERSION = "2.13.8"
|
||||
@@ -145,7 +148,7 @@ HDF5_VERSION = "1.13.1"
|
||||
|
||||
GMP_VERSION = "6.2.1"
|
||||
MPFR_VERSION = "3.1.6" # latest is 4.1.0
|
||||
CGAL_VERSION = "5.6.x-branch"
|
||||
CGAL_VERSION = "v5.6.3"
|
||||
USD_VERSION = "23.05"
|
||||
TBB_VERSION = "2021.9.0"
|
||||
ROCKSDB_VERSION = "9.11.2"
|
||||
@@ -165,6 +168,8 @@ date = "date"
|
||||
curl = "curl"
|
||||
wget = "wget"
|
||||
strip = "strip"
|
||||
xz = "xz" # Used implicitly for `tar -xf *.tar.xz`.
|
||||
brew = "brew"
|
||||
|
||||
explicit_targets = [s for s in sys.argv[1:] if not s.startswith("-")]
|
||||
"""Targets provided by CLI."""
|
||||
@@ -188,13 +193,20 @@ def cecho(message, color=NO_COLOR):
|
||||
logger.info(f"{color}{message}\033[0m")
|
||||
|
||||
|
||||
def which(cmd):
|
||||
for path in os.getenv("PATH").split(":"):
|
||||
def which(cmd: str) -> Union[str, None]:
|
||||
PATH = os.getenv("PATH")
|
||||
assert PATH
|
||||
for path in PATH.split(":"):
|
||||
if os.path.exists(path) and cmd in os.listdir(path):
|
||||
return cmd
|
||||
return None
|
||||
|
||||
|
||||
# Flags.
|
||||
MAC_CROSS_COMPILE_INTEL = "mac-cross-compile-intel" in flags
|
||||
assert platform.system() == "Darwin" or not MAC_CROSS_COMPILE_INTEL
|
||||
WASM = "wasm" in flags
|
||||
|
||||
# Set defaults for missing empty environment variables
|
||||
|
||||
USE_OCCT = os.environ.get("USE_OCCT", "true").lower() == "true"
|
||||
@@ -213,7 +225,15 @@ CMAKE_DIR = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", "cmak
|
||||
|
||||
build_dir = os.environ.get("BUILD_DIR", os.path.join(os.path.dirname(__file__), "..", "build"))
|
||||
|
||||
DEFAULT_DEPS_DIR = Path(build_dir) / platform.system() / ("wasm" if "wasm" in flags else platform.machine())
|
||||
|
||||
if WASM:
|
||||
arch = "wasm"
|
||||
elif MAC_CROSS_COMPILE_INTEL:
|
||||
arch = "x86_64"
|
||||
else:
|
||||
arch = platform.machine()
|
||||
DEFAULT_DEPS_DIR = Path(build_dir) / platform.system() / arch
|
||||
|
||||
if TOOLSET:
|
||||
DEFAULT_DEPS_DIR = DEFAULT_DEPS_DIR / TOOLSET
|
||||
DEFAULT_DEPS_DIR = os.path.realpath(DEFAULT_DEPS_DIR)
|
||||
@@ -263,7 +283,7 @@ cecho(
|
||||
|
||||
dependency_tree: "dict[str, tuple[str, ...]]" = {
|
||||
"IfcParse": ("boost", "libxml2", "hdf5", "rocksdb"),
|
||||
"IfcGeom": ("IfcParse", "occ", "json", "cgal", "eigen"),
|
||||
"IfcGeom": ("IfcParse", "occ", "json", "cgal", "eigen", "OpenCOLLADA"),
|
||||
"IfcConvert": ("IfcGeom",),
|
||||
"OpenCOLLADA": ("libxml2", "pcre"),
|
||||
"IfcGeomServer": ("IfcGeom",),
|
||||
@@ -301,6 +321,17 @@ if "v" in flags:
|
||||
else:
|
||||
logger.setLevel(logging.INFO)
|
||||
|
||||
if MAC_CROSS_COMPILE_INTEL:
|
||||
MAC_CROSS_COMPILE_INTEL_ARGS = ["-DCMAKE_OSX_ARCHITECTURES=x86_64"]
|
||||
MAC_CROSS_COMPILE_INTEL_BJAM_ARGS = ["architecture=x86"]
|
||||
MAC_CROSS_COMPILE_INTEL_CXX = "clang++ -arch x86_64"
|
||||
MAC_CROSS_COMPILE_INTEL_CC = "clang -arch x86_64"
|
||||
else:
|
||||
MAC_CROSS_COMPILE_INTEL_ARGS = []
|
||||
MAC_CROSS_COMPILE_INTEL_BJAM_ARGS = []
|
||||
MAC_CROSS_COMPILE_INTEL_CXX = ""
|
||||
MAC_CROSS_COMPILE_INTEL_CC = ""
|
||||
|
||||
OFF_ON = ["OFF", "ON"]
|
||||
BUILD_STATIC = "shared" not in flags
|
||||
ENABLE_FLAG = "--enable-static" if BUILD_STATIC else "--enable-shared"
|
||||
@@ -313,6 +344,11 @@ PIC = "-fPIC" if BUILD_STATIC else ""
|
||||
if any(f.startswith("py-") for f in flags):
|
||||
PYTHON_VERSIONS = [pyv for pyv in PYTHON_VERSIONS if f"py-{''.join(pyv.split('.')[:2])}" in flags]
|
||||
|
||||
if any(f.startswith("occt-") for f in flags):
|
||||
OCCT_VERSION = next(f.split("-", 1)[1] for f in flags if f.startswith("occt-"))
|
||||
|
||||
print(OCCT_VERSION)
|
||||
|
||||
if explicit_targets:
|
||||
targets = {dep for target in explicit_targets for dep in gather_dependencies(target)}
|
||||
else:
|
||||
@@ -323,10 +359,21 @@ targets = set(t for t in targets if "without-%s" % t.lower() not in flags)
|
||||
print("Building:", *sorted(targets, key=lambda t: len(list(gather_dependencies(t)))))
|
||||
|
||||
# Check that required tools are in PATH
|
||||
yacc = "yacc" # Used during swig building process, installed with `bison`.
|
||||
for cmd in [git, bunzip2, tar, cc, cplusplus, autoconf, automake, make, "patch", "cmake", yacc]:
|
||||
yacc = "yacc" # Used during swig building process, installed with `bison` on Debian / `byacc` on Red Hat.
|
||||
missing_commands: "list[str]" = []
|
||||
required_commands = [git, bunzip2, tar, cc, cplusplus, autoconf, automake, make, "patch", "cmake", yacc, xz]
|
||||
if "wasm" in flags:
|
||||
required_commands.remove(yacc) # yacc not needed for wasm builds
|
||||
for cmd in required_commands:
|
||||
if which(cmd) is None:
|
||||
raise ValueError(f"Required tool '{cmd}' not installed or not added to PATH")
|
||||
missing_commands.append(cmd)
|
||||
|
||||
if missing_commands:
|
||||
raise ValueError(f"Required tools not installed or not added to PATH: {', '.join(missing_commands)}")
|
||||
|
||||
if MAC_CROSS_COMPILE_INTEL:
|
||||
brew = "/usr/local/bin/brew"
|
||||
assert os.path.exists(brew), f"For intel cross compilation the brew path is expected to be '{brew}'."
|
||||
|
||||
# identifiers for the download tool (could be less memory consuming as ints, but are more verbose as strings)
|
||||
download_tool_default = download_tool_py = "py"
|
||||
@@ -359,7 +406,7 @@ def run(cmds: "Sequence[str]", cwd: "Union[str, None]" = None, can_fail: bool =
|
||||
def timestamp() -> str:
|
||||
return datetime.now().strftime("%Y-%m-%d %H:%M:%S,%f")[:-3] # same format as logging
|
||||
|
||||
def stream_reader(pipe, collector: list[str], log_file) -> None:
|
||||
def stream_reader(pipe, collector: "list[str]", log_file) -> None:
|
||||
for line in iter(pipe.readline, ""):
|
||||
log_file.write(f"{timestamp()} {line}")
|
||||
log_file.flush()
|
||||
@@ -733,6 +780,7 @@ if "hdf5" in targets:
|
||||
"-DBUILD_SHARED_LIBS=OFF",
|
||||
"-DHDF5_BUILD_UTILS=OFF",
|
||||
"-DHDF5_BUILD_CPP_LIB=ON",
|
||||
*MAC_CROSS_COMPILE_INTEL_ARGS,
|
||||
],
|
||||
download_url=f"https://github.com/HDFGroup/hdf5/archive/refs/tags/",
|
||||
download_name=f"hdf5-{HDF5_UNDERSCORE}.tar.gz",
|
||||
@@ -769,6 +817,13 @@ if "eigen" in targets:
|
||||
)
|
||||
|
||||
if "pcre" in targets:
|
||||
OLD_CC, OLD_CCXX = None, None
|
||||
if MAC_CROSS_COMPILE_INTEL:
|
||||
OLD_CC, OLD_CCXX = os.environ.get("CC"), os.environ.get("CXX")
|
||||
os.environ["CC"] = MAC_CROSS_COMPILE_INTEL_CC
|
||||
os.environ["CXX"] = MAC_CROSS_COMPILE_INTEL_CXX
|
||||
# Keep it autoconf as OpenCOLLADA is pretty old and might break
|
||||
# if we update it's dependencies for mmore modern cmake.
|
||||
build_dependency(
|
||||
name=f"pcre-{PCRE_VERSION}",
|
||||
mode="autoconf",
|
||||
@@ -776,6 +831,15 @@ if "pcre" in targets:
|
||||
download_url=f"https://downloads.sourceforge.net/project/pcre/pcre/{PCRE_VERSION}/",
|
||||
download_name=f"pcre-{PCRE_VERSION}.tar.bz2",
|
||||
)
|
||||
if MAC_CROSS_COMPILE_INTEL:
|
||||
if OLD_CC is None:
|
||||
del os.environ["CC"]
|
||||
else:
|
||||
os.environ["CC"] = OLD_CC
|
||||
if OLD_CCXX is None:
|
||||
del os.environ["CXX"]
|
||||
else:
|
||||
os.environ["CXX"] = OLD_CCXX
|
||||
|
||||
if "pcre2" in targets:
|
||||
build_dependency(
|
||||
@@ -824,6 +888,9 @@ if USE_OCCT and "occ" in targets:
|
||||
if OCCT_VERSION == "7.8.1":
|
||||
patches.append("./patches/occt/no_ExpToCasExe_7_8_1.patch")
|
||||
|
||||
if OCCT_VERSION == "7.9.1":
|
||||
patches.append("./patches/occt/no_ExpToCasExe_7_9_1.patch")
|
||||
|
||||
if "wasm" in flags:
|
||||
patches.append("./patches/occt/no_em_js.patch")
|
||||
|
||||
@@ -842,6 +909,8 @@ if USE_OCCT and "occ" in targets:
|
||||
f"-DUSE_OPENGL=OFF",
|
||||
f"-DUSE_GLES2=OFF",
|
||||
f"-D3RDPARTY_FREETYPE_DIR={DEPS_DIR}/install/freetype",
|
||||
f"-DCMAKE_POLICY_VERSION_MINIMUM=3.5",
|
||||
*MAC_CROSS_COMPILE_INTEL_ARGS,
|
||||
],
|
||||
download_url="https://github.com/Open-Cascade-SAS/OCCT",
|
||||
download_name="occt",
|
||||
@@ -867,24 +936,40 @@ elif "occ" in targets:
|
||||
)
|
||||
|
||||
if "libxml2" in targets:
|
||||
OLD_CC = ""
|
||||
if MAC_CROSS_COMPILE_INTEL:
|
||||
OLD_CC = os.environ.get("CC")
|
||||
os.environ["CC"] = MAC_CROSS_COMPILE_INTEL_CC
|
||||
build_tool_args = [
|
||||
"--without-python",
|
||||
ENABLE_FLAG,
|
||||
DISABLE_FLAG,
|
||||
"--without-zlib",
|
||||
"--without-iconv",
|
||||
"--without-lzma",
|
||||
]
|
||||
if "wasm" in flags:
|
||||
build_tool_args.append("--without-threads")
|
||||
build_dependency(
|
||||
f"libxml2-{LIBXML2_VERSION}",
|
||||
"autoconf",
|
||||
build_tool_args=[
|
||||
"--without-python",
|
||||
ENABLE_FLAG,
|
||||
DISABLE_FLAG,
|
||||
"--without-zlib",
|
||||
"--without-iconv",
|
||||
"--without-lzma",
|
||||
],
|
||||
build_tool_args=build_tool_args,
|
||||
download_url=f"https://download.gnome.org/sources/libxml2/{'.'.join(LIBXML2_VERSION.split('.')[0:2])}/",
|
||||
download_name=f"libxml2-{LIBXML2_VERSION}.tar.xz",
|
||||
)
|
||||
if MAC_CROSS_COMPILE_INTEL:
|
||||
if OLD_CC is None:
|
||||
del os.environ["CC"]
|
||||
else:
|
||||
os.environ["CC"] = OLD_CC
|
||||
|
||||
if "OpenCOLLADA" in targets:
|
||||
patches = ["./patches/opencollada/pr622_and_disable_subdirs.patch"]
|
||||
|
||||
# This patch allows static libraries config on Unix,
|
||||
# because the config is weird and doesn't allow non-shared libraries on Unix.
|
||||
patches.append("./patches/opencollada/allow_static_libraries_config_on_unix.patch")
|
||||
|
||||
if "wasm" in flags:
|
||||
# This is necessary for the WASM build, because recent versions of
|
||||
# clang don't have the tr1:: namespace anymore. However, it breaks
|
||||
@@ -902,6 +987,9 @@ if "OpenCOLLADA" in targets:
|
||||
f"-DPCRE_PCREPOSIX_LIBRARY={DEPS_DIR}/install/pcre-{PCRE_VERSION}/lib/libpcreposix.{LIBRARY_EXT}",
|
||||
f"-DPCRE_PCRE_LIBRARY={DEPS_DIR}/install/pcre-{PCRE_VERSION}/lib/libpcre.{LIBRARY_EXT}",
|
||||
f"-DCMAKE_INSTALL_PREFIX={DEPS_DIR}/install/OpenCOLLADA/",
|
||||
# OpenCOLLADA is ancient at this point and allows cmake 2.6+, which results in error in cmake 4.
|
||||
f"-DCMAKE_POLICY_VERSION_MINIMUM=3.5",
|
||||
*MAC_CROSS_COMPILE_INTEL_ARGS,
|
||||
],
|
||||
download_url="https://github.com/KhronosGroup/OpenCOLLADA.git",
|
||||
download_name="OpenCOLLADA",
|
||||
@@ -924,6 +1012,13 @@ if "python" in targets and not USE_CURRENT_PYTHON_VERSION and "wasm" not in flag
|
||||
PYTHON_CONFIGURE_ARGS: "list[str]" = []
|
||||
if platform.system() == "Darwin":
|
||||
PYTHON_CONFIGURE_ARGS = ["--enable-shared"]
|
||||
open_ssl_prefix = run([brew, "--prefix", "openssl@3"]).strip()
|
||||
# I'm not sure why, but if I do `"{open_ssl_prefix}"` (keep the quotes),
|
||||
# autconf fails to find ssl.
|
||||
PYTHON_CONFIGURE_ARGS.append(f"--with-openssl={open_ssl_prefix}")
|
||||
|
||||
if MAC_CROSS_COMPILE_INTEL:
|
||||
PYTHON_CONFIGURE_ARGS.extend(["--with-universal-archs=intel-64", "--enable-universalsdk"])
|
||||
|
||||
for PYTHON_VERSION in PYTHON_VERSIONS:
|
||||
try:
|
||||
@@ -972,6 +1067,7 @@ if "boost" in targets:
|
||||
"stage",
|
||||
"-s",
|
||||
"NO_BZIP2=1",
|
||||
*MAC_CROSS_COMPILE_INTEL_BJAM_ARGS,
|
||||
],
|
||||
download_url=BOOST_LOCATION,
|
||||
# don't remember what this is, but fail on 1.86
|
||||
@@ -992,6 +1088,11 @@ if "cgal" in targets:
|
||||
gmp_args.extend(("--disable-assembly", "--host", "none", "--enable-cxx"))
|
||||
mpfr_args.extend(("--host", "none"))
|
||||
|
||||
OLD_CC = None
|
||||
if MAC_CROSS_COMPILE_INTEL:
|
||||
OLD_CC = os.environ.get("CC")
|
||||
os.environ["CC"] = MAC_CROSS_COMPILE_INTEL_CC
|
||||
|
||||
build_dependency(
|
||||
name=f"gmp-{GMP_VERSION}",
|
||||
mode="autoconf",
|
||||
@@ -1011,6 +1112,12 @@ if "cgal" in targets:
|
||||
download_name=f"mpfr-{MPFR_VERSION}.tar.bz2",
|
||||
)
|
||||
|
||||
if MAC_CROSS_COMPILE_INTEL:
|
||||
if OLD_CC is None:
|
||||
del os.environ["CC"]
|
||||
else:
|
||||
os.environ["CC"] = OLD_CC
|
||||
|
||||
build_dependency(
|
||||
name=f"cgal-{CGAL_VERSION}",
|
||||
mode="cmake",
|
||||
@@ -1071,6 +1178,7 @@ if "zstd" in targets:
|
||||
f"-DZSTD_BUILD_STATIC=ON",
|
||||
f"-DZSTD_BUILD_SHARED=OFF",
|
||||
f"-DCMAKE_INSTALL_LIBDIR=lib",
|
||||
*MAC_CROSS_COMPILE_INTEL_ARGS,
|
||||
],
|
||||
cmake_dir="build/cmake/",
|
||||
download_url="https://github.com/facebook/zstd",
|
||||
@@ -1097,6 +1205,7 @@ if "rocksdb" in targets:
|
||||
f"-DWITH_ZSTD=On",
|
||||
f"-DPORTABLE=1",
|
||||
f"-DCMAKE_PREFIX_PATH={DEPS_DIR}/install/zstd-{ZSTD_VERSION}",
|
||||
*MAC_CROSS_COMPILE_INTEL_ARGS,
|
||||
],
|
||||
download_url="https://github.com/facebook/rocksdb",
|
||||
download_name="rocksdb",
|
||||
@@ -1124,16 +1233,17 @@ cmake_args = [
|
||||
"-DBoost_NO_BOOST_CMAKE=On",
|
||||
"-DADD_COMMIT_SHA=" + ("On" if ADD_COMMIT_SHA else "Off"),
|
||||
"-DVERSION_OVERRIDE=" + ("On" if ADD_COMMIT_SHA else "Off"),
|
||||
*MAC_CROSS_COMPILE_INTEL_ARGS,
|
||||
]
|
||||
"""Default CMake args to use for all CMake configs."""
|
||||
cmake_args_prefix_path: list[str] = [
|
||||
cmake_args_prefix_path: "list[str]" = [
|
||||
f"{DEPS_DIR}/install/boost-{BOOST_VERSION}",
|
||||
f"{DEPS_DIR}/install/eigen-install-{EIGEN_VERSION}",
|
||||
f"{DEPS_DIR}/install/json-{JSON_VERSION}",
|
||||
]
|
||||
|
||||
|
||||
def get_cmake_args_prefix_path(additional_paths: Sequence[str] = ()) -> list[str]:
|
||||
def get_cmake_args_prefix_path(additional_paths: "Sequence[str]" = ()) -> "list[str]":
|
||||
args_prefix_path = cmake_args_prefix_path.copy()
|
||||
args_prefix_path.extend(additional_paths)
|
||||
prefix_path = ";".join(args_prefix_path)
|
||||
@@ -1144,6 +1254,8 @@ if "wasm" in flags:
|
||||
# Boost is built by the build script so should not be found
|
||||
# inside of the sysroot set by the emscriptem toolchain
|
||||
cmake_args.append("-DWASM_BUILD=On")
|
||||
# set Eigen3 path for WASM to avoid find_package issues
|
||||
cmake_args.append(f"-DEIGEN_DIR={DEPS_DIR}/install/eigen-install-{EIGEN_VERSION}/include/eigen3")
|
||||
|
||||
schemas = os.environ.get("IFCOS_SCHEMAS")
|
||||
if schemas:
|
||||
@@ -1153,9 +1265,26 @@ if "cgal" in targets:
|
||||
cmake_args_prefix_path.append(f"{DEPS_DIR}/install/cgal-{CGAL_VERSION}")
|
||||
cmake_args_prefix_path.append(f"{DEPS_DIR}/install/gmp-{GMP_VERSION}")
|
||||
cmake_args_prefix_path.append(f"{DEPS_DIR}/install/mpfr-{MPFR_VERSION}")
|
||||
if "wasm" in flags:
|
||||
cmake_args.extend(
|
||||
[
|
||||
f"-DCGAL_INCLUDE_DIR={DEPS_DIR}/install/cgal-{CGAL_VERSION}/include",
|
||||
f"-DGMP_INCLUDE_DIR={DEPS_DIR}/install/gmp-{GMP_VERSION}/include",
|
||||
f"-DGMP_LIBRARY_DIR={DEPS_DIR}/install/gmp-{GMP_VERSION}/lib",
|
||||
f"-DMPFR_INCLUDE_DIR={DEPS_DIR}/install/mpfr-{MPFR_VERSION}/include",
|
||||
f"-DMPFR_LIBRARY_DIR={DEPS_DIR}/install/mpfr-{MPFR_VERSION}/lib",
|
||||
]
|
||||
)
|
||||
|
||||
if "occ" in targets and USE_OCCT:
|
||||
cmake_args_prefix_path.append(f"{DEPS_DIR}/install/occt-{OCCT_VERSION}")
|
||||
if "wasm" in flags:
|
||||
cmake_args.extend(
|
||||
[
|
||||
f"-DOCC_INCLUDE_DIR={DEPS_DIR}/install/occt-{OCCT_VERSION}/include/opencascade",
|
||||
f"-DOCC_LIBRARY_DIR={DEPS_DIR}/install/occt-{OCCT_VERSION}/lib",
|
||||
]
|
||||
)
|
||||
|
||||
elif "occ" in targets:
|
||||
# We don't support find_package for OCE.
|
||||
@@ -1176,6 +1305,13 @@ else:
|
||||
|
||||
if "libxml2" in targets:
|
||||
cmake_args_prefix_path.append(f"{DEPS_DIR}/install/libxml2-{LIBXML2_VERSION}")
|
||||
if "wasm" in flags:
|
||||
cmake_args.extend(
|
||||
[
|
||||
f"-DLIBXML2_INCLUDE_DIR={DEPS_DIR}/install/libxml2-{LIBXML2_VERSION}/include/libxml2",
|
||||
f"-DLIBXML2_LIBRARIES={DEPS_DIR}/install/libxml2-{LIBXML2_VERSION}/lib/libxml2.{LIBRARY_EXT}",
|
||||
]
|
||||
)
|
||||
|
||||
if "hdf5" in targets:
|
||||
cmake_args_prefix_path.append(f"{DEPS_DIR}/install/hdf5-{HDF5_VERSION}")
|
||||
@@ -1232,6 +1368,9 @@ if "IfcOpenShell-Python" in targets:
|
||||
if "wasm" in flags:
|
||||
ADDITIONAL_ARGS = f"-Wl,-undefined,suppress -sSIDE_MODULE=2 -sEXPORTED_FUNCTIONS=_PyInit__ifcopenshell_wrapper"
|
||||
|
||||
# NOTE: We don't use `CXXFLAGS` for wrappers, so wrapper is compiled with different flags
|
||||
# (e.g. ` -fdata-sections` is missing, which is set by default for executables)
|
||||
# So cache doesn't match and running build-all.py builds most of ifcopenshell libraries twice.
|
||||
os.environ["CPPFLAGS"] = f"{CXXFLAGS_MINIMAL} {ADDITIONAL_ARGS}"
|
||||
os.environ["CXXFLAGS"] = f"{CXXFLAGS_MINIMAL} {ADDITIONAL_ARGS}"
|
||||
os.environ["CFLAGS"] = f"{CFLAGS_MINIMAL} {ADDITIONAL_ARGS}"
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||
index 34300d41ad..09b2e0d45f 100644
|
||||
--- a/CMakeLists.txt
|
||||
+++ b/CMakeLists.txt
|
||||
@@ -721,6 +721,8 @@ else()
|
||||
OCCT_CHECK_AND_UNSET ("3RDPARTY_DOT_EXECUTABLE")
|
||||
endif()
|
||||
|
||||
+list (REMOVE_ITEM BUILD_TOOLKITS ExpToCasExe)
|
||||
+
|
||||
# bison
|
||||
if (BUILD_YACCLEX)
|
||||
list (APPEND OCCT_3RDPARTY_CMAKE_LIST "adm/cmake/bison")
|
||||
@@ -0,0 +1,13 @@
|
||||
diff --git a/OpenCOLLADAConfig.cmake.in b/OpenCOLLADAConfig.cmake.in
|
||||
index d765b108..6800c2a7 100644
|
||||
--- a/OpenCOLLADAConfig.cmake.in
|
||||
+++ b/OpenCOLLADAConfig.cmake.in
|
||||
@@ -31,7 +31,7 @@ set(OPENCOLLADA_INCLUDE_DIRS
|
||||
include("@PACKAGE_OPENCOLLADA_INST_CMAKECONFIG@/OpenCOLLADATargets.cmake")
|
||||
|
||||
# Set the library variable
|
||||
-if(UNIX)
|
||||
+if(0)
|
||||
set(OPENCOLLADA_LIBRARIES
|
||||
ftoa_shared
|
||||
buffer_shared
|
||||
+1
-1
@@ -7,5 +7,5 @@ setup(name='ifcopenshell',
|
||||
author_email='thomas@aecgeeks.com',
|
||||
url='http://ifcopenshell.org',
|
||||
packages=find_packages(),
|
||||
package_data={'': ['*.so', '*.json']},
|
||||
package_data={'ifcopenshell': ['util/schema/*.json', 'util/schema/*.ifc'], '': ['*.so', '*.json', '*.ifc']},
|
||||
)
|
||||
|
||||
+1
-3
@@ -38,6 +38,7 @@ exclude = [
|
||||
"src/ifc2ca/templates",
|
||||
]
|
||||
[tool.ruff.lint]
|
||||
preview = true
|
||||
select = [
|
||||
# Default Ruff rules.
|
||||
# "E4", # imports
|
||||
@@ -60,7 +61,4 @@ ignore = [
|
||||
"UP030", # implicit references for positional format fields
|
||||
"UP031", # Replace % with .format
|
||||
"UP032", # Replace .format with f-string
|
||||
|
||||
# Deprecated Ruff rules.
|
||||
"UP038", # Use X | Y in isinstance
|
||||
]
|
||||
|
||||
+3
-12
@@ -6,7 +6,7 @@ from numpy.typing import NDArray
|
||||
|
||||
def camera_vectors_from_element_placement(
|
||||
elem_placement: NDArray[np.float64],
|
||||
) -> tuple[NDArray[np.float64], NDArray[np.float64], NDArray[np.float64]]:
|
||||
) -> tuple[list[float], list[float], list[float]]:
|
||||
"""
|
||||
Calculate the vectors of a camera pointing to an element.
|
||||
|
||||
@@ -22,7 +22,7 @@ def camera_vectors_from_element_placement(
|
||||
|
||||
def camera_vectors_from_target_position(
|
||||
target_position: NDArray[np.float64], offset: Optional[NDArray[np.float64]] = None
|
||||
) -> tuple[NDArray[np.float64], NDArray[np.float64], NDArray[np.float64]]:
|
||||
) -> tuple[list[float], list[float], list[float]]:
|
||||
"""
|
||||
Calculate the vectors of a camera pointing to a target point.
|
||||
|
||||
@@ -38,16 +38,7 @@ def camera_vectors_from_target_position(
|
||||
camera_direction = unit_vector(-camera_offset) # pylint: disable=invalid-unary-operand-type
|
||||
camera_right = unit_vector(np.cross(np.array([0.0, 0.0, 1.0]), camera_direction))
|
||||
camera_up = unit_vector(np.cross(camera_direction, camera_right))
|
||||
return camera_position, camera_direction, camera_up
|
||||
# rotation_transform = np.eye(4)
|
||||
# rotation_transform[0, :3] = camera_right
|
||||
# rotation_transform[1, :3] = camera_up
|
||||
# rotation_transform[2, :3] = camera_direction
|
||||
# translation_transform = np.eye(4)
|
||||
# translation_transform[:3, -1] = -camera_position
|
||||
# look_at_transform = np.matmul(rotation_transform, translation_transform)
|
||||
# mat = np.linalg.inv(look_at_transform)
|
||||
# return camera_position, -mat[:3, 2], mat[:3, 1]
|
||||
return camera_position.tolist(), camera_direction.tolist(), camera_up.tolist()
|
||||
|
||||
|
||||
def unit_vector(v: NDArray[np.float64]) -> NDArray[np.float64]:
|
||||
|
||||
@@ -192,6 +192,8 @@ class TopicHandler:
|
||||
bcf_zip: The BCF zip file to save to.
|
||||
"""
|
||||
topic_dir = self.guid
|
||||
# simulating directory creation (ZipFile in python < 3.11 doesn't have mkdir)
|
||||
destination_zip.writestr(f"{topic_dir}/", "")
|
||||
self._save_xml(destination_zip, self._markup, "markup.bcf")
|
||||
self._save_viewpoints(destination_zip, topic_dir)
|
||||
self._save_bim_snippet(destination_zip)
|
||||
|
||||
@@ -319,9 +319,7 @@ def build_viewpoint(element: entity_instance) -> mdl.VisualizationInfo:
|
||||
ifc_file = element.wrapped_data.file
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(ifc_file)
|
||||
elem_placement = ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement)
|
||||
elem_placement[0][3] *= unit_scale
|
||||
elem_placement[1][3] *= unit_scale
|
||||
elem_placement[2][3] *= unit_scale
|
||||
elem_placement[:3, 3] *= unit_scale
|
||||
|
||||
return mdl.VisualizationInfo(
|
||||
guid=str(uuid.uuid4()),
|
||||
@@ -381,7 +379,7 @@ def build_camera(elem_placement: NDArray[np.float64]) -> mdl.PerspectiveCamera:
|
||||
|
||||
|
||||
def build_camera_from_vectors(
|
||||
camera_position: NDArray[np.float64], camera_dir: NDArray[np.float64], camera_up: NDArray[np.float64]
|
||||
camera_position: list[float], camera_dir: list[float], camera_up: list[float]
|
||||
) -> mdl.PerspectiveCamera:
|
||||
"""
|
||||
Return a BCF camera for an IFC element placement matrix.
|
||||
|
||||
@@ -179,6 +179,8 @@ class TopicHandler:
|
||||
bcf_zip: The BCF zip file to save to.
|
||||
"""
|
||||
topic_dir = self.guid
|
||||
# simulating directory creation (ZipFile in python < 3.11 doesn't have mkdir)
|
||||
destination_zip.writestr(f"{topic_dir}/", "")
|
||||
self._save_xml(destination_zip, self._markup, "markup.bcf")
|
||||
self._save_viewpoints(destination_zip, topic_dir)
|
||||
self._save_bim_snippet(destination_zip)
|
||||
|
||||
@@ -319,9 +319,7 @@ def build_viewpoint(element: entity_instance) -> mdl.VisualizationInfo:
|
||||
ifc_file = element.wrapped_data.file
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(ifc_file)
|
||||
elem_placement = ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement)
|
||||
elem_placement[0][3] *= unit_scale
|
||||
elem_placement[1][3] *= unit_scale
|
||||
elem_placement[2][3] *= unit_scale
|
||||
elem_placement[:3, 3] *= unit_scale
|
||||
|
||||
return mdl.VisualizationInfo(
|
||||
guid=str(uuid.uuid4()),
|
||||
@@ -381,7 +379,7 @@ def build_camera(elem_placement: NDArray[np.float64]) -> mdl.PerspectiveCamera:
|
||||
|
||||
|
||||
def build_camera_from_vectors(
|
||||
camera_position: NDArray[np.float64], camera_dir: NDArray[np.float64], camera_up: NDArray[np.float64]
|
||||
camera_position: list[float], camera_dir: list[float], camera_up: list[float]
|
||||
) -> mdl.PerspectiveCamera:
|
||||
"""
|
||||
Return a BCF camera for an IFC element placement matrix.
|
||||
|
||||
+2
-2
@@ -87,7 +87,7 @@ BLENDER_PLATFORM:=windows-x64
|
||||
endif
|
||||
|
||||
# Current build commit hash.
|
||||
OLD:=e795cec
|
||||
OLD:=6924012
|
||||
.PHONY: bump
|
||||
bump:
|
||||
ifndef NEW
|
||||
@@ -127,7 +127,7 @@ endif
|
||||
cd build && . env/$(VENV_ACTIVATE) && $(PIP) download GitPython --dest=./wheels
|
||||
# Provides audio playback for costing
|
||||
# This is a REALLY IMPORTANT feature
|
||||
cd build && . env/$(VENV_ACTIVATE) && $(PIP) wheel git+https://github.com/Andrej730/aud.git --wheel-dir=./wheels
|
||||
cd build && . env/$(VENV_ACTIVATE) && $(PIP) wheel git+https://github.com/zdhoward/aud --wheel-dir=./wheels
|
||||
# IfcOpenShell dependency - support for new typing features
|
||||
cd build && . env/$(VENV_ACTIVATE) && $(PIP) download typing_extensions --dest=./wheels
|
||||
# Required by IfcCSV
|
||||
|
||||
@@ -5,10 +5,10 @@ FILE_NAME('Psets_BBIM_Annotation.ifc','2020-01-01T00:00:00',$,$,'Psets_BBIM_Anno
|
||||
FILE_SCHEMA(('IFC4'));
|
||||
ENDSEC;
|
||||
DATA;
|
||||
#1=IFCPROPERTYSETTEMPLATE('3VuPUwdCD2Qx3XDDRs0R1N',$,'EPset_Annotation','',.PSET_TYPEDRIVENOVERRIDE.,'IfcAnnotation,IfcTypeProduct',(#2,#3,#4,#29));
|
||||
#1=IFCPROPERTYSETTEMPLATE('3VuPUwdCD2Qx3XDDRs0R1N',$,'EPset_Annotation','',.PSET_TYPEDRIVENOVERRIDE.,'IfcAnnotation,IfcTypeProduct',(#4,#33,#29,#32,#3,#2));
|
||||
#2=IFCSIMPLEPROPERTYTEMPLATE('2P7JN79n96Q9pElZ83LKe4',$,'ZIndex','',.P_SINGLEVALUE.,'IfcInteger',$,$,$,$,$,.READWRITE.);
|
||||
#3=IFCSIMPLEPROPERTYTEMPLATE('1Wpx_r2xj1_9w5JpI0QRJy',$,'Symbol','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#4=IFCSIMPLEPROPERTYTEMPLATE('3q0oxMUKP47vZ4jnyG$dDb',$,'Classes','Classes separarated by spaces that end up in classes for this element in svg. Can be used to specify the text font size: small - 1.8mm; regular - 2.5mm; large - 3.5mm; header - 5mm; title - 7mm. By default regular size is used.',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#4=IFCSIMPLEPROPERTYTEMPLATE('3q0oxMUKP47vZ4jnyG$dDb',$,'Classes','Classes separated by spaces that end up in classes for this element in svg. Can be used to specify the text font size: small - 1.8mm; regular - 2.5mm; large - 3.5mm; header - 5mm; title - 7mm. By default regular size is used.',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#5=IFCPROPERTYSETTEMPLATE('0iKwujnQL9IevVQato8f7Z',$,'BBIM_Batting','',.PSET_TYPEDRIVENOVERRIDE.,'IfcAnnotation/BATTING,IfcTypeProduct',(#6,#7));
|
||||
#6=IFCSIMPLEPROPERTYTEMPLATE('0t2LEesGT1QRQtrIZUAR8L',$,'Thickness','Batting thickness',.P_SINGLEVALUE.,'IfcPositiveLengthMeasure',$,$,$,$,$,.READWRITE.);
|
||||
#7=IFCSIMPLEPROPERTYTEMPLATE('082PndS6v2kBOiJoSboMnh',$,'Reverse pattern direction','Reverse batting pattern (swap starting and ending points)',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
|
||||
@@ -36,5 +36,7 @@ DATA;
|
||||
#29=IFCSIMPLEPROPERTYTEMPLATE('2pJmUDpB50VBdCOib1zcJJ',$,'Newline_At','',.P_SINGLEVALUE.,'IfcInteger',$,$,$,$,$,.READWRITE.);
|
||||
#30=IFCSIMPLEPROPERTYTEMPLATE('2TJn72t_v2cvBUG916Dpev',$,'CustomUnit','Dimension''s custom unit',.P_ENUMERATEDVALUE.,'IfcText',$,#31,$,$,$,.READWRITE.);
|
||||
#31=IFCPROPERTYENUMERATION('CustomUnit',(IFCTEXT('Feet and Inches - Fractional'),IFCTEXT('Feet - Decimal'),IFCTEXT('Inches - Fractional'),IFCTEXT('Inches - Decimal'),IFCTEXT('Meters'),IFCTEXT('Decimeters'),IFCTEXT('Centimeters'),IFCTEXT('Millimeters')),$);
|
||||
#32=IFCSIMPLEPROPERTYTEMPLATE('0gjJzDYBX8P85qn1xcAOOo',$,'Reverse_List','',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
|
||||
#33=IFCSIMPLEPROPERTYTEMPLATE('22TrcxF8jFNB4buSmzjGEF',$,'List_Separator','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
|
||||
ENDSEC;
|
||||
END-ISO-10303-21;
|
||||
|
||||
@@ -15,6 +15,8 @@ import pystache
|
||||
import json
|
||||
import base64
|
||||
import xml.etree.ElementTree as ET
|
||||
import platformdirs
|
||||
from pathlib import Path
|
||||
|
||||
sio_port = 8080 # default port
|
||||
|
||||
@@ -233,10 +235,17 @@ async def demo(request):
|
||||
return web.Response(text=html_content, content_type="text/html")
|
||||
|
||||
|
||||
async def on_startup(app):
|
||||
pid_file = "running_pid.json"
|
||||
def get_pid_file_path():
|
||||
"""Get the path to the PID file in the user's cache directory."""
|
||||
cache_dir = Path(platformdirs.user_cache_dir("bonsai"))
|
||||
cache_dir.mkdir(parents=True, exist_ok=True)
|
||||
return cache_dir / "running_pid.json"
|
||||
|
||||
if os.path.exists(pid_file):
|
||||
|
||||
async def on_startup(app):
|
||||
pid_file = get_pid_file_path()
|
||||
|
||||
if pid_file.exists():
|
||||
with open(pid_file, "r") as f:
|
||||
pids = json.load(f)
|
||||
else:
|
||||
|
||||
@@ -556,6 +556,7 @@ def draw_filter(
|
||||
elif ifc_filter.type == "query":
|
||||
row = box.row(align=True)
|
||||
row.prop(ifc_filter, "name", text="", icon="POINTCLOUD_DATA")
|
||||
row.prop(ifc_filter, "comparison", text="")
|
||||
row.prop(ifc_filter, "value", text="")
|
||||
elif ifc_filter.type == "instance":
|
||||
row = box.row(align=True)
|
||||
|
||||
@@ -283,6 +283,7 @@ class IfcImporter:
|
||||
self.place_objects_in_collections()
|
||||
self.profile_code("Place objects in collections")
|
||||
self.setup_arrays()
|
||||
self.update_linked_aggregates()
|
||||
self.profile_code("Setup arrays")
|
||||
tool.Project.load_linked_models_from_ifc()
|
||||
self.profile_code("Load linked models")
|
||||
@@ -624,18 +625,13 @@ class IfcImporter:
|
||||
print("Done creating geometry")
|
||||
|
||||
def create_spatial_elements(self) -> None:
|
||||
if tool.Blender.get_addon_preferences().spatial_elements_unselectable:
|
||||
self.create_generic_elements(self.spatial_elements, unselectable=True)
|
||||
else:
|
||||
self.create_generic_elements(self.spatial_elements, unselectable=False)
|
||||
self.create_generic_elements(self.spatial_elements)
|
||||
|
||||
def create_elements(self) -> None:
|
||||
self.create_generic_elements(self.elements)
|
||||
self.create_generic_elements(self.gross_elements, is_gross=True)
|
||||
|
||||
def create_generic_elements(
|
||||
self, elements: set[ifcopenshell.entity_instance], unselectable=False, is_gross=False
|
||||
) -> None:
|
||||
def create_generic_elements(self, elements: set[ifcopenshell.entity_instance], is_gross=False) -> None:
|
||||
if isinstance(self.file, ifcopenshell.sqlite):
|
||||
return self.create_generic_sqlite_elements(elements)
|
||||
|
||||
@@ -659,10 +655,6 @@ class IfcImporter:
|
||||
print("{} / {} elements processed ...".format(i, total))
|
||||
objects.add(self.create_product(element))
|
||||
|
||||
if unselectable:
|
||||
for obj in objects:
|
||||
obj.hide_select = True
|
||||
|
||||
def create_generic_sqlite_elements(self, elements: set[ifcopenshell.entity_instance]) -> None:
|
||||
assert isinstance(self.file, ifcopenshell.sql.sqlite)
|
||||
self.geometry_cache = self.file.get_geometry([e.id() for e in elements])
|
||||
@@ -1017,6 +1009,7 @@ class IfcImporter:
|
||||
)
|
||||
obj = self.create_product(self.project["ifc"])
|
||||
obj.hide_select = True
|
||||
obj.hide_viewport = True
|
||||
self.project["blender"].objects.link(obj)
|
||||
self.project["blender"].BIMCollectionProperties.obj = obj
|
||||
props = tool.Blender.get_object_bim_props(obj)
|
||||
@@ -1218,6 +1211,36 @@ class IfcImporter:
|
||||
tool.Blender.Modifier.Array.set_children_lock_state(element, i, True)
|
||||
tool.Blender.Modifier.Array.constrain_children_to_parent(element)
|
||||
|
||||
def update_linked_aggregates(self):
|
||||
# TODO Remove this after a while. See commit 17d6b8a
|
||||
# https://github.com/IfcOpenShell/IfcOpenShell/commit/17d6b8af61c8dd7cd82f6e72b2fb3831d851a33d#commitcomment-163151609
|
||||
|
||||
groups = self.file.by_type("IfcGroup")
|
||||
target_groups = [group for group in groups if group.Name == "BBIM_Linked_Aggregate"]
|
||||
if not target_groups:
|
||||
return
|
||||
|
||||
for i, group in enumerate(target_groups):
|
||||
name = "Default"
|
||||
elements = ifcopenshell.util.element.get_grouped_by(self.file.by_id(group.id()), is_recursive=True)
|
||||
for j, element in enumerate(elements):
|
||||
split = element.Name.rsplit("_")
|
||||
name = split[0]
|
||||
try:
|
||||
aggregate_index = int(split[1])
|
||||
except:
|
||||
aggregate_index = 0
|
||||
pset = ifcopenshell.util.element.get_pset(element, "BBIM_Linked_Aggregate", should_inherit=False)
|
||||
if not pset:
|
||||
return
|
||||
if "Aggregate_Index" not in pset.keys():
|
||||
ifcopenshell.api.run(
|
||||
"pset.edit_pset",
|
||||
self.file,
|
||||
pset=self.file.by_id(pset["id"]),
|
||||
properties={"Aggregate_Index": aggregate_index, "Name": name},
|
||||
)
|
||||
|
||||
|
||||
class IfcImportSettings:
|
||||
"""
|
||||
|
||||
@@ -1215,7 +1215,7 @@ class ActivateBcfViewpoint(bpy.types.Operator):
|
||||
assert blender_topic
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
if self.viewpoint_guid:
|
||||
viewpoint_guid = self.viewpoint_guid
|
||||
viewpoint_guid = self.viewpoint_guid + ".bcfv"
|
||||
if viewpoint_guid not in topic.viewpoints:
|
||||
self.report({"ERROR"}, f"No such viewpoint in the active topic: '{viewpoint_guid}'.")
|
||||
return {"CANCELLED"}
|
||||
@@ -1313,7 +1313,7 @@ class ActivateBcfViewpoint(bpy.types.Operator):
|
||||
x_axis = y_axis.cross(z_axis).normalized()
|
||||
rotation = Matrix((x_axis, y_axis, z_axis))
|
||||
rotation.invert()
|
||||
matrix = np.matrix(
|
||||
matrix = np.array(
|
||||
(
|
||||
[x_axis[0], y_axis[0], z_axis[0], camera.camera_view_point.x],
|
||||
[x_axis[1], y_axis[1], z_axis[1], camera.camera_view_point.y],
|
||||
|
||||
@@ -241,6 +241,7 @@ class DecoratorData:
|
||||
cls.is_loaded = True
|
||||
cls.cut_cache = {}
|
||||
cls.layerset_cache = {}
|
||||
cls.fill_cache = {}
|
||||
|
||||
text = {}
|
||||
dimension = {}
|
||||
@@ -346,12 +347,10 @@ class DecoratorData:
|
||||
(font_size_type for font_size_type in FONT_SIZES if font_size_type in classes_split), "regular"
|
||||
)
|
||||
font_size = FONT_SIZES[font_size_type]
|
||||
|
||||
# get symbol
|
||||
symbol = tool.Drawing.get_annotation_symbol(element)
|
||||
|
||||
# get newline_at
|
||||
newline_at = pset_data.get("Newline_At", 0)
|
||||
reverse_list = pset_data.get("Reverse_List", False)
|
||||
list_separator = pset_data.get("List_Separator") or ", "
|
||||
|
||||
# other attributes
|
||||
literals = tool.Drawing.get_text_literal(obj, return_list=True)
|
||||
@@ -363,11 +362,20 @@ class DecoratorData:
|
||||
literal_data = {
|
||||
"Literal": literal_value,
|
||||
"BoxAlignment": literal.BoxAlignment,
|
||||
"CurrentValue": tool.Drawing.replace_text_literal_variables(literal_value, product),
|
||||
"CurrentValue": tool.Drawing.replace_text_literal_variables(
|
||||
literal_value, product, reverse_list, list_separator
|
||||
),
|
||||
}
|
||||
literals_data.append(literal_data)
|
||||
|
||||
return {"Literals": literals_data, "FontSize": font_size, "Symbol": symbol, "Newline_At": newline_at}
|
||||
return {
|
||||
"Literals": literals_data,
|
||||
"FontSize": font_size,
|
||||
"Symbol": symbol,
|
||||
"Newline_At": newline_at,
|
||||
"Reverse_List": reverse_list,
|
||||
"List_Separator": list_separator,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def get_dimension_data(cls, obj: bpy.types.Object) -> dict[str, Any]:
|
||||
|
||||
@@ -1210,6 +1210,7 @@ class PlanLevelDecorator(BaseDecorator):
|
||||
rl = self.format_value(context, z)
|
||||
text = "{}{}".format("" if z < 0 else "+", rl)
|
||||
return text
|
||||
|
||||
self.draw_dimension_text(
|
||||
context,
|
||||
get_text,
|
||||
|
||||
@@ -106,14 +106,17 @@ class AddAnnotationType(bpy.types.Operator, tool.Ifc.Operator):
|
||||
obj = bpy.data.objects.new(object_type, None)
|
||||
|
||||
obj.name = props.type_name
|
||||
ifc_context = ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model", "Annotation", "MODEL_VIEW")
|
||||
element = tool.Drawing.run_root_assign_class(
|
||||
obj=obj,
|
||||
ifc_class="IfcTypeProduct",
|
||||
predefined_type=object_type,
|
||||
should_add_representation=has_representation,
|
||||
context=ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model", "Annotation", "MODEL_VIEW"),
|
||||
context=ifc_context,
|
||||
ifc_representation_class=tool.Drawing.get_ifc_representation_class(object_type),
|
||||
)
|
||||
if representation := tool.Drawing.get_representation(element, ifc_context):
|
||||
tool.Drawing.reload_representation(obj=obj, representation=representation)
|
||||
element.ApplicableOccurrence = f"IfcAnnotation/{object_type}"
|
||||
|
||||
if props.create_representation_for_type and object_type == "IMAGE":
|
||||
@@ -435,10 +438,6 @@ class CreateDrawing(bpy.types.Operator):
|
||||
if os.path.isfile(svg_path) and self.props.should_use_underlay_cache:
|
||||
return svg_path
|
||||
|
||||
visible_object_names = {obj.name for obj in bpy.context.visible_objects}
|
||||
for obj in bpy.context.view_layer.objects:
|
||||
obj.hide_render = obj.name not in visible_object_names
|
||||
|
||||
assert context.scene and context.view_layer and context.screen
|
||||
context.scene.render.filepath = str(Path(svg_path).with_suffix(".png"))
|
||||
assert (drawing_style := self.cprops.get_active_drawing_style())
|
||||
@@ -449,7 +448,7 @@ class CreateDrawing(bpy.types.Operator):
|
||||
previous_visibility: dict[str, bool] = {}
|
||||
collection = tool.Blender.get_object_bim_props(self.camera).collection
|
||||
assert collection
|
||||
# Hie annotations.
|
||||
# Hide annotations.
|
||||
for obj in collection.objects:
|
||||
if context.view_layer.objects.get(obj.name):
|
||||
previous_visibility[obj.name] = obj.hide_get()
|
||||
@@ -840,6 +839,7 @@ class CreateDrawing(bpy.types.Operator):
|
||||
|
||||
if tool.Drawing.is_camera_orthographic():
|
||||
self.generate_bisect_linework(context, root)
|
||||
self.generate_wall_layers(context, root)
|
||||
self.merge_linework_and_add_metadata(root)
|
||||
self.move_elements_to_top(root)
|
||||
|
||||
@@ -1266,9 +1266,15 @@ class CreateDrawing(bpy.types.Operator):
|
||||
|
||||
if layer:
|
||||
classes.append(layer.is_a())
|
||||
material_name = layer.Material.Name or "null"
|
||||
material_name = tool.Drawing.canonicalise_class_name(material_name)
|
||||
classes.append(f"layer-material-{material_name}")
|
||||
layer_material = layer.Material
|
||||
layer_material_name = layer_material.Name or "null"
|
||||
layer_material_name = tool.Drawing.canonicalise_class_name(layer_material_name)
|
||||
classes.append(f"layer-material-{layer_material_name}")
|
||||
|
||||
# Add material category if available
|
||||
if hasattr(layer_material, "Category") and layer_material.Category:
|
||||
layer_material_category = tool.Drawing.canonicalise_class_name(layer_material.Category)
|
||||
classes.append(f"layer-material-category-{layer_material_category}")
|
||||
|
||||
for key in self.metadata:
|
||||
value = ifcopenshell.util.selector.get_element_value(element, key)
|
||||
@@ -2165,27 +2171,10 @@ class ActivateModel(bpy.types.Operator):
|
||||
if model_props.show_cut_decorator:
|
||||
CutDecorator.uninstall()
|
||||
|
||||
# Preserve current visibility statuses for:
|
||||
# - non-ifc objects
|
||||
# - type product
|
||||
# - annotations (so we won't unhide other drawings)
|
||||
ifc_file = tool.Ifc.get()
|
||||
visibility_status: dict[bpy.types.Object, bool] = {}
|
||||
for obj in bpy.data.objects:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element:
|
||||
hide = obj.hide_get()
|
||||
elif element.is_a("IfcAnnotation"):
|
||||
hide = True
|
||||
elif element.is_a("IfcTypeProduct"):
|
||||
hide = obj.hide_get()
|
||||
else:
|
||||
continue
|
||||
visibility_status[obj] = hide
|
||||
|
||||
if not bpy.app.background:
|
||||
with context.temp_override(**tool.Blender.get_viewport_context()):
|
||||
bpy.ops.object.hide_view_clear(select=False)
|
||||
bpy.ops.bim.activate_status_filters(only_if_enabled=True)
|
||||
|
||||
elements = {e for obj in context.visible_objects if (e := tool.Ifc.get_entity(obj))}
|
||||
@@ -2230,15 +2219,9 @@ class ActivateModel(bpy.types.Operator):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=model,
|
||||
should_reload=False,
|
||||
is_global=True,
|
||||
should_sync_changes_first=True,
|
||||
)
|
||||
|
||||
# restore visibility after hide_view_clear()
|
||||
for obj, hide_status in visibility_status.items():
|
||||
obj.hide_set(hide_status)
|
||||
|
||||
tool.Blender.reset_object_visibility()
|
||||
tool.Blender.update_viewport()
|
||||
bonsai.bim.handler.refresh_ui_data()
|
||||
|
||||
|
||||
@@ -465,6 +465,12 @@ class DocProperties(PropertyGroup):
|
||||
return None
|
||||
return tool.Ifc.get().by_id(drawing_id)
|
||||
|
||||
def get_active_target_view(self) -> Union[str, None]:
|
||||
active_drawing = self.get_active_drawing()
|
||||
if not active_drawing:
|
||||
return None
|
||||
return tool.Drawing.get_drawing_target_view(active_drawing)
|
||||
|
||||
|
||||
def update_width_height(self: "BIMCameraProperties", context: bpy.types.Context) -> None:
|
||||
self.update_camera_resolution()
|
||||
@@ -738,6 +744,11 @@ class BIMTextProperties(PropertyGroup):
|
||||
name="Font Size",
|
||||
)
|
||||
newline_at: IntProperty(name="Newline At")
|
||||
reverse_list: BoolProperty(name="Reverse List", description="Reverses the order of any list.", default=False)
|
||||
list_separator: StringProperty( # pyright: ignore[reportRedeclaration]
|
||||
name="List Separator",
|
||||
description="Text used to separate lists. Uses a comma (, ) if empty.",
|
||||
)
|
||||
symbol: EnumProperty( # pyright: ignore[reportRedeclaration]
|
||||
name="Symbol",
|
||||
description="Symbol from symbols.svg to use for this text.",
|
||||
@@ -754,6 +765,8 @@ class BIMTextProperties(PropertyGroup):
|
||||
literals: bpy.types.bpy_prop_collection_idprop[LiteralProps]
|
||||
font_size: str
|
||||
newline_at: int
|
||||
reverse_list: bool
|
||||
list_separator: str
|
||||
symbol: Union[str, Literal["NO SYMBOL", "CUSTOM SYMBOL"]]
|
||||
custom_symbol: str
|
||||
|
||||
@@ -790,6 +803,8 @@ class BIMTextProperties(PropertyGroup):
|
||||
"FontSize": float(self.font_size),
|
||||
"Newline_At": int(self.newline_at),
|
||||
"Symbol": self.get_symbol(),
|
||||
"Reverse_List": self.reverse_list,
|
||||
"List_Separator": self.list_separator or ", ",
|
||||
}
|
||||
return text_data
|
||||
|
||||
|
||||
@@ -839,6 +839,12 @@ class SvgWriter:
|
||||
|
||||
symbol = tool.Drawing.get_annotation_symbol(element)
|
||||
newline_at = tool.Drawing.get_newline_at(element)
|
||||
|
||||
# Get reverse_list and list_separator from EPset_Annotation
|
||||
pset_data = ifcopenshell.util.element.get_pset(element, "EPset_Annotation") or {}
|
||||
reverse_list = pset_data.get("Reverse_List", False)
|
||||
list_separator = pset_data.get("List_Separator") or ", "
|
||||
|
||||
template_text_fields = []
|
||||
if symbol:
|
||||
symbol_transform = self.get_symbol_transform(text_position_svg_str, angle, text_obj)
|
||||
@@ -855,7 +861,7 @@ class SvgWriter:
|
||||
# NOTE: zip makes sure that we iterate over the shortest list
|
||||
for field, text_literal in zip(template_text_fields, text_literals):
|
||||
field.text = tool.Drawing.replace_text_literal_variables(
|
||||
text_literal.Literal, product or element
|
||||
text_literal.Literal, product or element, reverse_list, list_separator
|
||||
)
|
||||
field.attrib["class"] = classes_str
|
||||
|
||||
@@ -875,7 +881,9 @@ class SvgWriter:
|
||||
line_number = 0
|
||||
|
||||
for text_literal in text_literals:
|
||||
text = tool.Drawing.replace_text_literal_variables(text_literal.Literal, product or element)
|
||||
text = tool.Drawing.replace_text_literal_variables(
|
||||
text_literal.Literal, product or element, reverse_list, list_separator
|
||||
)
|
||||
text_tags = self.create_text_tag(
|
||||
text,
|
||||
text_position_svg,
|
||||
|
||||
@@ -590,6 +590,10 @@ class BIM_PT_text(Panel):
|
||||
row.prop(props, "font_size")
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(props, "newline_at")
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(props, "reverse_list")
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(props, "list_separator")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(props, "symbol")
|
||||
@@ -648,6 +652,12 @@ class BIM_PT_text(Panel):
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Newline_At")
|
||||
row.label(text=str(text_data["Newline_At"]))
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Reverse_List")
|
||||
row.label(text=str(text_data["Reverse_List"]))
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="List_Separator")
|
||||
row.label(text=str(text_data["List_Separator"]))
|
||||
|
||||
for literal_data in text_data["Literals"]:
|
||||
box = self.layout.box()
|
||||
|
||||
@@ -180,9 +180,6 @@ def create_annotation_occurrence(context):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
|
||||
if obj.data and not relating_type_rep:
|
||||
|
||||
@@ -427,9 +427,7 @@ class SwitchRepresentation(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
obj: bpy.props.StringProperty()
|
||||
ifc_definition_id: bpy.props.IntProperty()
|
||||
should_reload: bpy.props.BoolProperty()
|
||||
disable_opening_subtractions: bpy.props.BoolProperty()
|
||||
should_switch_all_meshes: bpy.props.BoolProperty()
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
@@ -465,9 +463,6 @@ class SwitchRepresentation(bpy.types.Operator, tool.Ifc.Operator):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=representation,
|
||||
should_reload=self.should_reload,
|
||||
is_global=self.should_switch_all_meshes,
|
||||
should_sync_changes_first=True,
|
||||
)
|
||||
|
||||
|
||||
@@ -721,9 +716,6 @@ class UpdateParametricRepresentation(bpy.types.Operator):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=tool.Ifc.get().by_id(props.ifc_definition_id),
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
if show_representation_parameters:
|
||||
core.get_representation_ifc_parameters(tool.Geometry, obj=obj)
|
||||
@@ -1427,7 +1419,13 @@ class RefreshLinkedAggregate(bpy.types.Operator, tool.Ifc.Operator):
|
||||
pset = ifcopenshell.util.element.get_pset(element, self.pset_name)
|
||||
index = pset["Index"]
|
||||
annotations = get_assignments(element)
|
||||
original_data[group][index] = {"Name": str(pset["Aggregate_Index"]), "Assignment": annotations}
|
||||
container = ifcopenshell.util.element.get_container(element)
|
||||
original_data[group][index] = {
|
||||
"Name": pset["Name"],
|
||||
"Aggregate_Index": pset["Aggregate_Index"],
|
||||
"Assignment": annotations,
|
||||
"Container": container,
|
||||
}
|
||||
|
||||
parts = ifcopenshell.util.element.get_parts(element)
|
||||
if parts:
|
||||
@@ -1470,13 +1468,22 @@ class RefreshLinkedAggregate(bpy.types.Operator, tool.Ifc.Operator):
|
||||
index = pset["Index"]
|
||||
|
||||
if index == 0:
|
||||
obj.name = pset["Name"]
|
||||
obj.name = pset["Name"] + "_" + str(original_data[group][index]["Aggregate_Index"])
|
||||
ifc_file = tool.Ifc.get()
|
||||
ifcopenshell.api.pset.edit_pset(
|
||||
ifc_file,
|
||||
ifc_file.by_id(pset["id"]),
|
||||
properties={"Aggregate_Index": int(original_data[group][index]["Name"])},
|
||||
properties={"Aggregate_Index": int(original_data[group][index]["Aggregate_Index"])},
|
||||
)
|
||||
bonsai.core.spatial.assign_container(
|
||||
tool.Ifc,
|
||||
tool.Collector,
|
||||
tool.Spatial,
|
||||
container=original_data[group][index]["Container"],
|
||||
element_obj=obj,
|
||||
)
|
||||
for part in ifcopenshell.util.element.get_parts(tool.Ifc.get_entity(obj)):
|
||||
tool.Collector.assign(tool.Ifc.get_object(part))
|
||||
assignments = original_data[group][index]["Assignment"]
|
||||
if assignments:
|
||||
assign_to_annotations(obj, assignments)
|
||||
@@ -1889,9 +1896,6 @@ class OverrideJoin(bpy.types.Operator, tool.Ifc.Operator):
|
||||
tool.Geometry,
|
||||
obj=self.target,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
apply_openings=True,
|
||||
)
|
||||
|
||||
|
||||
@@ -157,8 +157,6 @@ class BIM_PT_representations(Panel):
|
||||
icon="FILE_REFRESH" if representation["is_active"] else "OUTLINER_DATA_MESH",
|
||||
text="",
|
||||
)
|
||||
op.should_switch_all_meshes = True
|
||||
op.should_reload = True
|
||||
op.ifc_definition_id = representation["id"]
|
||||
op.disable_opening_subtractions = False
|
||||
row.operator("bim.remove_representation", icon="X", text="").representation_id = representation["id"]
|
||||
|
||||
@@ -323,20 +323,23 @@ class ObjectMaterialData:
|
||||
|
||||
@classmethod
|
||||
def total_thickness(cls):
|
||||
if cls.material:
|
||||
layers = []
|
||||
if cls.material.is_a("IfcMaterialLayerSetUsage"):
|
||||
layers = cls.material.ForLayerSet.MaterialLayers
|
||||
elif cls.material.is_a("IfcMaterialLayerSet"):
|
||||
layers = cls.material.MaterialLayers
|
||||
thickness = sum([l.LayerThickness for l in layers or []])
|
||||
prefs = tool.Blender.get_addon_preferences()
|
||||
assert bpy.context.scene
|
||||
unit_system = bpy.context.scene.unit_settings.system
|
||||
precision = None
|
||||
if unit_system == "IMPERIAL":
|
||||
precision = prefs.doc.imperial_precision
|
||||
return format_distance(thickness, precision=precision, suppress_zero_inches=True, in_unit_length=True)
|
||||
if not cls.material:
|
||||
return
|
||||
layers = []
|
||||
if cls.material.is_a("IfcMaterialLayerSetUsage"):
|
||||
layers = cls.material.ForLayerSet.MaterialLayers
|
||||
elif cls.material.is_a("IfcMaterialLayerSet"):
|
||||
layers = cls.material.MaterialLayers
|
||||
if not layers:
|
||||
return
|
||||
thickness = sum([l.LayerThickness for l in layers or []])
|
||||
prefs = tool.Blender.get_addon_preferences()
|
||||
assert bpy.context.scene
|
||||
unit_system = bpy.context.scene.unit_settings.system
|
||||
precision = None
|
||||
if unit_system == "IMPERIAL":
|
||||
precision = prefs.doc.imperial_precision
|
||||
return format_distance(thickness, precision=precision, suppress_zero_inches=True, in_unit_length=True)
|
||||
|
||||
@classmethod
|
||||
def set_item_name(cls) -> Union[str, None]:
|
||||
@@ -404,7 +407,7 @@ class ObjectMaterialData:
|
||||
material = cls.material
|
||||
if not cls.material or not material.is_a("IfcMaterialConstituentSet"):
|
||||
return []
|
||||
return [m.Name for m in material.MaterialConstituents if m.Name]
|
||||
return [m.Name for m in material.MaterialConstituents or [] if m.Name]
|
||||
|
||||
@classmethod
|
||||
def is_type_material_overridden(cls) -> bool:
|
||||
|
||||
@@ -254,6 +254,10 @@ class BIM_PT_object_material(Panel):
|
||||
active_object = bpy.context.active_object
|
||||
self.layerset_bounds(box, active_object, location="Top_Exterior")
|
||||
|
||||
if not ObjectMaterialData.data["set_items"]:
|
||||
row = box.row()
|
||||
row.label(text="No Materials Found")
|
||||
|
||||
for set_item in ObjectMaterialData.data["set_items"]:
|
||||
if (
|
||||
len(self.props.material_set_item_profile_attributes)
|
||||
@@ -357,6 +361,10 @@ class BIM_PT_object_material(Panel):
|
||||
active_object = bpy.context.active_object
|
||||
self.layerset_bounds(box, active_object, location="Top_Interior")
|
||||
|
||||
if not ObjectMaterialData.data["set_items"]:
|
||||
row = box.row()
|
||||
row.label(text="No Materials Found")
|
||||
|
||||
for set_item in ObjectMaterialData.data["set_items"]:
|
||||
material_name = set_item["material"]
|
||||
material_id = set_item["material_id"]
|
||||
@@ -411,7 +419,6 @@ class BIM_PT_object_material(Panel):
|
||||
row.enabled = self.props.use_custom_offset
|
||||
|
||||
|
||||
|
||||
class BIM_UL_materials(UIList):
|
||||
def draw_item(
|
||||
self,
|
||||
|
||||
@@ -193,9 +193,6 @@ class SplitAlongEdge(bpy.types.Operator, tool.Ifc.Operator):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
apply_openings=False,
|
||||
)
|
||||
|
||||
@@ -224,9 +221,6 @@ class SplitAlongEdge(bpy.types.Operator, tool.Ifc.Operator):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
apply_openings=True,
|
||||
)
|
||||
|
||||
|
||||
@@ -1226,7 +1226,7 @@ class BoundingBoxDecorator:
|
||||
def find_closest_trihedron(corners, edges, region, rv3d):
|
||||
|
||||
min_y = float("inf")
|
||||
best_origin = None
|
||||
best_origin = 0
|
||||
for idx, corner in enumerate(corners):
|
||||
screen_co = location_3d_to_region_2d(region, rv3d, corner)
|
||||
if screen_co is not None and screen_co.y < min_y:
|
||||
|
||||
@@ -95,15 +95,17 @@ def update_door_modifier_representation(obj: bpy.types.Object) -> None:
|
||||
representation_data["part_of_product"] = None
|
||||
tool.Model.replace_object_ifc_representation(body, obj, model_representation)
|
||||
if fallback_material := (int(props.lining_material) or int(props.framing_material) or int(props.glazing_material)):
|
||||
materials = {
|
||||
"Lining": tool.Ifc.get().by_id(int(props.lining_material) or fallback_material),
|
||||
"Framing": tool.Ifc.get().by_id(int(props.framing_material) or fallback_material),
|
||||
}
|
||||
if props.transom_thickness:
|
||||
materials["Glazing"] = tool.Ifc.get().by_id(int(props.glazing_material) or fallback_material)
|
||||
ifcopenshell.api.material.set_shape_aspect_constituents(
|
||||
ifc_file,
|
||||
element=element,
|
||||
context=body,
|
||||
materials={
|
||||
"Lining": tool.Ifc.get().by_id(int(props.lining_material) or fallback_material),
|
||||
"Framing": tool.Ifc.get().by_id(int(props.framing_material) or fallback_material),
|
||||
"Glazing": tool.Ifc.get().by_id(int(props.glazing_material) or fallback_material),
|
||||
},
|
||||
materials=materials,
|
||||
)
|
||||
elif material := ifcopenshell.util.element.get_material(element):
|
||||
ifcopenshell.api.material.unassign_material(ifc_file, products=[element])
|
||||
@@ -143,9 +145,6 @@ def update_door_modifier_representation(obj: bpy.types.Object) -> None:
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=ifcopenshell.util.representation.get_representation(element, active_context),
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
|
||||
# type attributes
|
||||
@@ -579,9 +578,6 @@ class CancelEditingDoor(bpy.types.Operator, tool.Ifc.Operator):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=body,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
|
||||
props.is_editing = False
|
||||
|
||||
@@ -186,9 +186,6 @@ class FilledOpeningGenerator:
|
||||
tool.Geometry,
|
||||
obj=voided_obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
|
||||
def regenerate_from_type(self, usecase_path: str, ifc_file: ifcopenshell.file, settings: dict[str, Any]) -> None:
|
||||
@@ -249,9 +246,6 @@ class FilledOpeningGenerator:
|
||||
tool.Geometry,
|
||||
obj=voided_obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
|
||||
def generate_opening_from_filling(
|
||||
@@ -420,9 +414,6 @@ class RecalculateFill(bpy.types.Operator, tool.Ifc.Operator):
|
||||
tool.Geometry,
|
||||
obj=building_obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -798,9 +789,6 @@ class CloneOpening(Operator, tool.Ifc.Operator):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -557,7 +557,33 @@ def get_generic_product_preview_data(context, relating_type):
|
||||
|
||||
mouse_point.z = snap_obj.matrix_world.translation.z
|
||||
|
||||
if snap_element and (container := ifcopenshell.util.element.get_container(snap_element)):
|
||||
container_obj = tool.Ifc.get_object(container)
|
||||
mouse_point.z = container_obj.location.z
|
||||
|
||||
obj_type = tool.Ifc.get_object(relating_type)
|
||||
|
||||
subcontexts = tool.Drawing.get_active_drawing_subcontexts()
|
||||
if not subcontexts:
|
||||
subcontexts = [("Model", "Body", "MODEL_VIEW")]
|
||||
|
||||
active_context = tool.Geometry.get_active_representation_context(obj_type)
|
||||
active_context_params = tool.Geometry.get_subcontext_parameters(active_context)
|
||||
for subcontext in subcontexts:
|
||||
if subcontext == active_context_params:
|
||||
break
|
||||
|
||||
representation = ifcopenshell.util.representation.get_representation(relating_type, *subcontext)
|
||||
if representation:
|
||||
bonsai.core.geometry.switch_representation(
|
||||
tool.Ifc,
|
||||
tool.Geometry,
|
||||
obj_type,
|
||||
representation,
|
||||
)
|
||||
context.view_layer.update()
|
||||
break
|
||||
|
||||
if obj_type.data:
|
||||
data = ItemDecorator.get_obj_data(obj_type)
|
||||
data["verts"] = [tuple(obj_type.matrix_world.inverted() @ Vector(v)) for v in data["verts"]]
|
||||
|
||||
@@ -327,9 +327,21 @@ class AddOccurrence(bpy.types.Operator, tool.Ifc.Operator):
|
||||
if self.from_invoke and str(self.relating_type_id) in AuthoringData.data["relating_type_id"]:
|
||||
props.relating_type_id = str(self.relating_type_id)
|
||||
|
||||
building_obj = None
|
||||
if len(context.selected_objects) == 1 and context.active_object:
|
||||
building_obj = context.active_object
|
||||
building_element = tool.Ifc.get_entity(building_obj)
|
||||
|
||||
self.container = None
|
||||
self.container_obj = None
|
||||
if container := tool.Root.get_default_container():
|
||||
if (
|
||||
building_obj
|
||||
and building_element
|
||||
and (container := ifcopenshell.util.element.get_container(building_element))
|
||||
):
|
||||
self.container = container
|
||||
self.container_obj = tool.Ifc.get_object(container)
|
||||
elif container := tool.Root.get_default_container():
|
||||
self.container = container
|
||||
self.container_obj = tool.Ifc.get_object(container)
|
||||
|
||||
@@ -394,9 +406,6 @@ class AddOccurrence(bpy.types.Operator, tool.Ifc.Operator):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
elif self.representation_template == "EXTRUSION":
|
||||
builder = ifcopenshell.util.shape_builder.ShapeBuilder(tool.Ifc.get())
|
||||
@@ -410,17 +419,9 @@ class AddOccurrence(bpy.types.Operator, tool.Ifc.Operator):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
return
|
||||
|
||||
building_obj = None
|
||||
if len(context.selected_objects) == 1 and context.active_object:
|
||||
building_obj = context.active_object
|
||||
building_element = tool.Ifc.get_entity(building_obj)
|
||||
|
||||
mesh = bpy.data.meshes.new(name="Instance")
|
||||
obj = bpy.data.objects.new(tool.Model.generate_occurrence_name(relating_type, instance_class), mesh)
|
||||
|
||||
@@ -446,9 +447,6 @@ class AddOccurrence(bpy.types.Operator, tool.Ifc.Operator):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
|
||||
# Update required as core.type.assign_type may change obj.data
|
||||
@@ -487,12 +485,15 @@ class AddOccurrence(bpy.types.Operator, tool.Ifc.Operator):
|
||||
building_obj
|
||||
and building_element
|
||||
and building_element.is_a() in ["IfcWall", "IfcWallStandardCase", "IfcCovering", "IfcElementAssembly"]
|
||||
and instance_class in ["IfcWindow", "IfcDoor"]
|
||||
):
|
||||
if instance_class in ["IfcWindow", "IfcDoor"]:
|
||||
# TODO For now we are hardcoding windows and doors as a prototype
|
||||
tool.Model.add_filled_opening(building_obj, obj)
|
||||
# TODO For now we are hardcoding windows and doors as a prototype
|
||||
tool.Model.add_filled_opening(building_obj, obj)
|
||||
else:
|
||||
if self.container_obj:
|
||||
bonsai.core.spatial.assign_container(
|
||||
tool.Ifc, tool.Collector, tool.Spatial, container=self.container, element_obj=obj
|
||||
)
|
||||
if props.rl_mode == "BOTTOM":
|
||||
obj.location.z = self.container_obj.location.z - tool.Blender.get_object_bounding_box(obj)["min_z"]
|
||||
elif props.rl_mode == "CONTAINER":
|
||||
@@ -728,7 +729,4 @@ def regenerate_profile_usage(usecase_path, ifc_file, settings):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
|
||||
@@ -98,7 +98,8 @@ class DumbProfileGenerator:
|
||||
vec1 = Vector((polyline_points[i].x, polyline_points[i].y, polyline_points[i].z))
|
||||
vec2 = Vector((polyline_points[i + 1].x, polyline_points[i + 1].y, polyline_points[i + 1].z))
|
||||
coords = (vec1, vec2)
|
||||
profiles.append(self.create_profile_from_2_points(coords))
|
||||
if profile := self.create_profile_from_2_points(coords):
|
||||
profiles.append(profile)
|
||||
return profiles, is_polyline_closed
|
||||
|
||||
def derive_from_cursor(self) -> bpy.types.Object:
|
||||
@@ -166,9 +167,6 @@ class DumbProfileGenerator:
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
|
||||
pset = ifcopenshell.api.pset.add_pset(self.file, product=element, name="EPset_Parametric")
|
||||
@@ -183,7 +181,7 @@ class DumbProfileGenerator:
|
||||
) -> ProfileFrom2PointsReturn:
|
||||
self.direction = coords[1] - coords[0]
|
||||
length = self.direction.length
|
||||
if round(length, 4) < 0.1:
|
||||
if round(length, 4) < 0.001:
|
||||
return
|
||||
data: dict[str, Any] = {"coords": coords}
|
||||
|
||||
@@ -567,9 +565,6 @@ class DumbProfileJoiner:
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=new_body,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
tool.Geometry.record_object_materials(obj)
|
||||
if element.is_a("IfcFlowSegment") or element.is_a("IfcFlowFitting"):
|
||||
@@ -1036,9 +1031,6 @@ def disable_editing_extrusion_axis(context):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=body,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -1100,9 +1092,6 @@ class EditExtrusionAxis(bpy.types.Operator, tool.Ifc.Operator):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=body,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
|
||||
bpy.context.view_layer.update()
|
||||
|
||||
@@ -538,9 +538,6 @@ def cancel_editing_railing_path(context: bpy.types.Context) -> set[str]:
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=body,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -167,9 +167,6 @@ class DumbSlabGenerator:
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
obj.matrix_world = obj.matrix_world @ Matrix.Rotation(self.x_angle, 4, "X")
|
||||
|
||||
@@ -360,9 +357,6 @@ class DumbSlabPlaner:
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=new_rep,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
bonsai.core.geometry.remove_representation(
|
||||
tool.Ifc, tool.Geometry, obj=obj, representation=representation
|
||||
@@ -386,9 +380,6 @@ class DumbSlabPlaner:
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
|
||||
|
||||
@@ -588,9 +579,6 @@ class EditSketchExtrusionProfile(bpy.types.Operator, tool.Ifc.Operator):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -632,9 +620,6 @@ def disable_editing_extrusion_profile(context):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=body,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -767,9 +752,6 @@ class EditExtrusionProfile(bpy.types.Operator, tool.Ifc.Operator):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=body,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
|
||||
# Only certain classes should have a footprint
|
||||
|
||||
@@ -550,7 +550,7 @@ class BIM_PT_door(bpy.types.Panel):
|
||||
|
||||
row = prop_with_search(self.layout, props, "lining_material")
|
||||
tool.Model.draw_material_ui_select(row, props.lining_material)
|
||||
row = prop_with_search(self.layout, props, "framing_material", text="Panel Material")
|
||||
row = prop_with_search(self.layout, props, "framing_material", text="Panel Framing Material")
|
||||
tool.Model.draw_material_ui_select(row, props.framing_material)
|
||||
if props.transom_thickness:
|
||||
row = prop_with_search(self.layout, props, "glazing_material")
|
||||
|
||||
@@ -517,9 +517,6 @@ class ChangeExtrusionXAngle(bpy.types.Operator, tool.Ifc.Operator):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
|
||||
# Object rotation
|
||||
@@ -1024,9 +1021,6 @@ class DumbWallGenerator:
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
pset = ifcopenshell.api.pset.add_pset(self.file, product=element, name="EPset_Parametric")
|
||||
ifcopenshell.api.pset.edit_pset(self.file, pset=pset, properties={"Engine": "Bonsai.DumbLayer2"})
|
||||
|
||||
@@ -123,9 +123,6 @@ def update_window_modifier_representation(context: bpy.types.Context) -> None:
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=ifcopenshell.util.representation.get_representation(element, active_context),
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=True,
|
||||
)
|
||||
|
||||
# type attributes
|
||||
@@ -495,9 +492,6 @@ class CancelEditingWindow(bpy.types.Operator, tool.Ifc.Operator):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=body,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
|
||||
props.is_editing = False
|
||||
|
||||
@@ -155,6 +155,7 @@ class CreateProject(bpy.types.Operator):
|
||||
tool.Ifc, tool.Georeference, tool.Project, tool.Spatial, schema=props.export_schema, template=template
|
||||
)
|
||||
tool.Blender.register_toolbar()
|
||||
tool.Loader.set_unit_scale(ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get()))
|
||||
|
||||
def rollback(self, data):
|
||||
IfcStore.file = None
|
||||
|
||||
@@ -333,9 +333,6 @@ class AssignClass(bpy.types.Operator, tool.Ifc.Operator):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
else:
|
||||
|
||||
@@ -579,9 +576,6 @@ class AddElement(bpy.types.Operator, tool.Ifc.Operator):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
if not tool.Ifc.get_entity(props.representation_obj):
|
||||
bpy.data.objects.remove(props.representation_obj)
|
||||
@@ -601,9 +595,6 @@ class AddElement(bpy.types.Operator, tool.Ifc.Operator):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
elif representation_template == "EXTRUSION":
|
||||
builder = ifcopenshell.util.shape_builder.ShapeBuilder(tool.Ifc.get())
|
||||
@@ -650,9 +641,6 @@ class AddElement(bpy.types.Operator, tool.Ifc.Operator):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
elif representation_template in ("LAYERSET_AXIS2", "LAYERSET_AXIS3"):
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
@@ -763,9 +751,6 @@ class AddElement(bpy.types.Operator, tool.Ifc.Operator):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
elif representation_template == "EDGE":
|
||||
builder = ifcopenshell.util.shape_builder.ShapeBuilder(tool.Ifc.get())
|
||||
@@ -778,9 +763,6 @@ class AddElement(bpy.types.Operator, tool.Ifc.Operator):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
elif representation_template == "FACE":
|
||||
builder = ifcopenshell.util.shape_builder.ShapeBuilder(tool.Ifc.get())
|
||||
@@ -793,9 +775,6 @@ class AddElement(bpy.types.Operator, tool.Ifc.Operator):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
|
||||
bpy.context.view_layer.update() # Ensures obj.matrix_world is correct
|
||||
|
||||
@@ -105,6 +105,7 @@ classes = (
|
||||
operator.RemoveWorkTime,
|
||||
operator.ReorderTask,
|
||||
operator.SaveAnimationColorScheme,
|
||||
operator.SelectTaskElements,
|
||||
operator.SelectTaskRelatedInputs,
|
||||
operator.SelectTaskRelatedProducts,
|
||||
operator.SelectUnassignedWorkScheduleProducts,
|
||||
|
||||
@@ -243,6 +243,9 @@ class SequenceData:
|
||||
data["NestingIndex"] = None
|
||||
for rel in task.Nests or []:
|
||||
data["NestingIndex"] = rel.RelatedObjects.index(task)
|
||||
data["TotalInputs"] = len(tool.Sequence.get_task_inputs(task))
|
||||
data["TotalOutputs"] = len(tool.Sequence.get_task_outputs(task))
|
||||
data["TotalElements"] = data["TotalInputs"] + data["TotalOutputs"]
|
||||
cls.data["tasks"][task.id()] = data
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -1281,6 +1281,17 @@ class DisableEditingSequence(bpy.types.Operator):
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class SelectTaskElements(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.select_task_elements"
|
||||
bl_label = "Select All Task Elements"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
task: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
core.select_task_inputs(tool.Sequence, tool.Spatial, task=tool.Ifc.get().by_id(self.task))
|
||||
core.select_task_outputs(tool.Sequence, tool.Spatial, task=tool.Ifc.get().by_id(self.task))
|
||||
|
||||
|
||||
class SelectTaskRelatedProducts(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.select_task_related_products"
|
||||
bl_label = "Select All Output Products"
|
||||
|
||||
@@ -75,6 +75,8 @@ def update_active_task_index(self, context):
|
||||
bonsai.bim.module.pset.data.refresh()
|
||||
if self.editing_task_type == "SEQUENCE":
|
||||
tool.Sequence.load_task_properties()
|
||||
if self.should_auto_select:
|
||||
tool.Sequence.select_active_task_elements(task)
|
||||
|
||||
|
||||
def update_active_task_outputs(self, context):
|
||||
@@ -485,6 +487,9 @@ class BIMWorkScheduleProperties(PropertyGroup):
|
||||
other_columns: EnumProperty(
|
||||
items=[
|
||||
("Controls.Calendar", "Calendar", ""),
|
||||
("Controls.TotalElements", "Total Elements", "The total number of assigned elements to this task"),
|
||||
("Controls.TotalInputs", "Total Inputs", "The total number of input elements to this task"),
|
||||
("Controls.TotalOutputs", "Total Outputs", "The total number of input elements to this task"),
|
||||
],
|
||||
name="Special Columns",
|
||||
)
|
||||
@@ -518,9 +523,9 @@ class BIMWorkScheduleProperties(PropertyGroup):
|
||||
active_task_input_index: IntProperty(name="Active Task Input Index")
|
||||
task_outputs: CollectionProperty(name="Task Outputs", type=TaskProduct)
|
||||
active_task_output_index: IntProperty(name="Active Task Output Index")
|
||||
show_nested_outputs: BoolProperty(name="Show Nested Tasks", default=False, update=update_active_task_outputs)
|
||||
show_nested_resources: BoolProperty(name="Show Nested Tasks", default=False, update=update_active_task_resources)
|
||||
show_nested_inputs: BoolProperty(name="Show Nested Tasks", default=False, update=update_active_task_inputs)
|
||||
show_nested_outputs: BoolProperty(name="Show Nested Task Elements", default=False, update=update_active_task_outputs)
|
||||
show_nested_resources: BoolProperty(name="Show Nested Task Elements", default=False, update=update_active_task_resources)
|
||||
show_nested_inputs: BoolProperty(name="Show Nested Task Elements", default=False, update=update_active_task_inputs)
|
||||
product_input_tasks: CollectionProperty(name="Product Task Inputs", type=TaskProduct)
|
||||
product_output_tasks: CollectionProperty(name="Product Task Outputs", type=TaskProduct)
|
||||
active_product_output_task_index: IntProperty(name="Active Product Output Task Index")
|
||||
@@ -531,6 +536,9 @@ class BIMWorkScheduleProperties(PropertyGroup):
|
||||
filter_by_active_schedule: BoolProperty(
|
||||
name="Filter By Active Schedule", default=False, update=update_filter_by_active_schedule
|
||||
)
|
||||
should_auto_select: BoolProperty(
|
||||
name="Auto Select", description="Auto select elements when task is selected", default=False
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
work_schedule_predefined_types: str
|
||||
|
||||
@@ -290,13 +290,21 @@ class BIM_PT_work_schedules(Panel):
|
||||
row2 = self.layout.row(align=True)
|
||||
row2.alignment = "RIGHT"
|
||||
|
||||
row2.prop(self.props, "enable_reorder", text="", icon="SORTALPHA")
|
||||
row2.operator("bim.enable_editing_task_sequence", text="", icon="TRACKING")
|
||||
row2.operator("bim.enable_editing_task_time", text="", icon="TIME").task = ifc_definition_id
|
||||
row2.operator("bim.enable_editing_task_calendar", text="", icon="VIEW_ORTHO").task = (
|
||||
col = row2.column()
|
||||
row_ = col.row(align=True)
|
||||
op = row_.operator("bim.select_task_elements", icon="RESTRICT_SELECT_OFF", text="Select Elements")
|
||||
op.task = task.ifc_definition_id
|
||||
row_.prop(self.props, "should_auto_select", icon="OBJECT_HIDDEN", text="")
|
||||
|
||||
col = row2.column()
|
||||
row_ = col.row(align=True)
|
||||
row_.prop(self.props, "enable_reorder", text="", icon="SORTALPHA")
|
||||
row_.operator("bim.enable_editing_task_sequence", text="", icon="TRACKING")
|
||||
row_.operator("bim.enable_editing_task_time", text="", icon="TIME").task = ifc_definition_id
|
||||
row_.operator("bim.enable_editing_task_calendar", text="", icon="VIEW_ORTHO").task = (
|
||||
ifc_definition_id
|
||||
)
|
||||
row2.operator("bim.enable_editing_task_attributes", text="", icon="GREASEPENCIL").task = (
|
||||
row_.operator("bim.enable_editing_task_attributes", text="", icon="GREASEPENCIL").task = (
|
||||
ifc_definition_id
|
||||
)
|
||||
row.operator("bim.add_task", text="Add", icon="ADD").task = ifc_definition_id
|
||||
@@ -716,11 +724,10 @@ class BIM_PT_task_icom(Panel):
|
||||
input_id = self.props.task_inputs[self.props.active_task_input_index].ifc_definition_id
|
||||
op.related_object = input_id
|
||||
|
||||
op = row2.operator("bim.select_task_related_inputs", icon="RESTRICT_SELECT_OFF", text="Select")
|
||||
op = row2.operator("bim.select_task_related_inputs", icon="RESTRICT_SELECT_OFF", text="")
|
||||
op.task = task.ifc_definition_id
|
||||
row2.prop(self.props, "show_nested_inputs", icon="OUTLINER", text="")
|
||||
|
||||
row2 = col.row()
|
||||
row2.prop(self.props, "show_nested_inputs", text="Show Nested")
|
||||
row2 = col.row()
|
||||
row2.template_list("BIM_UL_task_inputs", "", self.props, "task_inputs", self.props, "active_task_input_index")
|
||||
|
||||
@@ -744,8 +751,7 @@ class BIM_PT_task_icom(Panel):
|
||||
op.related_object_type = "RESOURCE"
|
||||
op.resource = self.props.task_resources[self.props.active_task_resource_index].ifc_definition_id
|
||||
|
||||
row2 = col.row()
|
||||
row2.prop(self.props, "show_nested_resources", text="Show Nested")
|
||||
row2.prop(self.props, "show_nested_resources", icon="OUTLINER", text="")
|
||||
|
||||
row2 = col.row()
|
||||
row2.template_list(
|
||||
@@ -773,10 +779,10 @@ class BIM_PT_task_icom(Panel):
|
||||
output_id = self.props.task_outputs[self.props.active_task_output_index].ifc_definition_id
|
||||
op.relating_product = output_id
|
||||
|
||||
op = row2.operator("bim.select_task_related_products", icon="RESTRICT_SELECT_OFF", text="Select")
|
||||
op = row2.operator("bim.select_task_related_products", icon="RESTRICT_SELECT_OFF", text="")
|
||||
op.task = task.ifc_definition_id
|
||||
row2 = col.row()
|
||||
row2.prop(self.props, "show_nested_outputs", text="Show Nested")
|
||||
row2.prop(self.props, "show_nested_outputs", icon="OUTLINER", text="")
|
||||
|
||||
row2 = col.row()
|
||||
row2.template_list(
|
||||
"BIM_UL_task_outputs", "", self.props, "task_outputs", self.props, "active_task_output_index"
|
||||
@@ -893,7 +899,7 @@ class BIM_UL_tasks(UIList):
|
||||
split2 = split1.split(factor=0.9 - min(0.5, 0.15 * len(self.props.columns)))
|
||||
split2.prop(item, "name", emboss=False, text="")
|
||||
|
||||
BIM_UL_tasks.draw_custom_columns(self.props, split2, item, task)
|
||||
BIM_UL_tasks.draw_custom_columns(self.props, split2, item, task, item.ifc_definition_id)
|
||||
|
||||
if self.props.active_task_id and self.props.editing_task_type == "ATTRIBUTES":
|
||||
row.prop(
|
||||
@@ -960,6 +966,7 @@ class BIM_UL_tasks(UIList):
|
||||
row: bpy.types.UILayout,
|
||||
item: Optional[bpy.types.PropertyGroup] = None,
|
||||
task: Optional[dict[str, Any]] = None,
|
||||
ifc_definition_id: int = 0,
|
||||
*,
|
||||
header: bool = False,
|
||||
) -> None:
|
||||
@@ -999,6 +1006,21 @@ class BIM_UL_tasks(UIList):
|
||||
row.label(text=item.derived_calendar + "*")
|
||||
else:
|
||||
row.label(text=item.calendar or "-")
|
||||
elif column.name == "Controls.TotalInputs":
|
||||
if header:
|
||||
row.label(text="# Inputs")
|
||||
else:
|
||||
row.label(text=str(SequenceData.data["tasks"][ifc_definition_id]["TotalInputs"]))
|
||||
elif column.name == "Controls.TotalOutputs":
|
||||
if header:
|
||||
row.label(text="# Outputs")
|
||||
else:
|
||||
row.label(text=str(SequenceData.data["tasks"][ifc_definition_id]["TotalOutputs"]))
|
||||
elif column.name == "Controls.TotalElements":
|
||||
if header:
|
||||
row.label(text="# Elements")
|
||||
else:
|
||||
row.label(text=str(SequenceData.data["tasks"][ifc_definition_id]["TotalElements"]))
|
||||
else:
|
||||
ifc_class, name = column.name.split(".")
|
||||
if header:
|
||||
|
||||
@@ -148,14 +148,14 @@ class SpatialDecompositionData:
|
||||
elif tool.Ifc.get_schema() == "IFC4X3":
|
||||
results = {
|
||||
"IfcBridge": ["IfcBridge", "IfcBridgePart", "IfcSpace"],
|
||||
"IfcBridgePart": ["IfcSpace"],
|
||||
"IfcBridgePart": ["IfcBridgePart", "IfcSpace"],
|
||||
"IfcBuilding": ["IfcBuilding", "IfcBuildingStorey", "IfcSpace"],
|
||||
"IfcBuildingStorey": ["IfcBuildingStorey", "IfcSpace"],
|
||||
"IfcExternalSpatialElement": ["IfcExternalSpatialElement"],
|
||||
"IfcFacility": ["IfcFacility", "IfcFacilityPartCommon", "IfcSpace"],
|
||||
"IfcFacilityPartCommon": ["IfcSpace"],
|
||||
"IfcFacilityPartCommon": ["IfcFacilityPartCommon", "IfcSpace"],
|
||||
"IfcMarineFacility": ["IfcMarineFacility", "IfcMarinePart", "IfcSpace"],
|
||||
"IfcMarinePart": ["IfcSpace"],
|
||||
"IfcMarinePart": ["IfcMarinePart", "IfcSpace"],
|
||||
"IfcProject": [
|
||||
"IfcBridge",
|
||||
"IfcBuilding",
|
||||
@@ -168,9 +168,9 @@ class SpatialDecompositionData:
|
||||
"IfcSpace",
|
||||
],
|
||||
"IfcRailway": ["IfcRailway", "IfcRailwayPart", "IfcSpace"],
|
||||
"IfcRailwayPart": ["IfcSpace"],
|
||||
"IfcRailwayPart": ["IfcRailwayPart", "IfcSpace"],
|
||||
"IfcRoad": ["IfcRoad", "IfcRoadPart", "IfcSpace"],
|
||||
"IfcRoadPart": ["IfcSpace"],
|
||||
"IfcRoadPart": ["IfcRoadPart", "IfcSpace"],
|
||||
"IfcSite": [
|
||||
"IfcBridge",
|
||||
"IfcBuilding",
|
||||
|
||||
@@ -70,7 +70,7 @@ class GridDecorator:
|
||||
|
||||
grid_props = tool.Spatial.get_grid_props()
|
||||
for axis in grid_props.grid_axes:
|
||||
if not (obj := axis.obj) or obj.hide_get() == True:
|
||||
if not (obj := axis.obj) or obj.visible_get() is False:
|
||||
continue
|
||||
if obj.select_get() and context.mode != "OBJECT":
|
||||
continue
|
||||
@@ -125,21 +125,22 @@ class GridDecorator:
|
||||
unselected_edges = []
|
||||
grid_props = tool.Spatial.get_grid_props()
|
||||
for axis in grid_props.grid_axes:
|
||||
if (obj := axis.obj) and obj.hide_get() == False:
|
||||
if obj.select_get():
|
||||
if context.mode != "OBJECT":
|
||||
continue
|
||||
edges = selected_edges
|
||||
verts = selected_verts
|
||||
else:
|
||||
edges = unselected_edges
|
||||
verts = unselected_verts
|
||||
i = len(verts)
|
||||
edges.append([i, i + 1])
|
||||
matrix_world = obj.matrix_world
|
||||
v1 = matrix_world @ obj.data.vertices[0].co
|
||||
v2 = matrix_world @ obj.data.vertices[1].co
|
||||
verts.extend([v1, v2])
|
||||
if not (obj := axis.obj) or obj.visible_get() is False:
|
||||
continue
|
||||
if obj.select_get():
|
||||
if context.mode != "OBJECT":
|
||||
continue
|
||||
edges = selected_edges
|
||||
verts = selected_verts
|
||||
else:
|
||||
edges = unselected_edges
|
||||
verts = unselected_verts
|
||||
i = len(verts)
|
||||
edges.append([i, i + 1])
|
||||
matrix_world = obj.matrix_world
|
||||
v1 = matrix_world @ obj.data.vertices[0].co
|
||||
v2 = matrix_world @ obj.data.vertices[1].co
|
||||
verts.extend([v1, v2])
|
||||
|
||||
if unselected_verts:
|
||||
self.draw_batch("LINES", unselected_verts, decorator_color_background, unselected_edges)
|
||||
|
||||
@@ -550,9 +550,7 @@ class ToggleGrids(bpy.types.Operator):
|
||||
is_visible: bpy.props.BoolProperty(name="Is Visible", default=False, options={"SKIP_SAVE"})
|
||||
|
||||
def execute(self, context):
|
||||
for element in tool.Ifc.get().by_type("IfcGrid") + tool.Ifc.get().by_type("IfcGridAxis"):
|
||||
if obj := tool.Ifc.get_object(element):
|
||||
obj.hide_set(not self.is_visible)
|
||||
tool.Spatial.set_grid_visibility(self.is_visible)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -564,11 +562,5 @@ class ToggleSpatialElements(bpy.types.Operator):
|
||||
is_visible: bpy.props.BoolProperty(name="Is Visible", default=False, options={"SKIP_SAVE"})
|
||||
|
||||
def execute(self, context):
|
||||
if tool.Ifc.get().schema == "IFC2X3":
|
||||
elements = tool.Ifc.get().by_type("IfcSpatialStructureElement")
|
||||
else:
|
||||
elements = tool.Ifc.get().by_type("IfcSpatialElement")
|
||||
for element in elements:
|
||||
if obj := tool.Ifc.get_object(element):
|
||||
obj.hide_set(not self.is_visible)
|
||||
tool.Spatial.set_space_visibility(self.is_visible)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -224,7 +224,7 @@ class BIMSpatialDecompositionProperties(PropertyGroup):
|
||||
is_visible: BoolProperty(
|
||||
name="Is Visible",
|
||||
description="Show or hide spatial elements, such as buildings, sites, etc",
|
||||
default=True,
|
||||
default=False,
|
||||
update=update_spatial_is_visible,
|
||||
)
|
||||
container_filter: StringProperty(name="Container Filter", default="", options={"TEXTEDIT_UPDATE"})
|
||||
|
||||
@@ -25,6 +25,7 @@ classes = (
|
||||
operator.StopIfcTesterWebapp,
|
||||
operator.OpenIfcTesterWebapp,
|
||||
operator.SelectRequirement,
|
||||
operator.ColorSpecefication,
|
||||
operator.SelectFailedEntities,
|
||||
operator.ExportBcf,
|
||||
prop.FailedEntities,
|
||||
|
||||
@@ -135,6 +135,7 @@ class IfcTesterNamespace(socketio.AsyncNamespace):
|
||||
try:
|
||||
request_id = data.get("id")
|
||||
ids_string = data.get("ids")
|
||||
props = tool.Tester.get_tester_props()
|
||||
|
||||
if not request_id:
|
||||
await self.emit("error", {"error": "No request ID provided"}, room=sid)
|
||||
@@ -176,7 +177,7 @@ class IfcTesterNamespace(socketio.AsyncNamespace):
|
||||
json_report = json_reporter.to_string()
|
||||
|
||||
# HTML report
|
||||
html_reporter = ifctester.reporter.Html(ids)
|
||||
html_reporter = ifctester.reporter.Html(ids, hide_skipped=props.hide_skipped_specs)
|
||||
html_reporter.report()
|
||||
html_report = html_reporter.to_string()
|
||||
|
||||
@@ -267,7 +268,7 @@ class ExecuteIfcTester(bpy.types.Operator):
|
||||
start = time.time()
|
||||
|
||||
if props.generate_html_report:
|
||||
engine = ifctester.reporter.Html(specs)
|
||||
engine = ifctester.reporter.Html(specs, props.hide_skipped_specs)
|
||||
engine.report()
|
||||
output_path = output.as_posix()
|
||||
engine.to_file(output_path)
|
||||
@@ -337,9 +338,21 @@ class StartIfcTesterWebapp(bpy.types.Operator):
|
||||
websocket_server_thread = threading.Thread(target=run_websocket_server, daemon=True)
|
||||
websocket_server_thread.start()
|
||||
|
||||
py_version = sys.version_info
|
||||
bonsai_lib_path = (
|
||||
Path(bpy.utils.user_resource("EXTENSIONS"))
|
||||
/ ".local"
|
||||
/ "lib"
|
||||
/ f"python{py_version.major}.{py_version.minor}"
|
||||
/ "site-packages"
|
||||
)
|
||||
env = os.environ.copy()
|
||||
env["BONSAI_LIB_PATH"] = str(bonsai_lib_path)
|
||||
env["BONSAI_VERSION"] = tool.Blender.get_bonsai_version()
|
||||
|
||||
# Start the Flask server as subprocess
|
||||
webapp_process = subprocess.Popen(
|
||||
[sys.executable, webapp_serve_path, "--host", "127.0.0.1", "--port", str(webapp_port)]
|
||||
[sys.executable, webapp_serve_path, "--host", "127.0.0.1", "--port", str(webapp_port)], env=env
|
||||
)
|
||||
|
||||
# Update properties
|
||||
@@ -472,6 +485,7 @@ class SelectRequirement(bpy.types.Operator):
|
||||
props.n_entities = len(failed_entities)
|
||||
props.has_entities = True if props.n_entities > 0 else False
|
||||
props.failed_entities.clear()
|
||||
props.active_requirement_index = self.req_index
|
||||
|
||||
for e in failed_entities:
|
||||
new_entity = props.failed_entities.add()
|
||||
@@ -521,6 +535,39 @@ class SelectFailedEntities(bpy.types.Operator):
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class ColorSpecefication(bpy.types.Operator):
|
||||
"""Colors all entities red that failed each test and color all entities yellow that failed some tests"""
|
||||
|
||||
bl_idname = "bim.color_specification"
|
||||
bl_label = "Color Failed Entities"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
spec_index: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.IfcTesterProperties
|
||||
report = tool.Tester.report
|
||||
|
||||
failures = [
|
||||
{e["id"] for e in requirement["failed_entities"]} for requirement in report[self.spec_index]["requirements"]
|
||||
]
|
||||
failed_all_ids = set.intersection(*failures)
|
||||
failed_some_ids = set.union(*failures) - failed_all_ids
|
||||
if props.flag:
|
||||
area = next(area for area in context.screen.areas if area.type == "VIEW_3D")
|
||||
area.spaces[0].shading.color_type = "OBJECT"
|
||||
area.spaces[0].shading.show_xray = True
|
||||
for obj in context.scene.objects:
|
||||
ifc_id = tool.Blender.get_ifc_definition_id(obj)
|
||||
if ifc_id in failed_all_ids:
|
||||
obj.color = (1, 0, 0, 1)
|
||||
elif ifc_id in failed_some_ids:
|
||||
obj.color = (1, 1, 0, 1)
|
||||
else:
|
||||
obj.color = (1, 1, 1, 1)
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class ExportBcf(bpy.types.Operator, ExportHelper):
|
||||
bl_idname = "bim.export_bcf"
|
||||
bl_label = "Export BCF"
|
||||
|
||||
@@ -71,6 +71,8 @@ class IfcTesterProperties(PropertyGroup):
|
||||
generate_ods_report: BoolProperty(default=False, name="Generate ODS report", options=set())
|
||||
flag: BoolProperty(default=False, name="Flag Failed Entities", options=set())
|
||||
active_specification_index: IntProperty(name="Active Specification Index", update=update_active_specification_index)
|
||||
active_requirement_index: IntProperty(name="Active Requirement Index")
|
||||
|
||||
old_index: IntProperty(name="", default=0)
|
||||
active_failed_entity_index: IntProperty(name="Active Failed Entity Index")
|
||||
specifications: CollectionProperty(name="Specifications", type=Specification)
|
||||
@@ -80,6 +82,7 @@ class IfcTesterProperties(PropertyGroup):
|
||||
webapp_server_port: IntProperty(name="Webapp Server Port", default=0)
|
||||
webapp_is_running: BoolProperty(default=False, name="Webapp Is Running", options=set())
|
||||
websocket_server_port: IntProperty(name="WebSocket Server Port", default=0)
|
||||
hide_skipped_specs: BoolProperty(default=False, name="Hide skipped Specifications", options=set())
|
||||
|
||||
if TYPE_CHECKING:
|
||||
specs: MultipleFileSelect
|
||||
@@ -89,6 +92,7 @@ class IfcTesterProperties(PropertyGroup):
|
||||
generate_ods_report: bool
|
||||
flag: bool
|
||||
active_specification_index: int
|
||||
active_requirement_index: int
|
||||
old_index: int
|
||||
active_failed_entity_index: int
|
||||
specifications: bpy.types.bpy_prop_collection_idprop[Specification]
|
||||
|
||||
@@ -20,6 +20,8 @@ from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from bonsai.bim.ui import draw_multiline_text
|
||||
|
||||
import bpy
|
||||
from bpy.types import Panel, UIList
|
||||
import bonsai.tool as tool
|
||||
@@ -60,7 +62,8 @@ class BIM_PT_tester(Panel):
|
||||
row.prop(props, "generate_ods_report")
|
||||
row = self.layout.row()
|
||||
row.prop(props, "flag")
|
||||
|
||||
row = self.layout.row()
|
||||
row.prop(props, "hide_skipped_specs")
|
||||
if not tool.Ifc.get() or not props.should_load_from_memory:
|
||||
row = self.layout.row(align=True)
|
||||
props.ifc_files.layout_file_select(row, "*.ifc;*.ifczip;*.ifcxml", "IFC File(s)")
|
||||
@@ -95,13 +98,16 @@ class BIM_PT_tester(Panel):
|
||||
"active_specification_index",
|
||||
)
|
||||
|
||||
self.draw_editable_ui()
|
||||
self.draw_editable_ui(context)
|
||||
row = self.layout.row()
|
||||
row.operator("bim.export_bcf", text="Export BCF", icon="EXPORT")
|
||||
|
||||
def draw_editable_ui(self) -> None:
|
||||
def draw_editable_ui(self, context: bpy.types.Context) -> None:
|
||||
props = tool.Tester.get_tester_props()
|
||||
specification = TesterData.data["specification"]
|
||||
is_skipped = specification["total_checks"] == 0 and specification["cardinality"] == "optional"
|
||||
if props.hide_skipped_specs and is_skipped:
|
||||
return
|
||||
|
||||
n_requirements = len(specification["requirements"])
|
||||
|
||||
@@ -110,8 +116,18 @@ class BIM_PT_tester(Panel):
|
||||
row.label(
|
||||
text=f'Passed: {specification["total_checks_pass"]}/{specification["total_checks"]} ({specification["percent_checks_pass"]}%)'
|
||||
)
|
||||
row = self.layout.row()
|
||||
if specification.get("instructions"):
|
||||
row.label(text="Instructions:")
|
||||
box = self.layout.box()
|
||||
column = box.column(align=True)
|
||||
draw_multiline_text(column, specification.get("instructions"), context=context)
|
||||
|
||||
row = self.layout.row()
|
||||
row.label(text=f"Requirements ({n_requirements}):")
|
||||
if props.flag:
|
||||
op = row.operator("bim.color_specification", text="", icon="COLOR")
|
||||
op.spec_index = props.active_specification_index
|
||||
box = self.layout.box()
|
||||
for i, requirement in enumerate(specification["requirements"]):
|
||||
row = box.row(align=True)
|
||||
@@ -129,6 +145,16 @@ class BIM_PT_tester(Panel):
|
||||
and props.n_entities > 0
|
||||
and len(props.failed_entities) > 0
|
||||
):
|
||||
|
||||
requirement = specification["requirements"][props.active_requirement_index]
|
||||
metadata = requirement.get("metadata")
|
||||
if metadata and metadata.get("@instructions"):
|
||||
row = self.layout.row()
|
||||
row.label(text="Instructions:")
|
||||
box = self.layout.box()
|
||||
column = box.column(align=True)
|
||||
draw_multiline_text(column, metadata.get("@instructions"), context=context)
|
||||
|
||||
row = self.layout.row()
|
||||
row.label(text=f"Failed entities [{props.n_entities}]:")
|
||||
self.layout.template_list(
|
||||
@@ -157,6 +183,34 @@ class BIM_UL_tester_specifications(UIList):
|
||||
row.label(text=item.name, icon="CHECKMARK" if item.status else "CANCEL")
|
||||
row.label(text=item.description)
|
||||
|
||||
def filter_items(self, context: bpy.types.Context, data: IfcTesterProperties, propname: str):
|
||||
items = getattr(data, propname)
|
||||
filter_flags = [self.bitflag_filter_item] * len(items)
|
||||
|
||||
props = tool.Tester.get_tester_props()
|
||||
if props.hide_skipped_specs:
|
||||
for idx, item in enumerate(items):
|
||||
report = tool.Tester.report[idx]
|
||||
if report["total_checks"] != 0:
|
||||
filter_flags[idx] |= self.bitflag_filter_item
|
||||
else:
|
||||
filter_flags[idx] &= ~self.bitflag_filter_item
|
||||
|
||||
filter_name = self.filter_name
|
||||
if filter_name:
|
||||
name_filtered = bpy.types.UI_UL_list.filter_items_by_name(
|
||||
filter_name,
|
||||
self.bitflag_filter_item,
|
||||
items,
|
||||
"name",
|
||||
)
|
||||
if len(name_filtered) == len(filter_flags):
|
||||
for idx, flag in enumerate(name_filtered):
|
||||
if flag == 0:
|
||||
filter_flags[idx] &= ~self.bitflag_filter_item
|
||||
|
||||
return filter_flags, []
|
||||
|
||||
|
||||
class BIM_UL_tester_failed_entities(UIList):
|
||||
def draw_item(
|
||||
|
||||
@@ -121,9 +121,6 @@ class UnassignType(bpy.types.Operator, tool.Ifc.Operator):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=new_active_representation,
|
||||
should_reload=False,
|
||||
is_global=False,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -148,11 +148,6 @@ class AddOpening(bpy.types.Operator, tool.Ifc.Operator):
|
||||
tool.Geometry,
|
||||
obj=voided_obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
# Don't sync changes because object has an opening,
|
||||
# therefore bim.update_representaiton wouldn't work either way.
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
tool.Geometry.lock_scale(voided_obj)
|
||||
|
||||
@@ -196,9 +191,6 @@ class RemoveOpening(bpy.types.Operator, tool.Ifc.Operator):
|
||||
tool.Geometry,
|
||||
obj=building_obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
tool.Geometry.unlock_scale_object_with_openings(obj)
|
||||
tool.Geometry.clear_cache(element)
|
||||
|
||||
@@ -40,6 +40,7 @@ from bonsai.bim.module.bsdd.prop import BIMBSDDProperties, BSDDProperty
|
||||
from bonsai.bim.module.pset.prop import IfcProperty
|
||||
from typing import Optional, TYPE_CHECKING, Literal
|
||||
from natsort import natsorted
|
||||
import textwrap
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -373,11 +374,6 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
|
||||
description="Path to create and store temporary files. If left blank, a system default will be used.",
|
||||
subtype="DIR_PATH",
|
||||
)
|
||||
spatial_elements_unselectable: BoolProperty(
|
||||
name="Make Spatial Elements Unselectable By Default",
|
||||
default=True,
|
||||
description="If disabled, it will be possible to select spatial elements in the 3D viewport.\nIt is recommended to keep this `Enabled`, as this can have unintended consequences",
|
||||
)
|
||||
decorations_colour: bpy.props.FloatVectorProperty(
|
||||
name="Decorations Color", subtype="COLOR", default=(1, 1, 1, 1), min=0.0, max=1.0, size=4
|
||||
)
|
||||
@@ -503,7 +499,6 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
|
||||
activate_workspace: bool
|
||||
should_setup_toolbar: bool
|
||||
should_play_chaching_sound: bool
|
||||
spatial_elements_unselectable: bool
|
||||
tmp_dir: str
|
||||
decorations_colour: tuple[float, float, float, float]
|
||||
decorator_color_selected: tuple[float, float, float, float]
|
||||
@@ -565,7 +560,6 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
|
||||
layout.prop(self, "should_setup_toolbar")
|
||||
layout.prop(self, "should_use_snap")
|
||||
layout.prop(self, "should_play_chaching_sound")
|
||||
layout.prop(self, "spatial_elements_unselectable")
|
||||
|
||||
def draw_model_settings(self, layout: bpy.types.UILayout, context: bpy.types.Context) -> None:
|
||||
layout.prop(self, "occurrence_name_style")
|
||||
@@ -1498,6 +1492,31 @@ def draw_custom_context_menu(self: bpy.types.Menu, context: bpy.types.Context) -
|
||||
url_op.uri = url
|
||||
|
||||
|
||||
def draw_multiline_text(
|
||||
layout: bpy.types.UILayout,
|
||||
text: str,
|
||||
*,
|
||||
context: bpy.types.Context | None = None,
|
||||
) -> None:
|
||||
"""Render a read-only text box that wraps long text."""
|
||||
assert layout
|
||||
|
||||
region_width = 200
|
||||
if context and context.region:
|
||||
region_width = context.region.width
|
||||
|
||||
approximate_char_width = 7 # Empirical average width for Blender UI font (px)
|
||||
wrap_width = max(20, int(region_width / approximate_char_width))
|
||||
|
||||
for paragraph in text.splitlines():
|
||||
if not paragraph:
|
||||
layout.label(text="")
|
||||
continue
|
||||
|
||||
for line in textwrap.wrap(paragraph, width=wrap_width):
|
||||
layout.label(text=line)
|
||||
|
||||
|
||||
class BIM_PT_decorators_overlay(Panel):
|
||||
bl_space_type = "VIEW_3D"
|
||||
bl_region_type = "HEADER"
|
||||
|
||||
@@ -39,7 +39,7 @@ def disable_editing_text(drawing: type[tool.Drawing], obj: bpy.types.Object) ->
|
||||
def edit_text(drawing: type[tool.Drawing], obj: bpy.types.Object) -> None:
|
||||
drawing.synchronise_ifc_and_text_attributes(obj)
|
||||
drawing.update_text_size_pset(obj)
|
||||
drawing.update_newline_at_and_symbol(obj)
|
||||
drawing.update_text_annotation_properties(obj)
|
||||
drawing.disable_editing_text(obj)
|
||||
|
||||
|
||||
@@ -440,8 +440,7 @@ def add_annotation(
|
||||
drawing_tool.show_decorations()
|
||||
obj = drawing_tool.create_annotation_object(drawing, object_type)
|
||||
element = ifc.get_entity(obj)
|
||||
# TODO: element is never None?
|
||||
if not element:
|
||||
if not element: # Brand new annotation
|
||||
relating_type_rep = drawing_tool.get_annotation_representation(relating_type) if relating_type else None
|
||||
element = drawing_tool.run_root_assign_class(
|
||||
obj=obj,
|
||||
|
||||
@@ -115,24 +115,9 @@ def switch_representation(
|
||||
geometry: type[tool.Geometry],
|
||||
obj: bpy.types.Object,
|
||||
representation: ifcopenshell.entity_instance,
|
||||
should_reload: bool = True,
|
||||
is_global: bool = True,
|
||||
should_sync_changes_first: bool = False,
|
||||
apply_openings: bool = True,
|
||||
) -> None:
|
||||
"""Function can switch to representation that wasn't yet assigned to that object. See #2766.
|
||||
|
||||
:param should_sync_changes_first: sync ifc representation with current state of `obj.data`
|
||||
:param should_reload: reload `obj.data` from ifc representation
|
||||
:param is_global: replace mesh data for all users of `obj.data`, not just `obj`
|
||||
|
||||
"""
|
||||
if should_sync_changes_first and ifc.is_edited(obj) and not geometry.is_box_representation(representation):
|
||||
representation_id = geometry.get_representation_id(representation)
|
||||
geometry.run_geometry_update_representation(obj=obj)
|
||||
if not geometry.does_representation_id_exist(representation_id):
|
||||
return
|
||||
|
||||
"""Switch obj representation (even if not yet assigned to that object. See #2766.)"""
|
||||
if not geometry.get_object_data(obj) and geometry.is_text_literal(representation):
|
||||
return
|
||||
|
||||
@@ -142,9 +127,7 @@ def switch_representation(
|
||||
geometry.reimport_element_representations(obj, representation, apply_openings=apply_openings)
|
||||
|
||||
|
||||
def get_representation_ifc_parameters(
|
||||
geometry: type[tool.Geometry], obj: bpy.types.Object, should_sync_changes_first: bool = False
|
||||
) -> None:
|
||||
def get_representation_ifc_parameters(geometry: type[tool.Geometry], obj: bpy.types.Object) -> None:
|
||||
geometry.import_representation_parameters(geometry.get_object_data(obj))
|
||||
|
||||
|
||||
|
||||
@@ -72,6 +72,10 @@ def assign_class(
|
||||
"""
|
||||
Args:
|
||||
context: is not optional if `should_add_representation` is True
|
||||
|
||||
TODO: Do NOT use should_add_representation. Because it internally calls
|
||||
geometry.add_representation which is 1,000 lines of Blender -> IFC magic.
|
||||
Instead, explicitly create a representation using tool.Geometry.
|
||||
"""
|
||||
if ifc.get_entity(obj):
|
||||
return
|
||||
|
||||
@@ -394,7 +394,7 @@ class Drawing:
|
||||
def sync_object_placement(cls, obj): pass
|
||||
def synchronise_ifc_and_text_attributes(cls, obj): pass
|
||||
def update_embedded_svg_location(cls, uri, old_location, new_location): pass
|
||||
def update_newline_at_and_symbol(cls, obj): pass
|
||||
def update_text_annotation_properties(cls, obj): pass
|
||||
def update_text_size_pset(cls, obj): pass
|
||||
|
||||
|
||||
@@ -1092,7 +1092,7 @@ class Type:
|
||||
def get_type_occurrences(cls, element_type): pass
|
||||
def has_material_usage(cls, element): pass
|
||||
def run_geometry_add_representation(cls, obj=None, context=None, ifc_representation_class=None, profile_set_usage=None): pass
|
||||
def run_geometry_switch_representation(cls, obj=None, representation=None, should_reload=None, is_global=None): pass
|
||||
def run_geometry_switch_representation(cls, obj=None, representation=None): pass
|
||||
|
||||
|
||||
@interface
|
||||
|
||||
@@ -477,6 +477,8 @@ class Blender(bonsai.core.tool.Blender):
|
||||
def ensure_bin_in_path(cls) -> None:
|
||||
"""Check 'bin' folder is in PATH, if not add for this session"""
|
||||
bin_dir = str(Path(__file__).parent.parent.resolve() / "libs" / "bin")
|
||||
if not os.path.isdir(bin_dir):
|
||||
return # Maybe the user is using a system-wide Python package. See #7157.
|
||||
current_path = os.environ["PATH"]
|
||||
if bin_dir not in current_path:
|
||||
os.environ["PATH"] = current_path + os.pathsep + bin_dir
|
||||
@@ -1859,6 +1861,75 @@ class Blender(bonsai.core.tool.Blender):
|
||||
|
||||
return unit_scale
|
||||
|
||||
@classmethod
|
||||
def reset_object_visibility(cls):
|
||||
override = cls.get_viewport_context()
|
||||
with bpy.context.temp_override(**override):
|
||||
bpy.ops.object.hide_view_clear(select=False)
|
||||
|
||||
@classmethod
|
||||
def isolate_objects(cls, objs):
|
||||
previously_selected = {o.name for o in bpy.context.selected_objects}
|
||||
previously_active = bpy.context.view_layer.objects.active
|
||||
|
||||
override = cls.get_viewport_context()
|
||||
with bpy.context.temp_override(**override):
|
||||
bpy.ops.object.hide_view_clear(select=False)
|
||||
|
||||
bpy.ops.object.select_all(action="DESELECT")
|
||||
for obj in objs:
|
||||
obj.select_set(True)
|
||||
with bpy.context.temp_override(**override):
|
||||
bpy.ops.object.hide_view_set(unselected=True)
|
||||
|
||||
bpy.ops.object.select_all(action="DESELECT")
|
||||
for name in previously_selected:
|
||||
obj = bpy.data.objects.get(name)
|
||||
if obj:
|
||||
obj.select_set(True)
|
||||
bpy.context.view_layer.objects.active = previously_active
|
||||
|
||||
@classmethod
|
||||
def hide_objects(cls, objs):
|
||||
previously_selected = {o.name for o in bpy.context.selected_objects}
|
||||
previously_active = bpy.context.view_layer.objects.active
|
||||
|
||||
override = cls.get_viewport_context()
|
||||
bpy.ops.object.select_all(action="DESELECT")
|
||||
for obj in objs:
|
||||
obj.select_set(True)
|
||||
with bpy.context.temp_override(**override):
|
||||
bpy.ops.object.hide_view_set(unselected=False)
|
||||
|
||||
for name in previously_selected:
|
||||
obj = bpy.data.objects.get(name)
|
||||
if obj:
|
||||
obj.select_set(True)
|
||||
bpy.context.view_layer.objects.active = previously_active
|
||||
|
||||
@classmethod
|
||||
def show_objects(cls, objs):
|
||||
previously_selected = {o.name for o in bpy.context.selected_objects}
|
||||
previously_active = bpy.context.view_layer.objects.active
|
||||
|
||||
bpy.ops.object.select_all(action="DESELECT")
|
||||
override = cls.get_viewport_context()
|
||||
with bpy.context.temp_override(**override):
|
||||
bpy.ops.object.hide_view_clear(select=True)
|
||||
|
||||
for obj in bpy.context.selected_objects:
|
||||
if obj in objs:
|
||||
obj.select_set(False)
|
||||
with bpy.context.temp_override(**override):
|
||||
bpy.ops.object.hide_view_set(unselected=False)
|
||||
|
||||
bpy.ops.object.select_all(action="DESELECT")
|
||||
for name in previously_selected:
|
||||
obj = bpy.data.objects.get(name)
|
||||
if obj:
|
||||
obj.select_set(True)
|
||||
bpy.context.view_layer.objects.active = previously_active
|
||||
|
||||
@classmethod
|
||||
def validate_shader_batch_data(cls, pos: Any, indices: Optional[Any]) -> bool:
|
||||
"""Validate shader batch data.
|
||||
|
||||
@@ -65,8 +65,11 @@ class Collector(bonsai.core.tool.Collector):
|
||||
collection = cls._create_project_child_collection("IfcTypeProduct")
|
||||
cls.link_collection_object_safe(collection, obj)
|
||||
elif element.is_a("IfcSpace"):
|
||||
if tool.Geometry.is_locked(element):
|
||||
tool.Geometry.lock_object(obj)
|
||||
collection = cls._create_project_child_collection("IfcSpace")
|
||||
cls.link_collection_object_safe(collection, obj)
|
||||
obj.hide_viewport = True
|
||||
elif element.is_a("IfcStructuralItem"):
|
||||
collection = cls._create_project_child_collection("IfcStructuralItem")
|
||||
cls.link_collection_object_safe(collection, obj)
|
||||
@@ -90,6 +93,7 @@ class Collector(bonsai.core.tool.Collector):
|
||||
cls.link_collection_object_safe(collection, obj)
|
||||
project_obj = tool.Ifc.get_object(tool.Ifc.get().by_type("IfcProject")[0])
|
||||
cls.link_collection_child_safe(tool.Blender.get_object_bim_props(project_obj).collection, collection)
|
||||
obj.hide_viewport = True
|
||||
elif (
|
||||
tool.Ifc.get_schema() != "IFC2X3"
|
||||
and element.is_a("IfcSpatialElement")
|
||||
@@ -101,6 +105,7 @@ class Collector(bonsai.core.tool.Collector):
|
||||
cls.link_collection_object_safe(collection, obj)
|
||||
project_obj = tool.Ifc.get_object(tool.Ifc.get().by_type("IfcProject")[0])
|
||||
cls.link_collection_child_safe(tool.Blender.get_object_bim_props(project_obj).collection, collection)
|
||||
obj.hide_viewport = True
|
||||
elif element.is_a("IfcAnnotation") and element.ObjectType == "DRAWING":
|
||||
if collection := cls._create_own_collection(obj):
|
||||
cls.link_collection_object_safe(collection, obj)
|
||||
|
||||
@@ -820,7 +820,11 @@ class Drawing(bonsai.core.tool.Drawing):
|
||||
@classmethod
|
||||
def import_annotations_in_group(cls, group: ifcopenshell.entity_instance) -> None:
|
||||
elements = set(
|
||||
[e for e in cls.get_group_elements(group) if e.is_a("IfcAnnotation") and e.ObjectType != "DRAWING"]
|
||||
[
|
||||
e
|
||||
for e in cls.get_group_elements(group)
|
||||
if e.is_a("IfcAnnotation") and e.ObjectType != "DRAWING" and not tool.Ifc.get_object(e)
|
||||
]
|
||||
)
|
||||
logger = logging.getLogger("ImportIFC")
|
||||
ifc_import_settings = bonsai.bim.import_ifc.IfcImportSettings.factory(bpy.context, None, logger)
|
||||
@@ -1098,6 +1102,8 @@ class Drawing(bonsai.core.tool.Drawing):
|
||||
props.font_size = str(text_data["FontSize"])
|
||||
props.newline_at = text_data["Newline_At"]
|
||||
props.set_symbol(text_data["Symbol"])
|
||||
props.reverse_list = text_data["Reverse_List"]
|
||||
props.list_separator = text_data["List_Separator"]
|
||||
|
||||
@classmethod
|
||||
def import_assigned_product(cls, obj: bpy.types.Object) -> None:
|
||||
@@ -1172,9 +1178,6 @@ class Drawing(bonsai.core.tool.Drawing):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
@@ -1235,22 +1238,25 @@ class Drawing(bonsai.core.tool.Drawing):
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def update_newline_at_and_symbol(cls, obj: bpy.types.Object) -> None:
|
||||
def update_text_annotation_properties(cls, obj: bpy.types.Object) -> None:
|
||||
"""Update all EPset_Annotation properties from the text props"""
|
||||
props = cls.get_text_props(obj)
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
assert element
|
||||
newline_at = int(props.newline_at)
|
||||
symbol = props.get_symbol()
|
||||
|
||||
ifc_file = tool.Ifc.get()
|
||||
pset = tool.Pset.get_element_pset(element, "EPset_Annotation")
|
||||
if not pset:
|
||||
pset = ifcopenshell.api.pset.add_pset(ifc_file, product=element, name="EPset_Annotation")
|
||||
|
||||
ifcopenshell.api.pset.edit_pset(
|
||||
ifc_file,
|
||||
pset=pset,
|
||||
properties={
|
||||
"Newline_At": newline_at,
|
||||
"Symbol": symbol,
|
||||
"Newline_At": int(props.newline_at),
|
||||
"Symbol": props.get_symbol(),
|
||||
"Reverse_List": props.reverse_list,
|
||||
"List_Separator": props.list_separator or "",
|
||||
},
|
||||
)
|
||||
|
||||
@@ -1461,10 +1467,8 @@ class Drawing(bonsai.core.tool.Drawing):
|
||||
def ensure_referenced_drawing_obj_exists(drawing: ifcopenshell.entity_instance):
|
||||
obj = tool.Ifc.get_object(drawing)
|
||||
if obj is None:
|
||||
# Annotations in that drawing are lazy loaded as needed
|
||||
obj = cls.import_drawing(drawing)
|
||||
# there is no need for other drawing annotations in that case
|
||||
# but we import them anyway so we won't break that drawing activation
|
||||
cls.import_annotations_in_group(cls.get_drawing_group(drawing))
|
||||
tool.Blender.get_layer_collection(obj.users_collection[0]).hide_viewport = True
|
||||
|
||||
target_view = ifcopenshell.util.element.get_pset(reference_element, "EPset_Drawing", "TargetView")
|
||||
@@ -1548,12 +1552,10 @@ class Drawing(bonsai.core.tool.Drawing):
|
||||
context=context,
|
||||
ifc_representation_class=None,
|
||||
)
|
||||
if representation := ifcopenshell.util.representation.get_representation(element, context):
|
||||
cls.reload_representation(obj=obj, representation=representation)
|
||||
bpy.data.curves.remove(data)
|
||||
|
||||
assert element
|
||||
representation = ifcopenshell.util.representation.get_representation(element, context)
|
||||
assert representation
|
||||
obj.data["ios_edges_item_ids"] = (representation.Items[0].id(),)
|
||||
return element
|
||||
|
||||
@classmethod
|
||||
@@ -1641,6 +1643,8 @@ class Drawing(bonsai.core.tool.Drawing):
|
||||
context=context,
|
||||
ifc_representation_class=None,
|
||||
)
|
||||
if representation := ifcopenshell.util.representation.get_representation(element, context):
|
||||
cls.reload_representation(obj=obj, representation=representation)
|
||||
return element
|
||||
|
||||
@classmethod
|
||||
@@ -1678,6 +1682,8 @@ class Drawing(bonsai.core.tool.Drawing):
|
||||
context=context,
|
||||
ifc_representation_class=None,
|
||||
)
|
||||
if representation := ifcopenshell.util.representation.get_representation(element, context):
|
||||
cls.reload_representation(obj=obj, representation=representation)
|
||||
return element
|
||||
|
||||
@classmethod
|
||||
@@ -1778,6 +1784,8 @@ class Drawing(bonsai.core.tool.Drawing):
|
||||
context=context,
|
||||
ifc_representation_class=None,
|
||||
)
|
||||
if representation := ifcopenshell.util.representation.get_representation(element, context):
|
||||
cls.reload_representation(obj=obj, representation=representation)
|
||||
return element
|
||||
|
||||
@classmethod
|
||||
@@ -1834,9 +1842,17 @@ class Drawing(bonsai.core.tool.Drawing):
|
||||
return bool(a_tree.overlap(b_tree))
|
||||
|
||||
@classmethod
|
||||
def replace_text_literal_variables(cls, text: str, product: Optional[ifcopenshell.entity_instance] = None) -> str:
|
||||
def replace_text_literal_variables(
|
||||
cls,
|
||||
text: str,
|
||||
product: Optional[ifcopenshell.entity_instance] = None,
|
||||
reverse_list: bool = False,
|
||||
list_separator: str = ", ",
|
||||
) -> str:
|
||||
if not product:
|
||||
return text
|
||||
if list_separator:
|
||||
list_separator = list_separator.encode().decode("unicode_escape")
|
||||
|
||||
for command in re.findall("``.*?``", text):
|
||||
original_command = command
|
||||
@@ -1845,13 +1861,14 @@ class Drawing(bonsai.core.tool.Drawing):
|
||||
value = '"' + str(value).replace('"', '\\"') + '"'
|
||||
command = command.replace(variable, value)
|
||||
text = text.replace(original_command, ifcopenshell.util.selector.format(command[2:-2]))
|
||||
|
||||
for variable in re.findall("{{.*?}}", text):
|
||||
value = ifcopenshell.util.selector.get_element_value(product, variable[2:-2])
|
||||
if isinstance(value, (list, tuple)):
|
||||
value = ", ".join(str(v) for v in value)
|
||||
if reverse_list:
|
||||
value = list_separator.join(str(v) for v in reversed(value))
|
||||
else:
|
||||
value = list_separator.join(str(v) for v in value)
|
||||
text = text.replace(variable, str(value))
|
||||
|
||||
return text
|
||||
|
||||
@classmethod
|
||||
@@ -2123,22 +2140,9 @@ class Drawing(bonsai.core.tool.Drawing):
|
||||
layer_collection2.hide_viewport = False
|
||||
|
||||
@classmethod
|
||||
def activate_drawing(cls, camera: bpy.types.Object) -> None:
|
||||
selected_objects_before = bpy.context.selected_objects
|
||||
non_ifc_objects_hide = {o: o.hide_get() for o in bpy.context.view_layer.objects if not tool.Ifc.get_entity(o)}
|
||||
|
||||
# Sync viewport objects visibility with selectors from EPset_Drawing/Include and /Exclude
|
||||
drawing = tool.Ifc.get_entity(camera)
|
||||
assert drawing and isinstance(camera.data, bpy.types.Camera)
|
||||
|
||||
filtered_elements = cls.get_drawing_elements(drawing) | cls.get_drawing_spaces(drawing)
|
||||
filtered_elements.add(drawing)
|
||||
|
||||
subcontexts: list[ifcopenshell.entity_instance] = []
|
||||
target_view = cls.get_drawing_target_view(drawing)
|
||||
|
||||
def get_drawing_context_filters(cls, target_view: str) -> list[tuple[str, str, str]]:
|
||||
if target_view in ("PLAN_VIEW", "REFLECTED_PLAN_VIEW"):
|
||||
context_filters = [
|
||||
return [
|
||||
("Plan", "Body", target_view),
|
||||
("Plan", "Body", "MODEL_VIEW"),
|
||||
("Plan", "Facetation", target_view),
|
||||
@@ -2153,7 +2157,7 @@ class Drawing(bonsai.core.tool.Drawing):
|
||||
("Model", "Annotation", "MODEL_VIEW"),
|
||||
]
|
||||
else:
|
||||
context_filters = [
|
||||
return [
|
||||
("Model", "Body", target_view),
|
||||
("Model", "Body", "MODEL_VIEW"),
|
||||
("Model", "Facetation", target_view),
|
||||
@@ -2162,18 +2166,41 @@ class Drawing(bonsai.core.tool.Drawing):
|
||||
("Model", "Annotation", "MODEL_VIEW"),
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def get_drawing_subcontexts(cls, target_view: str) -> list[tuple[str, str, str]]:
|
||||
context_filters = cls.get_drawing_context_filters(target_view)
|
||||
subcontexts = []
|
||||
|
||||
for context_filter in context_filters:
|
||||
subcontext = ifcopenshell.util.representation.get_context(tool.Ifc.get(), *context_filter)
|
||||
if subcontext:
|
||||
subcontexts.append(context_filter)
|
||||
|
||||
# Hide everything first, then selectively show. This is significantly faster.
|
||||
with bpy.context.temp_override(**tool.Blender.get_viewport_context()):
|
||||
bpy.ops.object.hide_view_set(unselected=False)
|
||||
bpy.ops.object.hide_view_set(unselected=True)
|
||||
return subcontexts
|
||||
|
||||
# switch representations and hide elements without representations
|
||||
element_obj_names = set()
|
||||
@classmethod
|
||||
def get_active_drawing_subcontexts(cls) -> list[tuple[str, str, str]] | None:
|
||||
props = cls.get_document_props()
|
||||
target_view = props.get_active_target_view()
|
||||
if not target_view:
|
||||
return None
|
||||
|
||||
return cls.get_drawing_subcontexts(target_view)
|
||||
|
||||
@classmethod
|
||||
def activate_drawing(cls, camera: bpy.types.Object) -> None:
|
||||
# Sync viewport objects visibility with selectors from EPset_Drawing/Include and /Exclude
|
||||
drawing = tool.Ifc.get_entity(camera)
|
||||
assert drawing and isinstance(camera.data, bpy.types.Camera)
|
||||
cls.import_annotations_in_group(cls.get_drawing_group(drawing))
|
||||
|
||||
filtered_elements = cls.get_drawing_elements(drawing) | cls.get_drawing_spaces(drawing)
|
||||
filtered_elements.add(drawing)
|
||||
|
||||
target_view = cls.get_drawing_target_view(drawing)
|
||||
subcontexts = cls.get_drawing_subcontexts(target_view)
|
||||
|
||||
# Switch representations
|
||||
for element in filtered_elements:
|
||||
obj = tool.Ifc.get_object(element)
|
||||
if not obj:
|
||||
@@ -2197,30 +2224,22 @@ class Drawing(bonsai.core.tool.Drawing):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=priority_representation,
|
||||
should_reload=False,
|
||||
is_global=True,
|
||||
should_sync_changes_first=True,
|
||||
)
|
||||
has_context = True
|
||||
break
|
||||
|
||||
# Don't hide IfcAnnotations or Aggregates as some of them might exist without representations
|
||||
if has_context or element.is_a("IfcAnnotation") or element.IsDecomposedBy:
|
||||
element_obj_names.add(obj.name)
|
||||
|
||||
# Note that render visibility is only set on drawing generation time for speed.
|
||||
visible_objects = []
|
||||
for obj in bpy.context.view_layer.objects:
|
||||
if obj.name in element_obj_names:
|
||||
obj.hide_set(False) # Show the object
|
||||
continue
|
||||
if (hide := non_ifc_objects_hide.get(obj)) is not None:
|
||||
obj.hide_set(hide)
|
||||
if element := tool.Ifc.get_entity(obj):
|
||||
if element in filtered_elements:
|
||||
visible_objects.append(obj)
|
||||
else:
|
||||
if obj.hide_get() is False:
|
||||
visible_objects.append(obj)
|
||||
tool.Blender.isolate_objects(visible_objects)
|
||||
|
||||
cls.import_camera_props(drawing, camera.data)
|
||||
|
||||
for obj in selected_objects_before:
|
||||
obj.select_set(True)
|
||||
|
||||
@classmethod
|
||||
def get_elements_in_camera_view(
|
||||
cls, camera: bpy.types.Object, objs: list[bpy.types.Object]
|
||||
|
||||
@@ -1350,9 +1350,6 @@ class Geometry(bonsai.core.tool.Geometry):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
apply_openings=True,
|
||||
)
|
||||
|
||||
@@ -1388,9 +1385,6 @@ class Geometry(bonsai.core.tool.Geometry):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=new_representation,
|
||||
should_reload=False,
|
||||
should_sync_changes_first=False,
|
||||
is_global=True,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -248,7 +248,7 @@ class Ifc(bonsai.core.tool.Ifc):
|
||||
|
||||
Marking object as edited is an optimization mechanism - instead of saving
|
||||
changed geometry to IFC, we mark it as changed and then it's saved later
|
||||
(typically during project save or switch_representation(should_sync_changes_first=True)).
|
||||
(typically during project save).
|
||||
|
||||
Other caveat of using edited objects is that it won't have an effect for objects with openings,
|
||||
since we can't deduce non-openings representation from edited representation with openings.
|
||||
|
||||
@@ -1091,9 +1091,6 @@ class Model(bonsai.core.tool.Model):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=new_representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
@@ -1306,9 +1303,6 @@ class Model(bonsai.core.tool.Model):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=body,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
@@ -1652,9 +1646,6 @@ class Model(bonsai.core.tool.Model):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
apply_openings=True,
|
||||
)
|
||||
|
||||
@@ -2468,9 +2459,6 @@ class Model(bonsai.core.tool.Model):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=rep,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
tool.Geometry.record_object_materials(obj)
|
||||
|
||||
|
||||
@@ -483,3 +483,22 @@ class Project(bonsai.core.tool.Project):
|
||||
organisation_email=organization_email,
|
||||
authorisation=authorization,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def get_clipping_planes_normals(cls):
|
||||
normals = []
|
||||
for clipping_plane in tool.Project.get_project_props().clipping_planes:
|
||||
plane = clipping_plane.obj
|
||||
if not plane or not plane.data:
|
||||
continue
|
||||
|
||||
if plane.mode == "EDIT":
|
||||
continue # A profile decorator or something else is used here.
|
||||
|
||||
v1 = plane.matrix_world @ plane.data.vertices[0].co
|
||||
v2 = plane.matrix_world @ plane.data.vertices[1].co
|
||||
v3 = plane.matrix_world @ plane.data.vertices[2].co
|
||||
d1 = v1 - v2
|
||||
d2 = v3 - v2
|
||||
normals.append((v1, d1.cross(d2).normalized()))
|
||||
return normals
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
import bmesh
|
||||
import bpy
|
||||
import copy
|
||||
import numpy as np
|
||||
from bpy_extras import view3d_utils
|
||||
import bonsai.core.tool
|
||||
import bonsai.tool as tool
|
||||
@@ -133,6 +134,36 @@ class Raycast(bonsai.core.tool.Raycast):
|
||||
else:
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def object_is_visible_in_clipping_plane(cls, obj):
|
||||
is_visible = True
|
||||
if obj.type == "EMPTY":
|
||||
vertex = obj.location
|
||||
is_visible = cls.point_is_visible_in_clipping_plane(vertex)
|
||||
|
||||
if obj.type == "CURVE":
|
||||
obj = bpy.data.objects.new("new_object", obj.to_mesh().copy())
|
||||
|
||||
if obj.type == "MESH":
|
||||
for v in obj.data.vertices:
|
||||
vertex = obj.matrix_world @ v.co
|
||||
is_visible = cls.point_is_visible_in_clipping_plane(vertex)
|
||||
if is_visible:
|
||||
break
|
||||
return is_visible
|
||||
|
||||
@classmethod
|
||||
def point_is_visible_in_clipping_plane(cls, vertex):
|
||||
normals = tool.Project.get_clipping_planes_normals()
|
||||
if not normals:
|
||||
return True
|
||||
for normal in normals:
|
||||
t = (vertex - normal[0]).normalized()
|
||||
result = normal[1].dot(t)
|
||||
if result < 0:
|
||||
return False
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def get_viewport_ray_data(
|
||||
cls, context: bpy.types.Context, event: bpy.types.Event, mouse_pos: tuple[int, int] = None
|
||||
@@ -213,10 +244,7 @@ class Raycast(bonsai.core.tool.Raycast):
|
||||
ray_origin, ray_target, ray_direction = cls.get_viewport_ray_data(context, event)
|
||||
points = []
|
||||
|
||||
# Makes the snapping point more or less sticky than others
|
||||
# It changes the distance and affects how the snapping point are sorted
|
||||
# We multiply by the increment snap which is based on the viewport zoom
|
||||
snap_threshold = rv3d.view_distance / 100
|
||||
snap_threshold = cls.calculate_snap_threshold(rv3d.view_distance)
|
||||
|
||||
try:
|
||||
loc = view3d_utils.region_2d_to_location_3d(region, rv3d, mouse_pos, ray_direction)
|
||||
@@ -309,7 +337,7 @@ class Raycast(bonsai.core.tool.Raycast):
|
||||
rv3d = context.region_data
|
||||
mouse_pos = event.mouse_region_x, event.mouse_region_y
|
||||
ray_origin, ray_target, ray_direction = cls.get_viewport_ray_data(context, event)
|
||||
snap_threshold = rv3d.view_distance / 100
|
||||
snap_threshold = cls.calculate_snap_threshold(rv3d.view_distance)
|
||||
|
||||
try:
|
||||
loc = view3d_utils.region_2d_to_location_3d(region, rv3d, mouse_pos, ray_direction)
|
||||
@@ -387,7 +415,7 @@ class Raycast(bonsai.core.tool.Raycast):
|
||||
rv3d = context.region_data
|
||||
mouse_pos = event.mouse_region_x, event.mouse_region_y
|
||||
ray_origin, ray_target, ray_direction = cls.get_viewport_ray_data(context, event)
|
||||
snap_threshold = rv3d.view_distance / 100
|
||||
snap_threshold = cls.calculate_snap_threshold(rv3d.view_distance)
|
||||
|
||||
try:
|
||||
loc = view3d_utils.region_2d_to_location_3d(region, rv3d, mouse_pos, ray_direction)
|
||||
@@ -423,7 +451,9 @@ class Raycast(bonsai.core.tool.Raycast):
|
||||
for obj, bbox_2d in objs_2d_bbox:
|
||||
if bbox_2d:
|
||||
if tool.Raycast.intersect_mouse_2d_bounding_box(mouse_pos, bbox_2d):
|
||||
objs_to_raycast.append(obj)
|
||||
if tool.Raycast.object_is_visible_in_clipping_plane(obj):
|
||||
objs_to_raycast.append(obj)
|
||||
|
||||
return objs_to_raycast
|
||||
|
||||
@classmethod
|
||||
@@ -500,3 +530,15 @@ class Raycast(bonsai.core.tool.Raycast):
|
||||
|
||||
else:
|
||||
return None, None, None
|
||||
|
||||
@classmethod
|
||||
def calculate_snap_threshold(cls, view_distance):
|
||||
snap_threshold = view_distance / 100
|
||||
area = tool.Blender.get_view3d_area()
|
||||
lens = area.spaces.active.lens
|
||||
xp = np.array([1, 10, 50])
|
||||
fp = np.array([50, 10, 1])
|
||||
value = np.interp(lens, xp, fp)
|
||||
if lens < 50:
|
||||
snap_threshold *= value
|
||||
return snap_threshold
|
||||
|
||||
@@ -326,9 +326,6 @@ class Root(bonsai.core.tool.Root):
|
||||
tool.Geometry,
|
||||
obj=voided_obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -121,7 +121,8 @@ class Search(bonsai.core.tool.Search):
|
||||
filter_group_query.append(f"parent{comparison}{value}")
|
||||
elif ifc_filter.type == "query":
|
||||
keys = cls.wrap_value(ifc_filter, ifc_filter.name)
|
||||
comparison, value = cls.get_comparison_and_value(ifc_filter)
|
||||
comparison = ifc_filter.comparison or "="
|
||||
value = cls.wrap_value(ifc_filter, ifc_filter.value)
|
||||
filter_group_query.append(f"query:{keys}{comparison}{value}")
|
||||
query.append(", ".join(filter_group_query))
|
||||
return " + ".join(query)
|
||||
|
||||
@@ -512,14 +512,12 @@ class Sequence(bonsai.core.tool.Sequence):
|
||||
@classmethod
|
||||
def get_task_inputs(cls, task: ifcopenshell.entity_instance) -> list[ifcopenshell.entity_instance]:
|
||||
props = cls.get_work_schedule_props()
|
||||
is_deep = props.show_nested_inputs
|
||||
return ifcopenshell.util.sequence.get_task_inputs(task, is_deep)
|
||||
return ifcopenshell.util.sequence.get_task_inputs(task, is_recursive=props.show_nested_inputs)
|
||||
|
||||
@classmethod
|
||||
def get_task_outputs(cls, task: ifcopenshell.entity_instance) -> list[ifcopenshell.entity_instance]:
|
||||
props = cls.get_work_schedule_props()
|
||||
is_deep = props.show_nested_outputs
|
||||
return ifcopenshell.util.sequence.get_task_outputs(task, is_deep)
|
||||
return ifcopenshell.util.sequence.get_task_outputs(task, is_recursive=props.show_nested_outputs)
|
||||
|
||||
@classmethod
|
||||
def are_entities_same_class(cls, entities: list[ifcopenshell.entity_instance]) -> bool:
|
||||
@@ -540,8 +538,7 @@ class Sequence(bonsai.core.tool.Sequence):
|
||||
if not task:
|
||||
return
|
||||
props = cls.get_work_schedule_props()
|
||||
is_deep = props.show_nested_resources
|
||||
return ifcopenshell.util.sequence.get_task_resources(task, is_deep)
|
||||
return ifcopenshell.util.sequence.get_task_resources(task, props.show_nested_resources)
|
||||
|
||||
@classmethod
|
||||
def load_task_inputs(cls, inputs: list[ifcopenshell.entity_instance]) -> None:
|
||||
@@ -1582,7 +1579,7 @@ class Sequence(bonsai.core.tool.Sequence):
|
||||
@classmethod
|
||||
def create_new_task_json(cls, task, json, type_map=None, baseline_schedule=None):
|
||||
task_time = task.TaskTime
|
||||
resources = ifcopenshell.util.sequence.get_task_resources(task, is_deep=False)
|
||||
resources = ifcopenshell.util.sequence.get_task_resources(task, is_recursive=False)
|
||||
|
||||
string_resources = ""
|
||||
resources_usage = ""
|
||||
@@ -1689,8 +1686,8 @@ class Sequence(bonsai.core.tool.Sequence):
|
||||
) -> list[ifcopenshell.entity_instance]:
|
||||
products = []
|
||||
for task in ifcopenshell.util.sequence.get_root_tasks(work_schedule):
|
||||
products.extend(ifcopenshell.util.sequence.get_task_inputs(task, is_deep=True))
|
||||
products.extend(ifcopenshell.util.sequence.get_task_outputs(task, is_deep=True))
|
||||
products.extend(ifcopenshell.util.sequence.get_task_inputs(task, is_recursive=True))
|
||||
products.extend(ifcopenshell.util.sequence.get_task_outputs(task, is_recursive=True))
|
||||
return products
|
||||
|
||||
@classmethod
|
||||
@@ -1789,6 +1786,16 @@ class Sequence(bonsai.core.tool.Sequence):
|
||||
cls.load_task_outputs(outputs)
|
||||
cls.load_task_resources(task)
|
||||
|
||||
@classmethod
|
||||
def select_active_task_elements(cls, task):
|
||||
if not task:
|
||||
return
|
||||
bpy.ops.object.select_all(action="DESELECT")
|
||||
props = cls.get_work_schedule_props()
|
||||
for element in cls.get_task_inputs(task) | cls.get_task_outputs(task):
|
||||
if obj := tool.Ifc.get_object(element):
|
||||
obj.select_set(True)
|
||||
|
||||
@classmethod
|
||||
def refresh_task_resources(cls):
|
||||
task = cls.get_highlighted_task()
|
||||
|
||||
@@ -18,10 +18,13 @@
|
||||
|
||||
from __future__ import annotations
|
||||
import bpy
|
||||
import bmesh
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.unit
|
||||
import bonsai.core.tool
|
||||
import bonsai.tool as tool
|
||||
from bonsai.bim.module.drawing.data import DecoratorData
|
||||
from bonsai.bim.module.drawing.decoration import CutDecorator
|
||||
from bonsai.bim.module.model.decorator import PolylineDecorator
|
||||
import math
|
||||
import mathutils
|
||||
@@ -79,7 +82,7 @@ class Snap(bonsai.core.tool.Snap):
|
||||
ortho_threshold = [-10.0, -4.75, -2.2, -0.75]
|
||||
distances = [3, 6, 10, 20]
|
||||
|
||||
increment = None
|
||||
increment = 1
|
||||
if rv3d.view_perspective == "PERSP":
|
||||
if rv3d.view_distance < distances[0]:
|
||||
increment = (1 / fractions[0]) * factor
|
||||
@@ -428,6 +431,53 @@ class Snap(bonsai.core.tool.Snap):
|
||||
point["group"] = "Object"
|
||||
detected_snaps.append(point)
|
||||
|
||||
# snap to cut geometry (e.g. in plan view)
|
||||
if CutDecorator.installed:
|
||||
cut_snaps = []
|
||||
|
||||
model_props = tool.Model.get_model_props()
|
||||
|
||||
for obj in [o for o in context.visible_objects if o.type == "MESH"]:
|
||||
if not (element := tool.Ifc.get_entity(obj)):
|
||||
continue
|
||||
|
||||
if model_props.show_cut_decorator and element.id() in DecoratorData.cut_cache:
|
||||
verts, edges = DecoratorData.cut_cache[element.id()]
|
||||
if not verts or not edges:
|
||||
continue
|
||||
|
||||
bm = bmesh.new()
|
||||
bverts = [bm.verts.new(pos) for pos in verts]
|
||||
for edge in edges:
|
||||
bm.edges.new([bverts[vi] for vi in edge])
|
||||
|
||||
snap_points = tool.Raycast.ray_cast_by_proximity(context, event, None, None, bm)
|
||||
if snap_points:
|
||||
for p in snap_points:
|
||||
p["group"] = "Object"
|
||||
p["object"] = obj
|
||||
cut_snaps.append(p)
|
||||
|
||||
if model_props.show_cut_decorator_fill and element.id() in DecoratorData.fill_cache:
|
||||
bm = bmesh.new()
|
||||
for color, verts_and_tris in DecoratorData.fill_cache[element.id()].items():
|
||||
for verts, tris in verts_and_tris:
|
||||
bverts = [bm.verts.new(pos) for pos in verts]
|
||||
for tri in tris:
|
||||
verts = [bverts[vi] for vi in tri]
|
||||
if not bm.faces.get(verts):
|
||||
bm.faces.new(verts)
|
||||
|
||||
snap_points = tool.Raycast.ray_cast_by_proximity(context, event, None, None, bm)
|
||||
if snap_points:
|
||||
for p in snap_points:
|
||||
p["group"] = "Object"
|
||||
p["object"] = obj
|
||||
cut_snaps.append(p)
|
||||
|
||||
if len(cut_snaps) > 0:
|
||||
detected_snaps = cut_snaps
|
||||
|
||||
# Axis and Plane
|
||||
if tool.Ifc.get():
|
||||
elevation = tool.Root.get_default_container_elevation()
|
||||
@@ -491,7 +541,11 @@ class Snap(bonsai.core.tool.Snap):
|
||||
"distance": 10, # High value so it has low priority
|
||||
}
|
||||
detected_snaps.append(snap_point)
|
||||
|
||||
detected_snaps = [
|
||||
snap
|
||||
for snap in detected_snaps
|
||||
if (tool.Raycast.point_is_visible_in_clipping_plane(snap["point"]) or snap["group"] == "Plane")
|
||||
]
|
||||
return detected_snaps
|
||||
|
||||
@classmethod
|
||||
@@ -517,7 +571,8 @@ class Snap(bonsai.core.tool.Snap):
|
||||
|
||||
def sort_points_by_weighted_distance(snapping_points):
|
||||
for snap in snapping_points:
|
||||
zoom_factor = bpy.context.region_data.view_distance
|
||||
rv3d = bpy.context.region_data
|
||||
zoom_factor = rv3d.view_distance
|
||||
if snap["type"] == "Vertex":
|
||||
snap["distance"] *= zoom_factor / 10
|
||||
if snap["type"] == "Edge Center":
|
||||
|
||||
@@ -175,7 +175,6 @@ class Spatial(bonsai.core.tool.Spatial):
|
||||
|
||||
@classmethod
|
||||
def select_products(cls, products: Iterable[ifcopenshell.entity_instance], unhide: bool = False) -> None:
|
||||
bpy.ops.object.select_all(action="DESELECT")
|
||||
for product in products:
|
||||
obj = tool.Ifc.get_object(product)
|
||||
if obj and bpy.context.view_layer.objects.get(obj.name):
|
||||
@@ -1198,11 +1197,30 @@ class Spatial(bonsai.core.tool.Spatial):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=body,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def set_space_visibility(cls, is_visible: bool) -> None:
|
||||
if tool.Ifc.get().schema == "IFC2X3":
|
||||
elements = tool.Ifc.get().by_type("IfcSpatialStructureElement")
|
||||
else:
|
||||
elements = tool.Ifc.get().by_type("IfcSpatialElement")
|
||||
for element in elements:
|
||||
if obj := tool.Ifc.get_object(element):
|
||||
if obj.hide_viewport is True and is_visible:
|
||||
obj.hide_viewport = False
|
||||
elif obj.hide_viewport is False and not is_visible:
|
||||
obj.hide_viewport = True
|
||||
|
||||
@classmethod
|
||||
def set_grid_visibility(cls, is_visible: bool) -> None:
|
||||
for element in tool.Ifc.get().by_type("IfcGrid") + tool.Ifc.get().by_type("IfcGridAxis"):
|
||||
if obj := tool.Ifc.get_object(element):
|
||||
if obj.hide_viewport is True and is_visible:
|
||||
obj.hide_viewport = False
|
||||
elif obj.hide_viewport is False and not is_visible:
|
||||
obj.hide_viewport = True
|
||||
|
||||
@classmethod
|
||||
def toggle_spaces_visibility_wired_and_textured(cls, spaces: list[ifcopenshell.entity_instance]) -> None:
|
||||
first_obj = tool.Ifc.get_object(spaces[0])
|
||||
|
||||
@@ -131,15 +131,10 @@ class Type(bonsai.core.tool.Type):
|
||||
cls,
|
||||
obj: bpy.types.Object,
|
||||
representation: ifcopenshell.entity_instance,
|
||||
should_reload: bool = False,
|
||||
is_global: bool = False,
|
||||
) -> None:
|
||||
return bonsai.core.geometry.switch_representation(
|
||||
tool.Ifc,
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=representation,
|
||||
should_reload=should_reload,
|
||||
is_global=is_global,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
|
||||
@@ -41,6 +41,7 @@ import socketio
|
||||
import threading
|
||||
import queue
|
||||
import json
|
||||
import platformdirs
|
||||
from time import sleep
|
||||
from pathlib import Path
|
||||
import bonsai.core.sequence
|
||||
@@ -65,6 +66,13 @@ IFC_TASK_ATTRIBUTE_MAP = {
|
||||
}
|
||||
|
||||
|
||||
def get_pid_file_path():
|
||||
"""Get the path to the PID file in the user's cache directory."""
|
||||
cache_dir = Path(platformdirs.user_cache_dir("bonsai"))
|
||||
cache_dir.mkdir(parents=True, exist_ok=True)
|
||||
return cache_dir / "running_pid.json"
|
||||
|
||||
|
||||
class Web(bonsai.core.tool.Web):
|
||||
@classmethod
|
||||
def get_web_props(cls) -> WebProperties:
|
||||
@@ -213,7 +221,7 @@ class Web(bonsai.core.tool.Web):
|
||||
cls.disconnect_websocket_server()
|
||||
|
||||
# sleep(0.5)
|
||||
pid_file = tool.Blender.get_data_dir_path("webui") / "running_pid.json"
|
||||
pid_file = get_pid_file_path()
|
||||
with open(pid_file, "r") as f:
|
||||
pids = json.load(f)
|
||||
|
||||
@@ -250,7 +258,7 @@ class Web(bonsai.core.tool.Web):
|
||||
while True:
|
||||
if time.time() - start > max_time:
|
||||
return False
|
||||
pid_file = tool.Blender.get_data_dir_path("webui") / "running_pid.json"
|
||||
pid_file = get_pid_file_path()
|
||||
try:
|
||||
with open(pid_file, "r") as f:
|
||||
data = json.load(f)
|
||||
|
||||
@@ -174,6 +174,14 @@ If there are changes to the IfcOpenShell binaries, you may replace the two
|
||||
``*ifcopenshell_wrapper*`` files with new ones downloaded from the automated
|
||||
`IfcOpenShell builds directory <https://builds.ifcopenshell.org/>`__.
|
||||
|
||||
If you wish to run the IfcTester webapp, you will also need to have ``npm`` and
|
||||
setup IfcTester:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
cd src/ifctester
|
||||
make webapp-prepare
|
||||
|
||||
.. seealso::
|
||||
|
||||
There is a `useful Blender Addon
|
||||
|
||||
@@ -37,7 +37,7 @@ class TestEditText:
|
||||
def test_run(self, drawing):
|
||||
drawing.synchronise_ifc_and_text_attributes("obj").should_be_called()
|
||||
drawing.update_text_size_pset("obj").should_be_called()
|
||||
drawing.update_newline_at_and_symbol("obj").should_be_called()
|
||||
drawing.update_text_annotation_properties("obj").should_be_called()
|
||||
drawing.disable_editing_text("obj").should_be_called()
|
||||
subject.edit_text(drawing, obj="obj")
|
||||
|
||||
|
||||
@@ -203,9 +203,6 @@ class TestSwitchRepresentation:
|
||||
geometry,
|
||||
obj="obj",
|
||||
representation="mapped_rep",
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=True,
|
||||
apply_openings=True,
|
||||
)
|
||||
|
||||
@@ -222,9 +219,6 @@ class TestSwitchRepresentation:
|
||||
geometry,
|
||||
obj="obj",
|
||||
representation="mapped_rep",
|
||||
should_reload=False,
|
||||
is_global=False,
|
||||
should_sync_changes_first=True,
|
||||
)
|
||||
|
||||
def test_not_switching_if_an_updated_representation_is_the_same_one_we_were_going_to_switch_to(self, ifc, geometry):
|
||||
@@ -238,9 +232,6 @@ class TestSwitchRepresentation:
|
||||
geometry,
|
||||
obj="obj",
|
||||
representation="mapped_rep",
|
||||
should_reload=False,
|
||||
is_global=False,
|
||||
should_sync_changes_first=True,
|
||||
)
|
||||
|
||||
|
||||
@@ -248,7 +239,7 @@ class TestGetRepresentationIfcParameters:
|
||||
def test_run(self, geometry):
|
||||
geometry.get_object_data("obj").should_be_called().will_return("data")
|
||||
geometry.import_representation_parameters("data").should_be_called()
|
||||
subject.get_representation_ifc_parameters(geometry, obj="obj", should_sync_changes_first=False)
|
||||
subject.get_representation_ifc_parameters(geometry, obj="obj")
|
||||
|
||||
|
||||
class TestRemoveRepresentation:
|
||||
|
||||
@@ -793,7 +793,7 @@ class TestShowDecorations(NewFile):
|
||||
|
||||
|
||||
class TestDrawingMaintainingSheetPosition(NewFile):
|
||||
def get_sheet_drawing_data(self, layout_path):
|
||||
def get_sheet_drawing_data(self, layout_path: Path) -> dict[str, tuple[float, ...]]:
|
||||
SVG = "{http://www.w3.org/2000/svg}"
|
||||
ET.register_namespace("", SVG)
|
||||
layout_tree = ET.parse(layout_path)
|
||||
@@ -801,7 +801,7 @@ class TestDrawingMaintainingSheetPosition(NewFile):
|
||||
|
||||
drawing_view = layout_root.findall(f'{SVG}g[@data-type="drawing"]')[0]
|
||||
|
||||
drawing_data = {}
|
||||
drawing_data: dict[str, tuple[float, ...]] = {}
|
||||
for image in drawing_view.findall(f"{SVG}image"):
|
||||
attribs = ["x", "y", "width", "height"]
|
||||
image_type = image.attrib["data-type"]
|
||||
|
||||
@@ -0,0 +1,248 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pydantic import BaseModel
|
||||
from .type_hints import *
|
||||
from typing import Optional, Literal
|
||||
from datetime import datetime
|
||||
from pydantic import BaseModel, Field, PrivateAttr, model_validator, ConfigDict
|
||||
import json
|
||||
import weakref
|
||||
|
||||
|
||||
def _lower_first(s: str) -> str:
|
||||
return s[:1].lower() + s[1:] if s else s
|
||||
|
||||
|
||||
class CaseInsensitiveModel(BaseModel):
|
||||
model_config = ConfigDict(populate_by_name=True, alias_generator=_lower_first)
|
||||
|
||||
|
||||
class BsddDictionary(CaseInsensitiveModel):
|
||||
OrganizationCode: str
|
||||
DictionaryCode: str
|
||||
DictionaryVersion: str
|
||||
LanguageIsoCode: LANGUAGE_ISO_CODE
|
||||
LanguageOnly: bool
|
||||
UseOwnUri: bool
|
||||
DictionaryName: Optional[str] = None
|
||||
DictionaryUri: Optional[str] = None
|
||||
License: Optional[str] = "MIT"
|
||||
LicenseUrl: Optional[str] = None
|
||||
ChangeRequestEmailAddress: Optional[str] = None
|
||||
ModelVersion: Optional[str] = "2.0"
|
||||
MoreInfoUrl: Optional[str] = None
|
||||
QualityAssuranceProcedure: Optional[str] = None
|
||||
QualityAssuranceProcedureUrl: Optional[str] = None
|
||||
ReleaseDate: Optional[datetime] = None
|
||||
Status: Optional[STATUS] = None
|
||||
Classes: list[BsddClass] = Field(default_factory=list)
|
||||
Properties: list[BsddProperty] = Field(default_factory=list)
|
||||
|
||||
@classmethod
|
||||
def load(cls, path) -> BsddDictionary:
|
||||
"""Load from a JSON file and validate via the normalizer above."""
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
raw = json.load(f)
|
||||
# The model_validator(before) handles list/dict/nested shapes
|
||||
return cls.model_validate(raw)
|
||||
|
||||
def save(self, path):
|
||||
with open(path, "w") as file:
|
||||
json.dump(self.model_dump(mode="json", exclude_none=True), file)
|
||||
|
||||
# add Parent to children after loading
|
||||
def model_post_init(self, context):
|
||||
for c in self.Classes:
|
||||
c._set_parent(self)
|
||||
for p in self.Properties:
|
||||
p._set_parent(self)
|
||||
|
||||
|
||||
class BsddClass(CaseInsensitiveModel):
|
||||
Code: str
|
||||
Name: str
|
||||
ClassType: CLASS_TYPE = "Class"
|
||||
Definition: Optional[str] = None
|
||||
Description: Optional[str] = None
|
||||
ParentClassCode: Optional[str | None] = None
|
||||
RelatedIfcEntityNamesList: Optional[list[str]] = None
|
||||
Synonyms: Optional[list[str]] = None
|
||||
ActivationDateUtc: Optional[datetime] = None
|
||||
ReferenceCode: Optional[str] = None
|
||||
CountriesOfUse: Optional[list[COUNTRY_CODE]] = None
|
||||
CountryOfOrigin: Optional[COUNTRY_CODE] = None
|
||||
CreatorLanguageIsoCode: Optional[LANGUAGE_ISO_CODE] = None
|
||||
DeActivationDateUtc: Optional[datetime] = None
|
||||
DeprecationExplanation: Optional[str] = None
|
||||
DocumentReference: Optional[str] = None
|
||||
OwnedUri: Optional[str] = None
|
||||
ReplacedObjectCodes: Optional[list[str]] = None
|
||||
ReplacingObjectCodes: Optional[list[str]] = None
|
||||
RevisionDateUtc: Optional[datetime] = None
|
||||
RevisionNumber: Optional[int] = None
|
||||
Status: Optional[CLASS_STATUS] = None
|
||||
SubdivisionsOfUse: Optional[list[str]] = None
|
||||
Uid: Optional[str] = None
|
||||
VersionDateUtc: Optional[datetime] = None
|
||||
VersionNumber: Optional[int] = None
|
||||
VisualRepresentationUri: Optional[str] = None
|
||||
ClassProperties: list[BsddClassProperty] = Field(default_factory=list)
|
||||
ClassRelations: list[BsddClassRelation] = Field(default_factory=list)
|
||||
|
||||
_parent_ref: Optional[weakref.ReferenceType[BsddDictionary]] = PrivateAttr(default=None)
|
||||
|
||||
def _set_parent(self, parent: BsddDictionary) -> None:
|
||||
self._parent_ref = weakref.ref(parent)
|
||||
|
||||
def parent(self) -> Optional[BsddDictionary]:
|
||||
return self._parent_ref() if self._parent_ref is not None else None
|
||||
|
||||
def model_post_init(self, context):
|
||||
for c in self.ClassProperties:
|
||||
c._set_parent(self)
|
||||
for cr in self.ClassRelations:
|
||||
cr._set_parent(self)
|
||||
|
||||
|
||||
class BsddClassRelation(CaseInsensitiveModel):
|
||||
RelationType: CLASS_RELATION_TYPE
|
||||
RelatedClassUri: str
|
||||
RelatedClassName: Optional[str] = None
|
||||
Fraction: Optional[float] = None
|
||||
OwnedUri: Optional[str] = None
|
||||
|
||||
def _set_parent(self, parent: BsddClass) -> None:
|
||||
self._parent_ref = weakref.ref(parent)
|
||||
|
||||
def parent(self) -> Optional[BsddClass]:
|
||||
return self._parent_ref() if self._parent_ref is not None else None
|
||||
|
||||
|
||||
class BsddAllowedValue(CaseInsensitiveModel):
|
||||
Code: str
|
||||
Value: str
|
||||
Description: Optional[str] = None
|
||||
Uri: Optional[str] = None
|
||||
SortNumber: Optional[int] = None
|
||||
OwnedUri: Optional[str] = None
|
||||
|
||||
|
||||
class BsddClassProperty(CaseInsensitiveModel):
|
||||
Code: str
|
||||
PropertyCode: Optional[str] = None
|
||||
PropertyUri: Optional[str] = None
|
||||
Description: Optional[str] = None
|
||||
PropertySet: Optional[str] = None
|
||||
Unit: Optional[str] = None
|
||||
PredefinedValue: Optional[str] = None
|
||||
IsRequired: Optional[bool] = None
|
||||
IsWritable: Optional[bool] = None
|
||||
MaxExclusive: Optional[float] = None
|
||||
MaxInclusive: Optional[float] = None
|
||||
MinExclusive: Optional[float] = None
|
||||
MinInclusive: Optional[float] = None
|
||||
Pattern: Optional[str] = None
|
||||
OwnedUri: Optional[str] = None
|
||||
PropertyType: Optional[Literal["Property", "Dependency"]] = None
|
||||
SortNumber: Optional[int] = None
|
||||
Symbol: Optional[str] = None
|
||||
AllowedValues: list[BsddAllowedValue] = Field(default_factory=list)
|
||||
_parent_ref: Optional[weakref.ReferenceType[BsddClass]] = PrivateAttr(default=None)
|
||||
|
||||
def _set_parent(self, parent: BsddClass) -> None:
|
||||
self._parent_ref = weakref.ref(parent)
|
||||
|
||||
def parent(self) -> Optional[BsddClass]:
|
||||
return self._parent_ref() if self._parent_ref is not None else None
|
||||
|
||||
@model_validator(mode="after")
|
||||
def _validate_property_code_or_uri(self):
|
||||
"""
|
||||
only one of PropertyCode or PropertyUri must be set (XOR)
|
||||
"""
|
||||
# normalize whitespace
|
||||
code = self.PropertyCode.strip() if self.PropertyCode and isinstance(self.PropertyCode, str) else None
|
||||
uri = self.PropertyUri.strip() if self.PropertyUri and isinstance(self.PropertyUri, str) else None
|
||||
|
||||
# XOR: exactly one must be provided
|
||||
if bool(code) == bool(uri):
|
||||
raise ValueError("Exactly one of PropertyCode or PropertyUri must be provided (not both, not neither)")
|
||||
|
||||
# assign normalized values back
|
||||
object.__setattr__(self, "PropertyCode", code)
|
||||
object.__setattr__(self, "PropertyUri", uri)
|
||||
return self
|
||||
|
||||
|
||||
class BsddProperty(CaseInsensitiveModel):
|
||||
Code: str
|
||||
Name: str
|
||||
Definition: Optional[str] = None
|
||||
Description: Optional[str] = None
|
||||
DataType: Optional[DATATYPE_TYPE] = None
|
||||
Units: Optional[list[UNITS_TYPE]] = None
|
||||
Example: Optional[str] = None
|
||||
ActivationDateUtc: Optional[datetime] = None
|
||||
ConnectedPropertyCodes: Optional[list[str]] = None
|
||||
CountriesOfUse: Optional[list[COUNTRY_CODE]] = None
|
||||
CountryOfOrigin: Optional[COUNTRY_CODE] = None
|
||||
CreatorLanguageIsoCode: Optional[LANGUAGE_ISO_CODE] = None
|
||||
DeActivationDateUtc: Optional[datetime] = None
|
||||
DeprecationExplanation: Optional[str] = None
|
||||
Dimension: Optional[str] = None
|
||||
DimensionLength: Optional[int] = None
|
||||
DimensionMass: Optional[int] = None
|
||||
DimensionTime: Optional[int] = None
|
||||
DimensionElectricCurrent: Optional[int] = None
|
||||
DimensionThermodynamicTemperature: Optional[int] = None
|
||||
DimensionAmountOfSubstance: Optional[int] = None
|
||||
DimensionLuminousIntensity: Optional[int] = None
|
||||
DocumentReference: Optional[DOCUMENT_TYPE] = None
|
||||
DynamicParameterPropertyCodes: Optional[list[str]] = None
|
||||
IsDynamic: Optional[bool] = None
|
||||
MaxExclusive: Optional[float] = None
|
||||
MaxInclusive: Optional[float] = None
|
||||
MinExclusive: Optional[float] = None
|
||||
MinInclusive: Optional[float] = None
|
||||
MethodOfMeasurement: Optional[str] = None
|
||||
OwnedUri: Optional[str] = None
|
||||
Pattern: Optional[str] = None
|
||||
PhysicalQuantity: Optional[str] = None
|
||||
PropertyValueKind: Optional[PROPERTY_VALUE_KIND_TYPE] = None
|
||||
ReplacedObjectCodes: Optional[list[str]] = None
|
||||
ReplacingObjectCodes: Optional[list[str]] = None
|
||||
RevisionDateUtc: Optional[datetime] = None
|
||||
RevisionNumber: Optional[int] = None
|
||||
Status: Optional[PROPERTY_STATUS] = None
|
||||
SubdivisionsOfUse: Optional[list[str]] = None
|
||||
TextFormat: Optional[str] = None
|
||||
Uid: Optional[str] = None
|
||||
VersionDateUtc: Optional[datetime] = None
|
||||
VersionNumber: Optional[int] = None
|
||||
VisualRepresentationUri: Optional[str] = None
|
||||
PropertyRelations: list[BsddPropertyRelation] = Field(default_factory=list)
|
||||
AllowedValues: list[BsddAllowedValue] = Field(default_factory=list)
|
||||
_parent_ref: Optional[weakref.ReferenceType[BsddDictionary]] = PrivateAttr(default=None)
|
||||
|
||||
def _set_parent(self, parent: BsddDictionary) -> None:
|
||||
self._parent_ref = weakref.ref(parent)
|
||||
|
||||
def parent(self) -> Optional[BsddDictionary]:
|
||||
return self._parent_ref() if self._parent_ref is not None else None
|
||||
|
||||
def model_post_init(self, context):
|
||||
for pr in self.PropertyRelations:
|
||||
pr._set_parent(self)
|
||||
|
||||
|
||||
class BsddPropertyRelation(CaseInsensitiveModel):
|
||||
RelatedPropertyName: Optional[str] = None
|
||||
RelatedPropertyUri: str
|
||||
RelationType: PROPERTY_RELATION_TYPE
|
||||
OwnedUri: Optional[str] = None
|
||||
|
||||
def _set_parent(self, parent: BsddProperty) -> None:
|
||||
self._parent_ref = weakref.ref(parent)
|
||||
|
||||
def parent(self) -> Optional[BsddProperty]:
|
||||
return self._parent_ref() if self._parent_ref is not None else None
|
||||
@@ -40,4 +40,4 @@ Documentation = "https://docs.ifcopenshell.org"
|
||||
Issues = "https://github.com/IfcOpenShell/IfcOpenShell/issues"
|
||||
|
||||
[tool.setuptools]
|
||||
py-modules = ["bsdd"]
|
||||
py-modules = ["bsdd","bsdd_json","type_hints"]
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -36,6 +36,7 @@ from typing_extensions import NotRequired
|
||||
class CsvHeader(TypedDict):
|
||||
Index: int
|
||||
Name: int
|
||||
Description: NotRequired[str]
|
||||
Unit: int
|
||||
Identification: NotRequired[int]
|
||||
Value: NotRequired[int]
|
||||
@@ -80,6 +81,7 @@ class CostItem(TypedDict):
|
||||
|
||||
Identification: Union[str, None]
|
||||
Name: Union[str, None]
|
||||
Description: Union[str, None]
|
||||
Unit: Union[str, None]
|
||||
CostValues: Union[dict[str, float], float, None]
|
||||
|
||||
@@ -183,15 +185,6 @@ class Csv2Ifc:
|
||||
available_fields = set(self.headers.keys())
|
||||
missing_fields = mandatory_fields - available_fields
|
||||
|
||||
# TODO: 25-04-17 Deprecated 'Description' argument, should fully remove later.
|
||||
if missing_fields and "Name" in missing_fields and "Description" in available_fields:
|
||||
print(
|
||||
"WARNING. 'Description' column is deprecated and should be renamed to 'Name'. It will be deprecated soon completely."
|
||||
)
|
||||
self.headers["Name"] = self.headers["Description"]
|
||||
del self.headers["Description"]
|
||||
missing_fields.remove("Name")
|
||||
|
||||
if missing_fields:
|
||||
raise Exception(f"Missing mandatory fields in CSV header: {', '.join(missing_fields)}")
|
||||
|
||||
@@ -212,6 +205,7 @@ class Csv2Ifc:
|
||||
|
||||
def get_row_cost_data(self, row: list[str]) -> CostItem:
|
||||
name = row[self.headers["Name"]]
|
||||
description = row[self.headers["Description"]] if "Description" in self.headers else None
|
||||
identification = row[self.headers["Identification"]] if "Identification" in self.headers else None
|
||||
quantity = row[(self.headers["Quantity"])] if "Quantity" in self.headers else None
|
||||
unit = row[self.headers["Unit"]]
|
||||
@@ -241,6 +235,7 @@ class Csv2Ifc:
|
||||
return {
|
||||
"Identification": str(identification) if identification else None,
|
||||
"Name": str(name) if name else None,
|
||||
"Description": str(description) if description else None,
|
||||
"Unit": str(unit) if unit else None,
|
||||
"CostValues": cost_values,
|
||||
"Quantity": float(quantity) if quantity else None,
|
||||
@@ -276,6 +271,7 @@ class Csv2Ifc:
|
||||
cost_item["ifc"] = ifcopenshell.api.cost.add_cost_item(self.file, cost_item=parent)
|
||||
|
||||
cost_item["ifc"].Name = cost_item["Name"]
|
||||
cost_item["ifc"].Description = cost_item["Description"]
|
||||
cost_item["ifc"].Identification = cost_item["Identification"]
|
||||
|
||||
cost_values = cost_item["CostValues"]
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
#include "../ifcgeom/IfcGeomFilter.h"
|
||||
#include "../ifcgeom/Iterator.h"
|
||||
#include "../ifcgeom/IfcGeomRenderStyles.h"
|
||||
#include "../ifcgeom/hybrid_kernel.h"
|
||||
|
||||
#include "../ifcparse/utils.h"
|
||||
|
||||
@@ -1018,7 +1019,7 @@ int main(int argc, char** argv) {
|
||||
if (is_tesselated && (center_model || center_model_geometry)) {
|
||||
std::vector<double> offset(3);
|
||||
|
||||
IfcGeom::Iterator tmp_context_iterator(geometry_kernel, geometry_settings, ifc_file, filter_funcs, num_threads);
|
||||
IfcGeom::Iterator tmp_context_iterator(ifcopenshell::geometry::kernels::construct(ifc_file, geometry_kernel, geometry_settings), geometry_settings, ifc_file, filter_funcs, num_threads);
|
||||
|
||||
time_t start, end;
|
||||
time(&start);
|
||||
@@ -1064,7 +1065,7 @@ int main(int argc, char** argv) {
|
||||
|
||||
std::unique_ptr<IfcGeom::Iterator> context_iterator;
|
||||
if (!elems_from_adaptor) {
|
||||
context_iterator.reset(new IfcGeom::Iterator(geometry_kernel, geometry_settings, ifc_file, filter_funcs, num_threads));
|
||||
context_iterator.reset(new IfcGeom::Iterator(ifcopenshell::geometry::kernels::construct(ifc_file, geometry_kernel, geometry_settings), geometry_settings, ifc_file, filter_funcs, num_threads));
|
||||
}
|
||||
|
||||
#if defined(WITH_HDF5) && defined(IFOPSH_WITH_OPENCASCADE)
|
||||
@@ -1639,7 +1640,7 @@ void fix_quantities(IfcParse::IfcFile& f, bool no_progress, bool quiet, bool std
|
||||
settings.get<ifcopenshell::geometry::settings::ConvertBackUnits>().value = true;
|
||||
settings.get<ifcopenshell::geometry::settings::IteratorOutput>().value = ifcopenshell::geometry::settings::NATIVE;
|
||||
|
||||
IfcGeom::Iterator context_iterator(settings, &f, {}, 1);
|
||||
IfcGeom::Iterator context_iterator(ifcopenshell::geometry::kernels::construct(&f, "opencascade", settings), settings, &f, {}, 1);
|
||||
|
||||
if (!context_iterator.initialize()) {
|
||||
return;
|
||||
@@ -1663,7 +1664,7 @@ void fix_quantities(IfcParse::IfcFile& f, bool no_progress, bool quiet, bool std
|
||||
latebound_access::set(application, "ApplicationDeveloper", org);
|
||||
latebound_access::set(application, "Version", std::string(IFCOPENSHELL_VERSION));
|
||||
latebound_access::set(application, "ApplicationFullName", std::string("IfcConvert"));
|
||||
latebound_access::set(application, "ApplicationIdentifier", std::string("IfcConvert" IFCOPENSHELL_VERSION));
|
||||
latebound_access::set(application, "ApplicationIdentifier", std::string("IfcConvert") + IFCOPENSHELL_VERSION);
|
||||
|
||||
auto ownerhist = latebound_access::create(f, "IfcOwnerHistory");
|
||||
latebound_access::set(ownerhist, "OwningUser", pando);
|
||||
|
||||
@@ -1,26 +1,20 @@
|
||||
#include "AbstractKernel.h"
|
||||
|
||||
#include "../ifcgeom/IfcGeomElement.h"
|
||||
#include "../ifcgeom/ConversionSettings.h"
|
||||
#include "../ifcgeom/abstract_mapping.h"
|
||||
#include "../ifcgeom/function_item_evaluator.h"
|
||||
|
||||
#ifdef IFOPSH_WITH_OPENCASCADE
|
||||
#include "../ifcgeom/kernels/opencascade/OpenCascadeKernel.h"
|
||||
#undef Handle
|
||||
#endif
|
||||
|
||||
#ifdef IFOPSH_WITH_CGAL
|
||||
#include "../ifcgeom/kernels/cgal/CgalKernel.h"
|
||||
#undef CGAL_KERNEL_H
|
||||
#undef CGALCONVERSIONRESULT_H
|
||||
#define IFOPSH_SIMPLE_KERNEL
|
||||
#include "../ifcgeom/kernels/cgal/CgalKernel.h"
|
||||
#undef CgalKernel
|
||||
#endif
|
||||
#include "AbstractKernel.h"
|
||||
|
||||
using namespace ifcopenshell::geometry;
|
||||
|
||||
const char* ifcopenshell::not_implemented_error::what() const noexcept {
|
||||
return "Not implemented.";
|
||||
}
|
||||
|
||||
const char* ifcopenshell::not_supported_error::what() const noexcept {
|
||||
return "Not supported.";
|
||||
}
|
||||
|
||||
bool ifcopenshell::geometry::kernels::AbstractKernel::convert(const taxonomy::ptr item, IfcGeom::ConversionResults& results) {
|
||||
if (settings_.get<settings::CacheShapes>().get()) {
|
||||
auto it = cache_.find(item);
|
||||
@@ -73,173 +67,6 @@ const Settings& ifcopenshell::geometry::kernels::AbstractKernel::settings() cons
|
||||
return settings_;
|
||||
}
|
||||
|
||||
|
||||
bool is_valid_for_kernel(const ifcopenshell::geometry::kernels::AbstractKernel* k, const IfcGeom::ConversionResult& shp) {
|
||||
#ifdef IFOPSH_WITH_OPENCASCADE
|
||||
if (k->geometry_library() == "opencascade") {
|
||||
return dynamic_cast<ifcopenshell::geometry::OpenCascadeShape*>(shp.Shape().get()) != nullptr;
|
||||
}
|
||||
#endif
|
||||
#ifdef IFOPSH_WITH_CGAL
|
||||
if (k->geometry_library() == "cgal-simple") {
|
||||
return dynamic_cast<ifcopenshell::geometry::SimpleCgalShape*>(shp.Shape().get()) != nullptr;
|
||||
}
|
||||
if (k->geometry_library() == "cgal") {
|
||||
return dynamic_cast<ifcopenshell::geometry::CgalShape*>(shp.Shape().get()) != nullptr;
|
||||
}
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
class HybridKernel : public ifcopenshell::geometry::kernels::AbstractKernel {
|
||||
std::vector<std::unique_ptr<AbstractKernel>> kernels_;
|
||||
ifcopenshell::geometry::abstract_mapping* mapping_;
|
||||
public:
|
||||
HybridKernel(const std::string& name, IfcParse::IfcFile* file, Settings& settings, std::vector<std::unique_ptr<AbstractKernel>>&& kernels)
|
||||
: AbstractKernel(name, settings)
|
||||
, kernels_(std::move(kernels))
|
||||
, mapping_(ifcopenshell::geometry::impl::mapping_implementations().construct(file, settings))
|
||||
{}
|
||||
virtual bool convert(const taxonomy::ptr item, IfcGeom::ConversionResults& rs) {
|
||||
auto ops = mapping_->find_openings(item->instance->as<IfcUtil::IfcBaseEntity>());
|
||||
bool has_openings = ops && ops->size();
|
||||
for (auto& k : kernels_) {
|
||||
#ifdef IFOPSH_WITH_CGAL
|
||||
if (has_openings && dynamic_cast<ifcopenshell::geometry::kernels::SimpleCgalKernel*>(k.get())) {
|
||||
// @todo this would fail later on in the find_openings() call, because we have a
|
||||
// SimpleCgalShape which cannot be used on a kernel that supports booleans.
|
||||
// @todo 1 implement the translation between various conversion result shapes
|
||||
// @todo 2 fold the boolean result openings into the taxonomy item. This should be possible
|
||||
// now that we have shared_ptr<item> and caching in place. So the inability
|
||||
// to instance wouldn't matter as much.
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
bool success = false;
|
||||
try {
|
||||
success = k->convert(item, rs);
|
||||
} catch(...) {}
|
||||
if (success) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
virtual bool apply_layerset(IfcGeom::ConversionResults& items, const ifcopenshell::geometry::layerset_information& layers) {
|
||||
for (auto& k : kernels_) {
|
||||
bool success = false;
|
||||
try {
|
||||
success = k->apply_layerset(items, layers);
|
||||
} catch (...) {}
|
||||
if (success) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
virtual bool apply_folded_layerset(IfcGeom::ConversionResults& items, const ifcopenshell::geometry::layerset_information& layers, const std::map<IfcUtil::IfcBaseEntity*, ifcopenshell::geometry::layerset_information>& folds) {
|
||||
for (auto& k : kernels_) {
|
||||
bool success = false;
|
||||
try {
|
||||
success = k->apply_folded_layerset(items, layers, folds);
|
||||
} catch (...) {}
|
||||
if (success) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
virtual bool convert_openings(const IfcUtil::IfcBaseEntity* entity, const std::vector<std::pair<taxonomy::ptr, ifcopenshell::geometry::taxonomy::matrix4>>& openings,
|
||||
const IfcGeom::ConversionResults& entity_shapes, const ifcopenshell::geometry::taxonomy::matrix4& entity_trsf, IfcGeom::ConversionResults& cut_shapes)
|
||||
{
|
||||
for (auto& k : kernels_) {
|
||||
bool is_valid = true;
|
||||
for (auto& s : entity_shapes) {
|
||||
if (!is_valid_for_kernel(k.get(), s)) {
|
||||
is_valid = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!is_valid) {
|
||||
continue;
|
||||
}
|
||||
bool success = false;
|
||||
try {
|
||||
success = k->convert_openings(entity, openings, entity_shapes, entity_trsf, cut_shapes);
|
||||
} catch (...) {}
|
||||
if (success) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
ifcopenshell::geometry::kernels::AbstractKernel* ifcopenshell::geometry::kernels::construct(IfcParse::IfcFile* file, const std::string& geometry_library, Settings& conv_settings) {
|
||||
std::string geometry_library_lower = boost::to_lower_copy(geometry_library);
|
||||
|
||||
#ifdef IFOPSH_WITH_OPENCASCADE
|
||||
if (geometry_library_lower == "opencascade") {
|
||||
return new IfcGeom::OpenCascadeKernel(conv_settings);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef IFOPSH_WITH_CGAL
|
||||
if (geometry_library_lower == "cgal") {
|
||||
return new CgalKernel(conv_settings);
|
||||
}
|
||||
|
||||
if (geometry_library_lower == "cgal-simple") {
|
||||
return new SimpleCgalKernel(conv_settings);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (geometry_library_lower.rfind("hybrid-", 0) == 0) {
|
||||
geometry_library_lower = geometry_library_lower.substr(strlen("hybrid"));
|
||||
std::vector<std::unique_ptr<AbstractKernel>> kernels;
|
||||
while (!geometry_library_lower.empty()) {
|
||||
if (geometry_library_lower.find("-", 0) == 0) {
|
||||
geometry_library_lower = geometry_library_lower.substr(strlen("-"));
|
||||
} else {
|
||||
throw IfcParse::IfcException("Invalid hybrid kernel " + geometry_library);
|
||||
}
|
||||
auto n = kernels.size();
|
||||
#ifdef IFOPSH_WITH_OPENCASCADE
|
||||
if (geometry_library_lower.find("opencascade", 0) == 0) {
|
||||
kernels.emplace_back(new IfcGeom::OpenCascadeKernel(conv_settings));
|
||||
geometry_library_lower = geometry_library_lower.substr(strlen("opencascade"));
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef IFOPSH_WITH_CGAL
|
||||
if (geometry_library_lower.find("cgal-simple", 0) == 0) {
|
||||
kernels.emplace_back(new SimpleCgalKernel(conv_settings));
|
||||
geometry_library_lower = geometry_library_lower.substr(strlen("cgal-simple"));
|
||||
}
|
||||
|
||||
if (geometry_library_lower.find("cgal", 0) == 0) {
|
||||
kernels.emplace_back(new CgalKernel(conv_settings));
|
||||
geometry_library_lower = geometry_library_lower.substr(strlen("cgal"));
|
||||
}
|
||||
#endif
|
||||
if (kernels.size() != n + 1) {
|
||||
throw IfcParse::IfcException("Invalid hybrid kernel " + geometry_library);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto it = kernels.begin(); it != kernels.end(); ++it) {
|
||||
(**it).propagate_exceptions = it == kernels.begin();
|
||||
(**it).partial_success_is_success = it == kernels.end() - 1;
|
||||
}
|
||||
|
||||
if (!kernels.empty()) {
|
||||
return new HybridKernel(geometry_library, file, conv_settings, std::move(kernels));
|
||||
}
|
||||
}
|
||||
|
||||
throw IfcParse::IfcException("No geometry kernel registered for " + geometry_library);
|
||||
}
|
||||
|
||||
bool ifcopenshell::geometry::kernels::AbstractKernel::convert_impl(const taxonomy::collection::ptr collection, IfcGeom::ConversionResults& r) {
|
||||
auto s = r.size();
|
||||
for (auto& c : collection->children) {
|
||||
|
||||
@@ -1,3 +1,22 @@
|
||||
/********************************************************************************
|
||||
* *
|
||||
* 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/>. *
|
||||
* *
|
||||
********************************************************************************/
|
||||
|
||||
#ifndef ABSTRACT_KERNEL_H
|
||||
#define ABSTRACT_KERNEL_H
|
||||
|
||||
@@ -7,6 +26,7 @@
|
||||
#include "../ifcgeom/IfcGeomRepresentation.h"
|
||||
#include "../ifcgeom/taxonomy.h"
|
||||
#include "../ifcgeom/ConversionSettings.h"
|
||||
#include "../ifcgeom/abstract_mapping.h"
|
||||
|
||||
static const double ALMOST_ZERO = 1.e-9;
|
||||
|
||||
@@ -16,21 +36,26 @@ inline static bool ALMOST_THE_SAME(const T& a, const T& b, double tolerance = AL
|
||||
}
|
||||
|
||||
namespace ifcopenshell {
|
||||
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable: 4275)
|
||||
#endif
|
||||
|
||||
class IFC_GEOM_API not_implemented_error : public std::exception {
|
||||
public:
|
||||
const char* what() const noexcept override {
|
||||
return "Not implemented.";
|
||||
}
|
||||
const char* what() const noexcept override;
|
||||
};
|
||||
|
||||
class IFC_GEOM_API not_supported_error : public std::exception {
|
||||
public:
|
||||
const char* what() const noexcept override {
|
||||
return "Not supported.";
|
||||
}
|
||||
const char* what() const noexcept override;
|
||||
};
|
||||
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
|
||||
namespace geometry { namespace kernels {
|
||||
|
||||
class IFC_GEOM_API AbstractKernel {
|
||||
@@ -55,6 +80,8 @@ namespace ifcopenshell {
|
||||
return geometry_library_;
|
||||
}
|
||||
|
||||
virtual bool supports_boolean_operations() const = 0;
|
||||
|
||||
virtual bool convert_impl(const taxonomy::matrix4::ptr, IfcGeom::ConversionResults&) { throw not_implemented_error(); }
|
||||
virtual bool convert_impl(const taxonomy::point3::ptr, IfcGeom::ConversionResults&) { throw not_implemented_error(); }
|
||||
virtual bool convert_impl(const taxonomy::direction3::ptr, IfcGeom::ConversionResults&) { throw not_implemented_error(); }
|
||||
@@ -98,10 +125,9 @@ namespace ifcopenshell {
|
||||
virtual bool convert_openings(const IfcUtil::IfcBaseEntity* entity, const std::vector<std::pair<taxonomy::ptr, ifcopenshell::geometry::taxonomy::matrix4>>& openings,
|
||||
const IfcGeom::ConversionResults& entity_shapes, const ifcopenshell::geometry::taxonomy::matrix4& entity_trsf, IfcGeom::ConversionResults& cut_shapes) = 0;
|
||||
virtual bool unify_shapes(const IfcGeom::ConversionResults&, IfcGeom::ConversionResults&) { throw not_implemented_error(); }
|
||||
|
||||
virtual AbstractKernel* clone() const = 0;
|
||||
};
|
||||
|
||||
AbstractKernel* construct(IfcParse::IfcFile* file, const std::string& geometry_library, Settings& conv_settings);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,3 +26,6 @@ std::string IfcGeom::NumberNativeDouble::to_string() const {
|
||||
ss << std::setprecision(std::numeric_limits<double>::digits10 + 1) << value_;
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
template struct IFC_GEOM_API IfcGeom::OpaqueCoordinate<3>;
|
||||
template struct IFC_GEOM_API IfcGeom::OpaqueCoordinate<4>;
|
||||
@@ -282,6 +282,7 @@ namespace IfcGeom {
|
||||
virtual ConversionResultShape* halfspaces() = 0;
|
||||
virtual ConversionResultShape* box() = 0;
|
||||
virtual ConversionResultShape* solid() = 0;
|
||||
virtual ConversionResultShape* wrap_in_compound() = 0;
|
||||
|
||||
virtual std::vector<ConversionResultShape*> vertices() = 0;
|
||||
virtual std::vector<ConversionResultShape*> edges() = 0;
|
||||
@@ -290,10 +291,13 @@ namespace IfcGeom {
|
||||
virtual ConversionResultShape* add(ConversionResultShape*) = 0;
|
||||
virtual ConversionResultShape* subtract(ConversionResultShape*) = 0;
|
||||
virtual ConversionResultShape* intersect(ConversionResultShape*) = 0;
|
||||
virtual ConversionResultShape* concat(ConversionResultShape*) = 0;
|
||||
|
||||
virtual void map(OpaqueCoordinate<4>& from, OpaqueCoordinate<4>& to) = 0;
|
||||
virtual void map(const std::vector<OpaqueCoordinate<4>>& from, const std::vector<OpaqueCoordinate<4>>& to) = 0;
|
||||
virtual ConversionResultShape* moved(ifcopenshell::geometry::taxonomy::matrix4::ptr) const = 0;
|
||||
|
||||
virtual bool surface_area_along_direction(double tol, const ifcopenshell::geometry::taxonomy::matrix4::ptr&, double& along_x, double& along_y, double& along_z) const = 0;
|
||||
|
||||
virtual ~ConversionResultShape() {}
|
||||
|
||||
@@ -327,6 +331,15 @@ namespace IfcGeom {
|
||||
ifcopenshell::geometry::taxonomy::style::ptr StylePtr() const { return style_; }
|
||||
void setStyle(ifcopenshell::geometry::taxonomy::style::ptr newStyle) { style_ = newStyle; }
|
||||
int ItemId() const { return id; }
|
||||
ConversionResultShape* apply_transform(double unit_scale = 1.) const {
|
||||
if (unit_scale != 1.) {
|
||||
auto m = ifcopenshell::geometry::taxonomy::matrix4::ptr(placement_->clone_());
|
||||
m->pre_multiply_scale(unit_scale);
|
||||
return shape_->moved(m);
|
||||
} else {
|
||||
return shape_->moved(placement_);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
typedef std::vector<ConversionResult> ConversionResults;
|
||||
|
||||
@@ -94,3 +94,7 @@ std::istream& ifcopenshell::geometry::settings::operator>>(std::istream& in, Tri
|
||||
}
|
||||
return in;
|
||||
}
|
||||
|
||||
IfcGeom::geometry_exception::~geometry_exception() = default;
|
||||
|
||||
IfcGeom::too_many_faces_exception::~too_many_faces_exception() = default;
|
||||
|
||||
@@ -22,9 +22,9 @@
|
||||
namespace po = boost::program_options;
|
||||
|
||||
namespace std {
|
||||
istream& operator>>(istream& in, set<int>& ints);
|
||||
istream& operator>>(istream& in, set<string>& ints);
|
||||
istream& operator>>(istream& in, vector<double>& vs);
|
||||
IFC_GEOM_API istream& operator>>(istream& in, set<int>& ints);
|
||||
IFC_GEOM_API istream& operator>>(istream& in, set<string>& ints);
|
||||
IFC_GEOM_API istream& operator>>(istream& in, vector<double>& vs);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -84,8 +84,9 @@ namespace ifcopenshell {
|
||||
}
|
||||
if constexpr (HasDefault<Derived>()) {
|
||||
return Derived::defaultvalue;
|
||||
} else {
|
||||
throw std::runtime_error("Setting not set");
|
||||
}
|
||||
throw std::runtime_error("Setting not set");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -249,7 +250,7 @@ namespace ifcopenshell {
|
||||
CURVES_SURFACES_AND_SOLIDS
|
||||
};
|
||||
|
||||
std::istream& operator>>(std::istream& in, OutputDimensionalityTypes& ioo);
|
||||
IFC_GEOM_API std::istream& operator>>(std::istream& in, OutputDimensionalityTypes& ioo);
|
||||
|
||||
struct OutputDimensionality : public SettingBase<OutputDimensionality, OutputDimensionalityTypes> {
|
||||
static constexpr const char* const name = "dimensionality";
|
||||
@@ -266,7 +267,7 @@ namespace ifcopenshell {
|
||||
SERIALIZED
|
||||
};
|
||||
|
||||
std::istream& operator>>(std::istream& in, IteratorOutputOptions& ioo);
|
||||
IFC_GEOM_API std::istream& operator>>(std::istream& in, IteratorOutputOptions& ioo);
|
||||
|
||||
struct IteratorOutput : public SettingBase<IteratorOutput, IteratorOutputOptions> {
|
||||
static constexpr const char* const name = "iterator-output";
|
||||
@@ -395,7 +396,7 @@ namespace ifcopenshell {
|
||||
MAXSTEPSIZE,
|
||||
MINSTEPS };
|
||||
|
||||
std::istream& operator>>(std::istream& in, FunctionStepMethod& ioo);
|
||||
IFC_GEOM_API std::istream& operator>>(std::istream& in, FunctionStepMethod& ioo);
|
||||
|
||||
struct FunctionStepType : public SettingBase<FunctionStepType, FunctionStepMethod> {
|
||||
static constexpr const char* const name = "function-step-type";
|
||||
@@ -425,7 +426,7 @@ namespace ifcopenshell {
|
||||
POLYHEDRON_WITH_HOLES
|
||||
};
|
||||
|
||||
std::istream& operator>>(std::istream& in, TriangulationMethod& ioo);
|
||||
IFC_GEOM_API std::istream& operator>>(std::istream& in, TriangulationMethod& ioo);
|
||||
|
||||
struct TriangulationType : public SettingBase<TriangulationType, TriangulationMethod> {
|
||||
static constexpr const char* const name = "triangulation-type";
|
||||
@@ -536,7 +537,7 @@ namespace ifcopenshell {
|
||||
}
|
||||
|
||||
template <typename settings_t>
|
||||
class IFC_GEOM_API SettingsContainer {
|
||||
class SettingsContainer {
|
||||
public:
|
||||
typedef boost::variant<bool, int, double, std::string, std::set<int>, std::set<std::string>, std::vector<double>, IteratorOutputOptions, FunctionStepMethod, OutputDimensionalityTypes, TriangulationMethod> value_variant_t;
|
||||
private:
|
||||
@@ -645,7 +646,7 @@ namespace ifcopenshell {
|
||||
}
|
||||
};
|
||||
|
||||
class IFC_GEOM_API Settings : public SettingsContainer<
|
||||
class Settings : public SettingsContainer<
|
||||
std::tuple<MesherLinearDeflection, MesherAngularDeflection, ReorientShells, LengthUnit, PlaneUnit, Precision, OutputDimensionality, LayersetFirst, DisableBooleanResult, NoWireIntersectionCheck, NoWireIntersectionTolerance, PrecisionFactor, DebugBooleanOperations, BooleanAttempt2d, SurfaceColour, WeldVertices, UseWorldCoords, UnifyShapes, UseMaterialNames, ConvertBackUnits, ContextIds, ContextTypes, ContextIdentifiers, IteratorOutput, DisableOpeningSubtractions, ApplyDefaultMaterials, DontEmitNormals, GenerateUvs, ApplyLayerSets, UseElementHierarchy, ValidateQuantities, EdgeArrows, BuildingLocalPlacement, SiteLocalPlacement, ForceSpaceTransparency, CircleSegments, CgalSmoothAngleDegrees, KeepBoundingBoxes, ComputeCurvature, FunctionStepType, FunctionStepParam, NoParallelMapping, PermissiveShapeReuse, ModelOffset, ModelRotation, TriangulationType, CgalEmitOriginalEdges, OcctNoCleanTriangulation, CacheShapes, DeferProcessingFirstElement, MaxOffset, MaxOffsetDeviation, ApplyOffset>
|
||||
>
|
||||
{};
|
||||
@@ -654,22 +655,32 @@ namespace ifcopenshell {
|
||||
|
||||
// @todo find a place
|
||||
namespace IfcGeom {
|
||||
class IFC_GEOM_API geometry_exception : public std::exception {
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable: 4275)
|
||||
#endif
|
||||
|
||||
class IFC_GEOM_API geometry_exception : public std::runtime_error {
|
||||
protected:
|
||||
std::string message;
|
||||
public:
|
||||
geometry_exception(const std::string& m)
|
||||
: message(m) {}
|
||||
virtual ~geometry_exception() throw () {}
|
||||
virtual const char* what() const throw() {
|
||||
return message.c_str();
|
||||
}
|
||||
: std::runtime_error(m)
|
||||
{}
|
||||
~geometry_exception() override;
|
||||
};
|
||||
|
||||
class IFC_GEOM_API too_many_faces_exception : public geometry_exception {
|
||||
public:
|
||||
too_many_faces_exception()
|
||||
: geometry_exception("Too many faces for operation") {}
|
||||
~too_many_faces_exception() override;
|
||||
};
|
||||
}
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
+2
-104
@@ -4,20 +4,16 @@
|
||||
|
||||
using namespace ifcopenshell::geometry;
|
||||
|
||||
ifcopenshell::geometry::Converter::Converter(const std::string& geometry_library, IfcParse::IfcFile* file, ifcopenshell::geometry::Settings& s)
|
||||
: geometry_library_(boost::to_lower_copy(geometry_library))
|
||||
ifcopenshell::geometry::Converter::Converter(std::unique_ptr<ifcopenshell::geometry::kernels::AbstractKernel>&& geometry_library, IfcParse::IfcFile* file, ifcopenshell::geometry::Settings& s)
|
||||
: kernel_(std::move(geometry_library))
|
||||
{
|
||||
mapping_ = impl::mapping_implementations().construct(file, s);
|
||||
kernel_ = kernels::construct(file, geometry_library, mapping_->settings());
|
||||
// Mapping reads unit information and applies to settings
|
||||
settings_ = mapping_->settings();
|
||||
}
|
||||
|
||||
ifcopenshell::geometry::Converter::~Converter()
|
||||
{
|
||||
if (kernel_ != nullptr) {
|
||||
delete kernel_;
|
||||
}
|
||||
if (mapping_ != nullptr) {
|
||||
delete mapping_;
|
||||
}
|
||||
@@ -414,101 +410,3 @@ IfcGeom::ConversionResults ifcopenshell::geometry::Converter::convert(IfcUtil::I
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
//#include "../../ifcparse/Ifc2x3.h"
|
||||
//#include "../../ifcparse/Ifc4.h"
|
||||
//
|
||||
//// @todo remove
|
||||
//#include "../../ifcgeom/schema_agnostic/opencascade/OpenCascadeConversionResult.h"
|
||||
//
|
||||
//#include <TopExp.hxx>
|
||||
//#include <TopTools_ListOfShape.hxx>
|
||||
//#include <TopTools_IndexedMapOfShape.hxx>
|
||||
//#include <TopTools_IndexedDataMapOfShapeListOfShape.hxx>
|
||||
//
|
||||
//IfcGeom::Kernel::Kernel(const std::string& geometry_library, IfcParse::IfcFile* file) {
|
||||
// if (file != 0) {
|
||||
// if (file->schema() == 0) {
|
||||
// throw IfcParse::IfcException("No schema associated with file");
|
||||
// }
|
||||
//
|
||||
// const std::string& schema_name = file->schema()->name();
|
||||
// implementation_ = impl::kernel_implementations().construct(schema_name, geometry_library, file);
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//int IfcGeom::Kernel::count(const ConversionResultShape* s_, int t_, bool unique) {
|
||||
// // @todo make kernel agnostic
|
||||
// const TopoDS_Shape& s = ((OpenCascadeShape*) s_)->shape();
|
||||
// TopAbs_ShapeEnum t = (TopAbs_ShapeEnum) t_;
|
||||
//
|
||||
// if (unique) {
|
||||
// TopTools_IndexedMapOfShape map;
|
||||
// TopExp::MapShapes(s, t, map);
|
||||
// return map.Extent();
|
||||
// } else {
|
||||
// int i = 0;
|
||||
// TopExp_Explorer exp(s, t);
|
||||
// for (; exp.More(); exp.Next()) {
|
||||
// ++i;
|
||||
// }
|
||||
// return i;
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//
|
||||
//int IfcGeom::Kernel::surface_genus(const ConversionResultShape* s_) {
|
||||
// // @todo make kernel agnostic
|
||||
// const TopoDS_Shape& s = ((OpenCascadeShape*) s_)->shape();
|
||||
// OpenCascadeShape Ss(s);
|
||||
//
|
||||
// int nv = count(&Ss, (int) TopAbs_VERTEX, true);
|
||||
// int ne = count(&Ss, (int) TopAbs_EDGE, true);
|
||||
// int nf = count(&Ss, (int) TopAbs_FACE, true);
|
||||
//
|
||||
// const int euler = nv - ne + nf;
|
||||
// const int genus = (2 - euler) / 2;
|
||||
//
|
||||
// return genus;
|
||||
//}
|
||||
//
|
||||
//
|
||||
//IfcUtil::IfcBaseEntity* IfcGeom::Kernel::get_decomposing_entity(IfcUtil::IfcBaseEntity* inst, bool include_openings) {
|
||||
// if (inst->as<Ifc2x3::IfcProduct>()) {
|
||||
// return get_decomposing_entity_impl(inst->as<Ifc2x3::IfcProduct>(), include_openings);
|
||||
// } else if (inst->as<Ifc4::IfcProduct>()) {
|
||||
// return get_decomposing_entity_impl(inst->as<Ifc4::IfcProduct>(), include_openings);
|
||||
// } else if (inst->declaration().name() == "IfcProject") {
|
||||
// return nullptr;
|
||||
// } else {
|
||||
// throw IfcParse::IfcException("Unexpected entity " + inst->declaration().name());
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//
|
||||
//bool IfcGeom::Kernel::is_manifold(const ConversionResultShape* s_) {
|
||||
// // @todo make kernel agnostic
|
||||
// const TopoDS_Shape& a = ((OpenCascadeShape*) s_)->shape();
|
||||
//
|
||||
// if (a.ShapeType() == TopAbs_COMPOUND || a.ShapeType() == TopAbs_SOLID) {
|
||||
// TopoDS_Iterator it(a);
|
||||
// for (; it.More(); it.Next()) {
|
||||
// OpenCascadeShape s(it.Value());
|
||||
// if (!is_manifold(&s)) {
|
||||
// return false;
|
||||
// }
|
||||
// }
|
||||
// return true;
|
||||
// } else {
|
||||
// TopTools_IndexedDataMapOfShapeListOfShape map;
|
||||
// TopExp::MapShapesAndAncestors(a, TopAbs_EDGE, TopAbs_FACE, map);
|
||||
//
|
||||
// for (int i = 1; i <= map.Extent(); ++i) {
|
||||
// if (map.FindFromIndex(i).Extent() != 2) {
|
||||
// return false;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// return true;
|
||||
// }
|
||||
//}
|
||||
|
||||
@@ -13,20 +13,19 @@
|
||||
|
||||
namespace ifcopenshell { namespace geometry {
|
||||
|
||||
class Converter {
|
||||
class IFC_GEOM_API Converter {
|
||||
public:
|
||||
typedef boost::shared_ptr<IfcGeom::Representation::BRep> brep_ptr;
|
||||
private:
|
||||
std::string geometry_library_;
|
||||
ifcopenshell::geometry::abstract_mapping* mapping_;
|
||||
ifcopenshell::geometry::kernels::AbstractKernel* kernel_;
|
||||
std::unique_ptr<ifcopenshell::geometry::kernels::AbstractKernel> kernel_;
|
||||
ifcopenshell::geometry::Settings settings_;
|
||||
std::map<ifcopenshell::geometry::taxonomy::ptr, brep_ptr, ifcopenshell::geometry::taxonomy::less_functor> cache_;
|
||||
|
||||
public:
|
||||
ifcopenshell::geometry::kernels::AbstractKernel* kernel() { return kernel_; }
|
||||
ifcopenshell::geometry::kernels::AbstractKernel* kernel() { return &*kernel_; }
|
||||
|
||||
Converter(const std::string& geometry_library, IfcParse::IfcFile* file, ifcopenshell::geometry::Settings& settings);
|
||||
Converter(std::unique_ptr<ifcopenshell::geometry::kernels::AbstractKernel>&& geometry_library, IfcParse::IfcFile* file, ifcopenshell::geometry::Settings& settings);
|
||||
|
||||
~Converter();
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user