Compare commits

..

3 Commits

Author SHA1 Message Date
Andrej730 f2129c81b7 build_rocky: set git identity before committing to build repo 2026-08-04 14:47:37 +05:00
Andrej730 34d76ef351 build_rocky: add install dir skeleton when creating shared branch 2026-08-04 14:46:20 +05:00
Andrej730 26aac488a2 create rocky-shared branch 2026-08-04 14:32:18 +05:00
608 changed files with 19891 additions and 16636 deletions
+1
View File
@@ -1,4 +1,5 @@
Checks: 'bugprone-*,cert-*,clang-analyzer-*,readability-*'
WarningsAsErrors: ''
HeaderFilterRegex: ''
AnalyzeTemporaryDtors: false
FormatStyle: none
+2 -4
View File
@@ -19,8 +19,6 @@
# normalize the line endings of the following files
*.bat text eol=crlf
*.cmd text eol=crlf
*.cpp text
*.css text
*.csv text
@@ -30,20 +28,20 @@
*.gitkeep
*.h text
*.html text
*.i text
*.ifc text
*.json text
*.md text
*.po text
*.pot text
*.py text
*.sh text eol=lf
*.txt text
# files not normalized ATM
# bat
# bnf
# blend
# i
# ico
# mo
# mpass
+5 -7
View File
@@ -35,9 +35,6 @@ jobs:
lfs: true
token: ${{ secrets.BUILD_REPO_TOKEN }}
- name: Set up uv
uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
- name: Install Dependencies
run: |
brew update
@@ -64,7 +61,7 @@ jobs:
- name: Unpack Dependencies
run: |
cd build
uv run ../nix/cache_dependencies.py unpack
python ../nix/cache_dependencies.py unpack
- name: ccache
uses: hendrikmuhs/ccache-action@v1.2.23
@@ -105,7 +102,7 @@ jobs:
# INSTALL_RPATH to "@loader_path" on Apple.
CXXFLAGS="-O3" CFLAGS="-O3 ${DARWIN_C_SOURCE}" ADD_COMMIT_SHA=1 BUILD_CFG=Release \
BUILD_BONSAIVIEWER=ON QT_DIR="${QT_DIR}" \
uv run ./nix/build-all.py -v --diskcleanup --ifcopenshell-shared ${MAC_INTEL} \
python3 ./nix/build-all.py -v --diskcleanup --shared ${MAC_INTEL} \
| tee build.log
- name: Upload Build Logs
@@ -122,7 +119,7 @@ jobs:
- name: Pack Dependencies
run: |
cd build
uv run ../nix/cache_dependencies.py pack
python ../nix/cache_dependencies.py pack
- name: Commit and Push Changes to Build Repository
run: |
@@ -139,7 +136,7 @@ jobs:
# packaging/build.py stages the connector binary + connector.json
# into dist/autodesk/; the .app loop below copies that folder into
# the bundle. Same on-disk shape as the Linux and Windows builds.
uv run src/bonsaiviewer-autodesk/packaging/build.py
python3 src/bonsaiviewer-autodesk/packaging/build.py
autodesk_connector_dir="$PWD/src/bonsaiviewer-autodesk/dist/autodesk"
test -d "$autodesk_connector_dir"
@@ -178,6 +175,7 @@ jobs:
popd > /dev/null
done
rm -f "$install_root"/bin/*.zip
find "$install_root/bin" -maxdepth 1 -type f -perm /111 ! -name "*.zip" ! -name "*.so" ! -name "*.so.*" ! -name "*.dylib" ! -name "*.dll" | while read exe_path; do
exe=`basename "$exe_path"`
package_dir="$install_root/.package-${exe}"
+27 -31
View File
@@ -8,13 +8,6 @@ jobs:
runs-on: ubuntu-22.04
steps:
- name: Set up uv
uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
- name: Install Python
# Installs latest Python version so it's preferred by uv over system Python.
run: uv python install
- name: Checkout Repository
uses: actions/checkout@v7
with:
@@ -33,7 +26,7 @@ jobs:
- name: Unpack Dependencies
run: |
cd ifcopenshell_build
uv run ../IfcOpenShell/nix/cache_dependencies.py unpack
python ../IfcOpenShell/nix/cache_dependencies.py unpack
- name: ccache
uses: hendrikmuhs/ccache-action@v1.2.23
@@ -47,6 +40,18 @@ jobs:
NEW_FILE=`echo $FILE | sed "s/-/+${GITHUB_SHA:0:7}-/2"`
mv $FILE $NEW_FILE
- name: Order wheel shared objects
run: |
python ./IfcOpenShell/pyodide/order_pyodide_wheel_shared_objects.py dist/ifcopenshell-*.whl
- name: Split packages
run: |
VERSION=v`cat ./IfcOpenShell/VERSION`
mkdir -p dist-modular
python ./IfcOpenShell/pyodide/split_pyodide_ifcopenshell_wheel.py dist/ifcopenshell-*.whl ./dist-modular
cd dist-modular
zip -r -qq ifcopenshell-modular-${VERSION}-${GITHUB_SHA:0:7}-pyodide.zip *.whl
- name: Upload Build Logs
if: always()
uses: actions/upload-artifact@v7
@@ -56,10 +61,23 @@ jobs:
ifcopenshell_build/*/*/logs/*.log
retention-days: 30
- name: Run wheel tests
run: |
cp -r IfcOpenShell/pyodide/test test
# venv set up in build_pyodide.sh.
source .venv/bin/activate
uv pip install pytest-pyodide
PYODIDE_ROOT_DIST=`pyodide config get pyodide_root`/dist
# `pytest-pyodide` requires pyodide in 'pyodide' directory in cwd, when running `pytest`.
cp -r $PYODIDE_ROOT_DIST test/pyodide
cp dist/ifcopenshell-*.whl test/pyodide
cd test
pytest --capture=no
- name: Pack Dependencies
run: |
cd ifcopenshell_build
uv run ../IfcOpenShell/nix/cache_dependencies.py pack
python ../IfcOpenShell/nix/cache_dependencies.py pack
- name: Commit and Push Changes to Build Repository
run: |
@@ -70,28 +88,6 @@ jobs:
git commit -m "Update build artifacts [skip ci]" || echo "No changes to commit"
git push || echo "Push failed"
- name: Order wheel shared objects
run: |
uv run ./IfcOpenShell/pyodide/order_pyodide_wheel_shared_objects.py dist/ifcopenshell-*.whl
- name: Split packages
run: |
VERSION=v`cat ./IfcOpenShell/VERSION`
mkdir -p dist-modular
uv run ./IfcOpenShell/pyodide/split_pyodide_ifcopenshell_wheel.py dist/ifcopenshell-*.whl ./dist-modular
cd dist-modular
zip -r -qq ifcopenshell-modular-${VERSION}-${GITHUB_SHA:0:7}-pyodide.zip *.whl
- name: Run wheel tests
run: |
# venv set up in build_pyodide.sh.
source .venv/bin/activate
ln -s "$PWD/dist" IfcOpenShell/dist
ln -s "$PWD/dist-modular" IfcOpenShell/dist-modular
cd IfcOpenShell/pyodide
./run_pytest.py setup
./run_pytest.py run
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v6
with:
+10 -268
View File
@@ -6,282 +6,24 @@ on:
jobs:
build_ifcopenshell:
runs-on: ubuntu-22.04
container: rockylinux:9
steps:
- name: Set up uv
uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
- name: Install Python
# Installs latest Python version so it's preferred by uv over Rocky's system Python.
run: uv python install
- name: Install Dependencies
run: |
dnf update -y
dnf install -y epel-release
# --enablerepo=crb: libstdc++-static (libsupc++.a, needed by the
# bundled FLTK link) lives in Rocky's CodeReady Builder repo, which
# is disabled by default.
dnf install -y --enablerepo=crb gcc gcc-c++ git autoconf automake bison make zip cmake \
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 byacc patchelf libxkbcommon-devel \
dbus-devel \
libXext-devel libXinerama-devel libXcursor-devel libXrender-devel \
libXfixes-devel libXft-devel pango-devel cairo-devel libstdc++-static
git config --global --add safe.directory '*'
- name: Install Rust
# The bonsaiviewer-autodesk connector is a Rust crate; the "Package
# .zip archives" step below runs `cargo build --release` via
# packaging/build.py. Match the dedicated connector workflow's stable
# toolchain (dtolnay/rust-toolchain@stable).
run: |
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal --default-toolchain stable
echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
- name: Install aws cli
run: |
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
unzip awscliv2.zip
./aws/install
rm -rf awscliv2.zip aws
aws --version
- name: Checkout Repository
uses: actions/checkout@v7
with:
submodules: recursive
- name: Checkout Build Repository
uses: actions/checkout@v7
with:
repository: IfcOpenShell/build-outputs
path: ./build
ref: rockylinux9-x64
lfs: true
ref: main
path: build-outputs-init
token: ${{ secrets.BUILD_REPO_TOKEN }}
- name: Unpack Dependencies
- name: Create Build Repository Branch
run: |
cd build
uv run ../nix/cache_dependencies.py unpack
- name: ccache
uses: hendrikmuhs/ccache-action@v1.2.23
with:
key: ubuntu-22.04-${{ runner.arch }}-rockylinux9
- name: Run Build Script
shell: bash
run: |
set -o pipefail
CXXFLAGS="-O3" CFLAGS="-O3" ADD_COMMIT_SHA=1 BUILD_CFG=Release BUILD_BONSAIVIEWER=ON \
uv run --with aqtinstall ./nix/build-all.py \
-v --diskcleanup --ifcopenshell-shared 2>&1 \
| tee build.log
- name: Upload Build Logs
if: always()
uses: actions/upload-artifact@v7
with:
name: build-logs-rocky
path: |
build.log
build/*/*/logs/*.log
retention-days: 30
- name: Pack Dependencies
run: |
cd build
uv run ../nix/cache_dependencies.py pack
- name: Commit and Push Changes to Build Repository
run: |
cd build
cd build-outputs-init
git config user.name "IfcOpenBot"
git config user.email "ifcopenbot@ifcopenshell.org"
git add "$(find . -maxdepth 4 -name install)/*.tar.gz"
git commit -m "Update build artifacts [skip ci]" || echo "No changes to commit"
git push || true
- name: Package .zip archives
shell: bash
run: |
VERSION=v`cat VERSION`
# bonsaiviewer-autodesk is now a Rust connector. packaging/build.py
# invokes `cargo build --release` and stages the binary +
# connector.json into dist/autodesk/. Same on-disk shape as the
# old PyInstaller flow so the symlink + zip steps below
# continue to work unchanged.
uv run src/bonsaiviewer-autodesk/packaging/build.py
autodesk_connector_dir="$PWD/src/bonsaiviewer-autodesk/dist/autodesk"
test -d "$autodesk_connector_dir"
cd ./build/`uname`/*/install/ifcopenshell
mkdir -p ~/output
install_root="$PWD"
QT6_VERSION="${QT6_VERSION:-6.8.3}"
if [ -z "${QT_DIR:-}" ]; then
for qt_candidate in "$(dirname "$install_root")"/qt6-${QT6_VERSION}-*/${QT6_VERSION}/*; do
if [ -d "$qt_candidate/lib" ]; then
QT_DIR="$qt_candidate"
break
fi
done
fi
# Ensure that all shared libraries in provided dest `$1`
# are present using their SONAMEs (at least as symlinks).
ensure_soname_links() {
dest="$1"
find "$dest" -maxdepth 1 -type f -name "*.so*" | while IFS= read -r shared_object; do
# TODO: actual pattern is "Library soname" instead of "Shared library"?
soname=$(readelf -d "$shared_object" 2>/dev/null | sed -n 's/.*(SONAME).*Shared library: \[\(.*\)\].*/\1/p' | head -n 1)
[ -n "$soname" ] || continue
[ -e "$dest/$soname" ] && continue
ln -s "$(basename "$shared_object")" "$dest/$soname"
done
}
# Copy all libs from `install/ifcopenshell` to the provided `$1`.
# Set `$2` to `0` to skip including geometry writers.
stage_runtime_payload() {
dest="$1"
include_geometry_writers="${2:-1}"
while IFS= read -r runtime_file; do
if [ "$include_geometry_writers" != "1" ] && [[ "$(basename "$runtime_file")" == ifcopenshell.geometry.writer.* ]]; then
continue
fi
cp -P "$runtime_file" "$dest/"
done < <(
for runtime_dir in "$install_root/bin" "$install_root/lib" "$install_root/lib64"; do
[ -d "$runtime_dir" ] || continue
find "$runtime_dir" \( -type f -o -type l \) \( -name "*.so" -o -name "*.so.*" -o -name "*.dylib" -o -name "*.dll" \)
done
)
ensure_soname_links "$dest"
}
# Copy all libs from `QT_DIR` to the provided `$2`.
stage_qt_runtime_payload() {
exe_path="$1"
dest="$2"
[ -n "${QT_DIR:-}" ] && [ -d "$QT_DIR/lib" ] || return 0
# Skip executables that don't depend on QT (don't have `libQt6` referenced).
if ! LD_LIBRARY_PATH="$QT_DIR/lib:${LD_LIBRARY_PATH:-}" ldd "$exe_path" 2>/dev/null | grep -q "libQt6"; then
return 0
fi
# Copy all QT libs to `dest`.
find "$QT_DIR/lib" -maxdepth 1 \( -type f -o -type l \) -name "*.so*" -exec cp -P {} "$dest/" \;
ensure_soname_links "$dest"
# Copy QT plugins.
if [ -d "$QT_DIR/plugins" ]; then
pushd "$QT_DIR/plugins" > /dev/null
find . \( -type f -o -type l \) -name "*.so*" | while IFS= read -r plugin_file; do
mkdir -p "$dest/plugins/$(dirname "$plugin_file")"
cp -P "$plugin_file" "$dest/plugins/$plugin_file"
done
popd > /dev/null
# Point plugins rpath to `$dest`.
if [ -d "$dest/plugins" ]; then
find "$dest/plugins" -type f -name "*.so*" -exec patchelf --set-rpath '$ORIGIN/../..:$ORIGIN' {} \;
fi
fi
find "$dest" -maxdepth 1 -type f -name "*.so*" -exec patchelf --set-rpath '$ORIGIN' {} \;
printf "[Paths]\nPrefix = .\n" > "$dest/qt.conf"
}
# Check all binaries in the dest `$1`
# and report if they're still missing dependencies or are static.
check_runtime_dependencies() {
package_dir="$1"
missing=0
# Iterate over all .so files.
while IFS= read -r binary_file; do
# Skip non-binaries.
readelf -h "$binary_file" >/dev/null 2>&1 || continue
# Report non-dynamic binaries.
if ! env -u LD_LIBRARY_PATH ldd "$binary_file" > "$package_dir/.ldd.out" 2>&1; then
echo "ldd failed for $binary_file"
cat "$package_dir/.ldd.out"
missing=1
continue
fi
# Report missing dependencies.
if grep -q "not found" "$package_dir/.ldd.out"; then
echo "Missing runtime dependencies for $binary_file"
grep "not found" "$package_dir/.ldd.out"
missing=1
fi
done < <(find "$package_dir" -type f \( -perm /111 -o -name "*.so" -o -name "*.so.*" \))
rm -f "$package_dir/.ldd.out"
# TODO: should error?
if [ "$missing" -ne 0 ]; then
echo "Runtime dependency check found issues; continuing packaging."
fi
return 0
}
# Iterate over all built Python wrappers in `install/ifcopenshell/python-x.y.z`.
# and zip them, bundling all dynamic libs from `lib`.
ls -d python-* | while read py_version; do
postfix=`echo ${py_version: -1} | sed s/[0-9]//`
numbers=`echo $py_version | grep -oE '[0-9]+\.[0-9]+' | tr -d '.'`
py_version_major=python-${numbers}$postfix
pushd . > /dev/null
cd $py_version
if [ ! -d ifcopenshell ]; then
mkdir ../ifcopenshell_
mv * ../ifcopenshell_
mv ../ifcopenshell_ ifcopenshell
fi
[ -d ifcopenshell/__pycache__ ] && rm -rf ifcopenshell/__pycache__
find ifcopenshell -name "*.pyc" -delete
# TODO: packs qt libs also?
stage_runtime_payload ifcopenshell
zip -y -r -qq ifcopenshell-${py_version_major}-${VERSION}-${GITHUB_SHA:0:7}-linux64.zip ifcopenshell
mv *.zip ~/output
popd > /dev/null
done
# Iterate over all executables in `install/ifcopenshell/bin` and zip them.
# Each zip bundles dynamic libs from `lib` and also qt libs.
find "$install_root/bin" -maxdepth 1 -type f -perm /111 ! -name "*.zip" ! -name "*.so" ! -name "*.so.*" ! -name "*.dylib" ! -name "*.dll" | while read exe_path; do
exe=`basename "$exe_path"`
package_dir="$install_root/.package-${exe}"
rm -rf "$package_dir"
mkdir -p "$package_dir"
cp "$exe_path" "$package_dir/"
patchelf --set-rpath '$ORIGIN' "$package_dir/$exe"
stage_runtime_payload "$package_dir" 0
stage_qt_runtime_payload "$exe_path" "$package_dir"
if [ "$exe" = "BonsaiViewer" ]; then
mkdir -p "$package_dir/connectors"
cp -a "$autodesk_connector_dir" "$package_dir/connectors/"
fi
check_runtime_dependencies "$package_dir"
pushd "$package_dir" > /dev/null
zip -y -qq -r "$HOME/output/${exe}-${VERSION}-${GITHUB_SHA:0:7}-linux64.zip" .
popd > /dev/null
rm -rf "$package_dir"
done
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v6
with:
aws-access-key-id: ${{ secrets.AWS_UPLOAD_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_UPLOAD_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- name: Upload .zip archives to S3
run: |
aws s3 cp ~/output s3://ifcopenshell-builds/ --recursive
git checkout -b rockylinux9-x64-shared
mkdir -p Linux/x86_64/install
touch Linux/x86_64/install/.gitkeep
git add Linux/x86_64/install/.gitkeep
git commit -m "Add install dir skeleton [skip ci]"
git push origin rockylinux9-x64-shared
+2 -1
View File
@@ -92,7 +92,7 @@ jobs:
set -o pipefail
CXXFLAGS="-O3" CFLAGS="-O3" ADD_COMMIT_SHA=1 BUILD_CFG=Release BUILD_BONSAIVIEWER=ON \
uv run --with aqtinstall ./nix/build-all.py \
-v --diskcleanup --ifcopenshell-shared 2>&1 \
-v --diskcleanup --shared 2>&1 \
| tee build.log
- name: Upload Build Logs
@@ -245,6 +245,7 @@ jobs:
popd > /dev/null
done
rm -f "$install_root"/bin/*.zip
find "$install_root/bin" -maxdepth 1 -type f -perm /111 ! -name "*.zip" ! -name "*.so" ! -name "*.so.*" ! -name "*.dylib" ! -name "*.dll" | while read exe_path; do
exe=`basename "$exe_path"`
package_dir="$install_root/.package-${exe}"
+1 -1
View File
@@ -72,7 +72,7 @@ jobs:
python-version: '3.11'
- name: Get current version
id: version
run: echo "version=$(sed -E 's/[[:alpha:]]+[0-9]+$//' VERSION)" >> $GITHUB_OUTPUT
run: echo "version=$(cat VERSION)" >> $GITHUB_OUTPUT
- name: Compile
run: |
cd src/bonsai && make dist PLATFORM=${{ matrix.config.short_name }} PYVERSION=${{ matrix.pyver }}
@@ -27,9 +27,7 @@ jobs:
- name: Get current version
id: version
# Strip any trailing prerelease label and number; the dated alpha
# suffix is added below.
run: echo "version=$(sed -E 's/[[:alpha:]]+[0-9]+$//' VERSION)" >> $GITHUB_OUTPUT
run: echo "version=$(cat VERSION)" >> $GITHUB_OUTPUT
- name: Get current date
id: date
+1 -1
View File
@@ -32,7 +32,7 @@ jobs:
python-version: '3.11'
- name: Get current version
id: version
run: echo "version=$(sed -E 's/[[:alpha:]]+[0-9]+$//' VERSION)" >> $GITHUB_OUTPUT
run: echo "version=$(cat VERSION)" >> $GITHUB_OUTPUT
- name: Get current date
id: date
run: echo "date=$(date +'%y%m%d')" >> $GITHUB_OUTPUT
+2 -13
View File
@@ -43,7 +43,6 @@ jobs:
sudo apt update
sudo apt-get install --no-install-recommends -y \
cmake \
bison \
gcc \
g++ \
libboost-date-time-dev \
@@ -62,23 +61,13 @@ jobs:
libocct-ocaf-dev \
libocct-visualization-dev \
libpcre3-dev \
libpcre2-dev \
libtbb-dev \
libxml2-dev \
libxi-dev \
occt-misc \
tcl-dev \
tk-dev
- name: Build SWIG
# IfcOpenShell requires SWIG 4.1+, ubuntu-22.04 ships 4.0.2.
run: |
sudo apt-get remove --purge -y swig swig4.0
git clone https://github.com/swig/swig --branch v4.2.1 --depth 1
cmake -S swig -B swig/build -DCMAKE_BUILD_TYPE=Release
cmake --build swig/build -j "$(nproc)"
sudo cmake --install swig/build
swig -version
tk-dev \
swig
- name: Configure minimal IfcOpenShell
run: |
+1 -1
View File
@@ -121,7 +121,7 @@ jobs:
cd OpenCOLLADA
git checkout v1.6.68
patch -p1 --batch --forward -i ../nix/patches/opencollada/pr622_and_disable_subdirs.patch
patch -p1 --batch --forward -i ../nix/patches/opencollada/config_select_libs_by_use_shared.patch
patch -p1 --batch --forward -i ../nix/patches/opencollada/allow_static_libraries_config_on_unix.patch
mkdir build && cd build
cmake .. \
-DCMAKE_BUILD_TYPE=Release \
@@ -1,87 +0,0 @@
# This file was generated with the assistance of an AI coding tool.
name: Publish C++ API documentation
on:
push:
branches:
- v0.9.0
paths:
- '.github/workflows/publish-cpp-api-docs.yml'
- 'docs/cpp-api/**'
- 'src/ifcgeom/**'
- 'src/ifcparse/**'
- 'src/serializers/**'
workflow_dispatch:
permissions:
contents: read
concurrency:
group: publish-cpp-api-docs
cancel-in-progress: false
jobs:
publish:
if: github.repository == 'IfcOpenShell/IfcOpenShell'
runs-on: ubuntu-24.04
steps:
- name: Checkout IfcOpenShell
uses: actions/checkout@v7
- name: Set up Python
uses: actions/setup-python@v7
with:
python-version: '3.10'
- name: Install documentation dependencies
run: |
sudo apt-get update
sudo apt-get install --yes doxygen graphviz
python -m pip install --requirement docs/cpp-api/requirements.txt
- name: Build C++ API documentation
working-directory: docs/cpp-api
run: |
export PROJECT_NUMBER="$(git rev-parse --short HEAD)"
python -m sphinx -M html . output -W --keep-going
- name: Checkout documentation repository
uses: actions/checkout@v7
with:
repository: IfcOpenShell/cpp_docs
ref: master
path: published-docs
token: ${{ secrets.BUILD_REPO_TOKEN }}
- name: Replace published documentation
run: |
publish_tree="${RUNNER_TEMP}/published-docs-tree"
mkdir -p "${publish_tree}/v0.9.0-latest"
rsync --archive docs/cpp-api/output/html/ "${publish_tree}/v0.9.0-latest/"
touch "${publish_tree}/.nojekyll"
if [[ -f published-docs/CNAME ]]; then
cp published-docs/CNAME "${publish_tree}/CNAME"
fi
rsync --archive --delete --exclude='.git/' "${publish_tree}/" published-docs/
- name: Commit and push if changed
working-directory: published-docs
env:
SOURCE_SHA: ${{ github.sha }}
run: |
git config user.name 'IfcOpenBot'
git config user.email 'IfcOpenBot@users.noreply.github.com'
git add --all
if git diff --cached --quiet; then
echo "No changes to commit"
exit 0
fi
git commit -m "Update C++ API docs from ${SOURCE_SHA:0:7}"
git push origin master
+3 -13
View File
@@ -16,9 +16,6 @@
/src/ifcmax/out/
/src/ifcwrap/out/
/src/ifctester/webapp/public/pyodide/
# pyodide wheels
/dist/
/dist-modular/
/win/BuildDepsCache*.txt
@@ -111,13 +108,9 @@ src/ifcopenshell-python/ifcopenshell/_ifcopenshell_wrapper*.so
src/ifcopenshell-python/ifcopenshell/ifcopenshell_wrapper.py
# plugins
src/ifcopenshell-python/ifcopenshell/ifcopenshell_document_*.so
src/ifcopenshell-python/ifcopenshell/ifcopenshell_geometry_*.so
src/ifcopenshell-python/ifcopenshell/ifcopenshell_parse_schema*.so
src/ifcopenshell-python/ifcopenshell/libifcopenshell.geometry.so
src/ifcopenshell-python/ifcopenshell/libifcopenshell.geometry.writer.so
src/ifcopenshell-python/ifcopenshell/libifcopenshell.parse.so
src/ifcopenshell-python/ifcopenshell/libifcopenshell.plugin.so
src/ifcopenshell-python/ifcopenshell/ifcopenshell.document.*.so
src/ifcopenshell-python/ifcopenshell/ifcopenshell.geometry.*.so
src/ifcopenshell-python/ifcopenshell/ifcopenshell.parse.schema*.so
# apple
.DS_Store
@@ -126,9 +119,6 @@ src/ifcopenshell-python/ifcopenshell/libifcopenshell.plugin.so
.clangd
# clangd cache
.cache
# Useful for symlinking json compilation database from cmake,
# allowing clang commands without `-p path/to/build`.
/compile_commands.json
# Brickschema
src/bonsai/bonsai/bim/schema/Brick.ttl
+3
View File
@@ -8,6 +8,9 @@
[submodule "src/ifcopenshell-python/test/Sample-BIM-Files"]
path = src/ifcopenshell-python/test/Sample-BIM-Files
url = https://github.com/IfcOpenShell/ids-test-files
[submodule "docs/cpp-api/assets/doxygen-awesome-css"]
path = docs/cpp-api/assets/doxygen-awesome-css
url = https://github.com/jothepro/doxygen-awesome-css.git
[submodule "src/ifcopenshell-python/ifcopenshell/simple_spf"]
path = src/ifcopenshell-python/ifcopenshell/simple_spf
url = https://github.com/IfcOpenShell/step-file-parser
+1 -1
View File
@@ -1 +1 @@
0.9.0alpha0
0.8.6
+14 -19
View File
@@ -36,14 +36,6 @@ file(READ "../VERSION" "RELEASE_VERSION_")
string(STRIP "${RELEASE_VERSION_}" RELEASE_VERSION)
message(STATUS "Detected version '${RELEASE_VERSION}'")
# CMake's project(VERSION) only accepts numeric components. Keep the complete
# release identifier for build information, but use its numeric release part
# for PROJECT_VERSION, SOVERSION, and generated CMake package metadata.
string(REGEX MATCH "^[0-9]+\\.[0-9]+\\.[0-9]+" PROJECT_VERSION_NUMERIC "${RELEASE_VERSION}")
if(NOT PROJECT_VERSION_NUMERIC)
message(FATAL_ERROR "VERSION must start with a numeric major.minor.patch version: '${RELEASE_VERSION}'")
endif()
add_definitions(-D_DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR)
if(POLICY CMP0141) # 3.25+
@@ -63,15 +55,9 @@ endif()
# Include utility macros and functions
include(utilities.cmake)
# Use a SemVer-compatible spelling for CPack artifact names. A trailing
# alphabetic label and number is separated from the numeric version by a
# hyphen: for example, 0.9.0alpha0 becomes 0.9.0-alpha0.
# use extra version to make pre-release using eg semver
if(NOT DEFINED EXTRA_VERSION)
if(RELEASE_VERSION MATCHES "^[0-9]+\\.[0-9]+\\.[0-9]+([A-Za-z]+)([0-9]+)$")
set(EXTRA_VERSION "-${CMAKE_MATCH_1}${CMAKE_MATCH_2}")
else()
set(EXTRA_VERSION "")
endif()
set(EXTRA_VERSION "-alpha.3")
endif()
option(MINIMAL_BUILD "The build is to make a minimal version of IFC converter from OCCT into IFC." OFF)
@@ -133,7 +119,7 @@ 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(USE_DEBUG_PYTHON "Use debug binaries when building Debug IfcPython on Windows." OFF)
option(ADD_COMMIT_SHA "Add commit sha and branch in version number, requires git" OFF)
option(VERSION_OVERRIDE "Use VERSION as the branch label when commit information is embedded" 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
@@ -141,7 +127,15 @@ set(
"Directory to install IfcPython package to. By default package is installed in found Python's site-packages."
)
project(IfcOpenShell VERSION ${PROJECT_VERSION_NUMERIC})
if (VERSION_OVERRIDE)
file(READ "../VERSION" "RELEASE_VERSION_")
string(STRIP "${RELEASE_VERSION_}" RELEASE_VERSION)
message(STATUS "Detected version '${RELEASE_VERSION}'")
else()
set(RELEASE_VERSION "0.8.0")
endif()
project(IfcOpenShell VERSION ${RELEASE_VERSION})
# Make sure CMake modules in this project are found first
list(PREPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR})
@@ -699,7 +693,8 @@ endif()
# Documentation
if(BUILD_DOCUMENTATION)
add_subdirectory(../docs/cpp-api docs/cpp-api)
set(CMAKE_MODULE_PATH "../docs/cmake")
add_subdirectory(../docs docs)
endif()
if(BUILD_EXAMPLES)
-1
View File
@@ -52,7 +52,6 @@ macro(SET_INSTALL_SELF_RPATH _target)
endmacro()
function(ifcopenshell_plugin_target TARGET)
# Plug-ins are loaded by exact filename and should not receive a platform library prefix.
set_target_properties(${TARGET} PROPERTIES PREFIX "")
if((NOT WIN32) AND BUILD_SHARED_LIBS AND NOT WASM_BUILD AND NOT CREATE_BUNDLE AND NOT CMAKE_INSTALL_RPATH AND COMMAND SET_INSTALL_SELF_RPATH)
SET_INSTALL_SELF_RPATH(${TARGET})
+34 -14
View File
@@ -1,15 +1,35 @@
find_package(Doxygen REQUIRED)
find_program(
SPHINX_EXECUTABLE
NAMES sphinx-build
REQUIRED
DOC "Path to the sphinx-build executable"
)
#Look for an executable called sphinx-build
find_program(SPHINX_EXECUTABLE NAMES sphinx-build DOC "Path to sphinx-build executable")
add_custom_target(
cpp_api_docs
COMMAND ${SPHINX_EXECUTABLE} -M html . output -W --keep-going
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
COMMENT "Generating the IfcOpenShell C++ API documentation"
VERBATIM
)
include(FindPackageHandleStandardArgs)
#Handle standard arguments to find_package like REQUIRED and QUIET
find_package_handle_standard_args(Sphinx "Failed to find sphinx-build executable" SPHINX_EXECUTABLE)
find_package(Doxygen REQUIRED)
#find_package(Sphinx REQUIRED)
set(SPHINX_SOURCE ${CMAKE_CURRENT_SOURCE_DIR})
set(SPHINX_BUILD ${CMAKE_CURRENT_BINARY_DIR}/docs/sphinx)
message(STATUS "SPHINX BUILD ${CMAKE_CURRENT_BINARY_DIR}")
file(MAKE_DIRECTORY ./output/doxygen)
if(DOXYGEN_FOUND)
add_custom_target(
Sphinx
ALL
COMMAND ${SPHINX_EXECUTABLE} -v -T -b html ${SPHINX_SOURCE} ${CMAKE_CURRENT_SOURCE_DIR}/output
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/output
COMMENT "Generating documentation with Sphinx"
)
# add_custom_target(ifcopenshell_python_docs ALL
# COMMAND make html
# WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/../src/ifcblenderexport/docs
# OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/../src/ifcblenderexport/docs
# COMMENT "Generating documentation with Sphinx")
else(DOXYGEN_FOUND)
message("Doxygen need to be installed to generate the doxygen documentation")
endif(DOXYGEN_FOUND)
+22 -63
View File
@@ -68,7 +68,7 @@ PROJECT_LOGO =
# entered, it will be relative to the location where doxygen was started. If
# left blank the current directory will be used.
OUTPUT_DIRECTORY = ./output/doxygen
OUTPUT_DIRECTORY = ./output
# If the CREATE_SUBDIRS tag is set to YES then doxygen will create up to 4096
# sub-directories (in 2 levels) under the output directory of each output format
@@ -852,7 +852,7 @@ WARNINGS = YES
# will automatically be disabled.
# The default value is: YES.
WARN_IF_UNDOCUMENTED = NO
WARN_IF_UNDOCUMENTED = YES
# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for
# potential errors in the documentation, such as documenting some parameters in
@@ -901,7 +901,7 @@ WARN_IF_UNDOC_ENUM_VAL = NO
# Possible values are: NO, YES, FAIL_ON_WARNINGS and FAIL_ON_WARNINGS_PRINT.
# The default value is: NO.
WARN_AS_ERROR = FAIL_ON_WARNINGS
WARN_AS_ERROR = NO
# The WARN_FORMAT tag determines the format of the warning messages that doxygen
# can produce. The string should contain the $file, $line, and $text tags, which
@@ -944,6 +944,7 @@ WARN_LOGFILE =
# Note: If this tag is empty the current directory is searched.
INPUT = ../../src/ifcgeom \
../../src/ifcgeom_schema_agnostic \
../../src/ifcparse \
../../src/serializers \
@@ -1000,7 +1001,7 @@ RECURSIVE = YES
# Note that relative paths are relative to the directory from which doxygen is
# run.
EXCLUDE = ../../src/ifcparse/schemas
EXCLUDE =
# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or
# directories that are symbolic links (a Unix file system feature) are excluded
@@ -1024,33 +1025,7 @@ EXCLUDE_PATTERNS =
# wildcard * is used, a substring. Examples: ANamespace, AClass,
# ANamespace::AClass, ANamespace::*Test
EXCLUDE_SYMBOLS = "ifcopenshell::geom::opaque_number::*" \
ifcopenshell::entity::attribute_by_name_cmp \
ifcopenshell::impl::rocks_db_file_storage::rocksdb_types_iterator \
ifcopenshell::impl::in_memory_file_storage::type_iterator \
"util::string_buffer::*_item" \
util::string_buffer::item \
ifcopenshell::geom::layer_filter::wildcards_match \
ifcopenshell::paged_file_impl::entry \
ifcopenshell::token \
attribute_value::pointer_type \
INCLUDE_PARENT_PARENT_DIR \
POSTFIX_SCHEMA_ \
POSTFIX_SCHEMA__ \
STRINGIFY_ \
MAKE_INIT_FN_ \
MAKE_INIT_FN__ \
key_from_string \
add_ \
subtract_ \
multiply_ \
divide_ \
equals_ \
less_than_ \
negate_ \
ifcopenshell::geom::utils::create_cube \
ifcopenshell::geom::utils::create_polyhedron \
ifcopenshell::geom::utils::create_nef_polyhedron
EXCLUDE_SYMBOLS =
# The EXAMPLE_PATH tag can be used to specify one or more files or directories
# that contain example code fragments that are included (see the \include
@@ -1261,7 +1236,7 @@ IGNORE_PREFIX =
# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output
# The default value is: YES.
GENERATE_HTML = NO
GENERATE_HTML = YES
# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a
# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
@@ -1336,7 +1311,7 @@ HTML_STYLESHEET =
# documentation.
# This tag requires that the tag GENERATE_HTML is set to YES.
HTML_EXTRA_STYLESHEET =
HTML_EXTRA_STYLESHEET = assets/doxygen-awesome-css/doxygen-awesome.css
# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or
# other source files which should be copied to the HTML output directory. Note
@@ -2191,7 +2166,7 @@ MAN_LINKS = NO
# captures the structure of the code including all documentation.
# The default value is: NO.
GENERATE_XML = YES
GENERATE_XML = NO
# The XML_OUTPUT tag is used to specify where the XML pages will be put. If a
# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
@@ -2328,7 +2303,7 @@ ENABLE_PREPROCESSING = YES
# The default value is: NO.
# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
MACRO_EXPANSION = YES
MACRO_EXPANSION = NO
# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then
# the macro expansion is limited to the macros specified with the PREDEFINED and
@@ -2336,7 +2311,7 @@ MACRO_EXPANSION = YES
# The default value is: NO.
# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
EXPAND_ONLY_PREDEF = YES
EXPAND_ONLY_PREDEF = NO
# If the SEARCH_INCLUDES tag is set to YES, the include files in the
# INCLUDE_PATH will be searched if a #include is found.
@@ -2369,17 +2344,7 @@ INCLUDE_FILE_PATTERNS =
# recursively expanded use the := operator instead of the = operator.
# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
PREDEFINED = IFC_PARSE_API= \
IFC_SCHEMA_API= \
IFC_GEOM_API= \
IFC_GEOMLIBRARY_API= \
IFC_GEOMSERIALIZATION_API= \
SERIALIZERS_API= \
"POSTFIX_SCHEMA(name)=name##_Schema" \
"Handle(name):=opencascade::handle<name>" \
kernel_=kernel \
Simplekernel_=Simplekernel \
inline=
PREDEFINED =
# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this
# tag can be used to specify a list of macro names that should be expanded. The
@@ -2388,22 +2353,7 @@ PREDEFINED = IFC_PARSE_API= \
# definition found in the source code.
# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
EXPAND_AS_DEFINED = kernel_ \
cgal_shape \
cgal_kernel \
cgal_placement \
cgal_point \
cgal_direction \
cgal_vector \
cgal_plane \
cgal_curve \
cgal_wire \
cgal_face \
cgal_polyhedron \
cgal_vertex_descriptor \
cgal_face_descriptor \
create_cube \
create_polyhedron
EXPAND_AS_DEFINED =
# If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will
# remove all references to function-like macros that are alone on a line, have
@@ -2781,6 +2731,15 @@ DOT_GRAPH_MAX_NODES = 50
MAX_DOT_GRAPH_DEPTH = 0
# Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output
# files in one run (i.e. multiple -o and -T options on the command line). This
# makes dot run faster, but since only newer versions of dot (>1.8.10) support
# this, this feature is disabled by default.
# The default value is: NO.
# This tag requires that the tag HAVE_DOT is set to YES.
DOT_MULTI_TARGETS = NO
# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page
# explaining the meaning of the various boxes and arrows in the dot generated
# graphs.
+18 -41
View File
@@ -1,56 +1,33 @@
# IfcOpenShell C++ API documentation
This directory contains the Sphinx, Doxygen, Breathe, and Exhale configuration
for the IfcOpenShell C++ API reference. During a Sphinx build, Exhale runs
Doxygen, Breathe consumes the generated XML, and Exhale creates the API pages.
## Prerequisites
- Python 3.10 or newer
- [Doxygen](https://www.doxygen.nl/)
- [Graphviz](https://graphviz.org/)
Install the Python dependencies from this directory:
```shell
python -m pip install -r requirements.txt
```
Both `doxygen` and `dot` must be available on `PATH`. For the standard Windows
install locations, this can be done for the current PowerShell session with:
```powershell
$env:Path = "C:\Program Files\doxygen\bin;C:\Program Files\Graphviz\bin;$env:Path"
```
This folder contains the setup to build the IfcOpenShell C++ API documentation from the source code.
## Generating the documentation
From this directory, run:
> Prerequisites:
>
> Make sure to have [Doxygen](https://www.doxygen.nl) and [Graphviz](https://graphviz.org) installed into your `$PATH` variable.
>
> The documentation also use the [doxygen-awesome](https://jothepro.github.io/doxygen-awesome-css) theme as a git submodule.
Build with the command (from within the `/docs/cpp-api` folder):
```shell
python -m sphinx -M html . output -W --keep-going
$ doxygen
```
To include the current Git commit in Doxygen's project metadata, set
`PROJECT_NUMBER` before building. For example, in PowerShell:
```powershell
$env:PROJECT_NUMBER = git rev-parse --short HEAD
python -m sphinx -M html . output -W --keep-going
```
Or in a POSIX shell:
To include the current git commit hash into the build documentation, use the following command:
```shell
PROJECT_NUMBER=$(git rev-parse --short HEAD) python -m sphinx -M html . output -W --keep-going
$ PROJECT_NUMBER=$(git rev-parse --short HEAD) doxygen
```
Alternatively, configure the main CMake project with
`-DBUILD_DOCUMENTATION=ON` and build the `cpp_api_docs` target.
This will extract the current commit hash in short version and sets the propper ENV variable used by doxygen.
The generated documentation is written to `output/html/index.html`. The
generated Doxygen XML and Exhale sources are kept under `output/` as build
artifacts.
The generation of the documentation might take a while depending on your systems hardware, as it is configured to generate the Class graphs using .
The generated headers under `src/ifcparse/schemas` are intentionally excluded
from this documentation build.
The resulting documentation is located unter `/cpp-api/output/html` and can be directly accessed with your browser:
```shell
$ open ./output/html/index.html
```
-59
View File
@@ -1,59 +0,0 @@
# This file was generated with the assistance of an AI coding tool.
import warnings
from pathlib import Path
from shutil import rmtree
from sphinx.deprecation import RemovedInSphinx90Warning
warnings.filterwarnings("ignore", category=RemovedInSphinx90Warning, module=r"exhale\.configs")
generated_directories = (
Path(__file__).parent / "output" / "api",
Path(__file__).parent / "output" / "doxygen",
)
for generated_directory in generated_directories:
if generated_directory.is_dir():
rmtree(generated_directory)
project = "IfcOpenShell"
copyright = "2020, IfcOpenShell"
extensions = [
"breathe",
"exhale",
]
primary_domain = "cpp"
highlight_language = "cpp"
html_theme = "alabaster"
breathe_projects = {
"IfcOpenShell": "./output/doxygen/xml",
}
breathe_default_project = "IfcOpenShell"
exhale_args = {
"containmentFolder": "./output/api",
"rootFileName": "library_root.rst",
"rootFileTitle": "IfcOpenShell C++ API",
"doxygenStripFromPath": "../..",
"createTreeView": False,
"exhaleExecutesDoxygen": True,
"exhaleUseDoxyfile": True,
}
cpp_id_attributes = [
"IFC_PARSE_API",
"IFC_SCHEMA_API",
"IFC_GEOM_API",
"IFC_GEOMLIBRARY_API",
"IFC_GEOMSERIALIZATION_API",
"SERIALIZERS_API",
]
exclude_patterns = [
"output/doctrees",
"output/doxygen",
"output/html",
]
-9
View File
@@ -1,9 +0,0 @@
.. This file was generated with the assistance of an AI coding tool.
IfcOpenShell C++ API
====================
.. toctree::
:maxdepth: 2
output/api/library_root
-5
View File
@@ -1,5 +0,0 @@
# This file was generated with the assistance of an AI coding tool.
Sphinx==8.1.3
breathe==4.36.0
exhale==0.3.7
+90 -121
View File
@@ -1,8 +1,5 @@
#!/usr/bin/python
# /// script
# dependencies = [
# "typing_extensions",
# ]
# ///
###############################################################################
# #
@@ -35,45 +32,35 @@ Example usage:
Available arguments:
``-py-313`` - build for specific Python version
(building for all supported Python version by default).
``-occt-xxx`` - use a specific OCCT version (e.g. ``-occt-7.8.1``) instead of the default
``-wasm`` - compile for wasm
``-without-xxx`` - do not build dependency ``xxx`` (e.g. ``--without-swig``)
``-mac-cross-compile-intel`` - cross compile for Intel Mac on Apple Silicon host
``-shared`` - build shared libraries. By default will build static.
``-ifcopenshell-shared`` - build only IfcOpenShell's own libraries as shared
(dependencies stay static). Redundant if ``-shared`` is also passed.
``-diskcleanup`` - clean up build directories after finishing building dependencies
``-build-examples`` - build IfcOpenShell examples
``-lto`` - enable link-time optimization (adds ``-flto`` to compiler flags)
``-v`` - enable verbose logs
Used environment variables:
Boolean-like env variables accept the following values (case-insensitive):
`opt-in` - `1`, `on`, `true`, `yes`; ``opt-out`` - `0`, `off`, `false`, `no`.
- ``CXXFLAGS``, ``CPPFLAGS``, ``CFLAGS``, ``LDFLAGS``
- ``BUILD_DIR`` - build directory. By default will use "build" folder in IfcOpenShell repository.
- ``DEPS_DIR`` - dependencies directory. By default will create automatic folder in build directory.
- ``BUILD_CFG`` - build configuration, 'RelWithDebInfo' by default.
- ``USE_CURRENT_PYTHON_VERSION`` - use current python config instead of compile from source
`off` by default.
- ``IFCOS_NUM_BUILD_PROCS`` - number of concurrent processes defaults to available cores + 1
- ``NO_CLEAN`` - do not clean `ifcopenshell` build directories but continue working on current build
(installed dependencies are never cleared).
By default option is disabled, to enable pass any value from `1`, `on`, `true`.
- ``IFCOS_SCHEMAS`` - schemas to be built; defaults to cmake default (8 schemas), to be supplied as `2x3;4;4x3_add2`
- ``USE_OCCT`` - whether to use official Open CASCADE instead of Community Edition
`on` by default
(`true` by default, any other value is considered `false`)
- ``WASM_PYTHON_PATH`` - path to WASM Python installation,
used to deduce `PYVERSION` (e.g. '3.13.2'), `PYTHONINCLUDE`,
`SIDE_MODULE_CFLAGS`, `SIDE_MODULE_LDFLAGS`.
Allows to build wasm without pyodide build environment, which can be useful for debugging build issues.
Example value: 'pyodide/cpython/installs/python-3.13.2'
- ``ADD_COMMIT_SHA`` - `off` by default. If enabled
- ``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
- ``BUILD_BONSAIVIEWER`` - enable building BonsaiViewer, `off` by default.
- ``IFCOS_BUILD_PYTHON_WRAPPER`` - enable building the Python wrapper, `on` by default.
- ``BUILD_BONSAIVIEWER`` - enable building BonsaiViewer, value of the env variable has to be truthy.
# This script builds IfcOpenShell and its dependencies #
# #
@@ -124,44 +111,33 @@ import multiprocessing
import os
import platform
import shutil
# @todo temporary for expired mpfr.org certificate on 2023-04-08
import ssl
import subprocess as sp
import sys
import sysconfig
import tarfile
import threading
from datetime import datetime
ssl._create_default_https_context = ssl._create_unverified_context # ty:ignore[invalid-assignment]
import time
from collections.abc import Generator, Sequence
from datetime import datetime
from pathlib import Path
from typing import Literal
from typing import Literal, Union
from urllib.request import urlretrieve
from typing_extensions import assert_never
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
ch = logging.StreamHandler()
logger.addHandler(ch)
def is_on_off(value: str | None, *, default: bool) -> bool:
if value is None:
return default
lowered = value.lower()
if lowered in {"1", "on", "true", "yes"}:
return True
if lowered in {"0", "off", "false", "no"}:
return False
return default
PROJECT_NAME = "IfcOpenShell"
USE_CURRENT_PYTHON_VERSION = is_on_off(os.getenv("USE_CURRENT_PYTHON_VERSION"), default=False)
ADD_COMMIT_SHA = is_on_off(os.getenv("ADD_COMMIT_SHA"), default=False)
IFCOS_BUILD_PYTHON_WRAPPER = is_on_off(os.getenv("IFCOS_BUILD_PYTHON_WRAPPER"), default=True)
BUILD_BONSAIVIEWER = is_on_off(os.getenv("BUILD_BONSAIVIEWER"), default=False)
USE_OCCT = is_on_off(os.getenv("USE_OCCT"), default=True)
USE_CURRENT_PYTHON_VERSION = os.getenv("USE_CURRENT_PYTHON_VERSION")
ADD_COMMIT_SHA = os.getenv("ADD_COMMIT_SHA")
BUILD_BONSAIVIEWER = os.getenv("BUILD_BONSAIVIEWER", "").lower() in {"1", "on", "true", "yes"}
PYTHON_VERSIONS = ["3.10.3", "3.11.8", "3.12.1", "3.13.6", "3.14.0"]
JSON_VERSION = "3.11.3"
@@ -225,11 +201,6 @@ def cecho(message, color=NO_COLOR):
# Flags.
BUILD_EXAMPLES = "build-examples" in flags
DISK_CLEANUP = "diskcleanup" in flags
LTO = "lto" in flags
VERBOSE = "v" in flags
APPLE = platform.system() == "Darwin"
MAC_CROSS_COMPILE_INTEL = "mac-cross-compile-intel" in flags
assert platform.system() == "Darwin" or not MAC_CROSS_COMPILE_INTEL
@@ -247,7 +218,6 @@ if WASM:
cecho("WARNING. Couldn't find 'PYODIDE_ROOT' in environment variables.", YELLOW)
cecho("Assuming building wasm outside pyodide build environment and resetting necessary variables.", YELLOW)
os.environ["SIDE_MODULE_CFLAGS"] = get_pyodide_config_var("cflags")
os.environ["SIDE_MODULE_CXXFLAGS"] = get_pyodide_config_var("cxxflags")
os.environ["SIDE_MODULE_LDFLAGS"] = get_pyodide_config_var("ldflags")
# Override cmake toolchain for all `emcmake` calls,
# needed for shared libraries (resulting .so wrapper)
@@ -255,14 +225,13 @@ if WASM:
os.environ["CMAKE_TOOLCHAIN_FILE"] = get_pyodide_config_var("cmake_toolchain_file")
required_vars = (
"SIDE_MODULE_CFLAGS",
"SIDE_MODULE_CXXFLAGS",
"SIDE_MODULE_LDFLAGS",
"CMAKE_TOOLCHAIN_FILE",
)
missing_vars = [v for v in required_vars if v not in os.environ]
assert not missing_vars, f"Some variables required for WASM compilation are missing: {', '.join(missing_vars)}"
def get_pyodide_build_version() -> tuple[int, ...]:
def get_pyodide_build_version() -> "tuple[int, ...]":
pyodide_build_suffix = "pyodide-build version:"
output = sp.check_output(["pyodide", "--version"], encoding="utf-8").strip()
assert pyodide_build_suffix in output, output
@@ -280,6 +249,10 @@ if WASM:
# 0.31 is required for SIDE_MODULE_CXXFLAGS to be provided.
assert get_pyodide_build_version() >= (0, 31)
# Set defaults for missing empty environment variables
USE_OCCT = os.environ.get("USE_OCCT", "true").lower() == "true"
TOOLSET = None
if platform.system() == "Darwin":
# C++11 features used in OCCT 7+ need a more recent stdlib
@@ -347,12 +320,13 @@ if BUILD_CFG == "MinSizeRel":
cecho(" WARNING: MinSizeRel build can suffer from a significant performance loss.", RED)
cecho(f"* IFCOS_NUM_BUILD_PROCS = {IFCOS_NUM_BUILD_PROCS}", MAGENTA)
cecho(""" - How many compiler processes may be run in parallel.""")
cecho(f"* IFCOS_SCHEMAS = '{os.environ.get('IFCOS_SCHEMAS')}'", MAGENTA)
cecho(""" - How many compiler processes may be run in parallel.
""")
cecho(f" * IFCOS_SCHEMAS = '{os.environ.get('IFCOS_SCHEMAS')}'", MAGENTA)
cecho(""" - IFC Schemas to compile. If not provided, fallback to default provided in cmake.
""")
dependency_tree: dict[str, tuple[str, ...]] = {
dependency_tree: "dict[str, tuple[str, ...]]" = {
"IfcParse": ("boost", "libxml2", "rocksdb"),
"IfcGeom": ("IfcParse", "occ", "manifold", "json", "cgal", "eigen", "OpenCOLLADA"),
"IfcConvert": ("IfcGeom",),
@@ -377,7 +351,7 @@ dependency_tree: dict[str, tuple[str, ...]] = {
}
def gather_dependencies(dep: str) -> Generator[str]:
def gather_dependencies(dep: str) -> "Generator[str]":
yield dep
for d in dependency_tree[dep]:
if f"without-{d.lower()}" not in flags:
@@ -385,7 +359,7 @@ def gather_dependencies(dep: str) -> Generator[str]:
yield x
if VERBOSE:
if "v" in flags:
logger.setLevel(logging.DEBUG)
formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s")
ch.setFormatter(formatter)
@@ -407,9 +381,6 @@ else:
OFF_ON = ["OFF", "ON"]
BUILD_STATIC = "shared" not in flags
"""Whether dependencies are built static."""
IFCOPENSHELL_STATIC = BUILD_STATIC and "ifcopenshell-shared" not in flags
"""Whether IfcOpenShell's own libraries are built static."""
ENABLE_FLAG = "--enable-static" if BUILD_STATIC else "--enable-shared"
DISABLE_FLAG = "--disable-shared" if BUILD_STATIC else "--disable-static"
LINK_TYPE = "static" if BUILD_STATIC else "shared"
@@ -439,12 +410,13 @@ if BUILD_BONSAIVIEWER:
# ifcopenshell.document.rdb.dylib but the CREATE_BUNDLE install rule does
# not actually drop the dylib next to the wrapper in site-packages — see
# the commit message that introduced this gate.
if not IFCOS_BUILD_PYTHON_WRAPPER:
if os.environ.get("IFCOS_BUILD_PYTHON_WRAPPER", "on").lower() in {"0", "off", "false", "no"}:
targets.discard("IfcOpenShell-Python")
if WASM:
SKIP_TARGETS_FOR_WASM = {
"rocksdb",
"opencollada",
"swig",
"pcre",
"IfcGeom",
"IfcConvert",
@@ -464,10 +436,14 @@ print("Building:", *sorted(targets, key=lambda t: len(list(gather_dependencies(t
yacc = "yacc" # Used during swig building process, installed with `bison` on Debian / `byacc` on Red Hat.
bison = "bison"
missing_commands: list[str] = []
missing_commands: "list[str]" = []
required_commands = [git, bunzip2, tar, cc, cplusplus, autoconf, automake, make, "patch", "cmake", yacc, xz, bison]
if WASM:
if "wasm" in flags:
# Skip swig build for WASM.
required_commands.append("swig")
required_commands.append("pyodide")
required_commands.remove(yacc)
required_commands.remove(bison)
if platform.system() == "Linux" and "BonsaiViewer" in targets:
required_commands.append("patchelf")
@@ -504,14 +480,14 @@ except:
pass
def restore_env(var_name: str, old_value: str | None) -> None:
def restore_env(var_name: str, old_value: Union[str, None]) -> None:
if old_value is None:
del os.environ[var_name]
else:
os.environ[var_name] = old_value
def run(cmds: Sequence[str], cwd: str | None = None, can_fail: bool = False) -> str:
def run(cmds: "Sequence[str]", cwd: "Union[str, None]" = None, can_fail: bool = False) -> str:
"""
Wraps `subprocess.Popen.communicate()` and logs the command being executed,
sets up logging `stderr` to `LOG_FILE` (in append mode) and returns stdout
@@ -521,7 +497,7 @@ def run(cmds: Sequence[str], cwd: str | None = None, can_fail: bool = False) ->
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()
@@ -574,7 +550,7 @@ BOOST_LOCATION = f"https://github.com/boostorg/boost/releases/download/boost-{BO
# Helper functions
def run_autoconf(dependency_name: str, configure_args: list[str], cwd: str) -> None:
def run_autoconf(dependency_name: str, configure_args: "list[str]", cwd: str) -> None:
configure_path = os.path.realpath(os.path.join(cwd, "..", "configure"))
if not os.path.exists(configure_path):
run(
@@ -584,7 +560,7 @@ def run_autoconf(dependency_name: str, configure_args: list[str], cwd: str) -> N
prefix = os.path.realpath(f"{DEPS_DIR}/install/{dependency_name}")
wasm = []
if WASM:
if "wasm" in flags:
wasm.append("emconfigure")
run(
@@ -592,7 +568,7 @@ def run_autoconf(dependency_name: str, configure_args: list[str], cwd: str) -> N
*wasm,
"/bin/sh",
"../configure",
*(["--host=wasm32"] if WASM and not any(s.startswith("--host") for s in configure_args) else []),
*(["--host=wasm32"] if "wasm" in flags and not any(s.startswith("--host") for s in configure_args) else []),
*configure_args,
f"--prefix={prefix}",
],
@@ -600,20 +576,18 @@ def run_autoconf(dependency_name: str, configure_args: list[str], cwd: str) -> N
)
def run_cmake(
name, cmake_args: list[str], cmake_dir: str | None = None, cwd: str | None = None, native: bool = False
) -> None:
def run_cmake(arg1, cmake_args: "list[str]", cmake_dir: Union[str, None] = None, cwd: Union[str, None] = None):
if cmake_dir is None:
P = ".."
else:
P = cmake_dir
wasm = []
if WASM and not native:
if "wasm" in flags:
wasm.append("emcmake")
cmake_flags: list[str] = []
if not native and (not WASM or not WASM_CMAKE_IS_USING_INIT_VARS):
if not WASM or not WASM_CMAKE_IS_USING_INIT_VARS:
# For WASM we provide flags using just environment variables.
# If we provide them using cmake vars, it will override emscripten toolchain flags.
# Unsure if we need this in general even for non-WASM builds.
@@ -629,10 +603,6 @@ def run_cmake(
f"-DBUILD_SHARED_LIBS={OFF_ON[not BUILD_STATIC]}",
)
if WASM and native:
# Override emscripten cmake toolchain coming from environment variable.
cmake_flags.append("-DCMAKE_TOOLCHAIN_FILE=")
run(
[
*wasm,
@@ -641,13 +611,13 @@ def run_cmake(
*cmake_flags,
*cmake_args,
f"-DCMAKE_BUILD_TYPE={BUILD_CFG}",
*([] if native else [f"-DCMAKE_SHARED_LINKER_FLAGS={os.environ['LDFLAGS']}"]),
f"-DCMAKE_SHARED_LINKER_FLAGS={os.environ['LDFLAGS']}",
],
cwd=cwd,
)
def git_clone_or_pull_repository(clone_url: str, target_dir: str, revision: str | None = None) -> None:
def git_clone_or_pull_repository(clone_url: str, target_dir: str, revision: Union[str, None] = None) -> None:
"""Lazily clones the `git` repository denoted by `clone_url` into
the `target_dir` or pulls latest changes if the `target_dir` exists (naively assumes
that a working clone exists there) and optionally checks out a revision
@@ -677,19 +647,19 @@ def build_dependency(
"autoconf",
"bjam",
],
build_tool_args: list[str],
build_tool_args: "list[str]",
download_url: str,
download_name: str,
*,
download_tool: Literal["py", "git"] = download_tool_default,
revision: str | None = None,
revision: "Union[str, None]" = None,
patch: list[str] | None = None,
shell=None,
pre_compile_subs: Sequence[tuple[str, str, str]] = (),
additional_files: dict[str, str] | None = None,
pre_compile_subs: "Sequence[tuple[str, str, str]]" = (),
additional_files: "Union[dict[str, str], None]" = None,
no_append_name=False,
cmake_dir=None,
cmake_native: bool = False,
**kwargs,
) -> None:
"""Handles building of dependencies with different tools (which are
distinguished with the `mode` argument. `build_tool_args` is expected to be
@@ -698,8 +668,7 @@ def build_dependency(
:param pre_compile_subs: A sequence of ``(fn, before, after)``
:param additional_files: Mapping path->url.
:param cmake_native: For ``mode="cmake"``, force a native (host) build
even when building for WASM. Needed for build-time tools like swig.
:param kwargs: Additional ``mode`` related kwargs.
"""
check_dir = os.path.join(DEPS_DIR, "install", name)
if os.path.exists(check_dir):
@@ -735,7 +704,7 @@ def build_dependency(
logger.info(f"\rChecking {name}... ")
git_clone_or_pull_repository(download_url, target_dir=os.path.join(build_dir, download_name), revision=revision)
else:
assert_never(download_tool)
raise ValueError(f"download tool '{download_tool}' is not supported")
download_dir = os.path.join(build_dir, download_name)
if os.path.isdir(download_dir):
@@ -796,9 +765,9 @@ def build_dependency(
if mode == "autoconf":
run_autoconf(name, build_tool_args, cwd=extract_build_dir)
elif mode == "cmake":
run_cmake(name, build_tool_args, cwd=extract_build_dir, native=cmake_native)
run_cmake(name, build_tool_args, cwd=extract_build_dir)
else:
assert_never(mode)
raise ValueError()
for fn, before, after in pre_compile_subs:
with open(os.path.join(extract_dir, fn), "r") as f:
s = f.read()
@@ -814,18 +783,18 @@ def build_dependency(
logger.info(f"\rConfiguring {name}...")
run([bash, "./bootstrap.sh"], cwd=extract_dir)
logger.info(f"\rBuilding {name}... ")
run(["./b2", f"-j{IFCOS_NUM_BUILD_PROCS}"] + build_tool_args, cwd=extract_dir, can_fail=WASM)
run(["./b2", f"-j{IFCOS_NUM_BUILD_PROCS}"] + build_tool_args, cwd=extract_dir, can_fail="wasm" in flags)
logger.info(f"\rInstalling {name}... ")
shutil.copytree(
os.path.join(extract_dir, "boost"), os.path.join(DEPS_DIR, "install", f"boost-{BOOST_VERSION}", "boost")
)
logger.info(f"\rInstalled {name} \n")
if DISK_CLEANUP:
if "diskcleanup" in flags:
shutil.rmtree(build_dir, ignore_errors=True)
def get_qt6_aqt_config() -> tuple[str, str, str]:
def get_qt6_aqt_config() -> "tuple[str, str, str]":
if platform.system() != "Linux":
raise ValueError("Automatic Qt6 installation with aqtinstall is only configured for Linux builds.")
@@ -927,7 +896,7 @@ ADDITIONAL_ARGS_STR = " ".join(ADDITIONAL_ARGS)
CXXFLAGS_MINIMAL = f"{CXXFLAGS} {PIC} {ADDITIONAL_ARGS_STR}"
CFLAGS_MINIMAL = f"{CFLAGS} {PIC} {ADDITIONAL_ARGS_STR}"
if WASM:
if "wasm" in flags:
# WASM `SIDE_MODULE_` are absorbed by `emcmake` automatically.
CXXFLAGS = CXXFLAGS_MINIMAL
CFLAGS = CFLAGS_MINIMAL
@@ -948,7 +917,7 @@ else:
CFLAGS = CFLAGS_MINIMAL
LDFLAGS = f"{LDFLAGS} {ADDITIONAL_ARGS_STR}"
if LTO:
if "lto" in flags:
for f in compiler_flags:
locals()[f] += f" -flto={IFCOS_NUM_BUILD_PROCS}"
@@ -1025,24 +994,23 @@ if "swig" in targets:
download_name="swig",
download_tool=download_tool_git,
revision=f"v{SWIG_VERSION}",
cmake_native=WASM,
)
if USE_OCCT and "occ" in targets:
occt_args: list[str] = []
patches: list[str] = []
occt_args: "list[str]" = []
patches: "list[str]" = []
if OCCT_VERSION < "7.4":
patches.append("./patches/occt/enable-exception-handling.patch")
# Skip ExpToCasExe as we don't need it and it requires additional dependencies.
# Before 7.7.2 ExpToCasExe is part of DataExchange, DETools doesn't exist yet.
# Since we do need DataExchange (used for iges_serializer), we use a patch to skip only ExpToCasExe.
# Since we do need DataExchange (used for IgesSerializer), we use a patch to skip only ExpToCasExe.
if "7.7.2" > OCCT_VERSION >= "7.7":
patches.append("./patches/occt/no_ExpToCasExe.patch")
elif OCCT_VERSION >= "7.7.2":
occt_args.append("-DBUILD_MODULE_DETools=OFF")
if WASM:
if "wasm" in flags:
patches.append("./patches/occt/no_em_js.patch")
build_dependency(
@@ -1126,7 +1094,7 @@ if "libxml2" in targets:
"--without-iconv",
"--without-lzma",
]
if WASM:
if "wasm" in flags:
build_tool_args.append("--without-threads")
build_dependency(
f"libxml2-{LIBXML2_VERSION}",
@@ -1148,7 +1116,7 @@ if "OpenCOLLADA" in targets:
# whether shared libs were actually built. We make it follow `USE_SHARED` instead.
patches.append("./patches/opencollada/config_select_libs_by_use_shared.patch")
if WASM:
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
# some versions of gcc (9.4.0 at least) due to specializing std::hash
@@ -1178,7 +1146,7 @@ if "OpenCOLLADA" in targets:
revision=OPENCOLLADA_VERSION,
)
if "python" in targets and not USE_CURRENT_PYTHON_VERSION and not WASM:
if "python" in targets and not USE_CURRENT_PYTHON_VERSION and "wasm" not in flags:
# Python should not be built with -fvisibility=hidden, from experience that introduces segfaults
OLD_CPP_FLAGS = os.environ["CPPFLAGS"]
OLD_CXX_FLAGS = os.environ["CXXFLAGS"]
@@ -1189,7 +1157,7 @@ if "python" in targets and not USE_CURRENT_PYTHON_VERSION and not WASM:
# On OSX a dynamic python library is built or it would not be compatible
# with the system python because of some threading initialization
PYTHON_CONFIGURE_ARGS: list[str] = []
PYTHON_CONFIGURE_ARGS: "list[str]" = []
original_path = ""
if platform.system() == "Darwin":
PYTHON_CONFIGURE_ARGS = ["--enable-shared"]
@@ -1239,7 +1207,7 @@ if "python" in targets and not USE_CURRENT_PYTHON_VERSION and not WASM:
if "boost" in targets:
str_concat = lambda prefix: lambda postfix: "" if postfix.strip() == "" else "=".join((prefix, postfix.strip()))
toolset = []
if WASM:
if "wasm" in flags:
toolset.append("toolset=emscripten")
build_dependency(
f"boost-{BOOST_VERSION}",
@@ -1267,7 +1235,7 @@ if "boost" in targets:
# patch="./patches/boost/boostorg_regex_62.patch",
download_name=f"boost-{BOOST_VERSION}-b2-nodocs.tar.gz",
)
if WASM:
if "wasm" in flags:
# only supported on nix for now
run(
("find", ".", "-name", "*.bc", "-exec", "bash", "-c", "emar q ${1%.bc}.a $1", "bash", "{}", ";"),
@@ -1275,8 +1243,8 @@ if "boost" in targets:
)
if "cgal" in targets:
gmp_args: list[str] = []
mpfr_args: list[str] = []
gmp_args: "list[str]" = []
mpfr_args: "list[str]" = []
OLD_HOST_CC = None
if WASM:
@@ -1309,7 +1277,9 @@ if "cgal" in targets:
name=f"gmp-{GMP_VERSION}",
mode="autoconf",
build_tool_args=[ENABLE_FLAG, DISABLE_FLAG, "--with-pic", *gmp_args],
pre_compile_subs=([("build/config.h", "HAVE_OBSTACK_VPRINTF 1", "HAVE_OBSTACK_VPRINTF 0")] if WASM else []),
pre_compile_subs=(
[("build/config.h", "HAVE_OBSTACK_VPRINTF 1", "HAVE_OBSTACK_VPRINTF 0")] if "wasm" in flags else []
),
patch=gmp_patches,
# Sometimes ftp.gnu.org is very slow, use ftpmirror.gnu.org as a workaround.
download_url="https://ftpmirror.gnu.org/gnu/gmp/",
@@ -1441,24 +1411,24 @@ os.makedirs(ifcos_build_dir, exist_ok=True)
cmake_args = [
"-DUSE_MMAP=OFF",
f"-DBUILD_EXAMPLES={OFF_ON[BUILD_EXAMPLES]}",
"-DBUILD_SHARED_LIBS=" + OFF_ON[not IFCOPENSHELL_STATIC],
"-DBUILD_EXAMPLES=OFF",
"-DBUILD_SHARED_LIBS=" + OFF_ON[not BUILD_STATIC],
"-DGLTF_SUPPORT=ON",
"-DBoost_NO_BOOST_CMAKE=On",
"-DCREATE_BUNDLE=On",
"-DADD_COMMIT_SHA=" + OFF_ON[ADD_COMMIT_SHA],
"-DVERSION_OVERRIDE=" + OFF_ON[ADD_COMMIT_SHA],
"-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)
@@ -1471,7 +1441,7 @@ def get_cmake_args_prefix_path(additional_paths: Sequence[str] = ()) -> list[str
return [f"-DCMAKE_PREFIX_PATH={prefix_path}"]
if WASM:
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")
@@ -1537,25 +1507,24 @@ if "rocksdb" in targets:
)
if "swig" in targets:
# `cmake_args_prefix_path` won't work on wasm
# because `find_program` in emscripten toolchain don't use `find_root_path`.
# As a workaround we provide executable path directly on all platforms.
cmake_args.append(f"-DSWIG_EXECUTABLE={DEPS_DIR}/install/swig-{SWIG_VERSION}/bin/swig")
cmake_args_prefix_path.append(f"{DEPS_DIR}/install/swig-{SWIG_VERSION}")
if os.environ.get("QT_DIR"):
cmake_args_prefix_path.append(os.environ["QT_DIR"])
cmake_args.append(f"-DQT_DIR={os.environ['QT_DIR']}")
build_bonsaiviewer = BUILD_BONSAIVIEWER or "BonsaiViewer" in targets
ifcos_build_args = [
f"-DBUILD_IFCGEOM={OFF_ON['IfcGeom' in targets]}",
f"-DBUILD_GEOMSERVER={OFF_ON['IfcGeomServer' in targets]}",
f"-DBUILD_CONVERT={OFF_ON['IfcConvert' in targets]}",
f"-DBUILD_BONSAIVIEWER={OFF_ON['BonsaiViewer' in targets]}",
f"-DBUILD_BONSAIVIEWER={OFF_ON[build_bonsaiviewer]}",
f"-DCMAKE_INSTALL_PREFIX={DEPS_DIR}/install/ifcopenshell",
]
if not WASM and (
"BonsaiViewer" in targets
build_bonsaiviewer
or not explicit_targets
or {"IfcGeom", "IfcConvert", "IfcGeomServer", "BonsaiViewer"} & set(explicit_targets)
):
@@ -1581,10 +1550,10 @@ if "IfcOpenShell-Python" in targets:
def compile_python_wrapper(
python_version: str,
python_include: str | None = None,
python_executable: str | None = None,
python_path: Path | None = None,
) -> str | None:
python_include: Union[str, None] = None,
python_executable: Union[str, None] = None,
python_path: Union[Path, None] = None,
) -> Union[str, None]:
"""
:return: Path to module dir if ``python_executable`` was provided, otherwise ``None``.
"""
@@ -1652,7 +1621,7 @@ if "IfcOpenShell-Python" in targets:
if platform.system() != "Darwin":
if BUILD_CFG == "Release":
for so in glob.glob(os.path.join(module_dir, "*.so")):
if WASM:
if "wasm" in flags:
run(["wasm-strip", so, "-k", "dylink.0"])
elif os.path.basename(so).startswith("_ifcopenshell_wrapper"):
# TODO: This symbol name depends on the Python version?
@@ -1662,7 +1631,7 @@ if "IfcOpenShell-Python" in targets:
return module_dir
if WASM:
if "wasm" in flags:
compile_python_wrapper(
run(["pyodide", "config", "get", "python_version"]),
run(["pyodide", "config", "get", "python_include_dir"]),
+2 -10
View File
@@ -28,13 +28,5 @@ since it's pure cmake without any additional moving parts.
- clone IfcOpenShell repo next to it to `IfcOpenShell` folder
- run `python nix/build-all.py -wasm -py-313` in `IfcOpenShell`
- it will produce Python package in `IfcOpenShell/ifcopenshell`
- run `python pyodide/build-all-pack-wheel-local.py`, it will
- clean up previous wheels
- run `pyodide build`
- prepare standalone and modular wheels
- produce final wheels in `IfcOpenShell/dist` and `IfcOpenshell/dist-modular`
- testing:
- ensure you're in pyodide environment
- `cd IfcOpenshell/pyodide`
- `./run_pytest.py setup`
- `./run_pytest.py run`
- run `pyodide build`
- it will produce a wheel in `IfcOpenShell/dist`
View File
-38
View File
@@ -1,38 +0,0 @@
#!/usr/bin/env python3
"""Intended to be run after nix/build-all.py has finished the wasm build."""
import shutil
import subprocess
from pathlib import Path
def get_repo_root() -> Path:
output = subprocess.check_output(["git", "rev-parse", "--show-toplevel"], text=True)
return Path(output.strip())
def run(cmd: list[str], **kwargs) -> None:
print("$", " ".join(cmd))
subprocess.check_call(cmd, **kwargs)
def main() -> None:
repo_root = get_repo_root()
shutil.rmtree(repo_root / "dist", ignore_errors=True)
shutil.rmtree(repo_root / "dist_modular", ignore_errors=True)
run(["pyodide", "build"], cwd=repo_root)
shutil.rmtree(repo_root / "ifcopenshell", ignore_errors=True)
(repo_root / "setup.py").unlink(missing_ok=True)
run(["git", "restore", "pyproject.toml"], cwd=repo_root)
wheel = next((repo_root / "dist").glob("ifcopenshell-*.whl"))
run(["uv", "run", "pyodide/order_pyodide_wheel_shared_objects.py", str(wheel)], cwd=repo_root)
run(
["uv", "run", "pyodide/split_pyodide_ifcopenshell_wheel.py", str(wheel), "dist-modular/"],
cwd=repo_root,
)
if __name__ == "__main__":
main()
+7 -9
View File
@@ -1,8 +1,10 @@
#!/usr/bin/bash
set -ex
PYODIDE_VERSION=0.29.4
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
PYODIDE_VERSION=0.29.3
PYODIDE_BUILD_VERSION=0.33.0
PYODIDE_XBUILDENV_ROOT="${HOME}/.cache/.pyodide-xbuildenv-${PYODIDE_BUILD_VERSION}"
PYODIDE_XBUILDENV="${PYODIDE_XBUILDENV_ROOT}/${PYODIDE_VERSION}"
# Script is assuming that it will be possible to execute it multiple times
# therefore we're clearing venv each time and ignoring existing 'emsdk' folder.
@@ -14,14 +16,12 @@ source .venv/bin/activate
# Install pyodide cross build environment.
# Instructions: https://pyodide.org/en/stable/development/building-packages.html
uv pip install -r "${SCRIPT_DIR}/requirements.txt"
uv pip install "pyodide-build==${PYODIDE_BUILD_VERSION}"
# `uv run` is required, so xbuildenv would skip using `pip`.
uv run pyodide xbuildenv install "${PYODIDE_VERSION}"
uv run pyodide xbuildenv install-emscripten
# Cache path includes a hash segment that varies by pyodide-build version,
# so query it instead of constructing it manually.
EMSDK_ROOT=$(uv run pyodide config get emsdk_dir)
EMSDK_ROOT="${PYODIDE_XBUILDENV}/emsdk"
[ -f "${EMSDK_ROOT}/emsdk_env.sh" ] && source "${EMSDK_ROOT}/emsdk_env.sh"
[ -f "${EMSDK_ROOT}/../../emsdk_env.sh" ] && source "${EMSDK_ROOT}/../../emsdk_env.sh"
which emcc
@@ -29,10 +29,8 @@ emcc --version
mkdir -p packages/ifcopenshell
VERSION=`cat IfcOpenShell/VERSION`
# Normalize to the canonical PEP 440 form (e.g. 0.9.0alpha0 -> 0.9.0a0).
VERSION=`python3 -c "from packaging.version import Version; print(Version('$VERSION'))"`
cp IfcOpenShell/pyodide/meta.yaml packages/ifcopenshell
sed -i s/9.9.9/$VERSION/g packages/ifcopenshell/meta.yaml
sed -i s/0.8.0/$VERSION/g packages/ifcopenshell/meta.yaml
# Use custom build ifcopenshell directory in build-all to make caching simpler
# Otherwise pyodide build path typically includes package version, so cached cmake configs might break.
+1 -2
View File
@@ -1,7 +1,6 @@
package:
name: ifcopenshell
# Placeholder, replaced by build_pyodide.sh with the actual version from VERSION file.
version: 9.9.9
version: 0.8.0
source:
# meta.yaml is placed as `packages/ifcopenshell/meta.yaml`.
+5 -17
View File
@@ -1,18 +1,6 @@
#!/usr/bin/env python3
# /// script
# ///
# This file was generated with the assistance of an AI coding tool.
"""Order Pyodide wheel shared objects so wasm side modules load safely.
Pyodide's package loader loads a wheel's bundled ``.so`` files in the order
they appear in the wheel's zip.
If a ``.so`` that depends on symbols from another ``.so`` is loaded first,
loading fails with errors like
- "Failed to load dynamic library"
- "Dynamic linking error: cannot resolve symbol"
This is a known issue upstream - https://github.com/pyodide/pyodide/issues/6020.
"""
"""Order Pyodide wheel shared objects so wasm side modules load safely."""
from __future__ import annotations
@@ -34,10 +22,10 @@ SCHEMA_ORDER = {
}
MAIN_SHARED_OBJECT_RE = re.compile(r"^_ifcopenshell_wrapper(?:\.|$)")
SCHEMA_PLUGIN_RE = re.compile(r"^ifcopenshell_parse_schema_(.+)\.so$")
MAPPING_PLUGIN_RE = re.compile(r"^ifcopenshell_geometry_mapping_(.+)\.so$")
DOCUMENT_PLUGIN_RE = re.compile(r"^ifcopenshell_document_[a-z0-9]+(?:_(.+))?\.so$")
GEOMETRY_SERIALIZATION_PLUGIN_RE = re.compile(r"^ifcopenshell_geometry_writer_(.+)\.so$")
SCHEMA_PLUGIN_RE = re.compile(r"^ifcopenshell\.parse\.schema\.([^.]+)\.so$")
MAPPING_PLUGIN_RE = re.compile(r"^ifcopenshell\.geometry\.mapping\.([^.]+)\.so$")
DOCUMENT_PLUGIN_RE = re.compile(r"^ifcopenshell\.document\.[^.]+\.([^.]+)\.so$")
GEOMETRY_SERIALIZATION_PLUGIN_RE = re.compile(r"^ifcopenshell\.geometry\.serialization\.([^.]+)\.so$")
def schema_key(schema: str) -> tuple[int, str]:
-1
View File
@@ -1 +0,0 @@
pyodide-build==0.39.0
-62
View File
@@ -1,62 +0,0 @@
#!/usr/bin/env python3
import argparse
import shlex
import shutil
import subprocess
from pathlib import Path
SCRIPT_DIR = Path(__file__).parent
DIST_DIRS = (
SCRIPT_DIR / "test/pyodide",
SCRIPT_DIR / "test/pyodide-modular",
)
WHEEL_SRCS = (
SCRIPT_DIR / "../dist",
SCRIPT_DIR / "../dist-modular",
)
def run(cmd: list, **kwargs) -> None:
print("$", shlex.join(str(part) for part in cmd))
subprocess.check_call(cmd, **kwargs)
def setup() -> None:
run(["uv", "pip", "install", "pytest-pyodide"])
# Copy pyodide installation so we can modify it locally just for tests.
pyodide_root = subprocess.check_output(["pyodide", "config", "get", "pyodide_root"], text=True).strip()
pyodide_root_dist = Path(pyodide_root) / "dist"
for dist_dir in DIST_DIRS:
if dist_dir.exists():
shutil.rmtree(dist_dir)
shutil.copytree(pyodide_root_dist, dist_dir)
def run_tests() -> None:
for dist_dir, wheel_src in zip(DIST_DIRS, WHEEL_SRCS):
if not wheel_src.exists():
raise RuntimeError(f"error: {wheel_src} does not exist")
# Clean up previous wheels.
for whl in dist_dir.glob("ifcopenshell*.whl"):
whl.unlink()
# Symlink new ones.
for whl in wheel_src.glob("ifcopenshell*.whl"):
(dist_dir / whl.name).symlink_to(whl.resolve())
for dist_dir in DIST_DIRS:
run(["pytest", f"--dist-dir={dist_dir}", "--capture=no"], cwd=SCRIPT_DIR)
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("command", choices=["setup", "run"])
args = parser.parse_args()
if args.command == "setup":
setup()
else:
run_tests()
+1 -16
View File
@@ -1,19 +1,5 @@
#!/usr/bin/env python3
# /// script
# ///
"""Split optional IfcOpenShell Pyodide payloads into separate wheels.
The main wheel bundles per-schema plugin ``.so`` files and pure Python
subpackages that most browser sessions probably don't need.
This splits each of those out into its own installable wheel,
so a Pyodide app can fetch just the base wheel plus whichever schema/plugin wheels it actually needs.
Resulting wheels (roughly):
- ifcopenshell.whl (main ifcopenshell.py files + _ifcopenshell_wrapper)
- ifcopenshell_pure_python.whl (api, express, python files only)
- splitted wheels with a single .so binary - e.g. `ifcopenshell_parse_schema_ifc4.whl`
"""
"""Split optional IfcOpenShell Pyodide payloads into separate wheels."""
from __future__ import annotations
@@ -173,7 +159,6 @@ def build_wheel(
entries[record_name] = None
write_record(zf, entries, record_name)
print(f"Splitting wheel to '{out}'.")
return out
-2
View File
@@ -1,2 +0,0 @@
pyodide
pyodide-modular
View File
+11 -22
View File
@@ -1,30 +1,18 @@
import zipfile
from pathlib import Path
from ..order_pyodide_wheel_shared_objects import shared_object_sort_key
WHEEL_FILENAME = next(
p.name for p in (Path.cwd() / "pyodide").iterdir() if p.name.startswith("ifcopenshell-") and p.suffix == ".whl"
)
def _first_so_name(wheel_path: Path) -> str:
with zipfile.ZipFile(wheel_path) as zf:
for name in zf.namelist():
if name.endswith(".so"):
return Path(name).name
return wheel_path.name
def test_ifcopenshell_import(selenium, request):
dist_dir = Path(request.config.getoption("--dist-dir"))
wheel_paths = list(dist_dir.glob("ifcopenshell*.whl"))
wheel_paths.sort(key=lambda path: shared_object_sort_key(_first_so_name(path), 0))
WHEEL_NAMES = tuple(path.name for path in wheel_paths)
def test_ifcopenshell_import(selenium):
selenium.load_package("micropip")
selenium.run_async(f"""
# Important to test it with `micropip.install`
# without any dependencies loaded to ensure micropip will load them automatically.
selenium.run_async(
f"""
import micropip
wheel_filenames = {WHEEL_NAMES!r}
for wheel_filename in wheel_filenames:
print(f"Loading {{wheel_filename}}...")
await micropip.install(f"./{{wheel_filename}}")
await micropip.install(f"./{WHEEL_FILENAME}")
import ifcopenshell
from pathlib import Path
ifcopenshell.set_plugin_search_paths([str(Path(ifcopenshell.__file__).parent)])
@@ -36,4 +24,5 @@ def test_ifcopenshell_import(selenium, request):
wall.Name = "Test"
assert wall.Name == "Test", f"Entity name wasn't changed: {{wall}}"
print(wall)
""")
"""
)
+10 -21
View File
@@ -9,7 +9,6 @@ line-length = 120
include = '''
src/.*.pyi?$
|nix/.*.pyi?$
|pyodide/.*.pyi?$
'''
extend-exclude = '''
src/ifcopenshell-python/ifcopenshell/express/rules/*
@@ -63,30 +62,20 @@ select = [
#
"FA", # future annotations
"UP", # pyupgrade
"unnecessary-iterable-allocation-for-first-element",
"unsorted-dunder-all",
"RUF015", # next() > list_comprehension[0]
"RUF022", # sort __all__
"I", # import sorting
"unused-noqa",
"rule-codes-in-selectors",
"noqa-comments",
"rule-codes-in-suppression-comments",
# General util rules.
"invalid-rule-code",
"redirected-noqa",
"invalid-pyproject-toml",
"invalid-suppression-comment",
]
ignore = [
# Conflicts with Blender using annotations for props definitions.
"future-rewritable-type-annotation",
"FA100", # Conflicts with Blender using annotations for props definitions.
# Maybe will enable later:
"non-pep604-annotation-union", # Union[X,Y] to X | Y
"non-pep604-annotation-optional", # Optional to X | None
"redundant-open-modes", # Unnecessary mode argument
"yield-in-for-loop", # yield for -> yield from
"format-literals", # implicit references for positional format fields
"printf-string-formatting", # Replace % with .format
"f-string", # Replace .format with f-string
"UP007", # Optional to X | Y
"UP045", # Optional to X | None
"UP015", # Unnecessary mode argument
"UP028", # yield for -> yield from
"UP030", # implicit references for positional format fields
"UP031", # Replace % with .format
"UP032", # Replace .format with f-string
]
[tool.ty.rules]
+3 -3
View File
@@ -10,7 +10,7 @@ name = "bcf-client"
# author = "IfcOpenShell"
description = "BCF-XML file handler."
readme = "README.md"
requires-python = ">=3.10"
requires-python = ">=3.8"
keywords = ["IFC", "BCF", "BIM"]
dependencies = [
"xsdata>=24.4",
@@ -65,6 +65,6 @@ commands = pytest --cov --cov-report=term tests
[tool.ruff]
extend = "../../pyproject.toml"
lint.extend-select = [
"unused-import", # unused imports
lint.select = [
"F401", # unused imports
]
+11 -13
View File
@@ -42,12 +42,10 @@ endif
IS_STABLE:=FALSE
VERSION:=$(shell cat ../../VERSION)
VERSION_BASE:=$(shell sed -E 's/[[:alpha:]]+[0-9]+$$//' ../../VERSION)
VERSION_PYTHON:=$(shell sed 's/alpha/a/' ../../VERSION)
VERSION_SEMVER:=$(shell sed -E 's/([[:alpha:]]+)([0-9]+)$$/-\\1\\2/' ../../VERSION)
VERSION_MAJOR:=$(shell cat '../../VERSION' | cut -d '.' -f 1)
VERSION_MINOR:=$(shell cat '../../VERSION' | cut -d '.' -f 2)
VERSION_PATCH:=$(shell cat '../../VERSION' | cut -d '.' -f 3)
VERSION_DATE:=$(shell date '+%y%m%d')
VERSION_DAILY:=$(VERSION_BASE)a$(VERSION_DATE)
VERSION_SEMVER_DAILY:=$(VERSION_BASE)-alpha$(VERSION_DATE)
LAST_COMMIT_HASH:=$(shell git rev-parse HEAD)
LAST_COMMIT_DATE:=$(shell git show -s --format=%cI)
LAST_GIT_BRANCH:=$(shell git rev-parse --abbrev-ref HEAD)
@@ -262,14 +260,14 @@ endif
cp pyproject.toml build/
ifeq ($(IS_STABLE), TRUE)
$(SED) "s/0.0.0/$(VERSION_SEMVER)/" build/bonsai/blender_manifest.toml
$(SED) 's/version = "0.0.0"/version = "$(VERSION_PYTHON)"/' build/pyproject.toml
$(SED) "s/0.0.0/$(VERSION)/" build/bonsai/blender_manifest.toml
$(SED) 's/version = "0.0.0"/version = "$(VERSION)"/' build/pyproject.toml
else
$(SED) "s/0.0.0/$(VERSION_SEMVER_DAILY)/" build/bonsai/blender_manifest.toml
$(SED) "s/0.0.0/$(VERSION)-alpha$(VERSION_DATE)/" build/bonsai/blender_manifest.toml
$(SED) "s/8888888/$(LAST_COMMIT_HASH)/" build/bonsai/__init__.py
$(SED) "s/9999999/$(LAST_COMMIT_DATE)/" build/bonsai/__init__.py
$(SED) "s/7777777/$(LAST_GIT_BRANCH)/" build/bonsai/__init__.py
$(SED) 's/version = "0.0.0"/version = "$(VERSION_DAILY)"/' build/pyproject.toml
$(SED) 's/version = "0.0.0"/version = "$(VERSION)-alpha$(VERSION_DATE)"/' build/pyproject.toml
endif
# Blender 5.1+ requires Python 3.13.
@@ -281,9 +279,9 @@ endif
# Provides bonsai Add-on functionality
ifeq ($(IS_STABLE), TRUE)
$(SED) 's/version = "0.0.0"/version = "$(VERSION_PYTHON)"/' build/pyproject.toml
$(SED) 's/version = "0.0.0"/version = "$(VERSION)"/' build/pyproject.toml
else
$(SED) 's/version = "0.0.0"/version = "$(VERSION_DAILY)"/' build/pyproject.toml
$(SED) 's/version = "0.0.0"/version = "$(VERSION)a$(VERSION_DATE)"/' build/pyproject.toml
endif
cd build && . env/$(VENV_ACTIVATE) && $(PYTHON) -m build
cp build/dist/*.whl build/wheels/
@@ -317,9 +315,9 @@ endif
rm -rf build/bonsai/libs/
ifeq ($(IS_STABLE), TRUE)
cd build && zip -r bonsai_$(PYVERSION)-$(VERSION_SEMVER)-$(BLENDER_PLATFORM).zip ./bonsai
cd build && zip -r bonsai_$(PYVERSION)-$(VERSION)-$(BLENDER_PLATFORM).zip ./bonsai
else
cd build && zip -r bonsai_$(PYVERSION)-$(VERSION_SEMVER_DAILY)-$(BLENDER_PLATFORM).zip ./bonsai
cd build && zip -r bonsai_$(PYVERSION)-$(VERSION)-alpha$(VERSION_DATE)-$(BLENDER_PLATFORM).zip ./bonsai
endif
mv build/bonsai*.zip dist/
+8 -3
View File
@@ -185,10 +185,13 @@ class IfcStore:
os.makedirs(os.path.dirname(cache_path), exist_ok=True)
IfcStore.cache_path = cache_path
cache_path = Path(IfcStore.cache_path)
settings = ifcopenshell.geom.settings()
cache_settings = ifcopenshell.geom.settings()
serializer_settings = ifcopenshell.geom.serializer_settings()
cache_preexists = cache_path.exists()
try:
IfcStore.cache = ifcopenshell.geom.serializers.hdf5(IfcStore.cache_path, settings)
IfcStore.cache = ifcopenshell.geom.serializers.hdf5(
IfcStore.cache_path, cache_settings, serializer_settings
)
if cache_preexists:
print(f"Successfully loaded existing cache: {cache_path.name}.")
else:
@@ -203,7 +206,9 @@ class IfcStore:
os.remove(IfcStore.cache_path)
try:
IfcStore.cache = ifcopenshell.geom.serializers.hdf5(IfcStore.cache_path, settings)
IfcStore.cache = ifcopenshell.geom.serializers.hdf5(
IfcStore.cache_path, cache_settings, serializer_settings
)
print("New cache was created.")
except Exception as e:
print(f"Failed to create a cache: {str(e)}.")
+5 -5
View File
@@ -740,7 +740,7 @@ class IfcImporter:
self.update_progress((percent_average / 100 * progress_range) + start_progress)
shape = iterator.get()
if shape:
assert isinstance(shape, W.triangulation_element)
assert isinstance(shape, W.TriangulationElement)
product = self.file.by_id(shape.id)
self.create_product(product, shape)
results.add(product)
@@ -1079,9 +1079,9 @@ class IfcImporter:
def create_curve(
self,
element: ifcopenshell.entity_instance,
shape: Union[W.triangulation, W.triangulation_element],
shape: Union[W.Triangulation, W.TriangulationElement],
) -> bpy.types.Curve:
if isinstance(shape, W.triangulation_element):
if isinstance(shape, W.TriangulationElement):
geometry = shape.geometry
else:
geometry = shape
@@ -1112,11 +1112,11 @@ class IfcImporter:
def create_mesh(
self,
element: ifcopenshell.entity_instance,
shape: Union[W.triangulation, W.triangulation_element],
shape: Union[W.Triangulation, W.TriangulationElement],
cartesian_point_offset: Union[npt.NDArray[np.float64], Literal[False]] = None,
) -> Union[bpy.types.Mesh, None]:
try:
if isinstance(shape, W.triangulation_element):
if isinstance(shape, W.TriangulationElement):
# shape is ShapeElementType
geometry = shape.geometry
else:
@@ -678,7 +678,7 @@ class AddBoundary(bpy.types.Operator, tool.Ifc.Operator):
# Identify all potential building elements
# TODO: don't select everything, use AABB culling in Blender
building_elements = list(
building_elements = (
tool.Ifc.get().by_type("IfcWall")
+ tool.Ifc.get().by_type("IfcSlab")
+ tool.Ifc.get().by_type("IfcVirtualElement")
@@ -708,7 +708,7 @@ class AddBoundary(bpy.types.Operator, tool.Ifc.Operator):
while True:
tree.add_element(iterator.get_native())
shape = iterator.get()
assert isinstance(shape, W.triangulation_element)
assert isinstance(shape, W.TriangulationElement)
shapes[shape.id] = {
"verts": ifcopenshell.util.shape.get_vertices(shape.geometry),
"faces": ifcopenshell.util.shape.get_faces(shape.geometry),
@@ -348,7 +348,10 @@ class AddClassificationReference(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
if self.obj_type == "Object":
objects = [o.name for o in tool.Blender.get_selected_objects()]
if context.selected_objects:
objects = [o.name for o in context.selected_objects]
else:
objects = [context.active_object.name]
else:
objects = [self.obj]
props = tool.Classification.get_classification_props()
@@ -516,7 +516,7 @@ def _world_segment_to_screen_pixels(
# ---------------------------------------------------------------------------
class BIM_GT_box_face_quad(bpy.types.Gizmo):
class BIM_GT_box_face_quad(bpy.types.Gizmo): # noqa: N801 — Blender bl_idname convention
"""Near-invisible face-quad click target with drag-to-resize modal.
Geometry: a unit quad in the local XY plane at z=0. The adapter
@@ -620,7 +620,7 @@ class BIM_GT_box_face_quad(bpy.types.Gizmo):
return {"RUNNING_MODAL"}
class BIM_GT_box_face_outline(bpy.types.Gizmo):
class BIM_GT_box_face_outline(bpy.types.Gizmo): # noqa: N801 — Blender bl_idname convention
"""Thin non-interactive colored edge outline for one face.
Drawn as 4 line segments in the face plane. The layout helper
@@ -160,7 +160,7 @@ def _make_face_set_cb(gz: Any, group: Any, axis: int, is_max: bool):
return setter
class OBJECT_GGT_bim_clip_box(bpy.types.GizmoGroup):
class OBJECT_GGT_bim_clip_box(bpy.types.GizmoGroup): # noqa: N801 — Blender bl_idname convention
"""Face-quad resize handles on the active clip box.
Renders six near-invisible click-target quads and six colored edge
@@ -987,7 +987,7 @@ class ExportCostSchedulesToPDF(bpy.types.Operator, ExportHelper):
@classmethod
def poll(cls, context):
try:
import typst # ruff: ignore[unused-import]
import typst # noqa: F401
return True
except ModuleNotFoundError:
@@ -313,7 +313,7 @@ class CreateAllShapes(bpy.types.Operator):
failures.append(element)
print("***** FAILURE *****")
if shape:
assert isinstance(shape, W.triangulation_element)
assert isinstance(shape, W.TriangulationElement)
geom = shape.geometry
print(
f"Success {time.time() - start:.3f}s "
@@ -28,7 +28,7 @@ operators via ``target_set_operator``; drag handles inherit modal state
from ``GizmoMovable``.
"""
__all__ = [ # ruff: ignore[unsorted-dunder-all]
__all__ = [ # noqa: RUF022 (unsorted `__all__`)
"GizmoColor",
"GizmoAxis",
"TextAlignment",
@@ -5660,7 +5660,7 @@ class BaseParametricGizmoGroup:
"""
return 0.0
def _update_view_dependent_dimensions(self, context: bpy.types.Context, mw: Matrix, props) -> None:
def _update_view_dependent_dimensions(self, context: bpy.types.Context, mw: Matrix, props) -> None: # noqa: ARG002
"""Update overall_width, overall_height, and lining_offset based on view direction.
This base implementation handles the common pattern for door/window gizmos.
@@ -5837,7 +5837,7 @@ class BaseParametricGizmoGroup:
self.update_dimension_gizmos(mw, props)
self._refresh_element_specific(context, mw, props)
def _refresh_element_specific(self, context: bpy.types.Context, mw: "Matrix", props) -> None:
def _refresh_element_specific(self, context: bpy.types.Context, mw: "Matrix", props) -> None: # noqa: ARG002
"""Override for element-specific refresh logic.
Called from both refresh() (on state change) and draw_prepare() (per frame),
@@ -6344,7 +6344,7 @@ class BaseParametricGizmoGroup:
"""
return (0.0, 0.0)
def get_icon_y_offset(self, context: bpy.types.Context, mw: Matrix) -> float:
def get_icon_y_offset(self, context: bpy.types.Context, mw: Matrix) -> float: # noqa: ARG002
"""Get Y offset for icons based on view direction.
Uses get_icon_y_extent() to determine how far to offset icons based on
@@ -6546,7 +6546,9 @@ class BaseParametricGizmoGroup:
self._refresh_element_specific(context, mw, props)
def _update_dimension_gizmo_positions(self, context: bpy.types.Context, mw: "Matrix", props) -> None:
def _update_dimension_gizmo_positions(
self, context: bpy.types.Context, mw: "Matrix", props # noqa: ARG002
) -> None:
"""Update dimension gizmo positions based on view direction.
Override this method in subclasses to implement view-dependent
@@ -1406,28 +1406,31 @@ class CreateDrawing(bpy.types.Operator):
# Backwards compatibility with older ifcopenshell builds that don't expose these keys.
pass
self.svg_buffer = ifcopenshell.geom.serializers.buffer()
self.svg_settings.set("svg-without-storeys", True)
self.svg_settings.set("svg-write-poly", True)
self.svg_settings.set("svg-poly", True)
self.serialiser_settings = ifcopenshell.geom.serializer_settings()
self.serialiser_settings.set("svg-without-storeys", True)
self.serialiser_settings.set("svg-write-poly", True)
self.serialiser_settings.set("svg-poly", True)
# Objects with more than these edges are rendered as wireframe instead of HLR for optimisation
self.svg_settings.set("profile-threshold", 10000)
self.svg_settings.set("svg-xmlns", True)
self.svg_settings.set("svg-project", True)
self.svg_settings.set("auto-elevation", False)
self.svg_settings.set("auto-section", False)
self.svg_settings.set("print-space-names", False)
self.svg_settings.set("print-space-areas", False)
self.svg_settings.set("door-arcs", False)
self.svg_settings.set("svg-no-css", True)
self.svg_settings.set("elevation-ref-guid", self.camera_element.GlobalId)
self.svg_settings.set("scale", str(self.scale))
self.svg_settings.set("svg-subtract-before", "always")
self.svg_settings.set("svg-prefilter", True) # See #3359
self.svg_settings.set("svg-unify-inputs", True)
self.svg_settings.set("svg-segment-projection", True)
self.serialiser_settings.set("profile-threshold", 10000)
self.serialiser_settings.set("svg-xmlns", True)
self.serialiser_settings.set("svg-project", True)
self.serialiser_settings.set("auto-elevation", False)
self.serialiser_settings.set("auto-section", False)
self.serialiser_settings.set("print-space-names", False)
self.serialiser_settings.set("print-space-areas", False)
self.serialiser_settings.set("door-arcs", False)
self.serialiser_settings.set("svg-no-css", True)
self.serialiser_settings.set("elevation-ref-guid", self.camera_element.GlobalId)
self.serialiser_settings.set("scale", str(self.scale))
self.serialiser_settings.set("svg-subtract-before", "always")
self.serialiser_settings.set("svg-prefilter", True) # See #3359
self.serialiser_settings.set("svg-unify-inputs", True)
self.serialiser_settings.set("svg-segment-projection", True)
if target_view == "REFLECTED_PLAN_VIEW":
self.svg_settings.set("svg-mirror-y", True)
self.serialiser = ifcopenshell.geom.serializers.svg(self.svg_buffer, self.svg_settings)
self.serialiser_settings.set("svg-mirror-y", True)
self.serialiser = ifcopenshell.geom.serializers.svg(
self.svg_buffer, self.svg_settings, self.serialiser_settings
)
# tree = ifcopenshell.geom.tree()
# This instructs the tree to explode BReps into faces and return
# the style of the face when running tree.select_ray()
@@ -72,10 +72,11 @@ class ExportOBJ(bpy.types.Operator):
# Conversion from IFC to OBJ
# Settings for obj
settings = ifcopenshell.geom.settings()
serializer_settings = ifcopenshell.geom.serializer_settings()
settings.set("dimensionality", ifcopenshell.ifcopenshell_wrapper.SURFACES_AND_SOLIDS)
settings.set("apply-default-materials", True)
settings.set("use-element-guids", True)
serializer_settings.set("use-element-guids", True)
settings.set("use-world-coords", True)
ifc_file: ifcopenshell.file
@@ -89,7 +90,7 @@ class ExportOBJ(bpy.types.Operator):
obj_file_path = os.path.join(output_dir, "model.obj")
mtl_file_path = os.path.join(output_dir, "model.mtl")
serialiser = ifcopenshell.geom.serializers.obj(obj_file_path, mtl_file_path, settings)
serialiser = ifcopenshell.geom.serializers.obj(obj_file_path, mtl_file_path, settings, serializer_settings)
serialiser.setFile(ifc_file)
serialiser.setUnitNameAndMagnitude("METER", 1.0)
serialiser.writeHeader()
@@ -106,7 +107,7 @@ class ExportOBJ(bpy.types.Operator):
if iterator.initialize():
while True:
shape = iterator.get()
assert isinstance(shape, W.triangulation_element)
assert isinstance(shape, W.TriangulationElement)
materials = shape.geometry.materials
for material in materials:
+1 -1
View File
@@ -430,7 +430,7 @@ class SverchokData:
@classmethod
def has_sverchok(cls) -> bool:
try:
import sverchok # ruff: ignore[unused-import]
import sverchok # noqa: F401
return True
except ModuleNotFoundError:
+6 -4
View File
@@ -560,7 +560,7 @@ class AddDoor(bpy.types.Operator, tool.Ifc.Operator):
)
update_door_modifier_representation(obj)
def _execute(self, context: bpy.types.Context) -> set[str]:
def _execute(self, context: bpy.types.Context) -> set[str]: # noqa: ARG002
for obj in tool.Blender.get_selected_objects():
if not tool.Blender.Modifier.is_eligible_for_door_modifier(obj):
continue
@@ -638,7 +638,7 @@ class RemoveDoor(bpy.types.Operator, tool.Ifc.Operator):
pset = tool.Pset.get_element_pset(element, "BBIM_Door")
ifcopenshell.api.pset.remove_pset(tool.Ifc.get(), product=element, pset=pset)
def _execute(self, context: bpy.types.Context) -> set[str]:
def _execute(self, context: bpy.types.Context) -> set[str]: # noqa: ARG002
for obj in tool.Blender.get_selected_objects():
self.remove_door_on_object(obj)
return {"FINISHED"}
@@ -683,7 +683,7 @@ class ToggleDoorSwing(bpy.types.Operator, tool.Ifc.Operator):
return True
return False
def _execute(self, context: bpy.types.Context) -> set[str]:
def _execute(self, context: bpy.types.Context) -> set[str]: # noqa: ARG002
obj = tool.Blender.get_active_object()
if not obj:
return {"CANCELLED"}
@@ -909,7 +909,9 @@ class GizmoDoorEdition(bpy.types.GizmoGroup, gizmo.BaseParametricGizmoGroup):
setattr(self, f"gizmo_swing_arc_{cfg.name}", main)
setattr(self, f"gizmo_swing_arc_{cfg.name}_flip", flip)
def _refresh_element_specific(self, context: bpy.types.Context, mw: Matrix, props: "BIMDoorProperties") -> None:
def _refresh_element_specific(
self, context: bpy.types.Context, mw: Matrix, props: "BIMDoorProperties" # noqa: ARG002
) -> None:
"""Update door-specific swing arc gizmos."""
self.update_swing_gizmos(mw, props)
+2 -2
View File
@@ -765,7 +765,7 @@ class GizmoRoofEdition(bpy.types.GizmoGroup, gizmo.BaseParametricGizmoGroup):
def is_element_type(cls, element: ifcopenshell.entity_instance) -> bool:
return tool.Parametric.is_roof(element)
def _update_dimension_gizmo_positions(self, context: bpy.types.Context, mw, props) -> None:
def _update_dimension_gizmo_positions(self, context: bpy.types.Context, mw, props) -> None: # noqa: ARG002
"""Anchor every dimension gizmo at the object origin. Each gizmo's
declared axis (height/slope along +Z, thickness along -Z) separates
them in 3D so they don't visually collide despite sharing a
@@ -776,7 +776,7 @@ class GizmoRoofEdition(bpy.types.GizmoGroup, gizmo.BaseParametricGizmoGroup):
self.set_dimension_gizmo_position("angle", mw, origin, (0, 0, 1))
self.set_dimension_gizmo_position("roof_thickness", mw, origin, (0, 0, -1))
def get_element_height(self, props) -> float:
def get_element_height(self, props) -> float: # noqa: ARG002
"""Object-local Z of the mesh's topmost vertex, so the pen / validate /
cancel / cycle row anchors visibly above sloped or stepped roof
bodies rather than at the parametric ``props.height`` which may not
+5 -3
View File
@@ -405,7 +405,7 @@ class SetStairTreads(bpy.types.Operator):
bl_label = "Set Number of Treads"
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
def invoke(self, context: bpy.types.Context, event: bpy.types.Event) -> set[str]:
def invoke(self, context: bpy.types.Context, event: bpy.types.Event) -> set[str]: # noqa: ARG002
obj = context.active_object
if not obj:
return {"CANCELLED"}
@@ -658,7 +658,9 @@ class GizmoStairEdition(bpy.types.GizmoGroup, gizmo.BaseParametricGizmoGroup):
self.tread_count_label_gizmo.alpha = 0.8
self.tread_count_label_gizmo.target_set_operator("bim.input_stair_treads")
def _refresh_element_specific(self, context: bpy.types.Context, mw: Matrix, props: "BIMStairProperties") -> None:
def _refresh_element_specific(
self, context: bpy.types.Context, mw: Matrix, props: "BIMStairProperties" # noqa: ARG002
) -> None:
"""Update stair-specific lock and tread count gizmos. Lock positioning is
handled per-frame in the dimension-positioning hook."""
self.update_lock_gizmo(props)
@@ -705,7 +707,7 @@ class GizmoStairEdition(bpy.types.GizmoGroup, gizmo.BaseParametricGizmoGroup):
self.update_gizmo_visibility(self.tread_count_label_gizmo, props.is_editing)
def _update_dimension_gizmo_positions(
self, context: bpy.types.Context, mw: Matrix, props: "BIMStairProperties"
self, context: bpy.types.Context, mw: Matrix, props: "BIMStairProperties" # noqa: ARG002
) -> None:
"""Update dimension gizmo positions based on camera view direction."""
viewing_from_negative_y, viewing_from_negative_x = self._frame_view_dir
+2 -2
View File
@@ -2174,7 +2174,7 @@ class GizmoWallEdition(bpy.types.GizmoGroup, gizmo.BaseParametricGizmoGroup):
return (far, near)
def _update_dimension_gizmo_positions(
self, context: bpy.types.Context, mw: Matrix, props: "BIMWallProperties"
self, context: bpy.types.Context, mw: Matrix, props: "BIMWallProperties" # noqa: ARG002
) -> None:
"""Re-position length / height / height_end dimensions to the camera-facing
Y-side of the wall every frame. Mirrors the door & stair pattern: when the
@@ -2530,7 +2530,7 @@ def _perpendicular_wall_params(
return clamped_x, abs(cursor_local_y), side_sign
def _commit_pending_wall_edits_for_selection(context: bpy.types.Context) -> None:
def _commit_pending_wall_edits_for_selection(context: bpy.types.Context) -> None: # noqa: ARG001
"""Thin wall-scoped alias for ``tool.Parametric.commit_pending_edits_for_selection``.
Encapsulates the ``names=("wall",)`` filter so the registry name is
+1 -1
View File
@@ -538,7 +538,7 @@ class RemoveWindow(bpy.types.Operator, tool.Ifc.Operator):
bl_label = "Remove Window"
bl_options = {"REGISTER"}
def _execute(self, context: bpy.types.Context) -> set[str]:
def _execute(self, context: bpy.types.Context) -> set[str]: # noqa: ARG002
obj = context.active_object
assert obj
element = tool.Ifc.get_entity(obj)
@@ -2442,7 +2442,7 @@ class LoadLinkedProject(bpy.types.Operator, ImportHelper):
if iterator.initialize():
while True: # Main loop.
shape = iterator.get()
assert isinstance(shape, W.triangulation_element)
assert isinstance(shape, W.TriangulationElement)
results.add(self.file.by_id(shape.id))
geometry = shape.geometry
@@ -2518,7 +2518,7 @@ class LoadLinkedProject(bpy.types.Operator, ImportHelper):
print("Finished", time.time() - start)
return {"FINISHED"}
def process_occurrence(self, shape: W.triangulation_element) -> None:
def process_occurrence(self, shape: W.TriangulationElement) -> None:
element = self.file.by_id(shape.id)
mat = ifcopenshell.util.shape.get_shape_matrix(shape)
@@ -558,7 +558,7 @@ class IntegerInputDialogMixin:
return None
return props
def invoke(self, context: bpy.types.Context, event: bpy.types.Event) -> set[str]:
def invoke(self, context: bpy.types.Context, event: bpy.types.Event) -> set[str]: # noqa: ARG002
props = self._resolve_props(context)
if props is None:
return {"CANCELLED"}
+2 -29
View File
@@ -142,35 +142,8 @@ def assign_material(
else:
element_material_type = material_type
# TODO: this whole dance is a stopgap and wants rewriting.
#
# material.assign_material creates material sets with no items in them,
# ignoring the material it was handed -- an IfcMaterialLayerSet with no
# MaterialLayers is not valid IFC, since the list is mandatory and
# [1:?]. So we repair it below, after the fact. Worse, the API rejects a
# plain IfcMaterial outright when asked for a usage, which is exactly
# what the Object Materials dropdown gives us, so we cannot even pass it
# on and have to let the API invent an empty set and then fill it in.
#
# The fix is for assign_material to build the set around the material it
# is given, rather than leaving an invalid one behind for its callers to
# patch up. That is a wider change than it looks: add_material_set has
# the same behaviour, and the create-empty-then-add-items idiom is
# spread through the API's own docstrings, examples and tests. Until
# that is untangled, keep the repair here where it is at least visible.
# Only a usage refuses a plain IfcMaterial; every other type still wants
# it, and IfcMaterial and IfcMaterialList cannot be created without it.
pass_material = material_tool.is_a_material_set(material) or not element_material_type.endswith("Usage")
ifc.run(
"material.assign_material",
products=[element],
type=element_material_type,
material=material if pass_material else None,
)
# A usage points at the set rather than being one, and it is the set
# that needs an item adding to it below.
assigned_material = material_tool.get_material(element, should_skip_usage=True)
ifc.run("material.assign_material", products=[element], type=element_material_type, material=material)
assigned_material = material_tool.get_material(element)
assert assigned_material # Type checker.
if material_tool.is_a_material_set(material):
+1 -1
View File
@@ -651,7 +651,7 @@ class Material:
def get_default_material(cls): pass
def get_elements_by_material(cls, material): pass
def get_material_attributes(cls): pass
def get_material(cls, element, should_inherit: bool = False, should_skip_usage: bool = False): pass
def get_material(cls, element, should_inherit: bool = False): pass
def get_object_ui_active_material(cls): pass
def get_object_ui_material_type(cls): pass
def get_style(cls, material): pass
+1 -1
View File
@@ -17,7 +17,7 @@
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
#
# Ignore unused imports.
# ruff: file-ignore[unused-import]
# ruff: noqa: F401
from bonsai.tool.aggregate import Aggregate
from bonsai.tool.array import Array
-2
View File
@@ -25,7 +25,6 @@ import importlib
import math
import os
import platform
import re
import subprocess
import sys
import tempfile
@@ -1757,7 +1756,6 @@ class Blender(bonsai.core.tool.Blender):
repo_path = repo.working_tree_dir
assert repo_path
version_ = (Path(repo_path) / "VERSION").read_text().strip()
version_ = re.sub(r"[A-Za-z]+\d+$", "", version_)
commit_date = bonsai.get_last_commit_date()
assert commit_date
commit_date = datetime.fromisoformat(commit_date)
+2 -2
View File
@@ -1187,7 +1187,7 @@ class Geometry(bonsai.core.tool.Geometry):
if iterator and iterator.initialize():
while True:
shape = iterator.get()
assert isinstance(shape, W.triangulation_element)
assert isinstance(shape, W.TriangulationElement)
element = tool.Ifc.get().by_id(shape.id)
if obj := tool.Ifc.get_object(element):
# It's possible that there will be multiple shapes for the same context,
@@ -2179,7 +2179,7 @@ class Geometry(bonsai.core.tool.Geometry):
item = tool.Ifc.get().by_id(props.ifc_definition_id)
allowed_attributes = [
a.name()
for a in item.declaration.as_entity().all_attributes()
for a in item.declaration().as_entity.all_attributes()
if a.type_of_attribute()._is("IfcLengthMeasure")
]
+2 -2
View File
@@ -872,7 +872,7 @@ class Loader(bonsai.core.tool.Loader):
cls,
element: ifcopenshell.entity_instance,
representation: ifcopenshell.entity_instance,
shape: W.triangulation_element,
shape: W.TriangulationElement,
) -> bpy.types.Camera:
"""Create camera data.
@@ -1026,7 +1026,7 @@ class Loader(bonsai.core.tool.Loader):
@classmethod
def convert_geometry_to_mesh(
cls,
geometry: W.triangulation,
geometry: W.Triangulation,
mesh: bpy.types.Mesh,
verts: Optional[npt.NDArray[np.float64]] = None,
*,
+2 -7
View File
@@ -220,14 +220,9 @@ class Material(bonsai.core.tool.Material):
@classmethod
def get_material(
cls,
element: ifcopenshell.entity_instance,
should_inherit: bool = False,
should_skip_usage: bool = False,
cls, element: ifcopenshell.entity_instance, should_inherit: bool = False
) -> Union[ifcopenshell.entity_instance, None]:
return ifcopenshell.util.element.get_material(
element, should_inherit=should_inherit, should_skip_usage=should_skip_usage
)
return ifcopenshell.util.element.get_material(element, should_inherit=should_inherit)
@classmethod
def is_a_material_set(cls, material: ifcopenshell.entity_instance) -> bool:
+1 -1
View File
@@ -2459,7 +2459,7 @@ class Model(bonsai.core.tool.Model):
polygons = {}
for curve in curves:
geometry = ifcopenshell.geom.create_shape(settings, curve)
assert isinstance(geometry, W.triangulation)
assert isinstance(geometry, W.Triangulation)
v = ifcopenshell.util.shape.get_vertices(geometry, is_2d=True)
v = np.round(v, 4) # Round to nearest 0.1mm, otherwise things like circles don't polygonise reliably
edges = ifcopenshell.util.shape.get_edges(geometry)
+1 -1
View File
@@ -53,7 +53,7 @@ class Profile(bonsai.core.tool.Profile):
settings.set("dimensionality", ifcopenshell.ifcopenshell_wrapper.CURVES_SURFACES_AND_SOLIDS)
shape = ifcopenshell.geom.create_shape(settings, profile)
assert isinstance(shape, W.triangulation)
assert isinstance(shape, W.Triangulation)
verts = ifcopenshell.util.shape.get_vertices(shape)
if verts.size == 0:
raise RuntimeError(f"Profile shape has no vertices, it probably is invalid: '{profile}'.")
@@ -76,10 +76,6 @@ Release
Notes:
- Typically all packages are released at once using the same version schema
- ``VERSION`` uses Python/PEP 440-compatible spelling. For example, an alpha
release may be ``0.9.0alpha0`` (canonicalized to ``0.9.0a0``); build scripts
derive numeric-only and SemVer forms such as ``0.9.0`` and
``0.9.0-alpha0`` where required.
- The ``README.md`` badges can serve as a visual reference for what versions have been released
- Corrective Release (if needed after a standard release):
+1 -1
View File
@@ -33,7 +33,7 @@ exclude = ["test*"]
[tool.ruff]
extend = "../../pyproject.toml"
lint.extend-select = [
"unused-import", # unused imports
"F401", # unused imports
]
[tool.ruff.lint.isort]
-1
View File
@@ -42,7 +42,6 @@ markers =
type
unit
void
wall
web
# Provide plugins explicitly, so it will be possible run tests with PYTEST_DISABLE_PLUGIN_AUTOLOAD.
+4 -4
View File
@@ -45,10 +45,10 @@ for dep in dependencies:
subprocess.check_call(command + [dep])
try:
import pygments # ruff: ignore[unused-import]
import pytest # ruff: ignore[unused-import]
import pytest_bdd # ruff: ignore[unused-import]
import pytest_blender # ruff: ignore[unused-import]
import pygments # noqa: F401
import pytest # noqa: F401
import pytest_bdd # noqa: F401
import pytest_blender # noqa: F401
print("Test dependency installation was successful!")
except Exception as e:
+24 -21
View File
@@ -163,29 +163,32 @@ class Drawer:
# self.svg_settings.set_deflection_tolerance(0.0001)
self.svg_buffer = ifcopenshell.geom.serializers.buffer()
self.svg_settings.set("svg-without-storeys", True)
self.svg_settings.set("svg-write-poly", True)
self.svg_settings.set("svg-poly", True)
self.serialiser_settings = ifcopenshell.geom.serializer_settings()
self.serialiser_settings.set("svg-without-storeys", True)
self.serialiser_settings.set("svg-write-poly", True)
self.serialiser_settings.set("svg-poly", True)
# Objects with more than these edges are rendered as wireframe instead of HLR for optimisation
self.svg_settings.set("profile-threshold", 10000)
self.svg_settings.set("svg-xmlns", True)
self.svg_settings.set("svg-project", True)
self.svg_settings.set("auto-elevation", False)
self.svg_settings.set("auto-section", False)
self.svg_settings.set("print-space-names", False)
self.svg_settings.set("print-space-areas", False)
self.svg_settings.set("door-arcs", False)
self.svg_settings.set("svg-no-css", True)
self.svg_settings.set("elevation-ref-guid", self.camera_element.GlobalId)
self.svg_settings.set("scale", "1/50")
self.svg_settings.set("svg-subtract-before", "always")
self.svg_settings.set("svg-prefilter", True) # See #3359
# self.svg_settings.set("svg-prefilter", False) # See #3359
self.svg_settings.set("svg-unify-inputs", True)
self.svg_settings.set("svg-segment-projection", True)
self.serialiser_settings.set("profile-threshold", 10000)
self.serialiser_settings.set("svg-xmlns", True)
self.serialiser_settings.set("svg-project", True)
self.serialiser_settings.set("auto-elevation", False)
self.serialiser_settings.set("auto-section", False)
self.serialiser_settings.set("print-space-names", False)
self.serialiser_settings.set("print-space-areas", False)
self.serialiser_settings.set("door-arcs", False)
self.serialiser_settings.set("svg-no-css", True)
self.serialiser_settings.set("elevation-ref-guid", self.camera_element.GlobalId)
self.serialiser_settings.set("scale", "1/50")
self.serialiser_settings.set("svg-subtract-before", "always")
self.serialiser_settings.set("svg-prefilter", True) # See #3359
# self.serialiser_settings.set("svg-prefilter", False) # See #3359
self.serialiser_settings.set("svg-unify-inputs", True)
self.serialiser_settings.set("svg-segment-projection", True)
if target_view == "REFLECTED_PLAN_VIEW":
self.svg_settings.set("svg-mirror-y", True)
self.serialiser = ifcopenshell.geom.serializers.svg(self.svg_buffer, self.svg_settings)
self.serialiser_settings.set("svg-mirror-y", True)
self.serialiser = ifcopenshell.geom.serializers.svg(
self.svg_buffer, self.svg_settings, self.serialiser_settings
)
self.serialiser.setFile(ifc)
@@ -72,12 +72,12 @@ Scenario: Add classification reference - object
And I press "bim.add_classification"
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I set "scene.BIMRootProperties.ifc_product" to "IfcElementType"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWallType"
And I press "bim.assign_class"
And I press "bim.change_classification_level(parent_id={classification})"
And the variable "reference" is "{classification_ifc}.by_type('IfcClassificationReference')[0].id()"
When I press "bim.add_classification_reference(reference={reference}, obj='IfcWall/Cube', obj_type='Object')"
When I press "bim.add_classification_reference(reference={reference}, obj='IfcWallType/Cube', obj_type='Object')"
Then nothing happens
Scenario: Change classification level
@@ -88,8 +88,8 @@ Scenario: Change classification level
And I press "bim.add_classification"
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I set "scene.BIMRootProperties.ifc_product" to "IfcElementType"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWallType"
And I press "bim.assign_class"
And I press "bim.change_classification_level(parent_id={classification})"
And the variable "reference" is "{classification_ifc}.by_type('IfcClassificationReference')[0].id()"
@@ -104,8 +104,8 @@ Scenario: Disable editing classification references
And I press "bim.add_classification"
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I set "scene.BIMRootProperties.ifc_product" to "IfcElementType"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWallType"
And I press "bim.assign_class"
And I press "bim.change_classification_level(parent_id={classification})"
When I press "bim.disable_editing_classification_references"
@@ -119,12 +119,12 @@ Scenario: Enable editing classification reference
And I press "bim.add_classification"
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I set "scene.BIMRootProperties.ifc_product" to "IfcElementType"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWallType"
And I press "bim.assign_class"
And I press "bim.change_classification_level(parent_id={classification})"
And the variable "reference" is "{classification_ifc}.by_type('IfcClassificationReference')[0].id()"
And I press "bim.add_classification_reference(reference={reference}, obj='IfcWall/Cube', obj_type='Object')"
And I press "bim.add_classification_reference(reference={reference}, obj='IfcWallType/Cube', obj_type='Object')"
And the variable "reference" is "{ifc}.by_type('IfcClassificationReference')[0].id()"
When I press "bim.enable_editing_classification_reference(reference={reference})"
Then nothing happens
@@ -137,12 +137,12 @@ Scenario: Disable editing classification reference
And I press "bim.add_classification"
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I set "scene.BIMRootProperties.ifc_product" to "IfcElementType"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWallType"
And I press "bim.assign_class"
And I press "bim.change_classification_level(parent_id={classification})"
And the variable "reference" is "{classification_ifc}.by_type('IfcClassificationReference')[0].id()"
And I press "bim.add_classification_reference(reference={reference}, obj='IfcWall/Cube', obj_type='Object')"
And I press "bim.add_classification_reference(reference={reference}, obj='IfcWallType/Cube', obj_type='Object')"
And the variable "reference" is "{ifc}.by_type('IfcClassificationReference')[0].id()"
And I press "bim.enable_editing_classification_reference(reference={reference})"
When I press "bim.disable_editing_classification_reference"
@@ -156,15 +156,15 @@ Scenario: Remove classification reference - object
And I press "bim.add_classification"
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I set "scene.BIMRootProperties.ifc_product" to "IfcElementType"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWallType"
And I press "bim.assign_class"
And I press "bim.change_classification_level(parent_id={classification})"
And the variable "reference" is "{classification_ifc}.by_type('IfcClassificationReference')[0].id()"
And I press "bim.add_classification_reference(reference={reference}, obj='IfcWall/Cube', obj_type='Object')"
And I press "bim.add_classification_reference(reference={reference}, obj='IfcWallType/Cube', obj_type='Object')"
And the variable "reference" is "{ifc}.by_type('IfcClassificationReference')[0].id()"
And I press "bim.enable_editing_classification_reference(reference={reference})"
When I press "bim.remove_classification_reference(reference={reference}, obj='IfcWall/Cube', obj_type='Object')"
When I press "bim.remove_classification_reference(reference={reference}, obj='IfcWallType/Cube', obj_type='Object')"
Then nothing happens
Scenario: Edit classification reference
@@ -175,12 +175,12 @@ Scenario: Edit classification reference
And I press "bim.add_classification"
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I set "scene.BIMRootProperties.ifc_product" to "IfcElementType"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWallType"
And I press "bim.assign_class"
And I press "bim.change_classification_level(parent_id={classification})"
And the variable "reference" is "{classification_ifc}.by_type('IfcClassificationReference')[0].id()"
And I press "bim.add_classification_reference(reference={reference}, obj='IfcWall/Cube', obj_type='Object')"
And I press "bim.add_classification_reference(reference={reference}, obj='IfcWallType/Cube', obj_type='Object')"
And the variable "reference" is "{ifc}.by_type('IfcClassificationReference')[0].id()"
And I press "bim.enable_editing_classification_reference(reference={reference})"
When I press "bim.edit_classification_reference"
@@ -185,7 +185,6 @@ Scenario: Update representation - updating a layered extrusion
And I set "scene.BIMRootProperties.ifc_class" to "IfcWallType"
And I press "bim.assign_class"
And I press "bim.add_material()"
And the object "IfcWallType/Empty" is selected
And I set "active_object.BIMObjectMaterialProperties.material_type" to "IfcMaterialLayerSet"
And I press "bim.assign_material"
And I press "bim.enable_editing_assigned_material"
@@ -214,7 +213,6 @@ Scenario: Update representation - updating a profiled extrusion
And I set "scene.BIMRootProperties.ifc_class" to "IfcWallType"
And I press "bim.assign_class"
And I press "bim.add_material()"
And the object "IfcWallType/Empty" is selected
And I set "active_object.BIMObjectMaterialProperties.material_type" to "IfcMaterialProfileSet"
And I press "bim.assign_material"
And I press "bim.enable_editing_assigned_material"
@@ -418,7 +416,6 @@ Scenario: Override duplicate move - copying a layered extrusion
And I set "scene.BIMRootProperties.ifc_class" to "IfcWallType"
And I press "bim.assign_class"
And I press "bim.add_material()"
And the object "IfcWallType/Empty" is selected
And I set "active_object.BIMObjectMaterialProperties.material_type" to "IfcMaterialLayerSet"
And I press "bim.assign_material"
And I press "bim.enable_editing_assigned_material"
@@ -450,7 +447,6 @@ Scenario: Override duplicate move - copying a profiled extrusion
And I set "scene.BIMRootProperties.ifc_class" to "IfcWallType"
And I press "bim.assign_class"
And I press "bim.add_material()"
And the object "IfcWallType/Empty" is selected
And I set "active_object.BIMObjectMaterialProperties.material_type" to "IfcMaterialProfileSet"
And I press "bim.assign_material"
And I press "bim.enable_editing_assigned_material"
@@ -121,7 +121,6 @@ Scenario: Assign material - material layer set
And I set "scene.BIMRootProperties.ifc_class" to "IfcWallType"
And I press "bim.assign_class"
And I press "bim.add_material()"
And the object "IfcWallType/Empty" is selected
When I set "active_object.BIMObjectMaterialProperties.material_type" to "IfcMaterialLayerSet"
And I press "bim.assign_material"
Then the object "IfcWallType/Empty" does not have the material "Default"
@@ -135,7 +134,6 @@ Scenario: Unassign material - material layer set
And I set "scene.BIMRootProperties.ifc_class" to "IfcWallType"
And I press "bim.assign_class"
And I press "bim.add_material()"
And the object "IfcWallType/Empty" is selected
And I set "active_object.BIMObjectMaterialProperties.material_type" to "IfcMaterialLayerSet"
And I press "bim.assign_material"
When I press "bim.unassign_material"
@@ -157,7 +155,6 @@ Scenario: Unassign material - removing inherited material
And I press "bim.assign_class"
And I press "bim.add_material()"
And the object "IfcWallType/Empty" is selected
And I set "active_object.BIMObjectMaterialProperties.material_type" to "IfcMaterialLayerSet"
And I press "bim.assign_material"
@@ -184,7 +181,6 @@ Scenario: Enable editing assigned material - material layer set
And I set "scene.BIMRootProperties.ifc_class" to "IfcWallType"
And I press "bim.assign_class"
And I press "bim.add_material()"
And the object "IfcWallType/Empty" is selected
And I set "active_object.BIMObjectMaterialProperties.material_type" to "IfcMaterialLayerSet"
And I press "bim.assign_material"
When I press "bim.enable_editing_assigned_material"
@@ -204,7 +200,6 @@ Scenario: Disable editing assigned material - material layer set
And I set "scene.BIMRootProperties.ifc_class" to "IfcWallType"
And I press "bim.assign_class"
And I press "bim.add_material()"
And the object "IfcWallType/Empty" is selected
And I set "active_object.BIMObjectMaterialProperties.material_type" to "IfcMaterialLayerSet"
And I press "bim.assign_material"
And I press "bim.enable_editing_assigned_material"
@@ -225,7 +220,6 @@ Scenario: Edit assigned material - material layer set
And I set "scene.BIMRootProperties.ifc_class" to "IfcWallType"
And I press "bim.assign_class"
And I press "bim.add_material()"
And the object "IfcWallType/Empty" is selected
And I set "active_object.BIMObjectMaterialProperties.material_type" to "IfcMaterialLayerSet"
And I press "bim.assign_material"
And I press "bim.enable_editing_assigned_material"
@@ -241,7 +235,6 @@ Scenario: Assign material - material profile set
And I set "scene.BIMRootProperties.ifc_class" to "IfcWallType"
And I press "bim.assign_class"
And I press "bim.add_material()"
And the object "IfcWallType/Empty" is selected
When I set "active_object.BIMObjectMaterialProperties.material_type" to "IfcMaterialProfileSet"
And I press "bim.assign_material"
Then the object "IfcWallType/Empty" does not have the material "Default"
@@ -255,7 +248,6 @@ Scenario: Unassign material - material profile set
And I set "scene.BIMRootProperties.ifc_class" to "IfcWallType"
And I press "bim.assign_class"
And I press "bim.add_material()"
And the object "IfcWallType/Empty" is selected
And I set "active_object.BIMObjectMaterialProperties.material_type" to "IfcMaterialProfileSet"
And I press "bim.assign_material"
When I press "bim.unassign_material"
@@ -275,7 +267,6 @@ Scenario: Enable editing assigned material - material profile set
And I set "scene.BIMRootProperties.ifc_class" to "IfcWallType"
And I press "bim.assign_class"
And I press "bim.add_material()"
And the object "IfcWallType/Empty" is selected
And I set "active_object.BIMObjectMaterialProperties.material_type" to "IfcMaterialProfileSet"
And I press "bim.assign_material"
When I press "bim.enable_editing_assigned_material"
@@ -295,7 +286,6 @@ Scenario: Disable editing assigned material - material profile set
And I set "scene.BIMRootProperties.ifc_class" to "IfcWallType"
And I press "bim.assign_class"
And I press "bim.add_material()"
And the object "IfcWallType/Empty" is selected
And I set "active_object.BIMObjectMaterialProperties.material_type" to "IfcMaterialProfileSet"
And I press "bim.assign_material"
And I press "bim.enable_editing_assigned_material"
@@ -316,7 +306,6 @@ Scenario: Edit assigned material - material profile set
And I set "scene.BIMRootProperties.ifc_class" to "IfcWallType"
And I press "bim.assign_class"
And I press "bim.add_material()"
And the object "IfcWallType/Empty" is selected
And I set "active_object.BIMObjectMaterialProperties.material_type" to "IfcMaterialProfileSet"
And I press "bim.assign_material"
And I press "bim.enable_editing_assigned_material"
@@ -465,7 +454,6 @@ Scenario: Add material set layer
And I set "scene.BIMRootProperties.ifc_class" to "IfcWallType"
And I press "bim.assign_class"
And I press "bim.add_material()"
And the object "IfcWallType/Empty" is selected
And I set "active_object.BIMObjectMaterialProperties.material_type" to "IfcMaterialLayerSet"
And I press "bim.assign_material"
And I press "bim.enable_editing_assigned_material"
@@ -489,7 +477,6 @@ Scenario: Remove material set layer
And I press "bim.assign_class"
And I press "bim.add_material()"
And the object "IfcWallType/Empty" is selected
And I set "active_object.BIMObjectMaterialProperties.material_type" to "IfcMaterialLayerSet"
And I press "bim.assign_material"
+97 -139
View File
@@ -314,12 +314,6 @@ Scenario: Load project elements - auto offset of cartesian points
Then the object "IfcBuildingElementProxy/NAME" is at "0,0,0"
Scenario: Load project elements - all georeferencing coordinate situations - disabled false origin mode
# D, G and J have their geometry far from their placement, so each is
# shifted onto one of its own verts to keep its precision. Which vert that
# is comes from the geometry kernel and has changed before, so these assert
# that the origin is on a vert rather than which one, and name verts rather
# than origins. In automatic mode the model origin is picked the same way
# and everything moves with it, so there they are relative to it.
Given an empty Blender session
And I press "bim.load_project(filepath='{cwd}/test/files/geolocation.ifc', is_advanced=True)"
When I set "scene.BIMProjectProperties.false_origin_mode" to "DISABLED"
@@ -332,19 +326,13 @@ Scenario: Load project elements - all georeferencing coordinate situations - dis
And the object "IfcActuator/A" is at "7,3,0"
And the object "IfcActuator/B" is at "6,1,0"
And the object "IfcActuator/C" is at "0,0,0"
And the object "IfcActuator/D" has its origin on a vertex
And the object "IfcActuator/D" has a vert at "13,4,-1" at map coordinates "13000,4000,-1000"
And the object "IfcActuator/D" has a vert at "15,2,1" at map coordinates "15000,2000,1000"
And the object "IfcActuator/D" is at "13,4,-1"
And the object "IfcActuator/E" is at "6,3,0"
And the object "IfcActuator/F" is at "3,3,0"
And the object "IfcActuator/G" has its origin on a vertex
And the object "IfcActuator/G" has a vert at "15,6,-1" at map coordinates "15000,6000,-1000"
And the object "IfcActuator/G" has a vert at "17,4,1" at map coordinates "17000,4000,1000"
And the object "IfcActuator/G" is at "15,6,-1"
And the object "IfcActuator/H" is at "9,2,0"
And the object "IfcActuator/I" is at "3,3,0"
And the object "IfcActuator/J" has its origin on a vertex
And the object "IfcActuator/J" has a vert at "11,3,-1" at map coordinates "11000,3000,-1000"
And the object "IfcActuator/J" has a vert at "13,1,1" at map coordinates "13000,1000,1000"
And the object "IfcActuator/J" is at "11,3,-1"
And the object "IfcActuator/K" is at "10,0,0"
Scenario: Load project elements - all georeferencing coordinate situations - automatic false origin mode
@@ -354,27 +342,24 @@ Scenario: Load project elements - all georeferencing coordinate situations - aut
When I set "scene.BIMProjectProperties.distance_limit" to "5"
And I press "bim.load_project_elements"
Then "scene.BIMGeoreferenceProperties.has_blender_offset" is "True"
And the model origin is on an object vertex
And "scene.BIMGeoreferenceProperties.model_origin" is "13000.0,4000.0,-1000.0"
And "scene.BIMGeoreferenceProperties.blender_offset_x" is "13000.0"
And "scene.BIMGeoreferenceProperties.blender_offset_y" is "4000.0"
And "scene.BIMGeoreferenceProperties.blender_offset_z" is "-1000.0"
And the object "IfcSite/My Site" is at "0,0,0"
And the object "IfcBuilding/My Building" is at "0,0,0"
And the object "IfcBuildingStorey/My Storey" is at "0,0,0"
And the object "IfcActuator/A" is at "7,3,0" relative to the model origin at map coordinates "7000,3000,0"
And the object "IfcActuator/B" is at "6,1,0" relative to the model origin at map coordinates "6000,1000,0"
And the object "IfcActuator/A" is at "-6,-1,1"
And the object "IfcActuator/B" is at "-7,-3,1"
And the object "IfcActuator/C" is at "0,0,0"
And the object "IfcActuator/D" has its origin on a vertex
And the object "IfcActuator/D" has a vert at "13,4,-1" relative to the model origin at map coordinates "13000,4000,-1000"
And the object "IfcActuator/D" has a vert at "15,2,1" relative to the model origin at map coordinates "15000,2000,1000"
And the object "IfcActuator/E" is at "6,3,0" relative to the model origin at map coordinates "6000,3000,0"
And the object "IfcActuator/F" is at "3,3,0" relative to the model origin at map coordinates "3000,3000,0"
And the object "IfcActuator/G" has its origin on a vertex
And the object "IfcActuator/G" has a vert at "15,6,-1" relative to the model origin at map coordinates "15000,6000,-1000"
And the object "IfcActuator/G" has a vert at "17,4,1" relative to the model origin at map coordinates "17000,4000,1000"
And the object "IfcActuator/H" is at "9,2,0" relative to the model origin at map coordinates "9000,2000,0"
And the object "IfcActuator/I" is at "3,3,0" relative to the model origin at map coordinates "3000,3000,0"
And the object "IfcActuator/J" has its origin on a vertex
And the object "IfcActuator/J" has a vert at "11,3,-1" relative to the model origin at map coordinates "11000,3000,-1000"
And the object "IfcActuator/J" has a vert at "13,1,1" relative to the model origin at map coordinates "13000,1000,1000"
And the object "IfcActuator/K" is at "10,0,0" relative to the model origin at map coordinates "10000,0,0"
And the object "IfcActuator/D" is at "0,0,0"
And the object "IfcActuator/E" is at "-7,-1,1"
And the object "IfcActuator/F" is at "-10,-1,1"
And the object "IfcActuator/G" is at "2,2,0"
And the object "IfcActuator/H" is at "-4,-2,1"
And the object "IfcActuator/I" is at "-10,-1,1"
And the object "IfcActuator/J" is at "-2,-1,0"
And the object "IfcActuator/K" is at "-3,-4,1"
Scenario: Load project elements - all georeferencing coordinate situations - manual false origin mode
Given an empty Blender session
@@ -394,20 +379,23 @@ Scenario: Load project elements - all georeferencing coordinate situations - man
And the object "IfcActuator/A" is at "-3,3,0"
And the object "IfcActuator/B" is at "-4,1,0"
And the object "IfcActuator/C" is at "0,0,0"
And the object "IfcActuator/D" has its origin on a vertex
And the object "IfcActuator/D" has a vert at "3,4,-1" at map coordinates "13000,4000,-1000"
And the object "IfcActuator/D" has a vert at "5,2,1" at map coordinates "15000,2000,1000"
And the object "IfcActuator/D" is at "3,4,-1"
And the object "IfcActuator/E" is at "-4,3,0"
And the object "IfcActuator/F" is at "-7,3,0"
And the object "IfcActuator/G" has its origin on a vertex
And the object "IfcActuator/G" has a vert at "5,6,-1" at map coordinates "15000,6000,-1000"
And the object "IfcActuator/G" has a vert at "7,4,1" at map coordinates "17000,4000,1000"
And the object "IfcActuator/G" is at "5,6,-1"
And the object "IfcActuator/H" is at "-1,2,0"
And the object "IfcActuator/I" is at "-7,3,0"
And the object "IfcActuator/J" has its origin on a vertex
And the object "IfcActuator/J" has a vert at "1,3,-1" at map coordinates "11000,3000,-1000"
And the object "IfcActuator/J" has a vert at "3,1,1" at map coordinates "13000,1000,1000"
And the object "IfcActuator/J" is at "1,3,-1"
And the object "IfcActuator/K" is at "0,0,0"
And the object "IfcActuator/D" has a cartesian point offset of "31,4,-1"
And the object "IfcActuator/G" has a cartesian point offset of "-25,6,-1"
And the object "IfcActuator/J" has a cartesian point offset of "11,3,-1"
And the object "IfcActuator/D" has a vertex at "3,2,-1"
And the object "IfcActuator/D" has a vertex at "5,2,-1"
And the object "IfcActuator/G" has a vertex at "5,4,-1"
And the object "IfcActuator/G" has a vertex at "7,4,-1"
And the object "IfcActuator/J" has a vertex at "1,1,-1"
And the object "IfcActuator/J" has a vertex at "3,1,-1"
Scenario: Load project elements - all georeferencing coordinate situations with an offset site - disabled false origin mode
Given an empty Blender session
@@ -422,19 +410,13 @@ Scenario: Load project elements - all georeferencing coordinate situations with
And the object "IfcActuator/A" is at "5.985,14.71,0"
And the object "IfcActuator/B" is at "5.5367,12.519,0"
And the object "IfcActuator/C" is at "0,10,0"
And the object "IfcActuator/D" has its origin on a vertex
And the object "IfcActuator/D" has a vert at "11.5218,17.2284,-1" at map coordinates "11521.758,17228.35,-1000"
And the object "IfcActuator/D" has a vert at "13.9712,15.8141,1" at map coordinates "13971.246,15814.136,1000"
And the object "IfcActuator/D" is at "11.522,17.228,-1"
And the object "IfcActuator/E" is at "5.0191,14.451,0"
And the object "IfcActuator/F" is at "2.1213,13.674,0"
And the object "IfcActuator/G" has its origin on a vertex
And the object "IfcActuator/G" has a vert at "12.936,19.6778,-1" at map coordinates "12935.975,19677.841,-1000"
And the object "IfcActuator/G" has a vert at "15.3855,18.2636,1" at map coordinates "15385.465,18263.627,1000"
And the object "IfcActuator/G" is at "12.936,19.678,-1"
And the object "IfcActuator/H" is at "8.1757,14.261,0"
And the object "IfcActuator/I" is at "2.1213,13.674,0"
And the object "IfcActuator/J" has its origin on a vertex
And the object "IfcActuator/J" has a vert at "9.8487,15.7448,-1" at map coordinates "9848.726,15744.786,-1000"
And the object "IfcActuator/J" has a vert at "12.2982,14.3306,1" at map coordinates "12298.216,14330.573,1000"
And the object "IfcActuator/J" is at "9.8487,15.745,-1"
And the object "IfcActuator/K" is at "9.6593,12.588,0"
Scenario: Load project elements - all georeferencing coordinate situations with an offset site - automatic false origin mode
@@ -454,19 +436,13 @@ Scenario: Load project elements - all georeferencing coordinate situations with
And the object "IfcActuator/A" is at "7,3,0"
And the object "IfcActuator/B" is at "6,1,0"
And the object "IfcActuator/C" is at "0,0,0"
And the object "IfcActuator/D" has its origin on a vertex
And the object "IfcActuator/D" has a vert at "13,4,-1" at map coordinates "11521.758,17228.35,-1000"
And the object "IfcActuator/D" has a vert at "15,2,1" at map coordinates "13971.246,15814.136,1000"
And the object "IfcActuator/D" is at "13,4,-1"
And the object "IfcActuator/E" is at "6,3,0"
And the object "IfcActuator/F" is at "3,3,0"
And the object "IfcActuator/G" has its origin on a vertex
And the object "IfcActuator/G" has a vert at "15,6,-1" at map coordinates "12935.975,19677.841,-1000"
And the object "IfcActuator/G" has a vert at "17,4,1" at map coordinates "15385.465,18263.627,1000"
And the object "IfcActuator/G" is at "15,6,-1"
And the object "IfcActuator/H" is at "9,2,0"
And the object "IfcActuator/I" is at "3,3,0"
And the object "IfcActuator/J" has its origin on a vertex
And the object "IfcActuator/J" has a vert at "11,3,-1" at map coordinates "9848.726,15744.786,-1000"
And the object "IfcActuator/J" has a vert at "13,1,1" at map coordinates "12298.216,14330.573,1000"
And the object "IfcActuator/J" is at "11,3,-1"
And the object "IfcActuator/K" is at "10,0,0"
Scenario: Load project elements - all georeferencing coordinate situations with an offset site - manual false origin mode
@@ -487,20 +463,23 @@ Scenario: Load project elements - all georeferencing coordinate situations with
And the object "IfcActuator/A" is at "5.985,4.71,0"
And the object "IfcActuator/B" is at "5.5367,2.519,0"
And the object "IfcActuator/C" is at "0,0,0"
And the object "IfcActuator/D" has its origin on a vertex
And the object "IfcActuator/D" has a vert at "11.5218,7.2284,-1" at map coordinates "11521.758,17228.35,-1000"
And the object "IfcActuator/D" has a vert at "13.9712,5.8141,1" at map coordinates "13971.246,15814.136,1000"
And the object "IfcActuator/D" is at "11.522,7.228,-1"
And the object "IfcActuator/E" is at "5.0191,4.451,0"
And the object "IfcActuator/F" is at "2.1213,3.674,0"
And the object "IfcActuator/G" has its origin on a vertex
And the object "IfcActuator/G" has a vert at "12.936,9.6778,-1" at map coordinates "12935.975,19677.841,-1000"
And the object "IfcActuator/G" has a vert at "15.3855,8.2636,1" at map coordinates "15385.465,18263.627,1000"
And the object "IfcActuator/G" is at "12.936,9.678,-1"
And the object "IfcActuator/H" is at "8.1757,4.261,0"
And the object "IfcActuator/I" is at "2.1213,3.674,0"
And the object "IfcActuator/J" has its origin on a vertex
And the object "IfcActuator/J" has a vert at "9.8487,5.7448,-1" at map coordinates "9848.726,15744.786,-1000"
And the object "IfcActuator/J" has a vert at "12.2982,4.3306,1" at map coordinates "12298.216,14330.573,1000"
And the object "IfcActuator/J" is at "9.8487,5.745,-1"
And the object "IfcActuator/K" is at "9.6593,2.588,0"
And the object "IfcActuator/D" has a cartesian point offset of "31,4,-1"
And the object "IfcActuator/G" has a cartesian point offset of "-25,6,-1"
And the object "IfcActuator/J" has a cartesian point offset of "11,3,-1"
And the object "IfcActuator/D" has a vertex at "12.039,5.296,-1"
And the object "IfcActuator/D" has a vertex at "13.971,5.814,-1"
And the object "IfcActuator/G" has a vertex at "13.454,7.746,-1"
And the object "IfcActuator/G" has a vertex at "15.385,8.264,-1"
And the object "IfcActuator/J" has a vertex at "10.366,3.813,-1"
And the object "IfcActuator/J" has a vertex at "12.298,4.331,-1"
Scenario: Load project elements - all georeferencing coordinate situations with an offset site - manual false origin mode - with custom project north
Given an empty Blender session
@@ -521,19 +500,13 @@ Scenario: Load project elements - all georeferencing coordinate situations with
And the object "IfcActuator/A" is at "7,3,0"
And the object "IfcActuator/B" is at "6,1,0"
And the object "IfcActuator/C" is at "0,0,0"
And the object "IfcActuator/D" has its origin on a vertex
And the object "IfcActuator/D" has a vert at "13,4,-1" at map coordinates "11521.758,17228.35,-1000"
And the object "IfcActuator/D" has a vert at "15,2,1" at map coordinates "13971.246,15814.136,1000"
And the object "IfcActuator/D" is at "13,4,-1"
And the object "IfcActuator/E" is at "6,3,0"
And the object "IfcActuator/F" is at "3,3,0"
And the object "IfcActuator/G" has its origin on a vertex
And the object "IfcActuator/G" has a vert at "15,6,-1" at map coordinates "12935.975,19677.841,-1000"
And the object "IfcActuator/G" has a vert at "17,4,1" at map coordinates "15385.465,18263.627,1000"
And the object "IfcActuator/G" is at "15,6,-1"
And the object "IfcActuator/H" is at "9,2,0"
And the object "IfcActuator/I" is at "3,3,0"
And the object "IfcActuator/J" has its origin on a vertex
And the object "IfcActuator/J" has a vert at "11,3,-1" at map coordinates "9848.726,15744.786,-1000"
And the object "IfcActuator/J" has a vert at "13,1,1" at map coordinates "12298.216,14330.573,1000"
And the object "IfcActuator/J" is at "11,3,-1"
And the object "IfcActuator/K" is at "10,0,0"
Scenario: Load project elements - all georeferencing coordinate situations with a map conversion - disabled false origin mode (this should be identical to the situation with no map conversion)
@@ -549,19 +522,13 @@ Scenario: Load project elements - all georeferencing coordinate situations with
And the object "IfcActuator/A" is at "7,3,0"
And the object "IfcActuator/B" is at "6,1,0"
And the object "IfcActuator/C" is at "0,0,0"
And the object "IfcActuator/D" has its origin on a vertex
And the object "IfcActuator/D" has a vert at "13,4,-1" at map coordinates "28000,4000,-1000"
And the object "IfcActuator/D" has a vert at "15,2,1" at map coordinates "30000,2000,1000"
And the object "IfcActuator/D" is at "13,4,-1"
And the object "IfcActuator/E" is at "6,3,0"
And the object "IfcActuator/F" is at "3,3,0"
And the object "IfcActuator/G" has its origin on a vertex
And the object "IfcActuator/G" has a vert at "15,6,-1" at map coordinates "30000,6000,-1000"
And the object "IfcActuator/G" has a vert at "17,4,1" at map coordinates "32000,4000,1000"
And the object "IfcActuator/G" is at "15,6,-1"
And the object "IfcActuator/H" is at "9,2,0"
And the object "IfcActuator/I" is at "3,3,0"
And the object "IfcActuator/J" has its origin on a vertex
And the object "IfcActuator/J" has a vert at "11,3,-1" at map coordinates "26000,3000,-1000"
And the object "IfcActuator/J" has a vert at "13,1,1" at map coordinates "28000,1000,1000"
And the object "IfcActuator/J" is at "11,3,-1"
And the object "IfcActuator/K" is at "10,0,0"
Scenario: Load project elements - all georeferencing coordinate situations with a map conversion - automatic false origin mode (this should affect the Blender eastings and northings, which is now different to the Blender offset XYZ, but is otherwise identical to the non-map conversion variant)
@@ -571,27 +538,24 @@ Scenario: Load project elements - all georeferencing coordinate situations with
When I set "scene.BIMProjectProperties.distance_limit" to "5"
And I press "bim.load_project_elements"
Then "scene.BIMGeoreferenceProperties.has_blender_offset" is "True"
And the model origin is on an object vertex
And "scene.BIMGeoreferenceProperties.model_origin" is "28000.0,4000.0,-1000.0"
And "scene.BIMGeoreferenceProperties.blender_offset_x" is "13000.0"
And "scene.BIMGeoreferenceProperties.blender_offset_y" is "4000.0"
And "scene.BIMGeoreferenceProperties.blender_offset_z" is "-1000.0"
And the object "IfcSite/My Site" is at "0,0,0"
And the object "IfcBuilding/My Building" is at "0,0,0"
And the object "IfcBuildingStorey/My Storey" is at "0,0,0"
And the object "IfcActuator/A" is at "22,3,0" relative to the model origin at map coordinates "22000,3000,0"
And the object "IfcActuator/B" is at "21,1,0" relative to the model origin at map coordinates "21000,1000,0"
And the object "IfcActuator/A" is at "-6,-1,1"
And the object "IfcActuator/B" is at "-7,-3,1"
And the object "IfcActuator/C" is at "0,0,0"
And the object "IfcActuator/D" has its origin on a vertex
And the object "IfcActuator/D" has a vert at "28,4,-1" relative to the model origin at map coordinates "28000,4000,-1000"
And the object "IfcActuator/D" has a vert at "30,2,1" relative to the model origin at map coordinates "30000,2000,1000"
And the object "IfcActuator/E" is at "21,3,0" relative to the model origin at map coordinates "21000,3000,0"
And the object "IfcActuator/F" is at "18,3,0" relative to the model origin at map coordinates "18000,3000,0"
And the object "IfcActuator/G" has its origin on a vertex
And the object "IfcActuator/G" has a vert at "30,6,-1" relative to the model origin at map coordinates "30000,6000,-1000"
And the object "IfcActuator/G" has a vert at "32,4,1" relative to the model origin at map coordinates "32000,4000,1000"
And the object "IfcActuator/H" is at "24,2,0" relative to the model origin at map coordinates "24000,2000,0"
And the object "IfcActuator/I" is at "18,3,0" relative to the model origin at map coordinates "18000,3000,0"
And the object "IfcActuator/J" has its origin on a vertex
And the object "IfcActuator/J" has a vert at "26,3,-1" relative to the model origin at map coordinates "26000,3000,-1000"
And the object "IfcActuator/J" has a vert at "28,1,1" relative to the model origin at map coordinates "28000,1000,1000"
And the object "IfcActuator/K" is at "25,0,0" relative to the model origin at map coordinates "25000,0,0"
And the object "IfcActuator/D" is at "0,0,0"
And the object "IfcActuator/E" is at "-7,-1,1"
And the object "IfcActuator/F" is at "-10,-1,1"
And the object "IfcActuator/G" is at "2,2,0"
And the object "IfcActuator/H" is at "-4,-2,1"
And the object "IfcActuator/I" is at "-10,-1,1"
And the object "IfcActuator/J" is at "-2,-1,0"
And the object "IfcActuator/K" is at "-3,-4,1"
Scenario: Load project elements - all georeferencing coordinate situations with a map conversion - manual false origin mode (this should affect the Blender eastings and northings, which is now different to the Blender offset XYZ, but is otherwise identical to the non-map conversion variant)
Given an empty Blender session
@@ -611,20 +575,23 @@ Scenario: Load project elements - all georeferencing coordinate situations with
And the object "IfcActuator/A" is at "-3,3,0"
And the object "IfcActuator/B" is at "-4,1,0"
And the object "IfcActuator/C" is at "0,0,0"
And the object "IfcActuator/D" has its origin on a vertex
And the object "IfcActuator/D" has a vert at "3,4,-1" at map coordinates "28000,4000,-1000"
And the object "IfcActuator/D" has a vert at "5,2,1" at map coordinates "30000,2000,1000"
And the object "IfcActuator/D" is at "3,4,-1"
And the object "IfcActuator/E" is at "-4,3,0"
And the object "IfcActuator/F" is at "-7,3,0"
And the object "IfcActuator/G" has its origin on a vertex
And the object "IfcActuator/G" has a vert at "5,6,-1" at map coordinates "30000,6000,-1000"
And the object "IfcActuator/G" has a vert at "7,4,1" at map coordinates "32000,4000,1000"
And the object "IfcActuator/G" is at "5,6,-1"
And the object "IfcActuator/H" is at "-1,2,0"
And the object "IfcActuator/I" is at "-7,3,0"
And the object "IfcActuator/J" has its origin on a vertex
And the object "IfcActuator/J" has a vert at "1,3,-1" at map coordinates "26000,3000,-1000"
And the object "IfcActuator/J" has a vert at "3,1,1" at map coordinates "28000,1000,1000"
And the object "IfcActuator/J" is at "1,3,-1"
And the object "IfcActuator/K" is at "0,0,0"
And the object "IfcActuator/D" has a cartesian point offset of "31,4,-1"
And the object "IfcActuator/G" has a cartesian point offset of "-25,6,-1"
And the object "IfcActuator/J" has a cartesian point offset of "11,3,-1"
And the object "IfcActuator/D" has a vertex at "3,2,-1"
And the object "IfcActuator/D" has a vertex at "5,2,-1"
And the object "IfcActuator/G" has a vertex at "5,4,-1"
And the object "IfcActuator/G" has a vertex at "7,4,-1"
And the object "IfcActuator/J" has a vertex at "1,1,-1"
And the object "IfcActuator/J" has a vertex at "3,1,-1"
Scenario: Load project elements - all georeferencing coordinate situations with map conversion and an offset site - disabled false origin mode
Given an empty Blender session
@@ -639,19 +606,13 @@ Scenario: Load project elements - all georeferencing coordinate situations with
And the object "IfcActuator/A" is at "5.985,14.71,0"
And the object "IfcActuator/B" is at "5.5367,12.519,0"
And the object "IfcActuator/C" is at "0,10,0"
And the object "IfcActuator/D" has its origin on a vertex
And the object "IfcActuator/D" has a vert at "11.5218,17.2284,-1" at map coordinates "26521.758,17228.35,-1000"
And the object "IfcActuator/D" has a vert at "13.9712,15.8141,1" at map coordinates "28971.246,15814.136,1000"
And the object "IfcActuator/D" is at "11.522,17.228,-1"
And the object "IfcActuator/E" is at "5.0191,14.451,0"
And the object "IfcActuator/F" is at "2.1213,13.674,0"
And the object "IfcActuator/G" has its origin on a vertex
And the object "IfcActuator/G" has a vert at "12.936,19.6778,-1" at map coordinates "27935.975,19677.841,-1000"
And the object "IfcActuator/G" has a vert at "15.3855,18.2636,1" at map coordinates "30385.465,18263.627,1000"
And the object "IfcActuator/G" is at "12.936,19.678,-1"
And the object "IfcActuator/H" is at "8.1757,14.261,0"
And the object "IfcActuator/I" is at "2.1213,13.674,0"
And the object "IfcActuator/J" has its origin on a vertex
And the object "IfcActuator/J" has a vert at "9.8487,15.7448,-1" at map coordinates "24848.726,15744.786,-1000"
And the object "IfcActuator/J" has a vert at "12.2982,14.3306,1" at map coordinates "27298.216,14330.573,1000"
And the object "IfcActuator/J" is at "9.8487,15.745,-1"
And the object "IfcActuator/K" is at "9.6593,12.588,0"
Scenario: Load project elements - all georeferencing coordinate situations with map conversion and an offset site - automatic false origin mode
@@ -671,19 +632,13 @@ Scenario: Load project elements - all georeferencing coordinate situations with
And the object "IfcActuator/A" is at "7,3,0"
And the object "IfcActuator/B" is at "6,1,0"
And the object "IfcActuator/C" is at "0,0,0"
And the object "IfcActuator/D" has its origin on a vertex
And the object "IfcActuator/D" has a vert at "13,4,-1" at map coordinates "26521.758,17228.35,-1000"
And the object "IfcActuator/D" has a vert at "15,2,1" at map coordinates "28971.246,15814.136,1000"
And the object "IfcActuator/D" is at "13,4,-1"
And the object "IfcActuator/E" is at "6,3,0"
And the object "IfcActuator/F" is at "3,3,0"
And the object "IfcActuator/G" has its origin on a vertex
And the object "IfcActuator/G" has a vert at "15,6,-1" at map coordinates "27935.975,19677.841,-1000"
And the object "IfcActuator/G" has a vert at "17,4,1" at map coordinates "30385.465,18263.627,1000"
And the object "IfcActuator/G" is at "15,6,-1"
And the object "IfcActuator/H" is at "9,2,0"
And the object "IfcActuator/I" is at "3,3,0"
And the object "IfcActuator/J" has its origin on a vertex
And the object "IfcActuator/J" has a vert at "11,3,-1" at map coordinates "24848.726,15744.786,-1000"
And the object "IfcActuator/J" has a vert at "13,1,1" at map coordinates "27298.216,14330.573,1000"
And the object "IfcActuator/J" is at "11,3,-1"
And the object "IfcActuator/K" is at "10,0,0"
Scenario: Load project elements - all georeferencing coordinate situations with map conversion and an offset site - manual false origin mode
@@ -704,20 +659,23 @@ Scenario: Load project elements - all georeferencing coordinate situations with
And the object "IfcActuator/A" is at "5.985,4.71,0"
And the object "IfcActuator/B" is at "5.5367,2.519,0"
And the object "IfcActuator/C" is at "0,0,0"
And the object "IfcActuator/D" has its origin on a vertex
And the object "IfcActuator/D" has a vert at "11.5218,7.2284,-1" at map coordinates "26521.758,17228.35,-1000"
And the object "IfcActuator/D" has a vert at "13.9712,5.8141,1" at map coordinates "28971.246,15814.136,1000"
And the object "IfcActuator/D" is at "11.522,7.228,-1"
And the object "IfcActuator/E" is at "5.0191,4.451,0"
And the object "IfcActuator/F" is at "2.1213,3.674,0"
And the object "IfcActuator/G" has its origin on a vertex
And the object "IfcActuator/G" has a vert at "12.936,9.6778,-1" at map coordinates "27935.975,19677.841,-1000"
And the object "IfcActuator/G" has a vert at "15.3855,8.2636,1" at map coordinates "30385.465,18263.627,1000"
And the object "IfcActuator/G" is at "12.936,9.678,-1"
And the object "IfcActuator/H" is at "8.1757,4.261,0"
And the object "IfcActuator/I" is at "2.1213,3.674,0"
And the object "IfcActuator/J" has its origin on a vertex
And the object "IfcActuator/J" has a vert at "9.8487,5.7448,-1" at map coordinates "24848.726,15744.786,-1000"
And the object "IfcActuator/J" has a vert at "12.2982,4.3306,1" at map coordinates "27298.216,14330.573,1000"
And the object "IfcActuator/J" is at "9.8487,5.745,-1"
And the object "IfcActuator/K" is at "9.6593,2.588,0"
And the object "IfcActuator/D" has a cartesian point offset of "31,4,-1"
And the object "IfcActuator/G" has a cartesian point offset of "-25,6,-1"
And the object "IfcActuator/J" has a cartesian point offset of "11,3,-1"
And the object "IfcActuator/D" has a vertex at "12.039,5.296,-1"
And the object "IfcActuator/D" has a vertex at "13.971,5.814,-1"
And the object "IfcActuator/G" has a vertex at "13.454,7.746,-1"
And the object "IfcActuator/G" has a vertex at "15.385,8.264,-1"
And the object "IfcActuator/J" has a vertex at "10.366,3.813,-1"
And the object "IfcActuator/J" has a vertex at "12.298,4.331,-1"
Scenario: Link IFC - from an empty IFC project
Given an empty IFC project
-4
View File
@@ -81,7 +81,6 @@ Scenario: Assign type - assign to a type with a material layer set, which automa
And I set "scene.BIMRootProperties.ifc_class" to "IfcWallType"
And I press "bim.assign_class"
And I press "bim.add_material()"
And the object "IfcWallType/Empty" is selected
And I set "active_object.BIMObjectMaterialProperties.material_type" to "IfcMaterialLayerSet"
And I press "bim.assign_material"
When the variable "type" is "{ifc}.by_type('IfcWallType')[0].id()"
@@ -103,7 +102,6 @@ Scenario: Assign type - assign to a type with a material layer set, which automa
And I set "scene.BIMRootProperties.ifc_class" to "IfcWallType"
And I press "bim.assign_class"
And I press "bim.add_material()"
And the object "IfcWallType/Empty" is selected
And I set "active_object.BIMObjectMaterialProperties.material_type" to "IfcMaterialLayerSet"
And I press "bim.assign_material"
When the variable "type" is "{ifc}.by_type('IfcWallType')[0].id()"
@@ -127,7 +125,6 @@ Scenario: Assign type - assign to a different type with a LAYER2 material layer
And I press "bim.assign_class"
And the variable "type" is "{ifc}.by_type('IfcWallType')[-1].id()"
And I press "bim.add_material()"
And the object "IfcWallType/Empty" is selected
And I set "active_object.BIMObjectMaterialProperties.material_type" to "IfcMaterialLayerSet"
And I press "bim.assign_material"
And I add an empty
@@ -183,7 +180,6 @@ Scenario: Assign type - assign to a type with a material profile set
And I set "scene.BIMRootProperties.ifc_class" to "IfcWallType"
And I press "bim.assign_class"
And I press "bim.add_material()"
And the object "IfcWallType/Empty" is selected
And I set "active_object.BIMObjectMaterialProperties.material_type" to "IfcMaterialProfileSet"
And I press "bim.assign_material"
And I press "bim.enable_editing_assigned_material"
@@ -45,14 +45,14 @@ def test_text_formatter_defaults_to_none():
def test_text_formatter_field_stores_callable():
formatter = lambda props, value: f"{value:.2f}m"
formatter = lambda props, value: f"{value:.2f}m" # noqa: E731
config = DimensionGizmoConfig(attr_name="length", axis=(1, 0, 0), text_formatter=formatter)
assert config.text_formatter is not None
assert callable(config.text_formatter)
def test_text_formatter_receives_props_and_value():
formatter = lambda props, value: f"{props.label}={value}"
formatter = lambda props, value: f"{props.label}={value}" # noqa: E731
config = DimensionGizmoConfig(attr_name="length", axis=(1, 0, 0), text_formatter=formatter)
props = SimpleNamespace(label="L")
assert config.text_formatter(props, 3.14) == "L=3.14"
@@ -104,7 +104,7 @@ class TestParametricGizmoPollsHideDuringTransformModal:
continue
try:
result = poll(bpy.context)
except Exception as exc:
except Exception as exc: # noqa: BLE001
offenders.append((name, f"poll raised: {type(exc).__name__}: {exc}"))
continue
if result:
@@ -98,7 +98,7 @@ class TestWallGizmoGroupsHideDuringPreview:
continue
try:
result = poll(bpy.context)
except Exception as exc:
except Exception as exc: # noqa: BLE001
offenders.append((name, f"poll raised: {type(exc).__name__}: {exc}"))
continue
if result:
@@ -119,7 +119,7 @@ class TestWallGizmoGroupsHideOnArrayChildSelection:
for name, cls in groups:
try:
result = cls.poll(bpy.context)
except Exception as exc:
except Exception as exc: # noqa: BLE001
offenders.append((name, f"poll raised: {type(exc).__name__}: {exc}"))
continue
if result:
@@ -159,7 +159,7 @@ class TestWallOperatorsRejectArrayChildSelection:
for name, cls in ops:
try:
result = cls.poll(bpy.context)
except Exception as exc:
except Exception as exc: # noqa: BLE001
offenders.append((name, f"poll raised: {type(exc).__name__}: {exc}"))
continue
if result:
-107
View File
@@ -36,7 +36,6 @@ import bpy
import ifcopenshell
import ifcopenshell.util.element
import ifcopenshell.util.representation
import ifcopenshell.util.unit
import numpy as np
import pytest
from mathutils import Vector
@@ -1001,7 +1000,6 @@ def i_click_button_and_expect_error_error_msg(button, error_msg):
@given(parsers.parse('I evaluate expression "{expression}"'))
@when(parsers.parse('I evaluate expression "{expression}"'))
@then(parsers.parse('I evaluate expression "{expression}"'))
def i_evaluate_expression(expression):
expression = replace_variables(expression)
exec(expression)
@@ -1682,111 +1680,6 @@ def the_object_name_has_a_vertex_at_location(name, location):
assert is_pass, f"No verts found at {location}: {verts}"
def get_model_origin() -> Vector:
"""Where the model was shifted to, in Blender units.
Geometry far from the origin is moved next to it so it keeps its precision,
and the shift is recorded as the model origin. Which vert of which object it
lands on is not something to depend on, so anything measured from it stays
put even when that choice changes.
"""
props = bpy.context.scene.BIMGeoreferenceProperties
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(an_ifc_file_exists())
return Vector([float(co) for co in props.model_origin.split(",")]) * unit_scale
def get_world_verts(obj: bpy.types.Object) -> list[Vector]:
mesh = obj.data
assert isinstance(mesh, bpy.types.Mesh) and len(mesh.vertices), f"Object {obj.name} has no mesh"
return [obj.matrix_world @ v.co for v in mesh.vertices]
def assert_vert_at_map_coordinates(obj: bpy.types.Object, vert: Vector, coordinates: str) -> None:
# Same conversion as the georeferencing calculator, which works in project
# units rather than Blender ones.
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(an_ifc_file_exists())
enh = Vector(tool.Georeference.xyz2enh(tuple(co / unit_scale for co in vert)))
expected = Vector([float(co) for co in coordinates.split(",")])
assert (enh - expected).length < 0.05, f"Vert {vert} is at map coordinates {enh[:]} instead of {coordinates}"
@then(
parsers.parse(
'the object "{name}" is at "{location}" relative to the model origin at map coordinates "{coordinates}"'
)
)
def the_object_name_is_at_location_relative_to_the_model_origin_at_map_coordinates(name, location, coordinates):
"""For objects with no geometry to name a vert on.
The Blender location is only meaningful next to the origin everything was
shifted by, since the two move together, but the map coordinates hold still
either way.
"""
obj = the_object_name_exists(name)
obj_location = obj.location + get_model_origin()
assert (
obj_location - Vector([float(co) for co in location.split(",")])
).length < 0.05, f"Object is at {obj_location} relative to the model origin instead of {location}"
assert_vert_at_map_coordinates(obj, obj.matrix_world.translation, coordinates)
@then(parsers.parse('the object "{name}" has a vert at "{location}" at map coordinates "{coordinates}"'))
def the_object_name_has_a_vert_at_location_at_map_coordinates(name, location, coordinates):
"""Check where a vert sits in Blender and where it is in the world.
Both matter: the Blender location is what the user sees, and checking only
the map coordinates would pass just as happily if the georeferencing maths
or the offsets it reads were wrong, since the same maths produces both.
"""
obj = the_object_name_exists(name)
target = Vector([float(co) for co in location.split(",")])
verts = get_world_verts(obj)
vert = next((v for v in verts if (v - target).length < 0.001), None)
assert vert is not None, f"No vert found at {location}: {verts}"
assert_vert_at_map_coordinates(obj, vert, coordinates)
@then(
parsers.parse(
'the object "{name}" has a vert at "{location}" relative to the model origin at map coordinates "{coordinates}"'
)
)
def the_object_name_has_a_vert_at_location_relative_to_the_model_origin_at_map_coordinates(name, location, coordinates):
"""As above, for when the whole model has been shifted onto the origin.
Blender locations are then only meaningful relative to that origin, since
everything moves together with it.
"""
obj = the_object_name_exists(name)
target = Vector([float(co) for co in location.split(",")]) - get_model_origin()
verts = get_world_verts(obj)
vert = next((v for v in verts if (v - target).length < 0.001), None)
assert vert is not None, f"No vert found at {location} relative to the model origin: {verts}"
assert_vert_at_map_coordinates(obj, vert, coordinates)
@then(parsers.parse('the object "{name}" has its origin on a vertex'))
def the_object_name_has_its_origin_on_a_vertex(name):
"""Far away geometry is shifted onto one of its own verts, which keeps the
origin on the geometry and the local coordinates small enough to keep their
precision. Which vert that is does not matter."""
obj = the_object_name_exists(name)
mesh = obj.data
assert isinstance(mesh, bpy.types.Mesh) and len(mesh.vertices), f"Object {obj.name} has no mesh"
nearest = min(v.co.length for v in mesh.vertices)
assert nearest < 0.001, f"Object origin is {nearest} away from its nearest vert"
@then("the model origin is on an object vertex")
def the_model_origin_is_on_an_object_vertex():
for obj in bpy.data.objects:
if not isinstance(obj.data, bpy.types.Mesh):
continue
if any(v.length < 0.001 for v in get_world_verts(obj)):
return
assert False, "No object has a vert at the model origin"
@then(parsers.parse('the object "{name}" has no scale'))
def the_object_name_has_no_scale(name):
assert the_object_name_exists(name).scale == Vector(
+1 -1
View File
@@ -1,5 +1,5 @@
[tool.ruff]
extend = "../pyproject.toml"
lint.ignore = [
"unused-import", # unused imports
"F401", # unused imports
]
+3 -3
View File
@@ -168,7 +168,7 @@ ifcopenshell_deploy_qt_runtime(BonsaiViewer)
# them explicitly. (In a static build these are absent from lib/
# and the glob just no-ops, so this rule is safe in both modes.)
#
# 2. Plug-ins (ifcopenshell_*.dylib, no `lib` prefix) dlopen-only
# 2. Plug-ins (ifcopenshell.*.dylib, no `lib` prefix) dlopen-only
# deps the plug-in loader resolves at runtime. macdeployqt has
# no way to know about these.
#
@@ -177,7 +177,7 @@ ifcopenshell_deploy_qt_runtime(BonsaiViewer)
# inside the bundle), so plug-ins and core libs both find each other
# on the first probe.
#
# The geometry-writer filter drops ifcopenshell_geometry_writer_*.dylib
# The geometry-writer filter drops ifcopenshell.geometry.writer.*.dylib
# (the per-schema OBJ / glTF / DAE / STP / IGS / SVG / TTL export
# converters heavy, viewer-irrelevant). Mirrors the Rocky workflow's
# filter in `stage_runtime_payload` (see 27249770e).
@@ -195,7 +195,7 @@ if(APPLE)
install(CODE [[
set(_fw "${CMAKE_INSTALL_PREFIX}/BonsaiViewer.app/Contents/Frameworks")
file(GLOB _ifc_dylibs "${CMAKE_INSTALL_PREFIX}/lib/*.dylib")
list(FILTER _ifc_dylibs EXCLUDE REGEX "ifcopenshell_geometry_writer_")
list(FILTER _ifc_dylibs EXCLUDE REGEX "ifcopenshell\\.geometry\\.writer\\.")
if(_ifc_dylibs)
message(STATUS "Staging IfcOpenShell dylibs (linked core + plug-ins) into BonsaiViewer.app/Contents/Frameworks")
file(COPY ${_ifc_dylibs} DESTINATION "${_fw}")
+1 -1
View File
@@ -70,7 +70,7 @@ std::optional<BasicElementInfo> ElementRegistry::findBasicElementInfo(uint32_t o
return it->second;
}
std::optional<express::base> ElementRegistry::findEntity(uint32_t object_id) const {
std::optional<express::Base> ElementRegistry::findEntity(uint32_t object_id) const {
if (!loader_) return std::nullopt;
auto info = findBasicElementInfo(object_id);
+1 -1
View File
@@ -54,7 +54,7 @@ public:
void removeModel(uint32_t session_model_id);
std::vector<BasicElementInfo> basicElementInfoForModel(uint32_t session_model_id) const;
std::optional<BasicElementInfo> findBasicElementInfo(uint32_t object_id) const;
std::optional<express::base> findEntity(uint32_t object_id) const;
std::optional<express::Base> findEntity(uint32_t object_id) const;
private:
void onSidecarElementsReady(uint32_t session_model_id,
@@ -44,7 +44,7 @@ Main components
code.
``GeometryStreamer``
Runs ``ifcopenshell::geom::iterator`` on a worker thread for raw IFC loads. It emits a
Runs ``IfcGeom::Iterator`` on a worker thread for raw IFC loads. It emits a
``StreamedMesh`` once for each unique representation mesh and a
``StreamedInstance`` for each placed occurrence.
+3 -3
View File
@@ -32,7 +32,7 @@
#include "../../../ifcviewer/SceneLoader.h"
#include "../../../ifcviewer/SidecarBuilder.h"
#include "../../../ifcviewer/ViewportWindow.h"
#include "../../../ifcgeom/serializer.h"
#include "../../../ifcgeom/Serializer.h"
#include "../../../serializers/document_serializer_plugin.h"
#include <QDebug>
@@ -685,7 +685,7 @@ void convertIfcToDatabase(SessionState& session, QWidget& host) {
throw ifcopenshell::exception("RDB serializer does not support streaming from an input filename");
}
std::shared_ptr<ifcopenshell::geom::serializer> serializer = registry.create("rdb", context);
boost::shared_ptr<Serializer> serializer = registry.create("rdb", context);
serializer->finalize();
} catch (const std::exception& e) {
*error_message = QString::fromUtf8(e.what());
@@ -791,7 +791,7 @@ void exportGeometryDatabase(SessionState& session, QWidget& host) {
throw ifcopenshell::exception("RDB serializer does not support streaming from an input filename");
}
std::shared_ptr<ifcopenshell::geom::serializer> serializer = registry.create("rdb", context);
boost::shared_ptr<Serializer> serializer = registry.create("rdb", context);
serializer->finalize();
serializer.reset();
@@ -75,16 +75,16 @@ QString formatCachedUnitScale(double meters_per_unit) {
return QString("Cached scale: 1 unit = %1 m").arg(formatNumber(meters_per_unit));
}
std::string enumString(const ifcopenshell::attribute_value& av) {
std::string enumString(const attribute_value& av) {
if (av.isNull()) return {};
if (av.type() != ifcopenshell::Argument_ENUMERATION) return {};
ifcopenshell::enumeration_reference enumeration = av;
enumeration_reference enumeration = av;
return std::string(enumeration.value() ? enumeration.value() : "");
}
QString formatNamedUnit(const express::base& unit) {
QString formatNamedUnit(const express::Base& unit) {
if (!unit) return "";
auto entity = unit.as<express::entity>();
auto entity = unit.as<express::Entity>();
if (unit.declaration().is("IfcSIUnit")) {
const std::string prefix = enumString(entity.get("Prefix"));
const std::string name = enumString(entity.get("Name"));
+4 -4
View File
@@ -109,7 +109,7 @@ void PropertiesPanelView::refresh(uint32_t object_id) {
state.entity = {"No item selected", ""};
auto entity = registry ? registry->findEntity(object_id)
: std::optional<express::base>{};
: std::optional<express::Base>{};
if (entity) {
state.entity.entity_class = QString::fromStdString(entity->declaration().name());
if (auto predefined_type = get_predefined_type(*entity)) {
@@ -121,16 +121,16 @@ void PropertiesPanelView::refresh(uint32_t object_id) {
}
// Relationships: the construction type and the spatial container, shown
// by name (falling back to the entity class when unnamed).
auto display_name = [](const express::base& related) -> QString {
auto display_name = [](const express::Base& related) -> QString {
if (auto name = get_string_attribute(related, "Name"); name && !name->empty()) {
return QString::fromStdString(*name);
}
return QString::fromStdString(related.declaration().name());
};
if (express::base type = get_type(*entity)) {
if (express::Base type = get_type(*entity)) {
state.relationships.append({"Type", display_name(type)});
}
if (express::base container = get_container(*entity)) {
if (express::Base container = get_container(*entity)) {
state.relationships.append({"Container", display_name(container)});
}
// Property sets (Pset_*) and quantity sets (Qto_* / BaseQuantities),
@@ -60,7 +60,7 @@ TreeNode* findNodeRecursive(QList<TreeNode>& nodes, const NodePath& path, int de
return nullptr;
}
ItemKind kindOf(const express::base& element) {
ItemKind kindOf(const express::Base& element) {
const auto& declaration = element.declaration();
if (declaration.is("IfcSite")) return ItemKind::Site;
if (declaration.is("IfcBuilding")) return ItemKind::Building;
@@ -68,14 +68,14 @@ ItemKind kindOf(const express::base& element) {
return ItemKind::Space; // IfcSpace, IfcSpatialZone, …
}
QString displayName(const express::base& element) {
QString displayName(const express::Base& element) {
if (auto name = get_string_attribute(element, "Name"); name && !name->empty()) {
return QString::fromStdString(*name);
}
return QString::fromStdString(element.declaration().name());
}
TreeNode buildNode(const express::base& element) {
TreeNode buildNode(const express::Base& element) {
TreeNode node;
node.name = displayName(element);
node.kind = kindOf(element);
+2 -2
View File
@@ -46,6 +46,6 @@ py-modules = ["bsdd","bsdd_json","type_hints"]
[tool.ruff]
extend = "../../pyproject.toml"
lint.extend-select = [
"unused-import", # unused imports
lint.select = [
"F401", # unused imports
]
+6 -9
View File
@@ -3,10 +3,7 @@ IS_STABLE:=FALSE
PYTHON:=python3
PIP:=pip3
VERSION:=$(shell cat ../../VERSION)
VERSION_BASE:=$(shell sed -E 's/[[:alpha:]]+[0-9]+$$//' ../../VERSION)
VERSION_PYTHON:=$(shell sed 's/alpha/a/' ../../VERSION)
VERSION_DATE:=$(shell date '+%y%m%d')
VERSION_DAILY:=$(VERSION_BASE)a$(VERSION_DATE)
SED:=sed -i
VENV_BIN:=bin
@@ -33,18 +30,18 @@ dist:
cp pyproject.toml build/
if [ -f README.md ]; then cp README.md build/; fi
ifeq ($(IS_STABLE), TRUE)
$(SED) 's/version = "0.0.0"/version = "$(VERSION_PYTHON)"/' build/pyproject.toml
$(SED) 's/version = "0.0.0"/version = "$(VERSION)"/' build/pyproject.toml
ifdef IS_MODULE
$(SED) 's/version = "0.0.0"/version = "$(VERSION_PYTHON)"/' build/$(PACKAGE_NAME)
$(SED) 's/version = "0.0.0"/version = "$(VERSION)"/' build/$(PACKAGE_NAME)
else
$(SED) 's/version = "0.0.0"/version = "$(VERSION_PYTHON)"/' build/$(PACKAGE_NAME)/__init__.py
$(SED) 's/version = "0.0.0"/version = "$(VERSION)"/' build/$(PACKAGE_NAME)/__init__.py
endif
else
$(SED) 's/version = "0.0.0"/version = "$(VERSION_DAILY)"/' build/pyproject.toml
$(SED) 's/version = "0.0.0"/version = "$(VERSION)a$(VERSION_DATE)"/' build/pyproject.toml
ifdef IS_MODULE
$(SED) 's/version = "0.0.0"/version = "$(VERSION_DAILY)"/' build/$(PACKAGE_NAME)
$(SED) 's/version = "0.0.0"/version = "$(VERSION)-alpha$(VERSION_DATE)"/' build/$(PACKAGE_NAME)
else
$(SED) 's/version = "0.0.0"/version = "$(VERSION_DAILY)"/' build/$(PACKAGE_NAME)/__init__.py
$(SED) 's/version = "0.0.0"/version = "$(VERSION)-alpha$(VERSION_DATE)"/' build/$(PACKAGE_NAME)/__init__.py
endif
endif
cd build && $(PYTHON) -m venv env && . env/$(VENV_ACTIVATE) && $(PIP) install build
+8 -8
View File
@@ -50,7 +50,7 @@
#include "ifcparse/hierarchy_helper.h"
#include "plugin/plugin.h"
#include "../ifcgeom/serialization/serialization.h"
#include "../ifcgeom/Serialization/Serialization.h"
#if USE_VLD
#include <vld.h>
@@ -101,7 +101,7 @@ int main() {
// IfcFacetedBRep. If it would not be a polyhedron, serialise() can only be successful when linked
// to the IFC4 model and with `advanced` set to `true` which introduces IfcAdvancedFace. It would
// return `0` otherwise.
auto building_shape = ifcopenshell::geom::serialise(file, building_shell, false).as<IfcSchema::IfcProductDefinitionShape>();
auto building_shape = IfcGeom::serialise(file, building_shell, false).as<IfcSchema::IfcProductDefinitionShape>();
file.add_entity(building_shape);
auto building_representations = building_shape.Representations();
@@ -117,9 +117,9 @@ int main() {
TopoDS_Shape shape;
createGroundShape(shape);
auto ground_representation = ifcopenshell::geom::serialise(file, shape, true);
auto ground_representation = IfcGeom::serialise(file, shape, true);
if (!ground_representation) {
ground_representation = ifcopenshell::geom::tesselate(file, shape, 100.);
ground_representation = IfcGeom::tesselate(file, shape, 100.);
}
file.getSingle<IfcSchema::IfcSite>().setRepresentation(ground_representation.as<IfcSchema::IfcProductDefinitionShape>());
@@ -131,13 +131,13 @@ int main() {
setSurfaceColour(file, ground_representation.as<IfcSchema::IfcProductDefinitionShape>(), 0.15, 0.25, 0.05);
/*
// Note that IFC lacks elementary surfaces that STEP does have, such as spherical_surface.
// BRepBuilderAPI_NurbsConvert can be used to serialize such surfaces as nurbs surfaces.
// Note that IFC lacks elementary surfaces that STEP does have, such as spherical_surface.
// BRepBuilderAPI_NurbsConvert can be used to serialize such surfaces as nurbs surfaces.
TopoDS_Shape sphere = BRepPrimAPI_MakeSphere(gp_Pnt(), 1000.).Shape();
auto sphere_representation = ifcopenshell::geom::serialise(file, sphere, true);
IfcSchema::IfcProductDefinitionShape* sphere_representation = IfcGeom::serialise(sphere, true);
if (S(IfcSchema::Identifier) == "IFC4") {
sphere = BRepBuilderAPI_NurbsConvert(sphere, true).Shape();
sphere_representation = ifcopenshell::geom::serialise(file, sphere, true);
sphere_representation = IfcGeom::serialise(sphere, true);
}
*/
+2 -2
View File
@@ -47,7 +47,7 @@
#include "ifcparse/hierarchy_helper.h"
#include "plugin/plugin.h"
#include "../ifcgeom/serialization/serialization.h"
#include "../ifcgeom/Serialization/Serialization.h"
#if USE_VLD
#include <vld.h>
@@ -301,7 +301,7 @@ int main() {
// will be tessellated using the deflection specified.
TopoDS_Shape shape;
createGroundShape(shape);
auto ground_representation = ifcopenshell::geom::tesselate(file, shape, 100.).as<IfcSchema::IfcProductDefinitionShape>();
auto ground_representation = IfcGeom::tesselate(file, shape, 100.).as<IfcSchema::IfcProductDefinitionShape>();
file.getSingle<IfcSchema::IfcSite>().setRepresentation(ground_representation);
GProp_GProps prop;
+4 -4
View File
@@ -70,10 +70,10 @@ typedef IfcSchema::IfcBuildingElement element_t;
typedef IfcSchema::IfcBuiltElement element_t;
#endif
std::string format_string(const ifcopenshell::attribute_value& argument) {
std::string format_string(const attribute_value& argument) {
// Argument is a runtime tagged variant for the various data types in a IFC model,
// in this particular case we only care about flattening it to a string.
// @todo mostly duplicated from xml_serializer.cpp
// @todo mostly duplicated from XmlSerializer.cpp
if (argument.isNull()) {
return "-";
}
@@ -194,7 +194,7 @@ void get_psets_s(element_properties& props, const typename Schema::IfcObjectDefi
}
}
element_properties get_psets(const express::base& inst) {
element_properties get_psets(const express::Base& inst) {
element_properties props;
if (auto object_definition = inst.as<IfcSchema::IfcObjectDefinition>()) {
get_psets_s<IfcSchema>(props, object_definition);
@@ -213,7 +213,7 @@ int main(int argc, char** argv) {
#endif
// Redirect the output (both progress and log) to stdout
ifcopenshell::logger::root().set_output(&std::cout, &std::cout);
::logger::root().set_output(&std::cout, &std::cout);
// Parse the IFC file provided in argv[1]
ifcopenshell::file file(argv[1]);
+97
View File
@@ -0,0 +1,97 @@
ISO-10303-21;
HEADER;
FILE_DESCRIPTION(('ViewDefinition[DesignTransferView]'),'2;1');
FILE_NAME('IfcParseExamples_test.ifc','2025-12-12T13:49:19+05:00',(''),(''),'IfcOpenShell 0.0.0','Bonsai 0.8.5-alpha251212-a47cbc3','Nobody');
FILE_SCHEMA(('IFC2X3'));
ENDSEC;
DATA;
#1=IFCPERSON('HSeldon','Seldon','Hari',$,$,$,$,$);
#2=IFCORGANIZATION('APTR','Aperture Science',$,$,$);
#3=IFCPERSONANDORGANIZATION(#1,#2,$);
#4=IFCACTORROLE(.USERDEFINED.,'CONTRIBUTOR',$);
#5=IFCORGANIZATION('IfcOpenShell','IfcOpenShell','IfcOpenShell is an open source software library that helps users and software developers to work with IFC data.',(#4),(#6));
#6=IFCTELECOMADDRESS(.USERDEFINED.,$,'WEBPAGE',$,$,$,$,'https://ifcopenshell.org');
#7=IFCAPPLICATION(#5,'0.8.5','Bonsai','Bonsai-0.8.5');
#8=IFCOWNERHISTORY(#3,#7,.READWRITE.,.ADDED.,1765520605,#3,#7,1765520605);
#9=IFCPROJECT('3GSSvm5SP5U96O5iar8uYb',#8,'My Project',$,$,$,$,(#22,#34),#17);
#10=IFCSIUNIT(*,.LENGTHUNIT.,$,.METRE.);
#11=IFCSIUNIT(*,.AREAUNIT.,$,.SQUARE_METRE.);
#12=IFCSIUNIT(*,.VOLUMEUNIT.,$,.CUBIC_METRE.);
#13=IFCDIMENSIONALEXPONENTS(0,0,0,0,0,0,0);
#14=IFCSIUNIT(*,.PLANEANGLEUNIT.,$,.RADIAN.);
#15=IFCMEASUREWITHUNIT(IFCREAL(0.0174532925199433),#14);
#16=IFCCONVERSIONBASEDUNIT(#13,.PLANEANGLEUNIT.,'degree',#15);
#17=IFCUNITASSIGNMENT((#12,#10,#16,#11));
#18=IFCCARTESIANPOINT((0.,0.,0.));
#19=IFCDIRECTION((0.,0.,1.));
#20=IFCDIRECTION((1.,0.,0.));
#21=IFCAXIS2PLACEMENT3D(#18,#19,#20);
#22=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Model',3,1.E-05,#21,$);
#23=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Body','Model',*,*,*,*,#22,$,.MODEL_VIEW.,$);
#24=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Axis','Model',*,*,*,*,#22,$,.GRAPH_VIEW.,$);
#25=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Box','Model',*,*,*,*,#22,$,.MODEL_VIEW.,$);
#26=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Annotation','Model',*,*,*,*,#22,$,.SECTION_VIEW.,$);
#27=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Annotation','Model',*,*,*,*,#22,$,.ELEVATION_VIEW.,$);
#28=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Annotation','Model',*,*,*,*,#22,$,.MODEL_VIEW.,$);
#29=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Annotation','Model',*,*,*,*,#22,$,.PLAN_VIEW.,$);
#30=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Profile','Model',*,*,*,*,#22,$,.ELEVATION_VIEW.,$);
#31=IFCCARTESIANPOINT((0.,0.));
#32=IFCDIRECTION((1.,0.));
#33=IFCAXIS2PLACEMENT2D(#31,#32);
#34=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Plan',2,1.E-05,#33,$);
#35=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Axis','Plan',*,*,*,*,#34,$,.GRAPH_VIEW.,$);
#36=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Body','Plan',*,*,*,*,#34,$,.PLAN_VIEW.,$);
#37=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Annotation','Plan',*,*,*,*,#34,$,.PLAN_VIEW.,$);
#38=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Annotation','Plan',*,*,*,*,#34,$,.REFLECTED_PLAN_VIEW.,$);
#39=IFCOWNERHISTORY(#3,#7,.READWRITE.,.MODIFIED.,1765520605,#3,#7,1765520605);
#40=IFCSITE('2Ys4j4OOf3x9UYeALDJ_g3',#39,'My Site',$,$,#66,$,$,.ELEMENT.,$,$,$,$,$);
#46=IFCOWNERHISTORY(#3,#7,.READWRITE.,.MODIFIED.,1765520605,#3,#7,1765520605);
#47=IFCBUILDING('3e580MQFTCD9tCSaZWRPi9',#46,'My Building',$,$,#73,$,$,.ELEMENT.,$,$,$);
#53=IFCOWNERHISTORY(#3,#7,.READWRITE.,.MODIFIED.,1765520605,#3,#7,1765520605);
#54=IFCBUILDINGSTOREY('3XFkAr6KbAsP5wR3gxi3my',#53,'My Storey',$,$,#80,$,$,.ELEMENT.,$);
#60=IFCOWNERHISTORY(#3,#7,.READWRITE.,.ADDED.,1765520605,#3,#7,1765520605);
#61=IFCRELAGGREGATES('2OWfFyDQn0qP0Vd6NWGX7k',#60,$,$,#9,(#40));
#62=IFCCARTESIANPOINT((0.,0.,0.));
#63=IFCDIRECTION((0.,0.,1.));
#64=IFCDIRECTION((1.,0.,0.));
#65=IFCAXIS2PLACEMENT3D(#62,#63,#64);
#66=IFCLOCALPLACEMENT($,#65);
#67=IFCOWNERHISTORY(#3,#7,.READWRITE.,.ADDED.,1765520605,#3,#7,1765520605);
#68=IFCRELAGGREGATES('3_X_vfPpXDaO3NODBJgmSp',#67,$,$,#40,(#47));
#69=IFCCARTESIANPOINT((0.,0.,0.));
#70=IFCDIRECTION((0.,0.,1.));
#71=IFCDIRECTION((1.,0.,0.));
#72=IFCAXIS2PLACEMENT3D(#69,#70,#71);
#73=IFCLOCALPLACEMENT(#66,#72);
#74=IFCOWNERHISTORY(#3,#7,.READWRITE.,.ADDED.,1765520605,#3,#7,1765520605);
#75=IFCRELAGGREGATES('3ScF98CZD3pAa0SW$mhBHF',#74,$,$,#47,(#54));
#76=IFCCARTESIANPOINT((0.,0.,0.));
#77=IFCDIRECTION((0.,0.,1.));
#78=IFCDIRECTION((1.,0.,0.));
#79=IFCAXIS2PLACEMENT3D(#76,#77,#78);
#80=IFCLOCALPLACEMENT(#73,#79);
#110=IFCOWNERHISTORY(#3,#7,.READWRITE.,.MODIFIED.,1765529359,#3,#7,1765520632);
#111=IFCWALL('1lfiJzSCH1a8vjDuSLL4Ii',#110,'Cube',$,$,#203,$,$);
#112=IFCOWNERHISTORY(#3,#7,.READWRITE.,.MODIFIED.,1765520818,#3,#7,1765520632);
#113=IFCRELCONTAINEDINSPATIALSTRUCTURE('2Gqx6vP7jB4x_h65Tawn0f',#112,$,$,(#111,#182,#150),#54);
#149=IFCOWNERHISTORY(#3,#7,.READWRITE.,.MODIFIED.,1765529359,#3,#7,1765520810);
#150=IFCWINDOW('1eE0btB_54tBL59wLXKO1B',#149,'Cube',$,$,#208,$,$,$,$);
#181=IFCOWNERHISTORY(#3,#7,.READWRITE.,.MODIFIED.,1765529359,#3,#7,1765520818);
#182=IFCBUILDINGELEMENTPART('0fmaoBJgz0kPBavO$EJkPn',#181,'Cube',$,$,#198,$,$);
#194=IFCCARTESIANPOINT((-3.82147288322449,-0.987768530845642,1.78445267677307));
#195=IFCDIRECTION((0.,0.,1.));
#196=IFCDIRECTION((1.,0.,0.));
#197=IFCAXIS2PLACEMENT3D(#194,#195,#196);
#198=IFCLOCALPLACEMENT(#80,#197);
#199=IFCCARTESIANPOINT((0.,0.,0.));
#200=IFCDIRECTION((0.,0.,1.));
#201=IFCDIRECTION((1.,0.,0.));
#202=IFCAXIS2PLACEMENT3D(#199,#200,#201);
#203=IFCLOCALPLACEMENT(#80,#202);
#204=IFCCARTESIANPOINT((0.,4.25822639465332,0.));
#205=IFCDIRECTION((0.,0.,1.));
#206=IFCDIRECTION((1.,0.,0.));
#207=IFCAXIS2PLACEMENT3D(#204,#205,#206);
#208=IFCLOCALPLACEMENT(#80,#207);
ENDSEC;
END-ISO-10303-21;
+147
View File
@@ -0,0 +1,147 @@
/********************************************************************************
* *
* 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/>. *
* *
********************************************************************************/
/********************************************************************************
* *
* Example that generates various representations from *
* IfcArbitraryOpenProfileDefs and its subclass IfcCenterLineProfileDef *
* *
********************************************************************************/
#include <string>
#include <iostream>
#include <fstream>
#include "ifcparse/macros.h"
#ifndef IfcSchema
#define IfcSchema Ifc4
#endif
#include INCLUDE_SCHEMA(ifcparse/schemas, IfcSchema)
#include INCLUDE_SCHEMA_DEFINITIONS(ifcparse/schemas, IfcSchema)
#include "ifcparse/hierarchy_helper.h"
typedef std::string S;
typedef ifcopenshell::global_id guid;
boost::none_t const null = boost::none;
static int i = 0;
void create_product_from_item(hierarchy_helper& file, IfcSchema::IfcRepresentationItem* item, const std::string& s) {
IfcSchema::IfcBuildingElementProxy* product = new IfcSchema::IfcBuildingElementProxy(
guid(), 0, S("product"), null, null, 0, 0, null, null);
file.addBuildingProduct(product);
product->setOwnerHistory(file.getSingle<IfcSchema::IfcOwnerHistory>());
product->setObjectPlacement(file.addLocalPlacement(0, 120 * i++));
IfcSchema::IfcRepresentation::list::ptr reps (new IfcSchema::IfcRepresentation::list());
IfcSchema::IfcRepresentationItem::list::ptr items (new IfcSchema::IfcRepresentationItem::list());
items->push(item);
if (s == "GeometricSet") {
IfcSchema::IfcGeometricSet* set = new IfcSchema::IfcGeometricSet(items->generalize());
file.add_entity(set);
items = IfcSchema::IfcRepresentationItem::list::ptr(new IfcSchema::IfcRepresentationItem::list());
items->push(set);
}
IfcSchema::IfcShapeRepresentation* rep = new IfcSchema::IfcShapeRepresentation(
file.getSingle<IfcSchema::IfcRepresentationContext>(), S("Body"), s, items);
reps->push(rep);
IfcSchema::IfcProductDefinitionShape* shape = new IfcSchema::IfcProductDefinitionShape(boost::none, boost::none, reps);
file.add_entity(rep);
file.add_entity(shape);
product->setRepresentation(shape);
}
void create_surfaces_from_profile(hierarchy_helper& file, IfcSchema::IfcProfileDef* profile) {
IfcSchema::IfcSurfaceOfLinearExtrusion* extrusion = new IfcSchema::IfcSurfaceOfLinearExtrusion(profile, file.addPlacement3d(), file.addTriplet<IfcSchema::IfcDirection>(0, 0, 1), 100.);
file.add_entity(extrusion);
IfcSchema::IfcAxis1Placement* ax1 = new IfcSchema::IfcAxis1Placement(file.addTriplet<IfcSchema::IfcCartesianPoint>(0,100,0), file.addTriplet<IfcSchema::IfcDirection>(1,0,0));
IfcSchema::IfcSurfaceOfRevolution* revolution = new IfcSchema::IfcSurfaceOfRevolution(profile, file.addPlacement3d(), ax1);
file.add_entity(ax1);
file.add_entity(revolution);
create_product_from_item(file, extrusion, "GeometricSet");
create_product_from_item(file, revolution, "GeometricSet");
}
void create_solids_from_profile(hierarchy_helper& file, IfcSchema::IfcProfileDef* profile) {
IfcSchema::IfcExtrudedAreaSolid* extrusion = new IfcSchema::IfcExtrudedAreaSolid(profile, file.addPlacement3d(), file.addTriplet<IfcSchema::IfcDirection>(0, 0, 1), 100.);
file.add_entity(extrusion);
IfcSchema::IfcAxis1Placement* ax1 = new IfcSchema::IfcAxis1Placement(file.addTriplet<IfcSchema::IfcCartesianPoint>(0,100,0), file.addTriplet<IfcSchema::IfcDirection>(1,0,0));
IfcSchema::IfcRevolvedAreaSolid* revolution1 = new IfcSchema::IfcRevolvedAreaSolid(profile, file.addPlacement3d(), ax1, 360.);
IfcSchema::IfcRevolvedAreaSolid* revolution2 = new IfcSchema::IfcRevolvedAreaSolid(profile, file.addPlacement3d(), ax1, 90.);
file.add_entity(ax1);
file.add_entity(revolution1);
file.add_entity(revolution2);
create_product_from_item(file, extrusion, "SweptSolid");
create_product_from_item(file, revolution1, "SweptSolid");
create_product_from_item(file, revolution2, "SweptSolid");
}
void create_products_from_curve(hierarchy_helper& file, IfcSchema::IfcBoundedCurve* curve) {
IfcSchema::IfcArbitraryOpenProfileDef* open = new IfcSchema::IfcArbitraryOpenProfileDef(IfcSchema::IfcProfileTypeEnum::IfcProfileType_CURVE, null, curve);
IfcSchema::IfcCenterLineProfileDef* center_line = new IfcSchema::IfcCenterLineProfileDef(IfcSchema::IfcProfileTypeEnum::IfcProfileType_AREA, null, curve, 10.);
file.add_entity(open);
file.add_entity(center_line);
create_surfaces_from_profile(file, open);
create_solids_from_profile(file, center_line);
}
int main(int argc, char** argv) {
const char filename[] = "IfcArbitraryOpenProfileDef.ifc";
hierarchy_helper file;
file.header().file_name().name(filename);
double coords1[] = {-50.0, 0.0};
double coords2[] = { 50.0, 0.0};
IfcSchema::IfcCartesianPoint::list::ptr points (new IfcSchema::IfcCartesianPoint::list());
points->push(new IfcSchema::IfcCartesianPoint(std::vector<double>(coords1, coords1+2)));
points->push(new IfcSchema::IfcCartesianPoint(std::vector<double>(coords2, coords2+2)));
file.addEntities(points->generalize());
IfcSchema::IfcPolyline* poly = new IfcSchema::IfcPolyline(points);
file.add_entity(poly);
create_products_from_curve(file, poly);
IfcSchema::IfcEllipse* ellipse = new IfcSchema::IfcEllipse(file.addPlacement2d(), 50., 25.);
file.add_entity(ellipse);
IfcEntityList::ptr trim1(new IfcEntityList);
IfcEntityList::ptr trim2(new IfcEntityList);
trim1->push(new IfcSchema::IfcParameterValue( 0.));
trim2->push(new IfcSchema::IfcParameterValue(180.));
IfcSchema::IfcTrimmedCurve* trim = new IfcSchema::IfcTrimmedCurve(ellipse, trim1, trim2, true, IfcSchema::IfcTrimmingPreference::IfcTrimmingPreference_PARAMETER);
file.add_entity(trim);
create_products_from_curve(file, trim);
file.getSingle<Ifc2x3::IfcProject>()->setName("IfcArbitraryOpenProfileDef");
std::ofstream f(filename);
f << file;
}
+150
View File
@@ -0,0 +1,150 @@
/********************************************************************************
* *
* 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/>. *
* *
********************************************************************************/
/********************************************************************************
* *
* Example that generates extrusions of parameterized profiles. *
* *
********************************************************************************/
#include <string>
#include <iostream>
#include <fstream>
#include "ifcparse/macros.h"
#ifndef IfcSchema
#define IfcSchema Ifc2x3
#endif
#include INCLUDE_SCHEMA(ifcparse/schemas, IfcSchema)
#include INCLUDE_SCHEMA_DEFINITIONS(ifcparse/schemas, IfcSchema)
#include "ifcparse/hierarchy_helper.h"
typedef std::string S;
typedef ifcopenshell::global_id guid;
boost::none_t const null = boost::none;
#ifdef SCHEMA_IfcIShapeProfileDef_HAS_FlangeEdgeRadius
#define IFC_I_SHAPE_PROFILE_DEF_EXTRA_ARGS , null, null
#else
#define IFC_I_SHAPE_PROFILE_DEF_EXTRA_ARGS
#endif
#ifdef SCHEMA_IfcLShapeProfileDef_HAS_CentreOfGravityInX
#define IFC_L_SHAPE_PROFILE_DEF_EXTRA_ARGS , null, null
#else
#define IFC_L_SHAPE_PROFILE_DEF_EXTRA_ARGS
#endif
#ifdef SCHEMA_IfcTShapeProfileDef_HAS_CentreOfGravityInY
#define IFC_T_SHAPE_PROFILE_DEF_EXTRA_ARGS , null
#else
#define IFC_T_SHAPE_PROFILE_DEF_EXTRA_ARGS
#endif
#ifdef SCHEMA_IfcCShapeProfileDef_HAS_CentreOfGravityInX
#define IFC_C_SHAPE_PROFILE_DEF_EXTRA_ARGS , null
#else
#define IFC_C_SHAPE_PROFILE_DEF_EXTRA_ARGS
#endif
int main(int argc, char** argv) {
const char filename[] = "IfcCompositeProfileDef.ifc";
hierarchy_helper file;
file.header().file_name().name(filename);
double coords1[] = {100.0, 0.0};
double coords2[] = {200.0, 0.0};
double coords3[] = {300.0, 0.0};
IfcSchema::IfcProfileDef::list::ptr profiles (new IfcSchema::IfcProfileDef::list());
IfcSchema::IfcCartesianTransformationOperator2D* transform1 = new IfcSchema::IfcCartesianTransformationOperator2D(file.addDoublet<IfcSchema::IfcDirection>(1, 0), file.addDoublet<IfcSchema::IfcDirection>(0, -1), file.addDoublet<IfcSchema::IfcCartesianPoint>(40, 0), null);
IfcSchema::IfcCartesianTransformationOperator2D* transform2 = new IfcSchema::IfcCartesianTransformationOperator2D(file.addDoublet<IfcSchema::IfcDirection>(0, -1), file.addDoublet<IfcSchema::IfcDirection>(1, 0), file.addDoublet<IfcSchema::IfcCartesianPoint>(40, 0), 0.3);
IfcSchema::IfcProfileDef* p1 = new IfcSchema::IfcIShapeProfileDef(
IfcSchema::IfcProfileTypeEnum::IfcProfileType_AREA,
null, file.addPlacement2d(), 25.0, 50.0, 5.0, 5.0, 2.0 IFC_I_SHAPE_PROFILE_DEF_EXTRA_ARGS);
IfcSchema::IfcProfileDef* p2 = new IfcSchema::IfcLShapeProfileDef(
IfcSchema::IfcProfileTypeEnum::IfcProfileType_AREA,
null, file.addPlacement2d(), 50.0, 25.0, 5.0, 1.0, 2.0, 2.0 IFC_L_SHAPE_PROFILE_DEF_EXTRA_ARGS);
IfcSchema::IfcProfileDef* p3 = new IfcSchema::IfcTShapeProfileDef(
IfcSchema::IfcProfileTypeEnum::IfcProfileType_AREA,
null, file.addPlacement2d(), 50.0, 40.0, 10.0, 10.0, 3.0, 2.0, 1.0, 2.0, 2.0 IFC_T_SHAPE_PROFILE_DEF_EXTRA_ARGS);
IfcSchema::IfcProfileDef* p4 = new IfcSchema::IfcCShapeProfileDef(
IfcSchema::IfcProfileTypeEnum::IfcProfileType_AREA,
null, file.addPlacement2d(80.), 50.0, 25.0, 5.0, 10.0, 2.0 IFC_C_SHAPE_PROFILE_DEF_EXTRA_ARGS);
file.add_entity(p2);
file.add_entity(p3);
file.add_entity(transform1);
file.add_entity(transform2);
IfcSchema::IfcDerivedProfileDef* p5 = new IfcSchema::IfcDerivedProfileDef(IfcSchema::IfcProfileTypeEnum::IfcProfileType_AREA, null, p2, transform1, null);
IfcSchema::IfcDerivedProfileDef* p6 = new IfcSchema::IfcDerivedProfileDef(IfcSchema::IfcProfileTypeEnum::IfcProfileType_AREA, null, p3, transform2, null);
profiles->push(p1);
profiles->push(p5);
profiles->push(p6);
profiles->push(p4);
file.addEntities(profiles->generalize());
IfcSchema::IfcCompositeProfileDef* composite = new IfcSchema::IfcCompositeProfileDef(IfcSchema::IfcProfileTypeEnum::IfcProfileType_AREA, S("IFC"), profiles, null);
IfcSchema::IfcBuildingElementProxy* product = new IfcSchema::IfcBuildingElementProxy(
guid(), 0, S("profile"), null, null, 0, 0, null, null);
file.addBuildingProduct(product);
product->setOwnerHistory(file.getSingle<IfcSchema::IfcOwnerHistory>());
product->setObjectPlacement(file.addLocalPlacement());
IfcSchema::IfcExtrudedAreaSolid* solid = new IfcSchema::IfcExtrudedAreaSolid(composite,
file.addPlacement3d(), file.addTriplet<IfcSchema::IfcDirection>(0, 0, 1), 20.0);
file.add_entity(composite);
file.add_entity(solid);
IfcSchema::IfcRepresentation::list::ptr reps (new IfcSchema::IfcRepresentation::list());
IfcSchema::IfcRepresentationItem::list::ptr items (new IfcSchema::IfcRepresentationItem::list());
items->push(solid);
IfcSchema::IfcShapeRepresentation* rep = new IfcSchema::IfcShapeRepresentation(
file.getSingle<IfcSchema::IfcRepresentationContext>(), S("Body"), S("SweptSolid"), items);
reps->push(rep);
IfcSchema::IfcProductDefinitionShape* shape = new IfcSchema::IfcProductDefinitionShape(boost::none, boost::none, reps);
file.add_entity(rep);
file.add_entity(shape);
product->setRepresentation(shape);
file.getSingle<IfcSchema::IfcProject>()->setName("IfcCompositeProfileDef");
std::ofstream f(filename);
f << file;
}
+200
View File
@@ -0,0 +1,200 @@
/********************************************************************************
* *
* 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/>. *
* *
********************************************************************************/
/********************************************************************************
* *
* Example that generates a Constructive Solid Geometry example *
* *
********************************************************************************/
#include <string>
#include <iostream>
#include <fstream>
#include "ifcparse/macros.h"
#ifndef IfcSchema
#define IfcSchema Ifc2x3
#endif
#include INCLUDE_SCHEMA(ifcparse/schemas, IfcSchema)
#include INCLUDE_SCHEMA_DEFINITIONS(ifcparse/schemas, IfcSchema)
#include "ifcparse/hierarchy_helper.h"
typedef std::string S;
typedef ifcopenshell::global_id guid;
boost::none_t const null = boost::none;
class Node {
private:
typedef enum {
OP_ADD, OP_SUBTRACT, OP_INTERSECT, OP_TERMINAL
} Op;
typedef enum {
PRIM_BOX, PRIM_CONE, PRIM_CYLINDER, PRIM_PYRAMID, PRIM_SPHERE
} Prim;
double x,y,z, zx,zy,zz, xx,xy,xz, a,b,c;
const Node *left, *right;
Op op;
Prim prim;
Node& operate(Op op, const Node& p) {
left = new Node(*this);
right = new Node(p);
this->op = op;
return *this;
}
Node(Prim p, double la, double lb=0., double lc=0.)
: prim(p), op(OP_TERMINAL),
x(0.), y(0.), z(0.),
zx(0.), zy(0.), zz(1.),
xx(1.), xy(0.), xz(0.),
a(la), b(lb), c(lc) {}
public:
static Node Sphere(double r) {
return Node(PRIM_SPHERE, r);
}
static Node Box(double dx, double dy, double dz) {
return Node(PRIM_BOX, dx, dy, dz);
}
static Node Pyramid(double dx, double dy, double dz) {
return Node(PRIM_PYRAMID, dx, dy, dz);
}
static Node Cylinder(double r, double h) {
return Node(PRIM_CYLINDER, r, h);
}
static Node Cone(double r, double h) {
return Node(PRIM_CONE, r, h);
}
Node& move(
double px = 0., double py = 0., double pz = 0.,
double zx = 0., double zy = 0., double zz = 1.,
double xx = 1., double xy = 0., double xz = 0.)
{
this->x = px; this->y = py; this->z = pz;
this->zx = zx; this->zy = zy; this->zz = zz;
this->xx = xx; this->xy = xy; this->xz = xz;
return *this;
}
Node& add(const Node& p) {
return operate(OP_ADD, p);
}
Node& subtract(const Node& p) {
return operate(OP_SUBTRACT, p);
}
Node& intersect(const Node& p) {
return operate(OP_INTERSECT, p);
}
IfcSchema::IfcRepresentationItem* serialize(hierarchy_helper& file) const {
IfcSchema::IfcRepresentationItem* my;
if (op == OP_TERMINAL) {
IfcSchema::IfcAxis2Placement3D* place = file.addPlacement3d(x,y,z,zx,zy,zz,xx,xy,xz);
if (prim == PRIM_SPHERE) {
my = new IfcSchema::IfcSphere(place, a);
} else if (prim == PRIM_BOX) {
my = new IfcSchema::IfcBlock(place, a, b, c);
} else if (prim == PRIM_PYRAMID) {
my = new IfcSchema::IfcRectangularPyramid(place, a, b, c);
} else if (prim == PRIM_CYLINDER) {
my = new IfcSchema::IfcRightCircularCylinder(place, b, a);
} else if (prim == PRIM_CONE) {
my = new IfcSchema::IfcRightCircularCone(place, b, a);
}
} else {
IfcSchema::IfcBooleanOperator::IfcBooleanOperator o;
if (op == OP_ADD) {
o = IfcSchema::IfcBooleanOperator::IfcBooleanOperator_UNION;
} else if (op == OP_SUBTRACT) {
o = IfcSchema::IfcBooleanOperator::IfcBooleanOperator_DIFFERENCE;
} else if (op == OP_INTERSECT) {
o = IfcSchema::IfcBooleanOperator::IfcBooleanOperator_INTERSECTION;
}
my = new IfcSchema::IfcBooleanResult(o, left->serialize(file), right->serialize(file));
}
file.add_entity(my);
return my;
}
};
int main(int argc, char** argv) {
const char filename[] = "IfcCsgPrimitive.ifc";
hierarchy_helper file;
file.header().file_name().name(filename);
IfcSchema::IfcRepresentationItem* csg1 = Node::Box(8000.,6000.,3000.).subtract(
Node::Box(7600.,5600.,2800.).move(200.,200.,200.)
).add(
Node::Pyramid(8000.,6000.,3000.).move(0,0,3000.).add(
Node::Cylinder(1000.,4000.).move(4000.,1000.,4000., 0.,1.,0.)
).subtract(
Node::Pyramid(7600.,5600.,2800.).move(200.,200.,3000.)
).subtract(
Node::Cylinder(900.,4000.).move(4000.,1000.,4000., 0.,1.,0.).intersect(
Node::Box(2000.,4000.,1000.).move(3000.,1000.,4000.)
)
)
).serialize(file);
const double x = 1000.; const double y = -4000.;
IfcSchema::IfcRepresentationItem* csg2 = Node::Sphere(5000.).move(x,y,-4500.).intersect(
Node::Box(6000., 6000., 6000.).move(x-3000., y-3000., 0.)
).add(
Node::Cone(500., 3000.).move(x,y).add(
Node::Cone(1500., 1000.).move(x,y, 900.).add(
Node::Cone(1100., 1000.).move(x,y, 1800.).add(
Node::Cone(750., 600.).move(x,y, 2700.)
)))).serialize(file);
IfcSchema::IfcBuildingElementProxy* product = new IfcSchema::IfcBuildingElementProxy(
guid(), 0, S("IfcCsgPrimitive"), null, null, 0, 0, null, null);
file.addBuildingProduct(product);
product->setOwnerHistory(file.getSingle<IfcSchema::IfcOwnerHistory>());
product->setObjectPlacement(file.addLocalPlacement());
IfcSchema::IfcRepresentation::list::ptr reps (new IfcSchema::IfcRepresentation::list());
IfcSchema::IfcRepresentationItem::list::ptr items (new IfcSchema::IfcRepresentationItem::list());
items->push(csg1);
items->push(csg2);
IfcSchema::IfcShapeRepresentation* rep = new IfcSchema::IfcShapeRepresentation(
file.getSingle<IfcSchema::IfcRepresentationContext>(), S("Body"), S("CSG"), items);
reps->push(rep);
IfcSchema::IfcProductDefinitionShape* shape = new IfcSchema::IfcProductDefinitionShape(null, null, reps);
file.add_entity(rep);
file.add_entity(shape);
product->setRepresentation(shape);
file.getSingle<IfcSchema::IfcProject>()->setName("IfcCompositeProfileDef");
std::ofstream f(filename);
f << file;
}

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