mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-22 04:55:59 +00:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cffb933efb | |||
| 223f358f28 | |||
| 8dc1ee55b5 | |||
| 33f61ef344 | |||
| f05dd4aea5 | |||
| 7ed8584edc | |||
| c5ba22451f | |||
| 048242783e | |||
| 6f3acc84ee | |||
| e077390e3d | |||
| 80cc603932 |
+2
-4
@@ -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
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
# Lint/test gate for the bonsaiviewer-autodesk crate — nothing here ships.
|
||||
# The connector binary that reaches users is built by the platform pipelines
|
||||
# (build_rocky.yml, build_rocky_arm.yml, build_win.yml, build_osx.yml), each
|
||||
# of which runs packaging/build.py itself and bundles dist/autodesk/ into the
|
||||
# Bonsai Viewer archive.
|
||||
name: Test Bonsai Viewer Autodesk Connector
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
paths:
|
||||
- 'src/bonsaiviewer-autodesk/**'
|
||||
- '.github/workflows/build-bonsaiviewer-autodesk.yml'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'src/bonsaiviewer-autodesk/**'
|
||||
- '.github/workflows/build-bonsaiviewer-autodesk.yml'
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: cargo-test
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: src/bonsaiviewer-autodesk
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
# cargo-target reuse across runs. Massive cold-build speedup,
|
||||
# cheap on the GitHub Actions cache budget.
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: src/bonsaiviewer-autodesk
|
||||
|
||||
- name: cargo fmt --check
|
||||
run: cargo fmt --all -- --check
|
||||
|
||||
- name: cargo clippy
|
||||
run: cargo clippy --all-targets --all-features -- -D warnings
|
||||
|
||||
- name: cargo test
|
||||
run: cargo test --all-features
|
||||
@@ -10,11 +10,10 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
# x64 (Intel cross-compile) dropped while wgpu Qt is required:
|
||||
# the runner is arm64 so `brew --prefix qt` returns the arm64
|
||||
# prefix; we'd need a separate x86_64 Qt install under
|
||||
# /usr/local to cross-build BonsaiViewer. Revisit if Intel-Mac
|
||||
# demand resurfaces.
|
||||
- os: macos
|
||||
runner: macos-14
|
||||
arch: x64
|
||||
oldarch:
|
||||
- os: macos
|
||||
runner: macos-14
|
||||
arch: arm64
|
||||
@@ -40,20 +39,10 @@ jobs:
|
||||
brew update
|
||||
# preinstalled: xz, cmake
|
||||
brew install git bison autoconf automake libffi findutils
|
||||
# qt brings in Qt6 + Svg; nix/build-all.py honours pre-set
|
||||
# QT_DIR so BonsaiViewer doesn't try to aqtinstall (which is
|
||||
# Linux-only).
|
||||
brew install qt
|
||||
echo "$(brew --prefix findutils)/libexec/gnubin" >> $GITHUB_PATH
|
||||
# Mac is using bison 2.5 by default, but we need 3.5+ for swig.
|
||||
echo "$(brew --prefix bison)/bin" >> $GITHUB_PATH
|
||||
|
||||
# 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, rather than whatever Rust the runner image happens to ship.
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: Install aws cli
|
||||
run: |
|
||||
python -m pip install awscli
|
||||
@@ -88,21 +77,8 @@ jobs:
|
||||
/usr/local/bin/brew install gettext openssl
|
||||
fi
|
||||
set -o pipefail
|
||||
export QT_DIR="$(brew --prefix qt)"
|
||||
# --shared mirrors build_rocky.yml after 27249770e: builds
|
||||
# IfcOpenShell as shared libs so each plug-in dylib references
|
||||
# libIfcParse / libIfcGeom via @rpath instead of statically
|
||||
# embedding them — the dominant size win for BonsaiViewer.app
|
||||
# (per-plugin libs go from ~30-50 MB to a few MB).
|
||||
#
|
||||
# IfcOpenShell-Python is back on after the ifcwrap rpath fix:
|
||||
# INSTALL_RPATH "$ORIGIN" is a Linux-ism that macOS dyld bakes
|
||||
# in as a literal string, so `@rpath/ifcopenshell.document.rdb
|
||||
# .dylib` failed to resolve at import time. ifcwrap now sets
|
||||
# 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}" \
|
||||
python3 ./nix/build-all.py -v --diskcleanup --ifcopenshell-shared ${MAC_INTEL} \
|
||||
python3 ./nix/build-all.py -v --diskcleanup ${MAC_INTEL} \
|
||||
| tee build.log
|
||||
|
||||
- name: Upload Build Logs
|
||||
@@ -133,28 +109,8 @@ jobs:
|
||||
- name: Package .zip archives
|
||||
run: |
|
||||
VERSION=v`cat VERSION`
|
||||
# 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.
|
||||
python3 src/bonsaiviewer-autodesk/packaging/build.py
|
||||
autodesk_connector_dir="$PWD/src/bonsaiviewer-autodesk/dist/autodesk"
|
||||
test -d "$autodesk_connector_dir"
|
||||
|
||||
cd ./build/`uname`/*/10.15/install/ifcopenshell
|
||||
mkdir -p ~/output
|
||||
install_root="$PWD"
|
||||
|
||||
stage_runtime_payload() {
|
||||
dest="$1"
|
||||
while IFS= read -r runtime_file; do
|
||||
cp -L "$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 \( -name "*.so" -o -name "*.so.*" -o -name "*.dylib" -o -name "*.dll" \)
|
||||
done
|
||||
)
|
||||
}
|
||||
mkdir ~/output
|
||||
|
||||
ls -d python-* | while read py_version; do
|
||||
postfix=`echo ${py_version: -1} | sed s/[0-9]//`
|
||||
@@ -169,42 +125,18 @@ jobs:
|
||||
fi
|
||||
[ -d ifcopenshell/__pycache__ ] && rm -rf ifcopenshell/__pycache__
|
||||
find ifcopenshell -name "*.pyc" -delete
|
||||
stage_runtime_payload ifcopenshell
|
||||
zip -r -qq ifcopenshell-${py_version_major}-${VERSION}-${GITHUB_SHA:0:7}-macos${{ matrix.oldarch }}64.zip ifcopenshell
|
||||
zip -r -qq ifcopenshell-${py_version_major}-${VERSION}-${GITHUB_SHA:0:7}-macos${{ matrix.oldarch }}64.zip ifcopenshell/*
|
||||
mv *.zip ~/output
|
||||
popd > /dev/null
|
||||
done
|
||||
|
||||
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/"
|
||||
stage_runtime_payload "$package_dir"
|
||||
pushd "$package_dir" > /dev/null
|
||||
zip -qq -r "$HOME/output/${exe}-${VERSION}-${GITHUB_SHA:0:7}-macos${{ matrix.oldarch }}64.zip" .
|
||||
popd > /dev/null
|
||||
rm -rf "$package_dir"
|
||||
done
|
||||
|
||||
# .app bundles (e.g. BonsaiViewer.app) live at the install-prefix
|
||||
# root because their install rule uses `BUNDLE DESTINATION "."` —
|
||||
# that's the layout Qt's macdeployqt expects. macdeployqt has
|
||||
# already embedded the Qt frameworks inside each bundle during
|
||||
# install/strip, so the only thing left to stage is the connector.
|
||||
find "$install_root" -maxdepth 1 -type d -name "*.app" | while read app_path; do
|
||||
app=`basename "$app_path" .app`
|
||||
if [ "$app" = "BonsaiViewer" ]; then
|
||||
# ConnectorDiscovery looks in applicationDirPath()/connectors,
|
||||
# which for a bundle is Contents/MacOS.
|
||||
mkdir -p "$app_path/Contents/MacOS/connectors"
|
||||
cp -a "$autodesk_connector_dir" "$app_path/Contents/MacOS/connectors/"
|
||||
fi
|
||||
pushd "$install_root" > /dev/null
|
||||
zip -qq -r "$HOME/output/${app}-${VERSION}-${GITHUB_SHA:0:7}-macos${{ matrix.oldarch }}64.zip" "$(basename "$app_path")"
|
||||
popd > /dev/null
|
||||
cd bin
|
||||
rm *.zip || true
|
||||
ls | while read exe; do
|
||||
zip -qq -r ${exe}-${VERSION}-${GITHUB_SHA:0:7}-macos${{ matrix.oldarch }}64.zip $exe
|
||||
done
|
||||
mv *.zip ~/output
|
||||
cd ..
|
||||
|
||||
- name: Configure AWS credentials
|
||||
uses: aws-actions/configure-aws-credentials@v6
|
||||
|
||||
@@ -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
|
||||
@@ -56,10 +49,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,32 +76,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: Setup tmate session
|
||||
if: failure()
|
||||
uses: mxschmitt/action-tmate@v3
|
||||
|
||||
- name: Configure AWS credentials
|
||||
uses: aws-actions/configure-aws-credentials@v6
|
||||
with:
|
||||
@@ -106,4 +86,3 @@ jobs:
|
||||
- name: Upload .zip archives to S3
|
||||
run: |
|
||||
aws s3 cp dist s3://ifcopenshell-builds/ --recursive --exclude "*" --include "*.whl"
|
||||
aws s3 cp dist-modular s3://ifcopenshell-builds/ --recursive --exclude "*" --include "*.zip"
|
||||
|
||||
@@ -19,29 +19,13 @@ jobs:
|
||||
- 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 \
|
||||
dnf install -y gcc gcc-c++ git autoconf automake bison make zip cmake python3 python3-pip \
|
||||
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
|
||||
findutils xz byacc
|
||||
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"
|
||||
@@ -78,10 +62,7 @@ jobs:
|
||||
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
|
||||
CXXFLAGS="-O3" CFLAGS="-O3 ${DARWIN_C_SOURCE}" ADD_COMMIT_SHA=1 BUILD_CFG=Release uv run ./nix/build-all.py -v --diskcleanup 2>&1 | tee build.log
|
||||
|
||||
- name: Upload Build Logs
|
||||
if: always()
|
||||
@@ -108,131 +89,11 @@ jobs:
|
||||
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}"
|
||||
mkdir ~/output
|
||||
|
||||
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 '.'`
|
||||
@@ -246,34 +107,18 @@ jobs:
|
||||
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
|
||||
zip -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"
|
||||
cd bin
|
||||
rm *.zip || true
|
||||
ls | while read exe; do
|
||||
zip -qq -r ${exe}-${VERSION}-${GITHUB_SHA:0:7}-linux64.zip $exe
|
||||
done
|
||||
mv *.zip ~/output
|
||||
cd ..
|
||||
|
||||
- name: Configure AWS credentials
|
||||
uses: aws-actions/configure-aws-credentials@v6
|
||||
|
||||
@@ -6,19 +6,7 @@ on:
|
||||
jobs:
|
||||
build_ifcopenshell:
|
||||
runs-on: ubuntu-22.04-arm
|
||||
# Rocky 10 (glibc 2.39) — aqt's official Qt6 ARM binaries are built
|
||||
# against glibc 2.38, which Rocky 9 (glibc 2.34) can't run (moc fails to
|
||||
# load). The legacy arm64v8/rockylinux Docker image stopped at 9; Rocky 10
|
||||
# is published under the rockylinux/rockylinux namespace.
|
||||
container:
|
||||
image: rockylinux/rockylinux:10
|
||||
# The community rockylinux image omits PATH from its config (the old
|
||||
# Docker Official arm64v8/rockylinux set it), so GitHub Actions `run:`
|
||||
# steps fail with `exec: "sh": not found` — docker exec has no /usr/bin
|
||||
# to resolve the shell. Restore a standard PATH; GITHUB_PATH prepends
|
||||
# (uv, cargo) are still layered on top by the runner.
|
||||
env:
|
||||
PATH: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
||||
container: arm64v8/rockylinux:9
|
||||
|
||||
steps:
|
||||
- name: Set up uv
|
||||
@@ -31,29 +19,13 @@ jobs:
|
||||
- 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 \
|
||||
dnf install -y gcc gcc-c++ git autoconf automake bison make zip cmake python3 python3-pip \
|
||||
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
|
||||
findutils xz byacc
|
||||
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-aarch64.zip" -o "awscliv2.zip"
|
||||
@@ -84,16 +56,13 @@ jobs:
|
||||
- name: ccache
|
||||
uses: hendrikmuhs/ccache-action@v1.2.23
|
||||
with:
|
||||
key: ubuntu-22.04-${{ runner.arch }}-rockylinux10
|
||||
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
|
||||
CXXFLAGS="-O3" CFLAGS="-O3 ${DARWIN_C_SOURCE}" ADD_COMMIT_SHA=1 BUILD_CFG=Release uv run ./nix/build-all.py -v --diskcleanup 2>&1 | tee build.log
|
||||
|
||||
- name: Upload Build Logs
|
||||
if: always()
|
||||
@@ -120,111 +89,10 @@ jobs:
|
||||
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_soname_links() {
|
||||
dest="$1"
|
||||
find "$dest" -maxdepth 1 -type f -name "*.so*" | while IFS= read -r shared_object; do
|
||||
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
|
||||
}
|
||||
|
||||
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"
|
||||
}
|
||||
|
||||
stage_qt_runtime_payload() {
|
||||
exe_path="$1"
|
||||
dest="$2"
|
||||
[ -n "${QT_DIR:-}" ] && [ -d "$QT_DIR/lib" ] || return 0
|
||||
|
||||
if ! LD_LIBRARY_PATH="$QT_DIR/lib:${LD_LIBRARY_PATH:-}" ldd "$exe_path" 2>/dev/null | grep -q "libQt6"; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
find "$QT_DIR/lib" -maxdepth 1 \( -type f -o -type l \) -name "*.so*" -exec cp -P {} "$dest/" \;
|
||||
ensure_soname_links "$dest"
|
||||
|
||||
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
|
||||
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_runtime_dependencies() {
|
||||
package_dir="$1"
|
||||
missing=0
|
||||
while IFS= read -r binary_file; do
|
||||
readelf -h "$binary_file" >/dev/null 2>&1 || continue
|
||||
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
|
||||
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"
|
||||
if [ "$missing" -ne 0 ]; then
|
||||
echo "Runtime dependency check found issues; continuing packaging."
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
mkdir ~/output
|
||||
|
||||
ls -d python-* | while read py_version; do
|
||||
postfix=`echo ${py_version: -1} | sed s/[0-9]//`
|
||||
@@ -239,31 +107,18 @@ jobs:
|
||||
fi
|
||||
[ -d ifcopenshell/__pycache__ ] && rm -rf ifcopenshell/__pycache__
|
||||
find ifcopenshell -name "*.pyc" -delete
|
||||
stage_runtime_payload ifcopenshell
|
||||
zip -y -r -qq ifcopenshell-${py_version_major}-${VERSION}-${GITHUB_SHA:0:7}-linuxarm64.zip ifcopenshell
|
||||
zip -r -qq ifcopenshell-${py_version_major}-${VERSION}-${GITHUB_SHA:0:7}-linuxarm64.zip ifcopenshell/*
|
||||
mv *.zip ~/output
|
||||
popd > /dev/null
|
||||
done
|
||||
|
||||
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}-linuxarm64.zip" .
|
||||
popd > /dev/null
|
||||
rm -rf "$package_dir"
|
||||
cd bin
|
||||
rm *.zip || true
|
||||
ls | while read exe; do
|
||||
zip -qq -r ${exe}-${VERSION}-${GITHUB_SHA:0:7}-linuxarm64.zip $exe
|
||||
done
|
||||
mv *.zip ~/output
|
||||
cd ..
|
||||
|
||||
- name: Configure AWS credentials
|
||||
uses: aws-actions/configure-aws-credentials@v6
|
||||
|
||||
@@ -48,11 +48,7 @@ jobs:
|
||||
run: |
|
||||
cd ${{ matrix.deps_dir }}
|
||||
Get-ChildItem -Path . -Filter 'cache-*.zip' | ForEach-Object {
|
||||
Write-Host "Extracting $($_.Name)"
|
||||
7z x -bso0 -bsp0 $_.FullName
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "Failed to extract $($_.Name) with 7z exit code $LASTEXITCODE."
|
||||
}
|
||||
7z x $_.FullName
|
||||
}
|
||||
|
||||
- name: ccache
|
||||
@@ -63,20 +59,6 @@ jobs:
|
||||
# and with default 500MB some cache gets deleted, leading to misses.
|
||||
max-size: 5000MB
|
||||
|
||||
- name: Set up Python for connector build
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: '3.12'
|
||||
|
||||
# Build the Autodesk connector before the C++ build: build-all-win.py
|
||||
# bundles it next to BonsaiViewer.exe while archiving the executables.
|
||||
# bonsaiviewer-autodesk is a Rust connector; packaging/build.py runs
|
||||
# `cargo build --release` and stages the binary + connector.json into
|
||||
# dist/autodesk/.
|
||||
- name: Build Autodesk connector
|
||||
working-directory: src/bonsaiviewer-autodesk
|
||||
run: python packaging/build.py
|
||||
|
||||
- name: Run Build Script And Pack .zip Archives
|
||||
shell: cmd
|
||||
env:
|
||||
|
||||
@@ -31,7 +31,7 @@ jobs:
|
||||
sudo apt-get install --no-install-recommends \
|
||||
git cmake gcc g++ libboost-all-dev python3-all-dev swig libpcre3-dev libxml2-dev \
|
||||
libocct-foundation-dev libocct-modeling-algorithms-dev libocct-modeling-data-dev libocct-ocaf-dev libocct-visualization-dev libocct-data-exchange-dev \
|
||||
libcgal-dev nlohmann-json3-dev libeigen3-dev
|
||||
libhdf5-dev libcgal-dev nlohmann-json3-dev libeigen3-dev
|
||||
|
||||
-
|
||||
name: ccache
|
||||
@@ -60,6 +60,7 @@ jobs:
|
||||
-DMPFR_INCLUDE_DIR=/usr/include \
|
||||
-DGMP_LIBRARY_DIR=/usr/lib/x86_64-linux-gnu \
|
||||
-DMPFR_LIBRARY_DIR=/usr/lib/x86_64-linux-gnu \
|
||||
-DHDF5_INCLUDE_DIR=/usr/include/hdf5/serial \
|
||||
-DGLTF_SUPPORT=On \
|
||||
-DJSON_INCLUDE_DIR=/usr/include \
|
||||
-DEIGEN_DIR=/usr/include/eigen3 \
|
||||
|
||||
+24
-10
@@ -18,6 +18,7 @@ on:
|
||||
- 'src/ifcparse/**'
|
||||
- 'src/ifcquery/**'
|
||||
- 'src/ifcwrap/**'
|
||||
- 'src/qtviewer/**'
|
||||
- 'src/svgfill/**'
|
||||
- 'src/serializers/**'
|
||||
- 'conda/**'
|
||||
@@ -37,10 +38,6 @@ jobs:
|
||||
compile-and-test:
|
||||
runs-on: ubuntu-22.04
|
||||
needs: activate
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
build_shared_libs: [ON, OFF]
|
||||
env:
|
||||
# Colored output for cmake.
|
||||
CLICOLOR_FORCE: "1"
|
||||
@@ -83,7 +80,7 @@ jobs:
|
||||
libtbb-dev nlohmann-json3-dev \
|
||||
libocct-foundation-dev libocct-modeling-algorithms-dev libocct-modeling-data-dev libocct-ocaf-dev libocct-visualization-dev libocct-data-exchange-dev \
|
||||
${OCCT_CMAKE_DEPS} \
|
||||
libcgal-dev libeigen3-dev
|
||||
libhdf5-dev libcgal-dev libeigen3-dev
|
||||
|
||||
- name: ccache
|
||||
uses: hendrikmuhs/ccache-action@v1.2.23
|
||||
@@ -163,7 +160,7 @@ jobs:
|
||||
# Remove default swig to avoid conflicts.
|
||||
sudo apt remove --purge swig swig4.0
|
||||
sudo apt-get install -y libpcre2-dev bison
|
||||
git clone https://github.com/swig/swig --branch v4.2.1 --depth 1
|
||||
git clone https://github.com/swig/swig --branch v4.1.0 --depth 1
|
||||
cd swig
|
||||
mkdir build && cd build
|
||||
cmake .. \
|
||||
@@ -185,7 +182,6 @@ jobs:
|
||||
-DPYTHON_EXECUTABLE:FILEPATH=${{ env.pythonLocation }}/bin/python \
|
||||
-DPYTHON_INCLUDE_DIR:PATH=${{ env.pythonLocation }}/include/python3.11 \
|
||||
-DUSE_MMAP=On \
|
||||
-DBUILD_SHARED_LIBS=${{ matrix.build_shared_libs }} \
|
||||
"-DSCHEMA_VERSIONS=2x3;4;4x3_add2" \
|
||||
-DGLTF_SUPPORT=On \
|
||||
-DWITH_ROCKSDB=On \
|
||||
@@ -221,11 +217,29 @@ jobs:
|
||||
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
|
||||
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache
|
||||
cmake --build .
|
||||
./arbitrary_open_profile_def && test -f arbitrary_open_profile_def.ifc
|
||||
./composite_profile_def && test -f composite_profile_def.ifc
|
||||
./csg_primitive && test -f csg_primitive.ifc
|
||||
./ellipse_pies && test -f ellipse_pies.ifc
|
||||
./faces && test -f faces.ifc
|
||||
./ifc_curve_rebar && test -f ifc_curve_rebar.ifc
|
||||
./profiles
|
||||
test -f IfcUShapeProfileDef.ifc
|
||||
test -f IfcTShapeProfileDef.ifc
|
||||
test -f IfcZShapeProfileDef.ifc
|
||||
test -f IfcEllipseProfileDef.ifc
|
||||
test -f IfcIShapeProfileDef.ifc
|
||||
test -f IfcLShapeProfileDef.ifc
|
||||
test -f IfcCShapeProfileDef.ifc
|
||||
test -f IfcCircleProfileDef.ifc
|
||||
test -f IfcRectangleProfileDef.ifc
|
||||
test -f IfcTrapeziumProfileDef.ifc
|
||||
./IfcParseExamples "../IfcParseExamples_test.ifc"
|
||||
./IfcOpenHouse && test -f IfcOpenHouse.ifc
|
||||
./IfcParseExamples IfcOpenHouse.ifc
|
||||
./IfcAdvancedHouse && test -f IfcAdvancedHouse.ifc
|
||||
./IfcAlignment && test -f FHWA_Bridge_Geometry_Alignment_Example.ifc
|
||||
./IfcSimplifiedAlignment && test -f FHWA_Bridge_Geometry_Alignment_Example_Simplified.ifc
|
||||
./IfcAlignment && test -f IfcAlignment.ifc
|
||||
./IfcSimplifiedAlignment && test -f IfcSimplifiedAlignment.ifc
|
||||
./triangulated_faceset && test -f triangulated_faceset.ifc
|
||||
|
||||
- name: Test ifcopenshell-python
|
||||
run: |
|
||||
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
swig libpcre3-dev libxml2-dev \
|
||||
libtbb-dev nlohmann-json3-dev \
|
||||
libocct-foundation-dev libocct-modeling-algorithms-dev libocct-modeling-data-dev libocct-ocaf-dev libocct-visualization-dev libocct-data-exchange-dev \
|
||||
libcgal-dev opencollada-dev
|
||||
libhdf5-dev libcgal-dev opencollada-dev
|
||||
|
||||
- name: Build
|
||||
env:
|
||||
@@ -64,6 +64,7 @@ jobs:
|
||||
-DMPFR_INCLUDE_DIR=/usr/include \
|
||||
-DGMP_LIBRARY_DIR=/usr/lib/x86_64-linux-gnu \
|
||||
-DMPFR_LIBRARY_DIR=/usr/lib/x86_64-linux-gnu \
|
||||
-DHDF5_INCLUDE_DIR=/usr/include/hdf5/serial \
|
||||
-DOPENCOLLADA_INCLUDE_DIR=/usr/include/opencollada \
|
||||
-DOPENCOLLADA_LIBRARY_DIR=/usr/lib/opencollada/ \
|
||||
../cmake
|
||||
|
||||
+1
-14
@@ -15,10 +15,8 @@
|
||||
/src/examples/out/
|
||||
/src/ifcmax/out/
|
||||
/src/ifcwrap/out/
|
||||
/src/qtviewer/out/
|
||||
/src/ifctester/webapp/public/pyodide/
|
||||
# pyodide wheels
|
||||
/dist/
|
||||
/dist-modular/
|
||||
|
||||
/win/BuildDepsCache*.txt
|
||||
|
||||
@@ -110,14 +108,6 @@ src/bonsai/bonsai/bim/data/webui/running_pid.json
|
||||
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.parse.so
|
||||
src/ifcopenshell-python/ifcopenshell/libifcopenshell.plugin.so
|
||||
|
||||
# apple
|
||||
.DS_Store
|
||||
|
||||
@@ -144,6 +134,3 @@ CLAUDE.local.md
|
||||
*.py.tmp*
|
||||
*.json.tmp*
|
||||
|
||||
# bonsaiviewer-autodesk connector build artifacts
|
||||
/src/bonsaiviewer-autodesk/build/
|
||||
/src/bonsaiviewer-autodesk/dist/
|
||||
|
||||
+1
-1
@@ -27,7 +27,7 @@ RUN echo "deb http://archive.ubuntu.com/ubuntu focal-proposed main restricted" |
|
||||
libboost-all-dev \
|
||||
libocct-foundation-dev libocct-modeling-algorithms-dev libocct-modeling-data-dev \
|
||||
libocct-ocaf-dev libocct-visualization-dev libocct-data-exchange-dev \
|
||||
python3-pytest ; \
|
||||
libhdf5-serial-dev python3-pytest ; \
|
||||
rm -rf /var/lib/apt/lists/* ;
|
||||
|
||||
COPY . /home/IfcOpenShell/
|
||||
|
||||
@@ -70,6 +70,7 @@ The IfcOpenShell C++ codebase is split into multiple interal libraries:
|
||||
| ifcjni | Internal library for IfcOpenShell | LGPL-3.0-or-later\* |
|
||||
| ifcparse | Internal library for IfcOpenShell | LGPL-3.0-or-later\* |
|
||||
| ifcwrap | Internal library for IfcOpenShell | LGPL-3.0-or-later\* |
|
||||
| qtviewer | Internal library for IfcOpenShell | LGPL-3.0-or-later\* |
|
||||
| serializers | Internal library for IfcOpenShell | LGPL-3.0-or-later\* |
|
||||
|
||||
[LGPL]: https://github.com/IfcOpenShell/IfcOpenShell/tree/master/COPYING.LESSER "LGPL-3.0-or-later"
|
||||
|
||||
+195
-240
@@ -18,10 +18,10 @@
|
||||
################################################################################
|
||||
|
||||
cmake_minimum_required(VERSION 3.21)
|
||||
if (NOT DEFINED CMAKE_CXX_STANDARD)
|
||||
if(NOT DEFINED CMAKE_CXX_STANDARD)
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
endif()
|
||||
if (CMAKE_CXX_STANDARD LESS 17)
|
||||
if(CMAKE_CXX_STANDARD LESS 17)
|
||||
message(FATAL_ERROR "C++17 or newer is required.")
|
||||
endif()
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON) # not necessary, but encouraged
|
||||
@@ -39,7 +39,8 @@ message(STATUS "Detected version '${RELEASE_VERSION}'")
|
||||
add_definitions(-D_DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR)
|
||||
|
||||
if(POLICY CMP0141) # 3.25+
|
||||
cmake_policy(SET CMP0141 NEW) # Support for `CMAKE_MSVC_DEBUG_INFORMATION_FORMAT`.
|
||||
# Has to be set before `project` to take effect.
|
||||
cmake_policy(SET CMP0141 NEW) # Support for `CMAKE_MSVC_DEBUG_INFORMATION_FORMAT`.
|
||||
endif()
|
||||
if(POLICY CMP0144) # 3.27
|
||||
cmake_policy(SET CMP0144 NEW) # find_package() uses upper-case <PACKAGENAME>_ROOT variables.
|
||||
@@ -48,6 +49,8 @@ if(POLICY CMP0167) # 3.30
|
||||
cmake_policy(SET CMP0167 OLD)
|
||||
endif()
|
||||
|
||||
project(IfcOpenShell VERSION ${RELEASE_VERSION})
|
||||
|
||||
if(NOT CMAKE_BUILD_TYPE)
|
||||
set(CMAKE_BUILD_TYPE "Release")
|
||||
endif()
|
||||
@@ -63,100 +66,67 @@ endif()
|
||||
option(MINIMAL_BUILD "The build is to make a minimal version of IFC converter from OCCT into IFC." OFF)
|
||||
option(WASM_BUILD "Build a WebAssembly binary." OFF)
|
||||
|
||||
option(ENABLE_BUILD_OPTIMIZATIONS "Enable certain compiler and linker optimizations on RelWithDebInfo and Release builds." OFF)
|
||||
option(BUILD_SHARED_LIBS "Build IfcOpenShell as shared libraries (required)." ON)
|
||||
option(
|
||||
ENABLE_BUILD_OPTIMIZATIONS
|
||||
"Enable certain compiler and linker optimizations on RelWithDebInfo and Release builds."
|
||||
OFF
|
||||
)
|
||||
option(BUILD_SHARED_LIBS "Build IfcParse and IfcGeom as shared libs (SO/DLL)." OFF)
|
||||
option(MSVC_PARALLEL_BUILD "Multi-threaded compilation in Microsoft Visual Studio (/MP)" OFF)
|
||||
option(USE_VLD "Use Visual Leak Detector for debugging memory leaks, MSVC-only." OFF)
|
||||
option(USE_MMAP "Adds a command line options to parse IFC files from memory mapped files using Boost.Iostreams" OFF)
|
||||
option(NO_WARN "Disable all warnings" OFF)
|
||||
option(CREATE_BUNDLE "Copy .so files and don't create RPATHS or SOVERSION symlinks" )
|
||||
|
||||
option(BUILD_IFCGEOM "Build IfcGeom." ON)
|
||||
option(BUILD_IFCPYTHON "Build IfcPython." ON)
|
||||
option(BUILD_IFCPARSE_EXPERIMENTAL_WRAPPER "Build the experimental Clang-generated ifcparse Python wrapper." OFF)
|
||||
option(BUILD_CONVERT "Build IfcConvert executable." ON)
|
||||
option(BUILD_DOCUMENTATION "Build IfcOpenShell Documentation." OFF)
|
||||
option(BUILD_EXAMPLES "Build example applications." ON)
|
||||
option(BUILD_EXAMPLES "Build example applications." OFF)
|
||||
option(BUILD_GEOMSERVER "Build IfcGeomServer executable (Open CASCADE is required)." ON)
|
||||
option(BUILD_IFCMAX "Build IfcMax, a 3ds Max plug-in, Windows-only." OFF)
|
||||
option(BUILD_IFCMODEL_UI "Build minimal Qt IFC model UI prototype" OFF)
|
||||
option(BUILD_BONSAIVIEWER "Build Bonsai Viewer" OFF) # Requires Qt6 + OpenGL 4.5
|
||||
option(BUILD_IFCOPENSHELL_PARSE_TESTS "Build C++ unit tests for IfcParse (fetches Catch2 v3)" OFF)
|
||||
option(BUILD_IFCOPENSHELL_GEOMETRY_TESTS "Build C++ unit tests for IfcGeom (fetches Catch2 v3)" OFF)
|
||||
option(BUILD_BONSAIVIEWER_TESTS "Build unit tests for Bonsai Viewer core (fetches Catch2 v3)" OFF)
|
||||
option(BUILD_BONSAIVIEWER_WGPU "Build the experimental wgpu backend (fetches wgpu-native binary release)" OFF)
|
||||
# IfcViewer (the GL static lib) now links against IfcViewerWgpu because
|
||||
# SceneLoader drives the wgpu viewport. Auto-enable the wgpu subproject
|
||||
# whenever BUILD_BONSAIVIEWER is on so the link target exists.
|
||||
if(BUILD_BONSAIVIEWER AND NOT BUILD_BONSAIVIEWER_WGPU)
|
||||
message(STATUS "BUILD_BONSAIVIEWER implies BUILD_BONSAIVIEWER_WGPU "
|
||||
"(SceneLoader uses ViewportWindow); auto-enabling.")
|
||||
set(BUILD_BONSAIVIEWER_WGPU ON)
|
||||
endif()
|
||||
option(BUILD_QTVIEWER "Build IfcOpenShell Qt GUI Viewer" OFF) # QtViewer requires Qt6
|
||||
option(BUILD_PACKAGE "" OFF)
|
||||
|
||||
# Most users probably need just common schemas,
|
||||
# but we're keeping it `OFF` by default to avoid disruption
|
||||
# (e.g. all Python distribution would need to adapt this option to be set).
|
||||
option(
|
||||
IFCOPENSHELL_DEPLOY_QT_RUNTIME
|
||||
"Deploy Qt runtime dependencies for installed Qt applications."
|
||||
ON
|
||||
)
|
||||
option(
|
||||
IFCOPENSHELL_DEPLOY_QT_TRANSLATIONS
|
||||
"Deploy Qt translation catalogs with installed Qt applications."
|
||||
BUILD_ONLY_COMMON_SCHEMAS
|
||||
"Build only common IFC schemas (2x3, 4, 4x3_add2). By default all schemas will be built."
|
||||
OFF
|
||||
)
|
||||
option(SCHEMA_VERSIONS "Explicitly specify schemas to build." "")
|
||||
|
||||
option(WITH_OPENCASCADE "Enable geometry interpretation using Open CASCADE" ON)
|
||||
option(WITH_CGAL "Enable geometry interpretation using CGAL" ON)
|
||||
option(WITH_MANIFOLD "Enable geometry interpretation using Manifold" OFF)
|
||||
option(COLLADA_SUPPORT "Build IfcConvert with COLLADA support (requires OpenCOLLADA)." ON)
|
||||
option(GLTF_SUPPORT "Build IfcConvert with glTF support (requires json.hpp)." OFF)
|
||||
option(HDF5_SUPPORT "Enable HDF5 support (requires HDF5, zlib)" ON)
|
||||
option(WITH_PROJ "Enable output of Earth-Centered Earth-Fixed glTF output using the PROJ library" OFF)
|
||||
option(IFCXML_SUPPORT "Build IfcParse with ifcXML support (requires libxml2)." ON)
|
||||
option(USD_SUPPORT "Build IfcConvert with USD support (requires pixar's USD library)." OFF)
|
||||
option(WITH_RELATIONSHIP_VALIDATION "Build IfcConvert with option to validate geometrical relationships." OFF)
|
||||
option(WITH_ROCKSDB "Support a RocksDB key-value store as a file backend in IfcOpenShell" OFF)
|
||||
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 "Override the version defined in buildinfo.cpp with the file VERSION in the repository root" OFF)
|
||||
option(
|
||||
VERSION_OVERRIDE
|
||||
"Override the version defined in buildinfo.cpp with the file VERSION in the repository root"
|
||||
OFF
|
||||
)
|
||||
option(USE_CCACHE "Enable use of ccache if it's available from PATH." ON)
|
||||
|
||||
set(
|
||||
PYTHON_MODULE_INSTALL_DIR
|
||||
"" CACHE PATH
|
||||
set(PYTHON_MODULE_INSTALL_DIR
|
||||
""
|
||||
CACHE PATH
|
||||
"Directory to install IfcPython package to. By default package is installed in found Python's site-packages."
|
||||
)
|
||||
|
||||
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})
|
||||
|
||||
# Catch2 is fetched only when an explicit C++ test family is enabled, so the
|
||||
# default build remains offline-capable.
|
||||
if(BUILD_IFCOPENSHELL_PARSE_TESTS OR BUILD_IFCOPENSHELL_GEOMETRY_TESTS OR BUILD_BONSAIVIEWER_TESTS)
|
||||
include(FetchContent)
|
||||
FetchContent_Declare(
|
||||
Catch2
|
||||
GIT_REPOSITORY https://github.com/catchorg/Catch2.git
|
||||
GIT_TAG v3.5.4
|
||||
GIT_SHALLOW TRUE
|
||||
)
|
||||
FetchContent_MakeAvailable(Catch2)
|
||||
list(APPEND CMAKE_MODULE_PATH ${catch2_SOURCE_DIR}/extras)
|
||||
include(CTest)
|
||||
include(Catch)
|
||||
enable_testing()
|
||||
endif()
|
||||
|
||||
if(MINIMAL_BUILD)
|
||||
message(STATUS "Setting options for minimal build")
|
||||
set(BUILD_GEOMSERVER OFF)
|
||||
@@ -164,16 +134,18 @@ if(MINIMAL_BUILD)
|
||||
set(WITH_CGAL OFF)
|
||||
set(COLLADA_SUPPORT OFF)
|
||||
set(GLTF_SUPPORT OFF)
|
||||
set(HDF5_SUPPORT OFF)
|
||||
set(IFCXML_SUPPORT OFF)
|
||||
set(USD_SUPPORT OFF)
|
||||
endif()
|
||||
|
||||
if((BUILD_CONVERT OR BUILD_GEOMSERVER OR BUILD_IFCPYTHON) AND(NOT BUILD_IFCGEOM))
|
||||
if((BUILD_CONVERT OR BUILD_GEOMSERVER OR BUILD_IFCPYTHON) AND (NOT BUILD_IFCGEOM))
|
||||
message(STATUS "'IfcGeom' is required with current outputs")
|
||||
set(BUILD_IFCGEOM ON)
|
||||
endif()
|
||||
|
||||
find_program(CCACHE_FOUND ccache)
|
||||
if(CCACHE_FOUND)
|
||||
if(USE_CCACHE AND CCACHE_FOUND)
|
||||
message(STATUS "`ccache` is found, using it as a compiler launcher.")
|
||||
set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "${CCACHE_FOUND}")
|
||||
if(MSVC)
|
||||
@@ -182,17 +154,15 @@ if(CCACHE_FOUND)
|
||||
set(CMAKE_MSVC_DEBUG_INFORMATION_FORMAT "$<$<CONFIG:Debug,RelWithDebInfo>:Embedded>")
|
||||
# Not needed for Ninja.
|
||||
if(CMAKE_GENERATOR MATCHES "Visual Studio")
|
||||
file(COPY_FILE
|
||||
${CCACHE_FOUND} ${CMAKE_BINARY_DIR}/cl.exe
|
||||
ONLY_IF_DIFFERENT)
|
||||
set(CMAKE_VS_GLOBALS
|
||||
"CLToolExe=cl.exe"
|
||||
"CLToolPath=${CMAKE_BINARY_DIR}"
|
||||
"UseMultiToolTask=true"
|
||||
)
|
||||
file(COPY_FILE ${CCACHE_FOUND} ${CMAKE_BINARY_DIR}/cl.exe ONLY_IF_DIFFERENT)
|
||||
set(CMAKE_VS_GLOBALS "CLToolExe=cl.exe" "CLToolPath=${CMAKE_BINARY_DIR}" "UseMultiToolTask=true")
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
mark_as_advanced(CCACHE_FOUND)
|
||||
|
||||
# Variable to accumulate swig definitions from various submodules.
|
||||
set(SWIG_DEFINES "")
|
||||
|
||||
if(MSVC AND MSVC_PARALLEL_BUILD)
|
||||
add_definitions("/MP")
|
||||
@@ -210,23 +180,26 @@ include(GNUInstallDirs)
|
||||
|
||||
set(IFCOPENSHELL_EXPORT_TARGETS "${PROJECT_NAME}Targets")
|
||||
|
||||
if(NOT INCLUDEDIR)
|
||||
set(INCLUDEDIR include)
|
||||
# On Windows Release and Debug binaries are not compatible.
|
||||
# So we add a postfix to avoid issues and allow release and debug installations to coexist.
|
||||
if(WIN32)
|
||||
set(CMAKE_DEBUG_POSTFIX "_d")
|
||||
endif()
|
||||
if(NOT IS_ABSOLUTE ${INCLUDEDIR})
|
||||
set(INCLUDEDIR ${CMAKE_INSTALL_INCLUDEDIR})
|
||||
endif()
|
||||
message(STATUS "INCLUDEDIR: ${INCLUDEDIR}")
|
||||
|
||||
if(MSVC)
|
||||
message(WARNING "Building DLLs against the static VC run-time. This is not recommended if the DLLs are to be redistributed.")
|
||||
# C4521: 'identifier' : class 'type' needs to have dll-interface to be used by clients of class 'type2'
|
||||
# There will be couple hundreds of these so suppress them away, https://msdn.microsoft.com/en-us/library/esew7y1w.aspx
|
||||
add_definitions(-wd4251)
|
||||
if(BUILD_SHARED_LIBS)
|
||||
add_definitions(-DIFC_SHARED_BUILD)
|
||||
if(MSVC)
|
||||
message(
|
||||
WARNING
|
||||
"Building DLLs against the static VC run-time. This is not recommended if the DLLs are to be redistributed."
|
||||
)
|
||||
# C4521: 'identifier' : class 'type' needs to have dll-interface to be used by clients of class 'type2'
|
||||
# There will be couple hundreds of these so suppress them away, https://msdn.microsoft.com/en-us/library/esew7y1w.aspx
|
||||
add_definitions(-wd4251)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
UNIFY_ENVVARS_AND_CACHE(BOOST_ROOT)
|
||||
UNIFY_ENVVARS_AND_CACHE(BOOST_LIBRARYDIR)
|
||||
|
||||
if(NOT MINIMAL_BUILD)
|
||||
UNIFY_ENVVARS_AND_CACHE(PYTHON_INCLUDE_DIR)
|
||||
@@ -242,64 +215,47 @@ foreach(option_flag IN LISTS option_flags)
|
||||
convert_env_var_to_bool("${option_flag}")
|
||||
endforeach()
|
||||
|
||||
if(WITH_CGAL)
|
||||
find_package(CGAL REQUIRED)
|
||||
set(SWIG_DEFINES ${SWIG_DEFINES} -DIFOPSH_WITH_CGAL)
|
||||
list(APPEND GEOMETRY_KERNELS cgal)
|
||||
endif()
|
||||
|
||||
if(BUILD_IFCGEOM AND WITH_OPENCASCADE)
|
||||
find_package(OpenCASCADE REQUIRED)
|
||||
# Map OpenCASCADE_LIBRARIES variable from OpenCASCADEConfig.cmake to OpenCASCADE_LIBRARIES used by kernel generic cmake file
|
||||
set(OpenCASCADE_LIBRARIES ${OpenCASCADE_LIBRARIES})
|
||||
add_definitions(-DIFOPSH_WITH_OPENCASCADE)
|
||||
set(SWIG_DEFINES ${SWIG_DEFINES} -DIFOPSH_WITH_OPENCASCADE)
|
||||
list(APPEND GEOMETRY_KERNELS opencascade)
|
||||
endif()
|
||||
|
||||
message(STATUS "BUILD_IFCGEOM WITH_MANIFOLD: ${BUILD_IFCGEOM} ${WITH_MANIFOLD}")
|
||||
|
||||
if(BUILD_IFCGEOM AND WITH_MANIFOLD)
|
||||
find_package(manifold CONFIG REQUIRED)
|
||||
if(TARGET manifold::manifold)
|
||||
set(MANIFOLD_LIBRARIES manifold::manifold)
|
||||
elseif(TARGET manifold)
|
||||
set(MANIFOLD_LIBRARIES manifold)
|
||||
else()
|
||||
message(FATAL_ERROR "Unable to determine manifold target")
|
||||
endif()
|
||||
list(APPEND GEOMETRY_KERNELS manifold)
|
||||
endif()
|
||||
|
||||
if(BUILD_IFCGEOM)
|
||||
list(APPEND GEOMETRY_KERNELS passthrough)
|
||||
endif()
|
||||
|
||||
set(GLTF_LIBRARIES "")
|
||||
if(GLTF_SUPPORT)
|
||||
find_package(nlohmann_json REQUIRED)
|
||||
set(GLTF_LIBRARIES nlohmann_json::nlohmann_json)
|
||||
add_definitions(-DWITH_GLTF)
|
||||
set(SWIG_DEFINES ${SWIG_DEFINES} -DWITH_GLTF)
|
||||
endif()
|
||||
|
||||
# Add USD support to serializers
|
||||
set(USD_LIBRARIES "")
|
||||
if(USD_SUPPORT)
|
||||
find_package(USD REQUIRED)
|
||||
set(USD_LIBRARIES pxr::USD)
|
||||
endif(USD_SUPPORT)
|
||||
|
||||
if (WITH_ROCKSDB)
|
||||
set(ROCKSDB_LIBRARIES "")
|
||||
if(WITH_ROCKSDB)
|
||||
# Temporaily mess with CMAKE_FIND_PACKAGE_PREFER_CONFIG to help RocksDB
|
||||
# find it's zstd dependency on Windows.
|
||||
# Only do it on Windows, otherwise it might create problems as
|
||||
# findzstd and zstd-config target names do not match.
|
||||
# https://github.com/facebook/rocksdb/pull/13975
|
||||
if(WIN32)
|
||||
set(TEMP CMAKE_FIND_PACKAGE_PREFER_CONFIG)
|
||||
set(CMAKE_FIND_PACKAGE_PREFER_CONFIG TRUE)
|
||||
endif()
|
||||
find_package(RocksDB CONFIG REQUIRED)
|
||||
mark_as_advanced(RocksDB_DIR)
|
||||
if(WIN32)
|
||||
set(CMAKE_FIND_PACKAGE_PREFER_CONFIG ${TEMP})
|
||||
endif()
|
||||
|
||||
message(STATUS "RocksDB: found at '${RocksDB_DIR}'.")
|
||||
add_library(IFCOPENSHELL_RocksDB INTERFACE)
|
||||
set(IFCOPENSHELL_ROCKSDB_TARGET IFCOPENSHELL_RocksDB)
|
||||
set(ROCKSDB_LIBRARIES "IFCOPENSHELL_RocksDB")
|
||||
target_compile_definitions(IFCOPENSHELL_RocksDB INTERFACE IFOPSH_WITH_ROCKSDB)
|
||||
set(SWIG_DEFINES ${SWIG_DEFINES} -DIFOPSH_WITH_ROCKSDB)
|
||||
@@ -312,12 +268,14 @@ if (WITH_ROCKSDB)
|
||||
message(FATAL_ERROR "RocksDB found but neither RocksDB::rocksdb nor RocksDB::rocksdb-shared target exists")
|
||||
endif()
|
||||
|
||||
if (WITH_ZSTD)
|
||||
if(WITH_ZSTD)
|
||||
# @todo do we actually need the zstd include dir or rather just pass
|
||||
# the libzstd.a along with the rocksdb library when needed and feature
|
||||
# detect based on rocksdb API?
|
||||
find_package(zstd CONFIG REQUIRED)
|
||||
mark_as_advanced(zstd_DIR)
|
||||
message(STATUS "zstd: found at '${zstd_DIR}'.")
|
||||
target_link_libraries(IFCOPENSHELL_RocksDB INTERFACE zstd::libzstd_static)
|
||||
endif()
|
||||
|
||||
install(TARGETS IFCOPENSHELL_RocksDB EXPORT ${IFCOPENSHELL_EXPORT_TARGETS})
|
||||
@@ -325,7 +283,7 @@ endif()
|
||||
|
||||
# Find Boost: On win32 the (hardcoded) default is to use static libraries and
|
||||
# runtime, when doing running conda-build we pick what conda prepared for us.
|
||||
if(WIN32 AND("$ENV{CONDA_BUILD}" STREQUAL ""))
|
||||
if(WIN32 AND NOT DEFINED ENV{CONDA_BUILD})
|
||||
set(Boost_USE_STATIC_LIBS ON)
|
||||
set(Boost_USE_STATIC_RUNTIME OFF)
|
||||
set(Boost_USE_MULTITHREADED ON)
|
||||
@@ -356,8 +314,18 @@ if(WASM_BUILD)
|
||||
else()
|
||||
# @todo review this, shouldn't this be all possible header-only now?
|
||||
# ... or rewritten using C++17 features?
|
||||
# set(BOOST_COMPONENTS system program_options regex thread date_time iostreams)
|
||||
set(BOOST_COMPONENTS program_options regex thread date_time iostreams)
|
||||
# Boost.System has been header-only since 1.69 and its compiled stub library
|
||||
# was dropped in newer Boost, so requesting it as a component makes
|
||||
# find_package fail on Boost 1.70 and up (for example Boost 1.90). It is
|
||||
# still pulled in transitively by thread / iostreams where needed, so do not
|
||||
# request it explicitly.
|
||||
set(BOOST_COMPONENTS
|
||||
program_options
|
||||
regex
|
||||
thread
|
||||
date_time
|
||||
iostreams
|
||||
)
|
||||
endif()
|
||||
|
||||
if(USE_MMAP)
|
||||
@@ -367,6 +335,17 @@ if(USE_MMAP)
|
||||
else()
|
||||
set(BOOST_COMPONENTS ${BOOST_COMPONENTS} iostreams)
|
||||
endif()
|
||||
|
||||
add_definitions(-DUSE_MMAP)
|
||||
endif()
|
||||
|
||||
# Handle CGAL after Boost settings are set, since CGAL will use them too.
|
||||
# Do `find_package(Boost)` only after this, to make sure `FindBoost` finds correct components.
|
||||
# Otherwise it will find components needed for CGAL and we might some libraries.
|
||||
if(WITH_CGAL)
|
||||
find_package(CGAL REQUIRED)
|
||||
set(CGAL_LIBRARIES IFCOPENSHELL_CGAL)
|
||||
list(APPEND GEOMETRY_KERNELS cgal)
|
||||
endif()
|
||||
|
||||
find_package(Boost REQUIRED COMPONENTS ${BOOST_COMPONENTS})
|
||||
@@ -375,8 +354,17 @@ message(STATUS "Boost libraries found in ${Boost_LIBRARY_DIRS}")
|
||||
|
||||
if(COLLADA_SUPPORT)
|
||||
find_package(OpenCOLLADA REQUIRED)
|
||||
add_definitions(-DWITH_OPENCOLLADA)
|
||||
endif()
|
||||
|
||||
if(HDF5_SUPPORT)
|
||||
find_package(HDF5 REQUIRED COMPONENTS C CXX)
|
||||
set(IFCOPENSHELL_LIBRARIES ${IFCOPENSHELL_LIBRARIES} hdf5::hdf5_cpp)
|
||||
|
||||
add_definitions(-DWITH_HDF5)
|
||||
set(SWIG_DEFINES ${SWIG_DEFINES} -DWITH_HDF5)
|
||||
endif(HDF5_SUPPORT)
|
||||
|
||||
if(ENABLE_BUILD_OPTIMIZATIONS)
|
||||
if(MSVC)
|
||||
# NOTE: RelWithDebInfo and Release use O2 (= /Ox /Gl /Gy/ = Og /Oi /Ot /Oy /Ob2 /Gs /GF /Gy) by default,
|
||||
@@ -389,12 +377,20 @@ if(ENABLE_BUILD_OPTIMIZATIONS)
|
||||
|
||||
# Linker
|
||||
# /OPT:REF enables also /OPT:ICF and disables INCREMENTAL
|
||||
set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} /LTCG /OPT:REF")
|
||||
|
||||
set(LINKER_FLAGS_RELEASE "/LTCG /OPT:REF")
|
||||
# /OPT:NOICF is recommended when /DEBUG is used (http://msdn.microsoft.com/en-us/library/xe4t6fc1.aspx)
|
||||
set(CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} /DEBUG /OPT:NOICF")
|
||||
set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} /LTCG /OPT:REF")
|
||||
set(CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO "${CMAKE_EXE_LINKER_FLAGS_RELEASE} /DEBUG /OPT:NOICF")
|
||||
set(LINKER_FLAGS_RELWITHDEBINFO "/DEBUG /OPT:NOICF")
|
||||
|
||||
set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} ${LINKER_FLAGS_RELEASE}")
|
||||
set(CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO
|
||||
"${CMAKE_SHARED_LINKER_FLAGS_RELEASE} ${LINKER_FLAGS_RELWITHDEBINFO}"
|
||||
)
|
||||
set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} ${LINKER_FLAGS_RELEASE}")
|
||||
set(CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO "${CMAKE_EXE_LINKER_FLAGS_RELEASE} ${LINKER_FLAGS_RELWITHDEBINFO}")
|
||||
set(CMAKE_MODULE_LINKER_FLAGS_RELEASE "${CMAKE_MODULE_LINKER_FLAGS_RELEASE} ${LINKER_FLAGS_RELEASE}")
|
||||
set(CMAKE_MODULE_LINKER_FLAGS_RELWITHDEBINFO
|
||||
"${CMAKE_MODULE_LINKER_FLAGS_RELEASE} ${LINKER_FLAGS_RELWITHDEBINFO}"
|
||||
)
|
||||
else()
|
||||
# GCC-like: Release should use O3 but RelWithDebInfo 02 so enforce 03. Anything other useful that could be added here?
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3")
|
||||
@@ -443,7 +439,7 @@ if(MSVC)
|
||||
endif()
|
||||
|
||||
# Enforce standards-conformance on VS > 2015, older Boost versions fail to compile with this
|
||||
if(MSVC_VERSION GREATER 1900 AND(Boost_MAJOR_VERSION GREATER 1 OR Boost_MINOR_VERSION GREATER 66))
|
||||
if(MSVC_VERSION GREATER 1900 AND (Boost_MAJOR_VERSION GREATER 1 OR Boost_MINOR_VERSION GREATER 66))
|
||||
add_definitions(-permissive-)
|
||||
endif()
|
||||
|
||||
@@ -462,11 +458,11 @@ if(MSVC)
|
||||
# endforeach()
|
||||
# endif()
|
||||
|
||||
add_definitions(-D_ENABLE_EXTENDED_ALIGNED_STORAGE)
|
||||
# See #5158.
|
||||
if(CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 19.40)
|
||||
add_definitions(-D_DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR)
|
||||
endif()
|
||||
add_definitions(-D_ENABLE_EXTENDED_ALIGNED_STORAGE)
|
||||
# See #5158.
|
||||
if(CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 19.40)
|
||||
add_definitions(-D_DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR)
|
||||
endif()
|
||||
else()
|
||||
add_definitions(-Wall -Wextra)
|
||||
|
||||
@@ -476,7 +472,10 @@ else()
|
||||
add_definitions(-Wno-maybe-uninitialized)
|
||||
endif()
|
||||
|
||||
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU" AND(CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 9.0 OR CMAKE_CXX_COMPILER_VERSION VERSION_EQUAL 9.0))
|
||||
if(
|
||||
CMAKE_CXX_COMPILER_ID MATCHES "GNU"
|
||||
AND (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 9.0 OR CMAKE_CXX_COMPILER_VERSION VERSION_EQUAL 9.0)
|
||||
)
|
||||
# OpenCascade spews a lot of deprecated-copy warnings
|
||||
add_definitions(-Wno-deprecated-copy)
|
||||
endif()
|
||||
@@ -487,28 +486,45 @@ else()
|
||||
endif()
|
||||
endif(MSVC)
|
||||
|
||||
include_directories(${INCLUDE_DIRECTORIES}
|
||||
${Boost_INCLUDE_DIRS}
|
||||
${CGAL_INCLUDE_DIR} ${GMP_INCLUDE_DIR} ${MPFR_INCLUDE_DIR}
|
||||
)
|
||||
include_directories(${OPENCOLLADA_INCLUDE_DIRS} ${Boost_INCLUDE_DIRS} ${HDF5_INCLUDE_DIR})
|
||||
|
||||
if(NOT SCHEMA_VERSIONS)
|
||||
if(WASM_BUILD)
|
||||
# super arbitrarily try to keep size down at least a little bit
|
||||
# `WASM_BUILD` - super arbitrarily try to keep size down at least a little bit
|
||||
if(BUILD_ONLY_COMMON_SCHEMAS OR WASM_BUILD)
|
||||
set(SCHEMA_VERSIONS "2x3" "4" "4x3_add2")
|
||||
else()
|
||||
set(SCHEMA_VERSIONS "2x3" "4" "4x1" "4x2" "4x3" "4x3_tc1" "4x3_add1" "4x3_add2")
|
||||
set(SCHEMA_VERSIONS
|
||||
"2x3"
|
||||
"4"
|
||||
"4x1"
|
||||
"4x2"
|
||||
"4x3"
|
||||
"4x3_tc1"
|
||||
"4x3_add1"
|
||||
"4x3_add2"
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
message(STATUS "IFC SCHEMA_VERSIONS that will be used for the build: ${SCHEMA_VERSIONS}.")
|
||||
|
||||
set(SCHEMA_DEFINITIONS "")
|
||||
foreach(schema ${SCHEMA_VERSIONS})
|
||||
list(APPEND SCHEMA_DEFINITIONS "-DHAS_SCHEMA_${schema}")
|
||||
endforeach()
|
||||
|
||||
string(REPLACE ";" ")(" schema_version_seq "(${SCHEMA_VERSIONS})")
|
||||
list(APPEND SCHEMA_DEFINITIONS "-DSCHEMA_SEQ=${schema_version_seq}")
|
||||
|
||||
if(COMPILE_SCHEMA)
|
||||
# @todo, this appears to be untested at the moment
|
||||
find_package(PythonInterp)
|
||||
|
||||
if(NOT PYTHONINTERP_FOUND)
|
||||
message(FATAL_ERROR "A Python interpreter is necessary when COMPILE_SCHEMA is enabled. Disable COMPILE_SCHEMA or fix Python paths to proceed.")
|
||||
message(
|
||||
FATAL_ERROR
|
||||
"A Python interpreter is necessary when COMPILE_SCHEMA is enabled. Disable COMPILE_SCHEMA or fix Python paths to proceed."
|
||||
)
|
||||
endif()
|
||||
|
||||
set(IFC_RELEASE_NOT_USED ${SCHEMA_VERSIONS})
|
||||
@@ -528,7 +544,10 @@ if(COMPILE_SCHEMA)
|
||||
|
||||
if("${PYPARSING_FOUND}" STREQUAL "-1")
|
||||
message(STATUS "Installing pyparsing")
|
||||
execute_process(COMMAND ${PYTHON_EXECUTABLE} -m pip "install" --user pyparsing RESULT_VARIABLE SUCCESS)
|
||||
execute_process(
|
||||
COMMAND ${PYTHON_EXECUTABLE} -m pip "install" --user pyparsing
|
||||
RESULT_VARIABLE SUCCESS
|
||||
)
|
||||
|
||||
if(NOT "${SUCCESS}" STREQUAL "0")
|
||||
execute_process(COMMAND pip "install" --user pyparsing RESULT_VARIABLE SUCCESS)
|
||||
@@ -547,7 +566,8 @@ if(COMPILE_SCHEMA)
|
||||
COMMAND ${PYTHON_EXECUTABLE} bootstrap.py
|
||||
WORKING_DIRECTORY ../src/ifcopenshell-python/ifcopenshell/express
|
||||
OUTPUT_FILE express_parser.py
|
||||
RESULT_VARIABLE SUCCESS)
|
||||
RESULT_VARIABLE SUCCESS
|
||||
)
|
||||
|
||||
if(NOT "${SUCCESS}" STREQUAL "0")
|
||||
message(FATAL_ERROR "Failed to bootstrap parser. Make sure pyparsing is installed")
|
||||
@@ -557,7 +577,8 @@ if(COMPILE_SCHEMA)
|
||||
execute_process(
|
||||
COMMAND ${PYTHON_EXECUTABLE} ../ifcopenshell-python/ifcopenshell/express/express_parser.py ../../${COMPILE_SCHEMA}
|
||||
WORKING_DIRECTORY ../src/ifcparse
|
||||
OUTPUT_VARIABLE COMPILED_SCHEMA_NAME)
|
||||
OUTPUT_VARIABLE COMPILED_SCHEMA_NAME
|
||||
)
|
||||
|
||||
# Prevent the schema that had just been compiled from being excluded
|
||||
foreach(schema ${SCHEMA_VERSIONS})
|
||||
@@ -572,52 +593,17 @@ if(NOT Boost_VERSION LESS 105800)
|
||||
add_definitions(-DBOOST_OPTIONAL_USE_OLD_DEFINITION_OF_NONE)
|
||||
endif()
|
||||
|
||||
add_subdirectory(../src/plugin plugin)
|
||||
|
||||
add_subdirectory(../src/ifcparse ifcparse)
|
||||
set(IFCOPENSHELL_LIBRARIES IfcParse)
|
||||
if(BUILD_IFCOPENSHELL_PARSE_TESTS)
|
||||
add_subdirectory(../src/ifcparse/tests ifcparse/tests)
|
||||
endif()
|
||||
|
||||
if(BUILD_EXAMPLES OR BUILD_BONSAIVIEWER)
|
||||
add_subdirectory(../src/helpers helpers)
|
||||
endif()
|
||||
|
||||
if(BUILD_IFCPARSE_EXPERIMENTAL_WRAPPER)
|
||||
add_subdirectory(../src/wrappergen wrappergen)
|
||||
endif()
|
||||
|
||||
if(BUILD_IFCGEOM)
|
||||
# CGAL::CGAL target already has dependencies resolved.
|
||||
if(WITH_CGAL AND CGAL_DIR)
|
||||
set(CGAL_LIBRARIES CGAL::CGAL)
|
||||
message(STATUS "Using found CGAL package at '${CGAL_DIR}'")
|
||||
elseif(WITH_CGAL AND NOT CGAL_DIR)
|
||||
find_library(libGMP NAMES gmp mpir PATHS ${GMP_LIBRARY_DIR} NO_DEFAULT_PATH)
|
||||
find_library(libMPFR NAMES mpfr PATHS ${MPFR_LIBRARY_DIR} NO_DEFAULT_PATH)
|
||||
if(NOT libGMP)
|
||||
message(FATAL_ERROR "Unable to find GMP library files, aborting")
|
||||
endif()
|
||||
if(NOT libMPFR)
|
||||
message(FATAL_ERROR "Unable to find MPFR library files, aborting")
|
||||
endif()
|
||||
|
||||
list(APPEND CGAL_LIBRARIES "${libMPFR}")
|
||||
list(APPEND CGAL_LIBRARIES "${libGMP}")
|
||||
endif()
|
||||
|
||||
add_subdirectory(../src/ifcgeom ifcgeom)
|
||||
if(BUILD_IFCOPENSHELL_GEOMETRY_TESTS)
|
||||
add_subdirectory(../src/ifcgeom/tests ifcgeom/tests)
|
||||
endif()
|
||||
elseif(BUILD_IFCOPENSHELL_GEOMETRY_TESTS)
|
||||
message(FATAL_ERROR "BUILD_IFCOPENSHELL_GEOMETRY_TESTS requires BUILD_IFCGEOM=ON.")
|
||||
endif(BUILD_IFCGEOM)
|
||||
|
||||
if(BUILD_CONVERT OR BUILD_IFCPYTHON OR BUILD_BONSAIVIEWER)
|
||||
if(BUILD_CONVERT OR BUILD_IFCPYTHON)
|
||||
add_subdirectory(../src/serializers serializers)
|
||||
endif(BUILD_CONVERT OR BUILD_IFCPYTHON OR BUILD_BONSAIVIEWER)
|
||||
set(IFCOPENSHELL_LIBRARIES ${IFCOPENSHELL_LIBRARIES} ${SERIALIZER_SCHEMA_LIBRARIES})
|
||||
endif(BUILD_CONVERT OR BUILD_IFCPYTHON)
|
||||
|
||||
if(BUILD_CONVERT)
|
||||
add_subdirectory(../src/ifcconvert ifcconvert)
|
||||
@@ -636,8 +622,8 @@ if(ADD_COMMIT_SHA)
|
||||
endif()
|
||||
|
||||
if(GIT_FOUND)
|
||||
if (VERSION_OVERRIDE)
|
||||
set (git_branch ${RELEASE_VERSION})
|
||||
if(VERSION_OVERRIDE)
|
||||
set(git_branch ${RELEASE_VERSION})
|
||||
else()
|
||||
message("git found: ${GIT_EXECUTABLE} with version ${GIT_VERSION_STRING}")
|
||||
execute_process(
|
||||
@@ -649,8 +635,8 @@ if(ADD_COMMIT_SHA)
|
||||
string(REPLACE "\n" ";" git_branch_list "${git_branches}")
|
||||
|
||||
foreach(git_branch_candidate IN ITEMS ${git_branch_list})
|
||||
string(REPLACE "*" "" git_branch_candidate_temp "${git_branch_candidate}")
|
||||
string(STRIP "${git_branch_candidate_temp}" git_branch_candidate_2)
|
||||
string(REPLACE "*" "" git_branch_candidate_temp "${git_branch_candidate}")
|
||||
string(STRIP "${git_branch_candidate_temp}" git_branch_candidate_2)
|
||||
if(NOT git_branch_candidate_2 MATCHES "^HEAD$")
|
||||
string(REPLACE "/" ";" git_branch_candidate_2_list "${git_branch_candidate_2}")
|
||||
list(GET git_branch_candidate_2_list -1 git_branch)
|
||||
@@ -668,13 +654,13 @@ if(ADD_COMMIT_SHA)
|
||||
message(STATUS "IfcOpenShell branch: \"${git_branch}\"")
|
||||
message(STATUS "IfcOpenShell commit: \"${git_sha}\"")
|
||||
|
||||
if ("${git_branch}" STREQUAL "" OR "${git_sha}" STREQUAL "")
|
||||
if("${git_branch}" STREQUAL "" OR "${git_sha}" STREQUAL "")
|
||||
message(FATAL_ERROR "Unable to determine commit sha and/or branch")
|
||||
endif()
|
||||
|
||||
target_compile_definitions(IfcParse PRIVATE
|
||||
-DIFCOPENSHELL_BRANCH=${git_branch}
|
||||
-DIFCOPENSHELL_COMMIT=${git_sha}
|
||||
target_compile_definitions(
|
||||
IfcParse
|
||||
PRIVATE -DIFCOPENSHELL_BRANCH=${git_branch} -DIFCOPENSHELL_COMMIT=${git_sha}
|
||||
)
|
||||
endif()
|
||||
endif(ADD_COMMIT_SHA)
|
||||
@@ -697,6 +683,10 @@ if(BUILD_DOCUMENTATION)
|
||||
add_subdirectory(../docs docs)
|
||||
endif()
|
||||
|
||||
if(BUILD_IFCPYTHON)
|
||||
add_subdirectory(../src/ifcwrap ifcwrap)
|
||||
endif()
|
||||
|
||||
if(BUILD_EXAMPLES)
|
||||
add_subdirectory(../src/examples examples)
|
||||
endif()
|
||||
@@ -709,48 +699,8 @@ if(BUILD_IFCPYTHON AND WITH_CGAL)
|
||||
add_subdirectory(../src/svgfill svgfill)
|
||||
endif()
|
||||
|
||||
if(BUILD_IFCPYTHON)
|
||||
add_subdirectory(../src/ifcwrap ifcwrap)
|
||||
endif()
|
||||
|
||||
if(BUILD_IFCMODEL_UI)
|
||||
add_subdirectory(../src/ifcmodel-ui ifcmodel-ui)
|
||||
endif()
|
||||
|
||||
if(BUILD_IFCGEOM)
|
||||
# install(FILES ${IFCGEOM_H_FILES}
|
||||
# DESTINATION ${INCLUDEDIR}/ifcgeom
|
||||
# )
|
||||
|
||||
install(FILES ${SCHEMA_AGNOSTIC_H_FILES}
|
||||
DESTINATION ${INCLUDEDIR}/ifcgeom
|
||||
)
|
||||
|
||||
file(GLOB SERIALIZATION_H_FILES ../src/ifcgeom/serialization/*.h)
|
||||
install(FILES ${SERIALIZATION_H_FILES}
|
||||
DESTINATION ${INCLUDEDIR}/ifcgeom/serialization
|
||||
)
|
||||
|
||||
foreach(kernel ${GEOMETRY_KERNELS})
|
||||
file(GLOB IFCGEOM_H_FILES ../src/ifcgeom/kernels/${kernel}/*.h)
|
||||
install(FILES ${IFCGEOM_H_FILES}
|
||||
DESTINATION ${INCLUDEDIR}/ifcgeom/kernels/${kernel}
|
||||
)
|
||||
endforeach()
|
||||
|
||||
install(
|
||||
TARGETS ${IFCGEOM_SCHEMA_LIBRARIES} ${kernel_libraries} IfcGeom
|
||||
EXPORT ${IFCOPENSHELL_EXPORT_TARGETS}
|
||||
)
|
||||
endif(BUILD_IFCGEOM)
|
||||
if(BUILD_BONSAIVIEWER)
|
||||
# IfcViewer is the unified scene + render lib since the wgpu/ifcviewer
|
||||
# merge — wgpu-native is fetched inside its CMakeLists.txt.
|
||||
add_subdirectory(../src/ifcviewer ifcviewer)
|
||||
if(BUILD_BONSAIVIEWER_WGPU)
|
||||
add_subdirectory(../src/ifcviewer-minimal ifcviewer-minimal)
|
||||
endif()
|
||||
add_subdirectory(../src/bonsaiviewer bonsaiviewer)
|
||||
if(BUILD_QTVIEWER)
|
||||
add_subdirectory(../src/qtviewer qtviewer)
|
||||
endif()
|
||||
|
||||
# Cmake uninstall target
|
||||
@@ -758,23 +708,23 @@ if(NOT TARGET uninstall)
|
||||
configure_file(
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/cmake_uninstall.cmake.in"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake"
|
||||
IMMEDIATE @ONLY)
|
||||
IMMEDIATE
|
||||
@ONLY
|
||||
)
|
||||
|
||||
add_custom_target(uninstall
|
||||
COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake)
|
||||
add_custom_target(uninstall COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake)
|
||||
endif()
|
||||
|
||||
# Packaging
|
||||
list(APPEND CPACK_SOURCE_IGNORE_FILES
|
||||
"/\\\\.git"
|
||||
"/build/"
|
||||
"/.pytest_cache/"
|
||||
"/__pycache__/"
|
||||
)
|
||||
list(APPEND CPACK_SOURCE_IGNORE_FILES "/\\\\.git" "/build/" "/.pytest_cache/" "/__pycache__/")
|
||||
set(CPACK_SOURCE_INSTALLED_DIRECTORIES "${CMAKE_SOURCE_DIR}/..;/")
|
||||
set(CPACK_PACKAGE_NAME "${PROJECT_NAME}-${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}${EXTRA_VERSION}")
|
||||
set(CPACK_PACKAGE_NAME
|
||||
"${PROJECT_NAME}-${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}${EXTRA_VERSION}"
|
||||
)
|
||||
set(CPACK_SOURCE_PACKAGE_FILE_NAME "${PROJECT_NAME}-${PROJECT_VERSION}${EXTRA_VERSION}")
|
||||
SET(CPACK_PACKAGE_FILE_NAME "${PROJECT_NAME}-${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}${EXTRA_VERSION}-${CMAKE_SYSTEM_NAME}")
|
||||
set(CPACK_PACKAGE_FILE_NAME
|
||||
"${PROJECT_NAME}-${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}${EXTRA_VERSION}-${CMAKE_SYSTEM_NAME}"
|
||||
)
|
||||
set(CPACK_PACKAGE_DIRECTORY "${PROJECT_BINARY_DIR}/assets")
|
||||
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "IfcOpenShell")
|
||||
set(CPACK_PACKAGE_DESCRIPTION "IfcOpenShell.")
|
||||
@@ -787,6 +737,7 @@ set(CPACK_PACKAGE_VERSION_PATCH "${PROJECT_VERSION_PATCH}")
|
||||
set(CPACK_GENERATOR "TGZ;DEB")
|
||||
set(CPACK_SOURCE_GENERATOR "TGZ")
|
||||
|
||||
set(BOOST_DEPS "")
|
||||
foreach(COMPONENT IN ITEMS ${BOOST_COMPONENTS})
|
||||
string(REPLACE "_" "-" COMP ${COMPONENT})
|
||||
set(BOOST_DEPS "${BOOST_DEPS}, libboost-${COMP}-dev")
|
||||
@@ -794,12 +745,16 @@ endforeach(COMPONENT)
|
||||
|
||||
set(CPACK_DEBIAN_PACKAGE_NAME "${PROJECT_NAME}")
|
||||
set(CPACK_DEBIAN_PACKAGE_MAINTAINER "${CPACK_PACKAGE_CONTACT}")
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "python3, libxml2, libocct-foundation-dev, libocct-modeling-algorithms-dev, libocct-modeling-data-dev, libocct-ocaf-dev, libocct-visualization-dev, libocct-data-exchange-dev, libpython3-dev, python3-pytest ${BOOST_DEPS}")
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS
|
||||
"python3, libxml2, libocct-foundation-dev, libocct-modeling-algorithms-dev, libocct-modeling-data-dev, libocct-ocaf-dev, libocct-visualization-dev, libocct-data-exchange-dev, libhdf5-serial-dev, libpython3-dev, python3-pytest ${BOOST_DEPS}"
|
||||
)
|
||||
set(CPACK_DEBIAN_PACKAGE_DESCRIPTION_SUMMARY "${CPACK_PACKAGE_DESCRIPTION_SUMMARY}")
|
||||
set(CPACK_DEBIAN_PACKAGE_DESCRIPTION "${CPACK_PACKAGE_DESCRIPTION}")
|
||||
set(CPACK_DEBIAN_PACKAGE_PRIORITY "optional")
|
||||
set(CPACK_DEBIAN_PACKAGE_SECTION "science")
|
||||
set(CPACK_DEBIAN_PACKAGE_VERSION "${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}${EXTRA_VERSION}")
|
||||
set(CPACK_DEBIAN_PACKAGE_VERSION
|
||||
"${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}${EXTRA_VERSION}"
|
||||
)
|
||||
set(CPACK_DEBIAN_ARCHITECTURE "${CMAKE_SYSTEM_PROCESSOR}")
|
||||
# set(CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA "${CMAKE_SOURCE_DIR}/cmake/debian/postinst")
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
"BUILD_CONVERT": "ON",
|
||||
"BUILD_IFCMAX": "OFF",
|
||||
"IFCXML_SUPPORT": "ON",
|
||||
"HDF5_SUPPORT": "ON",
|
||||
"SCHEMA_VERSIONS": "4x3_add2",
|
||||
"CMAKE_GENERATOR_PLATFORM": "",
|
||||
"CMAKE_GENERATOR_TOOLSET": ""
|
||||
@@ -49,6 +50,8 @@
|
||||
"MPFR_LIBRARY_DIR": "$env{LIBRARY_PREFIX}/lib",
|
||||
"Boost_LIBRARY_DIR": "$env{LIBRARY_PREFIX}/lib",
|
||||
"Boost_INCLUDE_DIR": "$env{LIBRARY_PREFIX}/include",
|
||||
"HDF5_INCLUDE_DIR": "$env{LIBRARY_PREFIX}/include",
|
||||
"HDF5_LIBRARY_DIR": "$env{LIBRARY_PREFIX}/lib",
|
||||
"ZLIB_INCLUDE_DIR": "$env{LIBRARY_PREFIX}/include"
|
||||
}
|
||||
},
|
||||
@@ -107,4 +110,4 @@
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@
|
||||
# - `GMP_LIBRARY_DIR`
|
||||
# - `MPFR_INCLUDE_DIR`
|
||||
# - `MPFR_LIBRARY_DIR`
|
||||
# If input variables are not specified, try to find CGAL config.
|
||||
# If input variables are not specified, try to find HDF5 config.
|
||||
# Input variables could also be provided as environment variables.
|
||||
#
|
||||
# Output targets:
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
#
|
||||
# Input variables:
|
||||
# - `HDF5_INCLUDE_DIR`
|
||||
# - `HDF5_LIBRARY_DIR`
|
||||
# - `HDF5_LIBRARIES`
|
||||
# If input variables are not specified, try to find HDF5 config.
|
||||
# Input variables could also be provided as environment variables.
|
||||
#
|
||||
# Output variables:
|
||||
# - `HDF5_INCLUDE_DIR`
|
||||
# - `HDF5_LIBRARY_DIR`
|
||||
# - `HDF5_LIBRARIES`
|
||||
#
|
||||
|
||||
UNIFY_ENVVARS_AND_CACHE(HDF5_INCLUDE_DIR)
|
||||
UNIFY_ENVVARS_AND_CACHE(HDF5_LIBRARY_DIR)
|
||||
UNIFY_ENVVARS_AND_CACHE(HDF5_LIBRARIES)
|
||||
|
||||
# To avoid cyclic calls to this file
|
||||
list(REMOVE_ITEM CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR})
|
||||
|
||||
if(NOT HDF5_INCLUDE_DIR)
|
||||
message(STATUS "No HDF5 include directory specified")
|
||||
else()
|
||||
set(HDF5_INCLUDE_DIR "${HDF5_INCLUDE_DIR}" CACHE FILEPATH "HDF5 header files")
|
||||
endif()
|
||||
|
||||
if(NOT HDF5_LIBRARY_DIR)
|
||||
message(STATUS "No HDF5 library directory specified")
|
||||
else()
|
||||
set(HDF5_LIBRARY_DIR "${HDF5_LIBRARY_DIR}" CACHE FILEPATH "HDF5 library files")
|
||||
endif()
|
||||
|
||||
if(HDF5_LIBRARY_DIR)
|
||||
# result of the HDF5 ctest package
|
||||
# Find zlib using cmake find_library. How should this be implemented?
|
||||
# FIND_LIBRARY(NAMES z libz libz_debug PATHS ... NO_DEFAULT_PATH)
|
||||
if(NOT DEFINED ENV{CONDA_BUILD})
|
||||
# result of the HDF5 ctest package
|
||||
if(WIN32)
|
||||
set(zlib_post lib)
|
||||
set(lib_ext lib)
|
||||
else()
|
||||
set(lib_ext a)
|
||||
endif()
|
||||
|
||||
if("${CMAKE_BUILD_TYPE}" STREQUAL "Debug")
|
||||
set(debug_postfix "_debug")
|
||||
endif()
|
||||
|
||||
set(HDF5_LIBRARIES
|
||||
"${HDF5_LIBRARY_DIR}/libhdf5_cpp${debug_postfix}.${lib_ext}"
|
||||
"${HDF5_LIBRARY_DIR}/libhdf5${debug_postfix}.${lib_ext}"
|
||||
"${HDF5_LIBRARY_DIR}/libz${zlib_post}${debug_postfix}.${lib_ext}"
|
||||
"${HDF5_LIBRARY_DIR}/libsz${debug_postfix}.${lib_ext}"
|
||||
"${HDF5_LIBRARY_DIR}/libaec${debug_postfix}.${lib_ext}"
|
||||
)
|
||||
else()
|
||||
message(STATUS "Packaging hdf5 and zlib for conda distribution")
|
||||
|
||||
if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
|
||||
# macOS
|
||||
set(zlib_post libz)
|
||||
set(lib_ext dylib)
|
||||
set(HDF5_LIBRARIES
|
||||
"${HDF5_LIBRARY_DIR}/libhdf5_cpp.${lib_ext}"
|
||||
"${HDF5_LIBRARY_DIR}/libhdf5.${lib_ext}"
|
||||
"${HDF5_LIBRARY_DIR}/${zlib_post}.${lib_ext}"
|
||||
)
|
||||
else()
|
||||
# linux and windows
|
||||
# Find HDF5 package
|
||||
find_package(HDF5 REQUIRED COMPONENTS C CXX)
|
||||
# Find ZLIB package
|
||||
find_package(ZLIB REQUIRED)
|
||||
# Include directories
|
||||
include_directories(${HDF5_INCLUDE_DIRS} ${ZLIB_INCLUDE_DIRS})
|
||||
# Link libraries
|
||||
set(HDF5_LIBRARIES ${HDF5_LIBRARIES} ${ZLIB_LIBRARIES})
|
||||
message(STATUS "HDF5 libraries: ${HDF5_LIBRARIES}")
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(NOT HDF5_INCLUDE_DIR OR NOT HDF5_LIBRARY_DIR)
|
||||
# First try to find it as a config.
|
||||
find_package(HDF5 CONFIG)
|
||||
mark_as_advanced(HDF5_DIR)
|
||||
if(HDF5_DIR)
|
||||
message(STATUS "HDF5: found config at '${HDF5_DIR}'.")
|
||||
if(TARGET hdf5_cpp-static)
|
||||
set(HDF5_LIBRARIES hdf5_cpp-static)
|
||||
elseif(TARGET hdf5_cpp-shared)
|
||||
set(HDF5_LIBRARIES hdf5_cpp-shared)
|
||||
elseif(TARGET hdf5::hdf5_cpp-shared)
|
||||
set(HDF5_LIBRARIES hdf5::hdf5_cpp-shared)
|
||||
else()
|
||||
find_package(HDF5 REQUIRED COMPONENTS CXX)
|
||||
endif()
|
||||
else()
|
||||
# If it failed, still try to find as a module.
|
||||
# E.g. on Ubuntu `libhdf5-dev` doesn't provie hdf5-config.cmake.
|
||||
# Will automatically fill HDF5_LIBRARIES and HDF5_INCLUDE_DIR.
|
||||
find_package(HDF5 COMPONENTS CXX)
|
||||
if(NOT HDF5_INCLUDE_DIR)
|
||||
message(
|
||||
FATAL_ERROR
|
||||
"HDF5_INCLUDE_DIR is not provided (current value: '${HDF5_INCLUDE_DIR}'). "
|
||||
"HDF5_LIBRARY_DIR is not provided (current value: '${HDF5_LIBRARY_DIR}'). "
|
||||
"Also could not find HDF5 package (neither module or config)."
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Restore module path.
|
||||
list(PREPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR})
|
||||
@@ -139,8 +139,7 @@ if(NOT OpenCOLLADA_DIR)
|
||||
endif()
|
||||
endif(NOT OpenCOLLADA_DIR)
|
||||
|
||||
if(OPENCOLLADA_FOUND AND NOT TARGET OpenCOLLADA::OpenCOLLADA)
|
||||
add_library(OpenCOLLADA::OpenCOLLADA INTERFACE IMPORTED)
|
||||
target_include_directories(OpenCOLLADA::OpenCOLLADA INTERFACE ${OPENCOLLADA_INCLUDE_DIRS})
|
||||
target_link_libraries(OpenCOLLADA::OpenCOLLADA INTERFACE ${OPENCOLLADA_LIBRARIES})
|
||||
if(OPENCOLLADA_FOUND)
|
||||
add_definitions(-DWITH_OPENCOLLADA)
|
||||
set(SWIG_DEFINES ${SWIG_DEFINES} -DWITH_OPENCOLLADA)
|
||||
endif()
|
||||
|
||||
+6
-12
@@ -6,7 +6,7 @@
|
||||
# Input variables could also be provided as environment variables.
|
||||
#
|
||||
# Output targets:
|
||||
# - `proj::proj`
|
||||
# - `PROJ::proj`
|
||||
#
|
||||
|
||||
# To avoid cyclic calls to this file
|
||||
@@ -34,12 +34,10 @@ if((NOT PROJ_INCLUDE_DIR AND NOT PROJ_LIBRARIES))
|
||||
message(FATAL_ERROR "Unable to find PROJ libraries in: ${PROJ_LIBRARY_DIR}")
|
||||
endif()
|
||||
|
||||
if(NOT TARGET proj::proj)
|
||||
add_library(proj::proj INTERFACE IMPORTED)
|
||||
target_include_directories(proj::proj INTERFACE "${PROJ_INCLUDE_DIR}")
|
||||
target_link_libraries(proj::proj INTERFACE ${PROJ_LIBRARIES})
|
||||
target_link_directories(proj::proj INTERFACE "${PROJ_LIBRARY}")
|
||||
endif()
|
||||
add_library(PROJ::proj INTERFACE IMPORTED)
|
||||
target_include_directories(PROJ::proj INTERFACE "${PROJ_INCLUDE_DIR}")
|
||||
target_link_libraries(PROJ::proj INTERFACE ${PROJ_LIBRARIES})
|
||||
target_link_directories(PROJ::proj INTERFACE "${PROJ_LIBRARY}")
|
||||
endif()
|
||||
else()
|
||||
find_library(PROJ_LIBRARY NAMES proj PATHS ${PROJ_LIBRARY_DIR})
|
||||
@@ -52,11 +50,7 @@ else()
|
||||
|
||||
set(PROJ_INCLUDE_DIR ${PROJ_INCLUDE_DIR} CACHE FILEPATH "PROJ header files")
|
||||
message(STATUS "Looking for PROJ include files in: ${PROJ_INCLUDE_DIR}")
|
||||
if(NOT TARGET proj::proj)
|
||||
add_library(proj::proj INTERFACE IMPORTED)
|
||||
target_include_directories(proj::proj INTERFACE "${PROJ_INCLUDE_DIR}")
|
||||
target_link_libraries(proj::proj INTERFACE ${PROJ_LIBRARIES})
|
||||
endif()
|
||||
include_directories(${PROJ_INCLUDE_DIR})
|
||||
endif()
|
||||
|
||||
list(PREPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR})
|
||||
|
||||
@@ -64,6 +64,7 @@ set(USD_LIBRARIES
|
||||
find_library(USD_LIBRARY NAMES ${USD_LIBRARIES} PATHS ${USD_LIBRARY_DIR})
|
||||
if(USD_LIBRARY)
|
||||
message(STATUS "USD libraries ${USD_LIBRARIES} found in: ${USD_LIBRARY_DIR}")
|
||||
link_directories(${USD_LIBRARY_DIR})
|
||||
else()
|
||||
message(FATAL_ERROR "Unable to find USD libraries in: ${USD_LIBRARY_DIR}")
|
||||
endif()
|
||||
@@ -81,3 +82,5 @@ if(MSVC)
|
||||
endif()
|
||||
|
||||
target_compile_definitions(pxr::USD INTERFACE PXR_STATIC WITH_USD)
|
||||
|
||||
set(SWIG_DEFINES ${SWIG_DEFINES} -DWITH_USD)
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
@PACKAGE_INIT@
|
||||
|
||||
set_and_check(IFCOPENSHELL_LIBRARY_DIR "@PACKAGE_CMAKE_INSTALL_LIBDIR@")
|
||||
|
||||
# Variable to inspect installed schema versions.
|
||||
set(IFCOPENSHELL_SCHEMA_VERSIONS @SCHEMA_VERSIONS@)
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@ configure_package_config_file(
|
||||
${CONFIG_PACKAGE_INPUT}
|
||||
${CONFIG_PACKAGE_OUTPUT}
|
||||
INSTALL_DESTINATION ${CONFIG_PACKAGE_LOCATION}
|
||||
PATH_VARS CMAKE_INSTALL_LIBDIR
|
||||
)
|
||||
|
||||
install(FILES "${CONFIG_PACKAGE_OUTPUT}" "${CONFIG_VERSION_OUTPUT}" DESTINATION ${CONFIG_PACKAGE_LOCATION})
|
||||
|
||||
@@ -41,121 +41,6 @@ macro(SET_INSTALL_RPATHS _target _paths)
|
||||
set_target_properties(${_target} PROPERTIES INSTALL_RPATH "${${_target}_rpaths}")
|
||||
endmacro()
|
||||
|
||||
macro(SET_INSTALL_SELF_RPATH _target)
|
||||
if(IS_ABSOLUTE "${CMAKE_INSTALL_LIBDIR}")
|
||||
SET_INSTALL_RPATHS(${_target} "${CMAKE_INSTALL_LIBDIR}")
|
||||
elseif(APPLE)
|
||||
SET_INSTALL_RPATHS(${_target} "@loader_path")
|
||||
else()
|
||||
SET_INSTALL_RPATHS(${_target} "$ORIGIN")
|
||||
endif()
|
||||
endmacro()
|
||||
|
||||
function(ifcopenshell_plugin_target TARGET)
|
||||
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})
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
function(ifcopenshell_wasm_plugin_link_options TARGET REGISTRATION_SYMBOL)
|
||||
ifcopenshell_plugin_target(${TARGET})
|
||||
|
||||
if(NOT WASM_BUILD)
|
||||
return()
|
||||
endif()
|
||||
|
||||
cmake_parse_arguments(PLUGIN "" "OPTIMIZATION" "" ${ARGN})
|
||||
if(NOT PLUGIN_OPTIMIZATION)
|
||||
set(PLUGIN_OPTIMIZATION -O1)
|
||||
endif()
|
||||
|
||||
set(plugin_symbols
|
||||
ifcopenshell_plugin_abi_v1
|
||||
ifcopenshell_plugin_metadata_v1
|
||||
${REGISTRATION_SYMBOL}
|
||||
)
|
||||
|
||||
target_link_options(${TARGET} PRIVATE "SHELL:-s SIDE_MODULE=2" ${PLUGIN_OPTIMIZATION})
|
||||
foreach(symbol IN LISTS plugin_symbols)
|
||||
target_link_options(${TARGET} PRIVATE "LINKER:--export=${symbol}")
|
||||
endforeach()
|
||||
endfunction()
|
||||
|
||||
function(ifcopenshell_deploy_qt_runtime TARGET)
|
||||
if(NOT IFCOPENSHELL_DEPLOY_QT_RUNTIME)
|
||||
return()
|
||||
endif()
|
||||
|
||||
if(NOT TARGET ${TARGET})
|
||||
message(FATAL_ERROR "Cannot deploy Qt runtime for unknown target '${TARGET}'.")
|
||||
endif()
|
||||
|
||||
get_target_property(target_type ${TARGET} TYPE)
|
||||
if(NOT target_type STREQUAL "EXECUTABLE")
|
||||
message(FATAL_ERROR "Qt runtime deployment target '${TARGET}' is not an executable.")
|
||||
endif()
|
||||
|
||||
if(NOT DEFINED QT_DEFAULT_MAJOR_VERSION)
|
||||
if(DEFINED QT_VERSION)
|
||||
set(QT_DEFAULT_MAJOR_VERSION ${QT_VERSION})
|
||||
else()
|
||||
set(QT_DEFAULT_MAJOR_VERSION 6)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(NOT TARGET Qt${QT_DEFAULT_MAJOR_VERSION}::Core)
|
||||
set(qt_find_args Qt${QT_DEFAULT_MAJOR_VERSION} COMPONENTS Core REQUIRED)
|
||||
if(DEFINED QT_DIR AND NOT QT_DIR STREQUAL "")
|
||||
list(APPEND qt_find_args PATHS ${QT_DIR})
|
||||
endif()
|
||||
find_package(${qt_find_args})
|
||||
endif()
|
||||
|
||||
if(COMMAND _qt_internal_setup_deploy_support)
|
||||
if(NOT DEFINED QT_CMAKE_EXPORT_NAMESPACE AND TARGET Qt${QT_DEFAULT_MAJOR_VERSION}::Core)
|
||||
set(QT_CMAKE_EXPORT_NAMESPACE Qt${QT_DEFAULT_MAJOR_VERSION})
|
||||
endif()
|
||||
|
||||
if(QT_DEFAULT_MAJOR_VERSION EQUAL 6 AND TARGET Qt6::Core)
|
||||
get_target_property(qt_core_type Qt6::Core TYPE)
|
||||
if(qt_core_type STREQUAL "SHARED_LIBRARY")
|
||||
set(QT6_IS_SHARED_LIBS_BUILD ON)
|
||||
else()
|
||||
set(QT6_IS_SHARED_LIBS_BUILD OFF)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
_qt_internal_setup_deploy_support()
|
||||
endif()
|
||||
|
||||
set(deploy_args
|
||||
TARGET ${TARGET}
|
||||
OUTPUT_SCRIPT deploy_script
|
||||
NO_UNSUPPORTED_PLATFORM_ERROR
|
||||
)
|
||||
|
||||
if(NOT IFCOPENSHELL_DEPLOY_QT_TRANSLATIONS)
|
||||
list(APPEND deploy_args NO_TRANSLATIONS)
|
||||
endif()
|
||||
|
||||
list(APPEND deploy_args ${ARGN})
|
||||
|
||||
if(COMMAND qt_generate_deploy_app_script)
|
||||
qt_generate_deploy_app_script(${deploy_args})
|
||||
elseif(COMMAND qt6_generate_deploy_app_script)
|
||||
qt6_generate_deploy_app_script(${deploy_args})
|
||||
else()
|
||||
message(WARNING
|
||||
"Qt runtime deployment requested for '${TARGET}', but this Qt version "
|
||||
"does not provide qt_generate_deploy_app_script()."
|
||||
)
|
||||
return()
|
||||
endif()
|
||||
|
||||
install(SCRIPT ${deploy_script})
|
||||
endfunction()
|
||||
|
||||
# Get a list of all OPTION flags from the CMakeLists.txt and store in an output LIST
|
||||
function(get_all_option_flags output_list)
|
||||
# Read the contents of the CMakeLists.txt
|
||||
|
||||
@@ -22,6 +22,9 @@ cmake -G "Ninja" ^
|
||||
-D GMP_LIBRARY_DIR:FILEPATH="%LIBRARY_PREFIX%\lib" ^
|
||||
-D MPFR_LIBRARY_DIR:FILEPATH="%LIBRARY_PREFIX%\lib" ^
|
||||
-D COLLADA_SUPPORT=OFF ^
|
||||
-D HDF5_SUPPORT=ON ^
|
||||
-D HDF5_INCLUDE_DIR="%LIBRARY_PREFIX%\include" ^
|
||||
-D HDF5_LIBRARY_DIR="%LIBRARY_PREFIX%\lib" ^
|
||||
-D JSON_INCLUDE_DIR="%LIBRARY_PREFIX%\include" ^
|
||||
-D PYTHON_INCLUDE_DIR=%PREFIX%\include ^
|
||||
-D PYTHON_EXECUTABLE:FILEPATH=%PREFIX%\python.exe ^
|
||||
@@ -34,6 +37,7 @@ cmake -G "Ninja" ^
|
||||
-D GLTF_SUPPORT:BOOL=ON ^
|
||||
-D BUILD_CONVERT:BOOL=ON ^
|
||||
-D BUILD_IFCMAX:BOOL=OFF ^
|
||||
-D IFCXML_SUPPORT:BOOL=ON ^
|
||||
-D Boost_LIBRARY_DIR:FILEPATH="%LIBRARY_PREFIX%\lib" ^
|
||||
-D Boost_INCLUDE_DIR:FILEPATH="%LIBRARY_PREFIX%\include" ^
|
||||
-D Boost_USE_STATIC_LIBS:BOOL=OFF ^
|
||||
|
||||
+5
-1
@@ -24,6 +24,9 @@ cmake ${CMAKE_ARGS} -G Ninja \
|
||||
-DMPFR_LIBRARY_DIR=$PREFIX/lib \
|
||||
-DOCC_INCLUDE_DIR=$PREFIX/include/opencascade \
|
||||
-DOCC_LIBRARY_DIR=$PREFIX/lib \
|
||||
-DHDF5_SUPPORT:BOOL=ON \
|
||||
-DHDF5_INCLUDE_DIR=$PREFIX/include \
|
||||
-DHDF5_LIBRARY_DIR=$PREFIX/lib \
|
||||
-DJSON_INCLUDE_DIR=$PREFIX/include \
|
||||
-DCGAL_INCLUDE_DIR=$PREFIX/include \
|
||||
-DLIBXML2_INCLUDE_DIR=$PREFIX/include/libxml2 \
|
||||
@@ -31,6 +34,7 @@ cmake ${CMAKE_ARGS} -G Ninja \
|
||||
-DEIGEN_DIR:FILEPATH=$PREFIX/include/eigen3 \
|
||||
-DCOLLADA_SUPPORT:BOOL=OFF \
|
||||
-DBUILD_EXAMPLES:BOOL=OFF \
|
||||
-DIFCXML_SUPPORT:BOOL=ON \
|
||||
-DGLTF_SUPPORT:BOOL=ON \
|
||||
-DBUILD_CONVERT:BOOL=ON \
|
||||
-DBUILD_IFCPYTHON:BOOL=ON \
|
||||
@@ -43,4 +47,4 @@ ninja
|
||||
|
||||
ninja install -j 1
|
||||
|
||||
python "${RECIPE_DIR}/update_version_init.py" "${PKG_VERSION}" "${SP_DIR}/ifcopenshell/__init__.py"
|
||||
python "${RECIPE_DIR}/update_version_init.py" "${PKG_VERSION}" "${SP_DIR}/ifcopenshell/__init__.py"
|
||||
@@ -26,6 +26,8 @@ c_stdlib_version:
|
||||
- 2.17 # [linux]
|
||||
- 10.13 # [osx and x86_64]
|
||||
- 11.0 # [osx and arm64]
|
||||
hdf5:
|
||||
- 1.14.6
|
||||
libboost_devel:
|
||||
- '1.86'
|
||||
libxml2:
|
||||
|
||||
@@ -33,6 +33,7 @@ requirements:
|
||||
- occt
|
||||
- libxml2
|
||||
- cgal-cpp
|
||||
- hdf5
|
||||
- eigen
|
||||
- mpfr
|
||||
- nlohmann_json
|
||||
@@ -284,6 +285,11 @@ about:
|
||||
<td>Internal library for IfcOpenShell</td>
|
||||
<td>LGPL-3.0-or-later*</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>qtviewer</td>
|
||||
<td>Internal library for IfcOpenShell</td>
|
||||
<td>LGPL-3.0-or-later*</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>serializers</td>
|
||||
<td>Internal library for IfcOpenShell</td>
|
||||
|
||||
-317
@@ -1,317 +0,0 @@
|
||||
# Build fix: remove `boost_system` from CMake components
|
||||
|
||||
`Boost.System` became header-only in Boost 1.69. Boost 1.90.0 no longer ships a compiled library or CMake config for it, so `find_package(Boost REQUIRED COMPONENTS system ...)` fails.
|
||||
|
||||
## Fix
|
||||
|
||||
`cmake/CMakeLists.txt`:
|
||||
|
||||
```diff
|
||||
- set(BOOST_COMPONENTS system program_options regex thread date_time iostreams)
|
||||
+ set(BOOST_COMPONENTS program_options regex thread date_time iostreams)
|
||||
```
|
||||
|
||||
The headers are still available; no linking is needed.
|
||||
|
||||
# Build fix: add `template` keyword for dependent template member calls
|
||||
|
||||
Calling a template member function through a dependent expression (e.g. `storage->has_attribute_value<T>(...)` where `storage`'s type depends on a template parameter) requires the `template` keyword to disambiguate from a less-than comparison.
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
src/ifcparse/IfcParse.cpp:1856:67: error: expected primary-expression before '>' token
|
||||
1856 | if (storage->has_attribute_value<express::Base>(attr_index)) {
|
||||
| ^
|
||||
```
|
||||
|
||||
Six identical errors at lines 1856, 1865, 1896, 1905, 1934, 1943.
|
||||
|
||||
## Fix
|
||||
|
||||
`src/ifcparse/IfcParse.cpp`:
|
||||
|
||||
```diff
|
||||
-storage->has_attribute_value<express::Base>(attr_index)
|
||||
+storage->template has_attribute_value<express::Base>(attr_index)
|
||||
|
||||
-storage->has_attribute_value<Blank>(attr_index)
|
||||
+storage->template has_attribute_value<Blank>(attr_index)
|
||||
```
|
||||
|
||||
Applied at all six call sites in `in_memory_file_storage::read_from_stream`.
|
||||
|
||||
# Linker fix: missing explicit template instantiations for `InstanceStreamer`
|
||||
|
||||
`InstanceStreamer` is a class template with methods defined in `IfcParse.cpp`, not the header. Without explicit instantiations, the linker can't find the symbols when the SWIG wrapper loads.
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
ImportError: undefined symbol: _ZN8IfcParse16InstanceStreamerINS_10FileReaderINS_14FullBufferImplEEEEC1EPS3_PNS_7IfcFileE
|
||||
(IfcParse::InstanceStreamer<FileReader<FullBufferImpl>>::InstanceStreamer(FileReader<FullBufferImpl>*, IfcFile*))
|
||||
```
|
||||
|
||||
## Fix
|
||||
|
||||
Cannot use `template class InstanceStreamer<...>` because some constructors have `static_assert` guards that reject certain reader types. Instead, instantiate each member function individually per reader type, only including the constructors valid for that type.
|
||||
|
||||
`src/ifcparse/IfcParse.cpp` (after the last `InstanceStreamer` method definition):
|
||||
|
||||
```cpp
|
||||
// FullBufferImpl
|
||||
template IfcParse::InstanceStreamer<FileReader<FullBufferImpl>>::InstanceStreamer(IfcParse::IfcFile*);
|
||||
template IfcParse::InstanceStreamer<FileReader<FullBufferImpl>>::InstanceStreamer(const std::string&, bool, IfcParse::IfcFile*);
|
||||
template IfcParse::InstanceStreamer<FileReader<FullBufferImpl>>::InstanceStreamer(void*, int, IfcParse::IfcFile*);
|
||||
template IfcParse::InstanceStreamer<FileReader<FullBufferImpl>>::InstanceStreamer(FileReader<FullBufferImpl>*, IfcParse::IfcFile*);
|
||||
// ... plus ensure_header, initialize_header, hasSemicolon, semicolonCount,
|
||||
// pushPage, bypassTypes, readInstance
|
||||
|
||||
// PushedSequentialImpl — same pattern, different valid constructors
|
||||
|
||||
// MMapFileReader (ifdef USE_MMAP) — same pattern
|
||||
```
|
||||
|
||||
# Linker fix: `FullBufferImpl` missing buffer constructor
|
||||
|
||||
SWIG's `stream_from_string` calls `InstanceStreamer<FileReader<FullBufferImpl>>(void*, int, IfcFile*)`, but the `(void*, int)` constructor previously hit a `static_assert` for `FullBufferImpl` — it only allowed `PushedSequentialImpl`.
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
ImportError: undefined symbol: _ZN8IfcParse16InstanceStreamerINS_10FileReaderINS_14FullBufferImplEEEEC1EPviPNS_7IfcFileE
|
||||
(InstanceStreamer<FileReader<FullBufferImpl>>::InstanceStreamer(void*, int, IfcFile*))
|
||||
```
|
||||
|
||||
## Fix
|
||||
|
||||
Three changes to make `FullBufferImpl` support buffer-based and default construction:
|
||||
|
||||
`src/ifcparse/FileReader.h` — add buffer constructor to `FullBufferImpl`:
|
||||
|
||||
```diff
|
||||
class IFC_PARSE_API FullBufferImpl {
|
||||
public:
|
||||
explicit FullBufferImpl(const std::string& fn);
|
||||
+ FullBufferImpl(void* data, size_t length);
|
||||
```
|
||||
|
||||
`src/ifcparse/FileReader.h` — add `FileReader(void*, size_t)` forwarding constructor:
|
||||
|
||||
```diff
|
||||
+ FileReader(void* data, size_t length)
|
||||
+ : cursor_(0) {
|
||||
+ if constexpr (std::is_same_v<Impl, FullBufferImpl>) {
|
||||
+ impl_ = std::make_shared<Impl>(data, length);
|
||||
+ } else {
|
||||
+ static_assert(...);
|
||||
+ }
|
||||
+ }
|
||||
```
|
||||
|
||||
`src/ifcparse/FileReader.cpp` — implement the constructor:
|
||||
|
||||
```cpp
|
||||
FullBufferImpl::FullBufferImpl(void* data, size_t length)
|
||||
: buf_(static_cast<char*>(data), static_cast<char*>(data) + length)
|
||||
, size_(length) {
|
||||
}
|
||||
```
|
||||
|
||||
`src/ifcparse/IfcParse.cpp` — extend the two `InstanceStreamer` constructors to accept `FullBufferImpl`:
|
||||
|
||||
```diff
|
||||
// InstanceStreamer(IfcFile*):
|
||||
+ } else if constexpr (std::is_same_v<Reader, FileReader<FullBufferImpl>>) {
|
||||
+ owned_stream_ = std::make_unique<Reader>(nullptr, (size_t)0);
|
||||
|
||||
// InstanceStreamer(void*, int, IfcFile*):
|
||||
+ } else if constexpr (std::is_same_v<Reader, FileReader<FullBufferImpl>>) {
|
||||
+ owned_stream_ = std::make_unique<Reader>(data, (size_t)length);
|
||||
```
|
||||
|
||||
# Runtime fix: segfault in `parse_context::push()` due to vector reallocation
|
||||
|
||||
`parse_context_pool` stores nodes in a `std::vector<parse_context>`. During parsing, `load()` takes a `parse_context&` parameter and calls `context.push()`, which calls `pool_->make()`. If the pool's vector reallocates (via `emplace_back`), all existing references into the vector — including the `context` reference held by the caller — become dangling. Subsequent access through the dangling reference causes a segfault.
|
||||
|
||||
Triggered by larger IFC files (e.g. `ISSUE_159_kleine_Wohnung_R22.ifc`, 9.5 MB) that cause enough pool growth to trigger reallocation.
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
Thread 1 received signal SIGSEGV, Segmentation fault.
|
||||
0x... in IfcParse::parse_context::push()
|
||||
#1 in_memory_file_storage::load(...) // context& is dangling after reallocation
|
||||
#2 in_memory_file_storage::load(...) // parent call
|
||||
#3 InstanceStreamer::readInstance()
|
||||
```
|
||||
|
||||
## Fix
|
||||
|
||||
`src/ifcparse/storage.h` — change the pool container from `std::vector` to `std::deque`, which does not invalidate references on `push_back`/`emplace_back`:
|
||||
|
||||
```diff
|
||||
+#include <deque>
|
||||
|
||||
struct parse_context_pool {
|
||||
- std::vector<parse_context> nodes_;
|
||||
+ std::deque<parse_context> nodes_;
|
||||
```
|
||||
|
||||
# Runtime fix: `express::Base` comparison operators throw on null/expired instances
|
||||
|
||||
`express::Base::operator<` and `operator==` called `data()`, which throws `std::runtime_error("Trying to access deleted instance reference")` when the internal `weak_ptr` is expired. A default-constructed `express::Base` (the value-type equivalent of a null pointer) always has an expired `weak_ptr`.
|
||||
|
||||
## Why this model triggers it
|
||||
|
||||
The bug requires two conditions to coincide:
|
||||
|
||||
1. A representation is shared by **more than one product** (via `IfcRepresentationMap` / `IfcMappedItem`).
|
||||
2. At least one of those products has **no material association**, so `get_single_material_association()` returns `express::Base{}` (the null equivalent).
|
||||
|
||||
In `advanced_model.ifc`, Body representations like `#449` (Body/Brep) have a single `IfcRepresentationMap` (`#453`) with 13 `IfcMappedItem` usages, meaning 13 products share the geometry. Some of those products (e.g. `IfcFlowTerminal` instances) have no `IfcRelAssociatesMaterial`, so `get_single_material_association` returns `express::Base{}`.
|
||||
|
||||
Smaller or simpler models don't hit this because either:
|
||||
- Every representation maps to only 1 product → `reuse_ok_` short-circuits at `products.size() == 1` before reaching the material check.
|
||||
- Every product has a material association → no null `express::Base` is ever inserted into the set.
|
||||
|
||||
## Exact call sequence
|
||||
|
||||
```
|
||||
Iterator::initialize()
|
||||
try {
|
||||
mapping::get_representations(reps, filters_)
|
||||
addRepresentationsFromDefaultContexts(representations)
|
||||
→ collects reps from subcontexts in order:
|
||||
Axis (#115): 143 reps
|
||||
Body (#117): 7550 reps
|
||||
FootPrint (#119): 12 reps
|
||||
|
||||
for (auto representation : representations):
|
||||
|
||||
── Axis reps (indices 0–142) ──────────────────────────
|
||||
products_represented_by(rep, rmap)
|
||||
→ OfProductRepresentation: 1 product each
|
||||
filter_products(products, filters) → 1 product
|
||||
reuse_ok_(ifcproducts)
|
||||
→ products.size() == 1 → return true ← SHORT-CIRCUIT, no material check
|
||||
representation_mapped_to(rep) → null (no MappedItem)
|
||||
→ task created. 143 tasks accumulated.
|
||||
|
||||
── First Body rep #449 (Body/Brep) ────────────────────
|
||||
products_represented_by(#449, rmap)
|
||||
→ OfProductRepresentation: empty
|
||||
→ RepresentationMap: 1 map (#453)
|
||||
→ MapUsage: 13 MappedItems → traces through to 13 IfcProducts
|
||||
filter_products(products, filters) → 13 products
|
||||
reuse_ok_(ifcproducts) ← CRASH HERE
|
||||
→ products.size() == 1? NO (13 products)
|
||||
→ for each product:
|
||||
find_openings(product) → OK
|
||||
get_single_material_association(product)
|
||||
→ some products have no IfcRelAssociatesMaterial
|
||||
→ returns express::Base{} (expired weak_ptr)
|
||||
associated_single_materials.insert(result)
|
||||
→ std::set::insert calls operator<
|
||||
→ operator< calls data()
|
||||
→ data() calls data_.lock() → expired → THROWS
|
||||
"Trying to access deleted instance reference"
|
||||
|
||||
} catch (const std::exception& e) {
|
||||
Logger::Error(e) ← exception caught here, get_representations aborted
|
||||
}
|
||||
|
||||
→ reps contains only the 143 Axis tasks created before the throw
|
||||
→ all 143 Axis reps have Curve2D geometry → map(representation) returns null
|
||||
→ no valid elements produced → initialize() returns false
|
||||
```
|
||||
|
||||
In the old pointer-based code, `reuse_ok_` used `std::set<const IfcUtil::IfcBaseEntity*>` and `get_single_material_association` returned `nullptr`. Inserting `nullptr` into a `std::set<T*>` is a plain pointer comparison — no dereference, no throw. The refactoring to `std::set<express::Base>` changed the comparison from pointer comparison to `express::Base::operator<`, which unconditionally dereferences through `data()`.
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
[Error] Trying to access deleted instance reference
|
||||
[Notice] Created 143 tasks for 143 products ← only Axis reps; all Body reps lost
|
||||
initialize() returned: False
|
||||
```
|
||||
|
||||
## Fix
|
||||
|
||||
`src/ifcparse/express.h` — use `weak_ptr::lock().get()` instead of `data()` so that expired pointers compare as `nullptr` (matching old raw-pointer semantics):
|
||||
|
||||
```diff
|
||||
bool operator<(const Base& other) const {
|
||||
- return data() < other.data();
|
||||
+ auto a = data_.lock();
|
||||
+ auto b = other.data_.lock();
|
||||
+ return a.get() < b.get();
|
||||
}
|
||||
|
||||
bool operator==(const Base& other) const {
|
||||
- return data() == other.data();
|
||||
+ auto a = data_.lock();
|
||||
+ auto b = other.data_.lock();
|
||||
+ return a.get() == b.get();
|
||||
}
|
||||
```
|
||||
|
||||
# Runtime fix: `entity_instance` missing `get_inverse` due to SWIG `%rename` collision
|
||||
|
||||
Accessing inverse attributes (e.g. `element.IsDecomposedBy`) on any entity raises `AttributeError: entity instance of type 'IFC2X3.IfcProject' has no attribute 'get_inverse'`.
|
||||
|
||||
## Why
|
||||
|
||||
`entity_instance_mixin.__getattr__` (line 106 of `entity_instance.py`) calls `self.get_inverse(name)` when it detects an inverse attribute. Since the mixin inherits into the SWIG-generated `entity_instance` class (via the `object = custom_base` hack in `IfcParseWrapper.i:936`), `self.get_inverse` must resolve to a method on the SWIG class.
|
||||
|
||||
However, `IfcParseWrapper.i:70` has a global rename:
|
||||
|
||||
```
|
||||
%rename("get_inverses_by_declaration") get_inverse;
|
||||
```
|
||||
|
||||
This was intended for `ifcopenshell::file::get_inverse` (which takes an entity + declaration and returns instances by reference), but SWIG `%rename` is global — it also renames the `%extend express::Base` method `get_inverse(const std::string& a)` at line 551. So the Python-side `entity_instance` class exposes the method as `get_inverses_by_declaration`, not `get_inverse`.
|
||||
|
||||
The old code (`v0.8.0`) didn't hit this because `__getattr__` called `self.wrapped_data.get_inverse(name)` on an inner `ifcopenshell_wrapper.entity_instance` object — but in that old layout, the inner object was constructed differently and the rename didn't apply the same way (or the method had a different path). In the new mixin approach, `self` **is** the SWIG object, so the rename is directly visible.
|
||||
|
||||
## Fix
|
||||
|
||||
`src/ifcwrap/IfcParseWrapper.i` — override the global rename specifically for `express::Base::get_inverse`, restoring the original name on entity instances:
|
||||
|
||||
```diff
|
||||
+%rename("get_inverse") express::Base::get_inverse;
|
||||
%rename("get_inverses_by_declaration") get_inverse;
|
||||
```
|
||||
|
||||
Add this line **before** the global rename (or anywhere before the `%extend express::Base` block). This scoped rename takes precedence for `express::Base`, so:
|
||||
- `entity_instance.get_inverse(name)` works as the mixin expects
|
||||
- `file.get_inverses_by_declaration(...)` keeps its intended name
|
||||
|
||||
## Python-side workaround
|
||||
|
||||
`entity_instance.py:106` — call the method by its SWIG-renamed name:
|
||||
|
||||
```diff
|
||||
- vs = self.get_inverse(name)
|
||||
+ vs = self.get_inverses_by_declaration(name)
|
||||
```
|
||||
|
||||
# Runtime fix: `entity_instance` class no longer importable from `entity_instance` module
|
||||
|
||||
The class rename from `entity_instance` to `entity_instance_mixin` broke external code that does `from ifcopenshell.entity_instance import entity_instance`.
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
ImportError: cannot import name 'entity_instance' from 'ifcopenshell.entity_instance'
|
||||
```
|
||||
|
||||
Triggered at import time via `ifcopenshell.util.pset` (and likely other modules).
|
||||
|
||||
## Fix
|
||||
|
||||
`src/ifcopenshell-python/ifcopenshell/entity_instance.py` — add a backwards-compatible alias at the bottom of the module:
|
||||
|
||||
```python
|
||||
entity_instance = entity_instance_mixin
|
||||
```
|
||||
+164
-287
@@ -22,7 +22,7 @@
|
||||
|
||||
"""
|
||||
Example usage:
|
||||
# Build all targets by default, except BonsaiViewer (it's set explicitly).
|
||||
# Build all targets by default.
|
||||
python build-all.py
|
||||
|
||||
# Build just the provided targets.
|
||||
@@ -36,38 +36,30 @@ Available arguments:
|
||||
``-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
|
||||
``-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.
|
||||
|
||||
# This script builds IfcOpenShell and its dependencies #
|
||||
# #
|
||||
@@ -117,18 +109,24 @@ import logging
|
||||
import multiprocessing
|
||||
import os
|
||||
import platform
|
||||
import re
|
||||
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
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -136,24 +134,9 @@ 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")
|
||||
|
||||
PYTHON_VERSIONS = ["3.10.3", "3.11.8", "3.12.1", "3.13.6", "3.14.0"]
|
||||
JSON_VERSION = "3.11.3"
|
||||
@@ -165,6 +148,7 @@ PCRE_VERSION = "8.41"
|
||||
LIBXML2_VERSION = "2.13.8"
|
||||
SWIG_VERSION = "4.2.1"
|
||||
OPENCOLLADA_VERSION = "v1.6.68"
|
||||
HDF5_VERSION = "1.13.1"
|
||||
|
||||
GMP_VERSION = "6.3.0"
|
||||
MPFR_VERSION = "3.1.6" # latest is 4.1.0
|
||||
@@ -173,9 +157,6 @@ USD_VERSION = "23.05"
|
||||
TBB_VERSION = "2021.9.0"
|
||||
ROCKSDB_VERSION = "10.4.2"
|
||||
ZSTD_VERSION = "1.5.7"
|
||||
MANIFOLD_VERSION = "3.2.1"
|
||||
QT6_VERSION = os.getenv("QT6_VERSION", "6.8.3")
|
||||
|
||||
# binaries
|
||||
cp = "cp"
|
||||
bash = "bash"
|
||||
@@ -234,7 +215,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)
|
||||
@@ -242,14 +222,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
|
||||
@@ -267,6 +246,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
|
||||
@@ -334,19 +317,19 @@ 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, ...]] = {
|
||||
"IfcParse": ("boost", "libxml2", "rocksdb"),
|
||||
"IfcGeom": ("IfcParse", "occ", "manifold", "json", "cgal", "eigen", "OpenCOLLADA"),
|
||||
dependency_tree: "dict[str, tuple[str, ...]]" = {
|
||||
"IfcParse": ("boost", "libxml2", "hdf5", "rocksdb"),
|
||||
"IfcGeom": ("IfcParse", "occ", "json", "cgal", "eigen", "OpenCOLLADA"),
|
||||
"IfcConvert": ("IfcGeom",),
|
||||
"OpenCOLLADA": ("libxml2", "pcre"),
|
||||
"IfcGeomServer": ("IfcGeom",),
|
||||
"IfcOpenShell-Python": ("python", "swig", "IfcGeom"),
|
||||
"BonsaiViewer": ("IfcGeom", "qt6"),
|
||||
"swig": (),
|
||||
"boost": (),
|
||||
"libxml2": (),
|
||||
@@ -354,17 +337,16 @@ dependency_tree: dict[str, tuple[str, ...]] = {
|
||||
"occ": (),
|
||||
"pcre": (),
|
||||
"json": (),
|
||||
"hdf5": (),
|
||||
"cgal": (),
|
||||
"eigen": (),
|
||||
"rocksdb": ("zstd",),
|
||||
"zstd": (),
|
||||
"manifold": (),
|
||||
"qt6": (),
|
||||
# 'usd': ('boost', 'oneTBB')
|
||||
}
|
||||
|
||||
|
||||
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:
|
||||
@@ -394,9 +376,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"
|
||||
@@ -416,20 +395,9 @@ else:
|
||||
targets = set(dependency_tree.keys())
|
||||
|
||||
targets = set(t for t in targets if "without-%s" % t.lower() not in flags)
|
||||
if not explicit_targets and not BUILD_BONSAIVIEWER:
|
||||
targets.difference_update({"BonsaiViewer", "qt6"})
|
||||
if BUILD_BONSAIVIEWER:
|
||||
targets.update(gather_dependencies("BonsaiViewer"))
|
||||
|
||||
# Opt-out for the Python wrapper. Currently used by the bonsai CI workflow
|
||||
# on macOS, where the post-plug-in-refactor wrapper hard-links
|
||||
# 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:
|
||||
targets.discard("IfcOpenShell-Python")
|
||||
if WASM:
|
||||
SKIP_TARGETS_FOR_WASM = {
|
||||
"hdf5",
|
||||
"rocksdb",
|
||||
"opencollada",
|
||||
"swig",
|
||||
@@ -437,8 +405,6 @@ if WASM:
|
||||
"IfcGeom",
|
||||
"IfcConvert",
|
||||
"IfcGeomServer",
|
||||
"BonsaiViewer",
|
||||
"qt6",
|
||||
}
|
||||
SKIP_TARGETS_FOR_WASM = {t.lower() for t in SKIP_TARGETS_FOR_WASM}
|
||||
skip_targets = {t for t in targets if t.lower() in SKIP_TARGETS_FOR_WASM}
|
||||
@@ -452,7 +418,7 @@ 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" in flags:
|
||||
# Skip swig build for WASM.
|
||||
@@ -460,8 +426,6 @@ if "wasm" in flags:
|
||||
required_commands.append("pyodide")
|
||||
required_commands.remove(yacc)
|
||||
required_commands.remove(bison)
|
||||
if platform.system() == "Linux" and "BonsaiViewer" in targets:
|
||||
required_commands.append("patchelf")
|
||||
|
||||
for cmd in required_commands:
|
||||
if shutil.which(cmd) is None:
|
||||
@@ -496,14 +460,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
|
||||
@@ -513,7 +477,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()
|
||||
@@ -566,7 +530,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(
|
||||
@@ -592,7 +556,7 @@ def run_autoconf(dependency_name: str, configure_args: list[str], cwd: str) -> N
|
||||
)
|
||||
|
||||
|
||||
def run_cmake(arg1, cmake_args: list[str], cmake_dir: str | None = None, cwd: str | None = 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:
|
||||
@@ -614,11 +578,6 @@ def run_cmake(arg1, cmake_args: list[str], cmake_dir: str | None = None, cwd: st
|
||||
]
|
||||
)
|
||||
|
||||
if not any("BUILD_SHARED_LIBS" in f for f in cmake_args):
|
||||
cmake_flags.append(
|
||||
f"-DBUILD_SHARED_LIBS={OFF_ON[not BUILD_STATIC]}",
|
||||
)
|
||||
|
||||
run(
|
||||
[
|
||||
*wasm,
|
||||
@@ -627,13 +586,14 @@ def run_cmake(arg1, cmake_args: list[str], cmake_dir: str | None = None, cwd: st
|
||||
*cmake_flags,
|
||||
*cmake_args,
|
||||
f"-DCMAKE_BUILD_TYPE={BUILD_CFG}",
|
||||
f"-DBUILD_SHARED_LIBS={OFF_ON[not BUILD_STATIC]}",
|
||||
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
|
||||
@@ -661,18 +621,19 @@ def build_dependency(
|
||||
mode: Literal[
|
||||
"cmake",
|
||||
"autoconf",
|
||||
"ctest",
|
||||
"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,
|
||||
**kwargs,
|
||||
@@ -771,7 +732,23 @@ def build_dependency(
|
||||
if shell is not None:
|
||||
sp.run(shell, shell=True, check=True, cwd=extract_dir)
|
||||
|
||||
if mode != "bjam":
|
||||
if mode == "ctest":
|
||||
try:
|
||||
run(
|
||||
["ctest", "-S", "HDF5config.cmake,BUILD_GENERATOR=Unix", "-C", BUILD_CFG, "-V", "-O", "hdf5.log"],
|
||||
cwd=extract_dir,
|
||||
)
|
||||
except Exception as e:
|
||||
print("-" * 70)
|
||||
print(open(os.path.join(extract_dir, "hdf5.log")))
|
||||
print("-" * 70)
|
||||
raise e
|
||||
run([tar, "-xf", kwargs["ctest_result"] + ".tar.gz"], cwd=os.path.join(extract_dir, "build"))
|
||||
shutil.copytree(
|
||||
os.path.join(extract_dir, "build", kwargs["ctest_result"], kwargs["ctest_result_path"]),
|
||||
os.path.join(DEPS_DIR, "install", name),
|
||||
)
|
||||
elif mode != "bjam":
|
||||
extract_build_dir = os.path.join(extract_dir, *([cmake_dir] if cmake_dir else []), "build")
|
||||
if os.path.exists(extract_build_dir):
|
||||
shutil.rmtree(extract_build_dir)
|
||||
@@ -810,85 +787,6 @@ def build_dependency(
|
||||
shutil.rmtree(build_dir, ignore_errors=True)
|
||||
|
||||
|
||||
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.")
|
||||
|
||||
machine = platform.machine().lower()
|
||||
if machine in {"x86_64", "amd64"}:
|
||||
return "linux", "linux_gcc_64", "gcc_64"
|
||||
if machine in {"aarch64", "arm64"}:
|
||||
return "linux_arm64", "linux_gcc_arm64", "gcc_arm64"
|
||||
|
||||
raise ValueError(f"Automatic Qt6 installation is not configured for architecture '{platform.machine()}'.")
|
||||
|
||||
|
||||
def install_qt6() -> str:
|
||||
# If the caller pre-set QT_DIR (e.g. macOS CI using Homebrew-installed
|
||||
# Qt6), validate it points at a real Qt6 install and skip aqtinstall
|
||||
# entirely. The aqt download is only wired for Linux; on macOS/Windows
|
||||
# the supported flow is a pre-installed Qt6 advertised via QT_DIR.
|
||||
preset_qt_dir = os.environ.get("QT_DIR", "").strip()
|
||||
if preset_qt_dir:
|
||||
preset_qt_config = Path(preset_qt_dir) / "lib" / "cmake" / "Qt6" / "Qt6Config.cmake"
|
||||
if preset_qt_config.exists():
|
||||
logger.info(f"Using pre-set QT_DIR={preset_qt_dir}, skipping aqt install")
|
||||
return preset_qt_dir
|
||||
logger.warning(
|
||||
f"QT_DIR={preset_qt_dir} is set but {preset_qt_config} not found; " f"falling through to aqtinstall"
|
||||
)
|
||||
|
||||
host, qt_arch, install_suffix = get_qt6_aqt_config()
|
||||
qt_install_root = INSTALL_DIR / f"qt6-{QT6_VERSION}-{install_suffix}"
|
||||
qt_dir = qt_install_root / QT6_VERSION / install_suffix
|
||||
os.environ["QT_DIR"] = str(qt_dir)
|
||||
|
||||
qt_config = qt_dir / "lib" / "cmake" / "Qt6" / "Qt6Config.cmake"
|
||||
qt_core = qt_dir / "lib" / "libQt6Core.so.6"
|
||||
qt_svg = qt_dir / "lib" / "cmake" / "Qt6Svg" / "Qt6SvgConfig.cmake"
|
||||
if qt_config.exists() and qt_core.exists() and qt_svg.exists():
|
||||
logger.info(f"Found existing Qt6 at {qt_dir}, skipping")
|
||||
return str(qt_dir)
|
||||
|
||||
os.makedirs(qt_install_root, exist_ok=True)
|
||||
|
||||
try:
|
||||
import aqt # ty:ignore[unresolved-import]
|
||||
except ModuleNotFoundError:
|
||||
logger.error(
|
||||
"Could not find an existing Qt6 install, so aqtinstall is needed to fetch it automatically. "
|
||||
"Install the `aqtinstall` PyPI package or set QT_DIR."
|
||||
)
|
||||
exit(1)
|
||||
|
||||
run(
|
||||
[
|
||||
sys.executable,
|
||||
"-m",
|
||||
"aqt",
|
||||
"install-qt",
|
||||
host,
|
||||
"desktop",
|
||||
QT6_VERSION,
|
||||
qt_arch,
|
||||
"-O",
|
||||
str(qt_install_root),
|
||||
# Keep the install lean by filtering archives: qtbase provides
|
||||
# Core/Gui/Widgets (and the Qt6::CorePrivate target), qtsvg provides
|
||||
# Qt6::Svg. Both are base-Qt archives, not add-on modules.
|
||||
"--archives",
|
||||
"icu",
|
||||
"qtbase",
|
||||
"qtsvg",
|
||||
]
|
||||
)
|
||||
|
||||
if not (qt_config.exists() and qt_core.exists() and qt_svg.exists()):
|
||||
raise RuntimeError(f"Qt6 installation did not produce a usable Qt at {qt_dir}.")
|
||||
|
||||
return str(qt_dir)
|
||||
|
||||
|
||||
cecho("Collecting dependencies:", GREEN)
|
||||
|
||||
# Set compiler flags for 32bit builds on 64bit system
|
||||
@@ -942,16 +840,41 @@ os.environ["CPPFLAGS"] = CXXFLAGS
|
||||
os.environ["CFLAGS"] = CFLAGS
|
||||
os.environ["LDFLAGS"] = LDFLAGS
|
||||
|
||||
# Use colors in logs to make them more readable locally.
|
||||
# Output from compiler used by cmake.
|
||||
os.environ["CMAKE_COLOR_DIAGNOSTICS"] = "ON"
|
||||
# Output from cmake itself.
|
||||
os.environ["CLICOLOR_FORCE"] = "1"
|
||||
|
||||
# Some dependencies need a more recent CMake version than most distros provide
|
||||
# @tfk: this is no longer needed
|
||||
# build_dependency(name="cmake-%s" % (CMAKE_VERSION,), mode="autoconf", build_tool_args=[], download_url="https://cmake.org/files/v%s" % (CMAKE_VERSION_2,), download_name="cmake-%s.tar.gz" % (CMAKE_VERSION,))
|
||||
|
||||
if "hdf5" in targets:
|
||||
# not supported
|
||||
orig = [os.environ[f] for f in compiler_flags]
|
||||
for f in compiler_flags:
|
||||
os.environ[f] = re.sub(r"-flto(=\w+)?", "", os.environ[f])
|
||||
|
||||
HDF5_UNDERSCORE = "_".join(HDF5_VERSION.split("."))
|
||||
HDF5_MAJOR = ".".join(HDF5_VERSION.split(".")[:-1])
|
||||
dependency_name = f"hdf5-{HDF5_VERSION}"
|
||||
build_dependency(
|
||||
name=dependency_name,
|
||||
mode="cmake",
|
||||
build_tool_args=[
|
||||
f"-DCMAKE_INSTALL_PREFIX={DEPS_DIR}/install/{dependency_name}",
|
||||
"-DHDF5_ENABLE_Z_LIB_SUPPORT=OFF",
|
||||
"-DBUILD_TESTING=OFF",
|
||||
"-DHDF5_BUILD_TOOLS=OFF",
|
||||
"-DHDF5_BUILD_EXAMPLES=OFF",
|
||||
"-DBUILD_SHARED_LIBS=OFF",
|
||||
"-DHDF5_BUILD_UTILS=OFF",
|
||||
"-DHDF5_BUILD_CPP_LIB=ON",
|
||||
*MAC_CROSS_COMPILE_INTEL_ARGS,
|
||||
],
|
||||
download_url=f"https://github.com/HDFGroup/hdf5/archive/refs/tags/",
|
||||
download_name=f"hdf5-{HDF5_UNDERSCORE}.tar.gz",
|
||||
)
|
||||
|
||||
for f, o in zip(compiler_flags, orig):
|
||||
os.environ[f] = o
|
||||
|
||||
|
||||
if "json" in targets:
|
||||
dependency_name = f"json-{JSON_VERSION}"
|
||||
build_dependency(
|
||||
@@ -1013,8 +936,8 @@ if "swig" in targets:
|
||||
)
|
||||
|
||||
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")
|
||||
|
||||
@@ -1070,33 +993,6 @@ elif "occ" in targets:
|
||||
download_name=f"OCE-{OCE_VERSION}.tar.gz",
|
||||
)
|
||||
|
||||
if "manifold" in targets:
|
||||
dependency_name = f"manifold-{MANIFOLD_VERSION}"
|
||||
patches = []
|
||||
if WASM:
|
||||
patches.append("./patches/manifold/install-metadata-for-emscripten.patch")
|
||||
build_dependency(
|
||||
name=dependency_name,
|
||||
mode="cmake",
|
||||
build_tool_args=[
|
||||
f"-DCMAKE_INSTALL_PREFIX={DEPS_DIR}/install/{dependency_name}",
|
||||
"-DMANIFOLD_PAR=OFF",
|
||||
"-DMANIFOLD_CROSS_SECTION=OFF",
|
||||
"-DMANIFOLD_PYBIND=OFF",
|
||||
"-DMANIFOLD_JSBIND=OFF",
|
||||
"-DMANIFOLD_CBIND=OFF",
|
||||
"-DMANIFOLD_TEST=OFF",
|
||||
"-DMANIFOLD_EXPORT=OFF",
|
||||
"-DMANIFOLD_DOWNLOADS=OFF",
|
||||
*MAC_CROSS_COMPILE_INTEL_ARGS,
|
||||
],
|
||||
download_url="https://github.com/elalish/manifold.git",
|
||||
download_name="manifold",
|
||||
download_tool=download_tool_git,
|
||||
revision=f"v{MANIFOLD_VERSION}",
|
||||
patch=patches,
|
||||
)
|
||||
|
||||
if "libxml2" in targets:
|
||||
OLD_CC = ""
|
||||
if MAC_CROSS_COMPILE_INTEL:
|
||||
@@ -1128,9 +1024,9 @@ if "libxml2" in targets:
|
||||
if "OpenCOLLADA" in targets:
|
||||
patches = ["./patches/opencollada/pr622_and_disable_subdirs.patch"]
|
||||
|
||||
# OpenCOLLADAConfig.cmake.in hardcodes shared-lib targets on Unix regardless of
|
||||
# whether shared libs were actually built. We make it follow `USE_SHARED` instead.
|
||||
patches.append("./patches/opencollada/config_select_libs_by_use_shared.patch")
|
||||
# This patch allows static libraries config on Unix,
|
||||
# because the config is weird and doesn't allow non-shared libraries on Unix.
|
||||
patches.append("./patches/opencollada/allow_static_libraries_config_on_unix.patch")
|
||||
|
||||
if "wasm" in flags:
|
||||
# This is necessary for the WASM build, because recent versions of
|
||||
@@ -1151,8 +1047,6 @@ if "OpenCOLLADA" in targets:
|
||||
f"-DCMAKE_INSTALL_PREFIX={DEPS_DIR}/install/OpenCOLLADA/",
|
||||
# OpenCOLLADA is ancient at this point and allows cmake 2.6+, which results in error in cmake 4.
|
||||
f"-DCMAKE_POLICY_VERSION_MINIMUM=3.5",
|
||||
f"-DUSE_STATIC={OFF_ON[BUILD_STATIC]}",
|
||||
f"-DUSE_SHARED={OFF_ON[not BUILD_STATIC]}",
|
||||
*MAC_CROSS_COMPILE_INTEL_ARGS,
|
||||
],
|
||||
download_url="https://github.com/KhronosGroup/OpenCOLLADA.git",
|
||||
@@ -1173,7 +1067,7 @@ if "python" in targets and not USE_CURRENT_PYTHON_VERSION and "wasm" not in flag
|
||||
|
||||
# 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"]
|
||||
@@ -1259,8 +1153,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:
|
||||
@@ -1411,9 +1305,6 @@ if "rocksdb" in targets:
|
||||
revision=f"v{ROCKSDB_VERSION}",
|
||||
)
|
||||
|
||||
if "qt6" in targets:
|
||||
install_qt6()
|
||||
|
||||
cecho("Building IfcOpenShell:", GREEN)
|
||||
|
||||
IFCOS_DIR = os.path.join(DEPS_DIR, "build", "ifcopenshell")
|
||||
@@ -1421,30 +1312,29 @@ if os.environ.get("NO_CLEAN", "").lower() not in {"1", "on", "true"}:
|
||||
if os.path.exists(IFCOS_DIR):
|
||||
shutil.rmtree(IFCOS_DIR)
|
||||
os.makedirs(IFCOS_DIR, exist_ok=True)
|
||||
ifcos_build_dir = os.path.join(IFCOS_DIR, "build")
|
||||
os.makedirs(ifcos_build_dir, exist_ok=True)
|
||||
executables_dir = os.path.join(IFCOS_DIR, "executables")
|
||||
os.makedirs(executables_dir, exist_ok=True)
|
||||
|
||||
|
||||
cmake_args = [
|
||||
"-DUSE_MMAP=OFF",
|
||||
"-DBUILD_EXAMPLES=OFF",
|
||||
"-DBUILD_SHARED_LIBS=" + OFF_ON[not IFCOPENSHELL_STATIC],
|
||||
"-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)
|
||||
@@ -1481,10 +1371,6 @@ elif "occ" in targets:
|
||||
occ_library_dir = f"{DEPS_DIR}/install/oce-{OCE_VERSION}/lib"
|
||||
cmake_args.extend(["-DOCC_INCLUDE_DIR=" + occ_include_dir, "-DOCC_LIBRARY_DIR=" + occ_library_dir])
|
||||
|
||||
if "manifold" in targets:
|
||||
cmake_args_prefix_path.append(f"{DEPS_DIR}/install/manifold-{MANIFOLD_VERSION}")
|
||||
cmake_args.append("-DWITH_MANIFOLD=On")
|
||||
|
||||
if "OpenCOLLADA" in targets:
|
||||
# pcre is a dependency of OpenCOLLADA, but since we `find_package`,
|
||||
# we don't need to add it explicitly here as cmake will find it from the config.
|
||||
@@ -1499,6 +1385,11 @@ else:
|
||||
if "libxml2" in targets:
|
||||
cmake_args_prefix_path.append(f"{DEPS_DIR}/install/libxml2-{LIBXML2_VERSION}")
|
||||
|
||||
if "hdf5" in targets:
|
||||
cmake_args_prefix_path.append(f"{DEPS_DIR}/install/hdf5-{HDF5_VERSION}")
|
||||
else:
|
||||
cmake_args.append("-DHDF5_SUPPORT=Off")
|
||||
|
||||
if "usd" in targets:
|
||||
cmake_args.append("-DUSD_SUPPORT=ON")
|
||||
cmake_args_prefix_path.extend(
|
||||
@@ -1525,49 +1416,47 @@ if "rocksdb" in targets:
|
||||
if "swig" in targets:
|
||||
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']}")
|
||||
|
||||
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"-DCMAKE_INSTALL_PREFIX={DEPS_DIR}/install/ifcopenshell",
|
||||
]
|
||||
|
||||
if not WASM and (
|
||||
"BonsaiViewer" in targets
|
||||
or not explicit_targets
|
||||
or {"IfcGeom", "IfcConvert", "IfcGeomServer", "BonsaiViewer"} & set(explicit_targets)
|
||||
):
|
||||
if not WASM and (not explicit_targets or {"IfcGeom", "IfcConvert", "IfcGeomServer"} & set(explicit_targets)):
|
||||
logger.info("\rConfiguring executables...")
|
||||
|
||||
exec_args = [
|
||||
*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_IFCPYTHON=OFF",
|
||||
f"-DCMAKE_INSTALL_PREFIX={DEPS_DIR}/install/ifcopenshell",
|
||||
]
|
||||
|
||||
run_cmake("", exec_args + cmake_args + get_cmake_args_prefix_path(), cmake_dir=CMAKE_DIR, cwd=ifcos_build_dir)
|
||||
run_cmake("", exec_args + cmake_args + get_cmake_args_prefix_path(), cmake_dir=CMAKE_DIR, cwd=executables_dir)
|
||||
|
||||
logger.info("\rBuilding executables... ")
|
||||
|
||||
run([make, f"-j{IFCOS_NUM_BUILD_PROCS}", "VERBOSE=1"], cwd=ifcos_build_dir)
|
||||
run([make, "install/strip" if BUILD_CFG == "Release" else "install"], cwd=ifcos_build_dir)
|
||||
run([make, f"-j{IFCOS_NUM_BUILD_PROCS}", "VERBOSE=1"], cwd=executables_dir)
|
||||
run([make, "install/strip" if BUILD_CFG == "Release" else "install"], cwd=executables_dir)
|
||||
|
||||
if "IfcOpenShell-Python" in targets:
|
||||
wrapper_ldflags = ""
|
||||
# On OSX the actual Python library is not linked against.
|
||||
ADDITIONAL_ARGS = ""
|
||||
if platform.system() == "Darwin":
|
||||
# On OSX the actual Python library is not linked against.
|
||||
wrapper_ldflags = "-Wl,-undefined,dynamic_lookup"
|
||||
ADDITIONAL_ARGS = "-Wl,-undefined,dynamic_lookup"
|
||||
|
||||
# NOTE: We don't use `CXXFLAGS` for wrappers, so wrapper is compiled with different flags
|
||||
# (e.g. ` -fdata-sections` is missing, which is set by default for executables)
|
||||
# So cache doesn't match and running build-all.py builds most of ifcopenshell libraries twice.
|
||||
os.environ["CPPFLAGS"] = f"{CXXFLAGS_MINIMAL} {ADDITIONAL_ARGS}"
|
||||
os.environ["CXXFLAGS"] = f"{CXXFLAGS_MINIMAL} {ADDITIONAL_ARGS}"
|
||||
os.environ["CFLAGS"] = f"{CFLAGS_MINIMAL} {ADDITIONAL_ARGS}"
|
||||
os.environ["LDFLAGS"] = f"{LDFLAGS} {ADDITIONAL_ARGS}"
|
||||
|
||||
python_dir = os.path.join(IFCOS_DIR, "pythonwrapper")
|
||||
os.makedirs(python_dir, exist_ok=True)
|
||||
|
||||
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``.
|
||||
"""
|
||||
@@ -1575,6 +1464,10 @@ if "IfcOpenShell-Python" in targets:
|
||||
|
||||
logger.info(f"\rConfiguring python {python_version} wrapper...")
|
||||
|
||||
cache_path = os.path.join(python_dir, "CMakeCache.txt")
|
||||
if os.path.exists(cache_path):
|
||||
os.remove(cache_path)
|
||||
|
||||
if python_path:
|
||||
# We couldn't just prefix PATH and have to provide all variables explicitly,
|
||||
# see ifcwrap/cmake for the details.
|
||||
@@ -1588,38 +1481,27 @@ if "IfcOpenShell-Python" in targets:
|
||||
)
|
||||
|
||||
assert python_include
|
||||
old_ldflags = os.environ["LDFLAGS"]
|
||||
if wrapper_ldflags:
|
||||
os.environ["LDFLAGS"] = f"{old_ldflags} {wrapper_ldflags}"
|
||||
|
||||
try:
|
||||
run_cmake(
|
||||
"",
|
||||
ifcos_build_args
|
||||
+ [
|
||||
"-DBUILD_IFCPYTHON=ON",
|
||||
]
|
||||
+ cmake_args
|
||||
+ get_cmake_args_prefix_path()
|
||||
+ [
|
||||
*([f"-DPYTHON_EXECUTABLE={python_executable}"] if python_executable else []),
|
||||
# Needed because pyodide is expecting setup.py to be in the root.
|
||||
*([f"-DPYTHON_MODULE_INSTALL_DIR={REPO_PATH}"] * WASM),
|
||||
f"-DPYTHON_INCLUDE_DIR={python_include}",
|
||||
f"-DCMAKE_INSTALL_PREFIX={DEPS_DIR}/install/ifcopenshell/tmp",
|
||||
"-DUSERSPACE_PYTHON_PREFIX="
|
||||
+ ["Off", "On"][os.environ.get("PYTHON_USER_SITE", "").lower() in {"1", "on", "true"}],
|
||||
],
|
||||
cmake_dir=CMAKE_DIR,
|
||||
cwd=ifcos_build_dir,
|
||||
)
|
||||
finally:
|
||||
os.environ["LDFLAGS"] = old_ldflags
|
||||
run_cmake(
|
||||
"",
|
||||
cmake_args
|
||||
+ get_cmake_args_prefix_path()
|
||||
+ [
|
||||
*([f"-DPYTHON_EXECUTABLE={python_executable}"] if python_executable else []),
|
||||
# Needed because pyodide is expecting setup.py to be in the root.
|
||||
*([f"-DPYTHON_MODULE_INSTALL_DIR={REPO_PATH}"] * WASM),
|
||||
f"-DPYTHON_INCLUDE_DIR={python_include}",
|
||||
f"-DCMAKE_INSTALL_PREFIX={DEPS_DIR}/install/ifcopenshell/tmp",
|
||||
"-DUSERSPACE_PYTHON_PREFIX="
|
||||
+ ["Off", "On"][os.environ.get("PYTHON_USER_SITE", "").lower() in {"1", "on", "true"}],
|
||||
],
|
||||
cmake_dir=CMAKE_DIR,
|
||||
cwd=python_dir,
|
||||
)
|
||||
|
||||
logger.info(f"\rBuilding python {python_version} wrapper... ")
|
||||
|
||||
run([make, f"-j{IFCOS_NUM_BUILD_PROCS}", "ifcopenshell_wrapper", "VERBOSE=1"], cwd=ifcos_build_dir)
|
||||
run([make, "install/local"], cwd=os.path.join(ifcos_build_dir, "ifcwrap"))
|
||||
run([make, f"-j{IFCOS_NUM_BUILD_PROCS}", "ifcopenshell_wrapper", "VERBOSE=1"], cwd=python_dir)
|
||||
run([make, "install/local"], cwd=os.path.join(python_dir, "ifcwrap"))
|
||||
|
||||
if python_executable:
|
||||
run([python_executable, "-m", "ensurepip"])
|
||||
@@ -1634,14 +1516,12 @@ 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" 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?
|
||||
run([strip, "-s", "-K", "PyInit__ifcopenshell_wrapper", so], cwd=module_dir)
|
||||
else:
|
||||
run([strip, "--strip-unneeded", so], cwd=module_dir)
|
||||
# TODO: This symbol name depends on the Python version?
|
||||
so = glob.glob(os.path.join(module_dir, "_ifcopenshell_wrapper*.so"))[0]
|
||||
if "wasm" in flags:
|
||||
run(["wasm-strip", so, "-k", "dylink.0"])
|
||||
else:
|
||||
run([strip, "-s", "-K", "PyInit__ifcopenshell_wrapper", so], cwd=module_dir)
|
||||
|
||||
return module_dir
|
||||
|
||||
@@ -1668,9 +1548,6 @@ if "IfcOpenShell-Python" in targets:
|
||||
# cp: /Users/runner/work/IfcOpenShell/IfcOpenShell/build/Darwin/x86_64/10.15/install/ifcopenshell/python-3.9.11: No such file or directory
|
||||
# D'oh this was just due to a missing f-string f but doesn't hurt to keep it in.
|
||||
run(["mkdir", "-p", os.path.join(DEPS_DIR, "install", "ifcopenshell")])
|
||||
dest = os.path.join(DEPS_DIR, "install", "ifcopenshell", f"python-{python_version}")
|
||||
if os.path.exists(dest):
|
||||
shutil.rmtree(dest)
|
||||
run([cp, "-R", module_dir, dest])
|
||||
run([cp, "-R", module_dir, os.path.join(DEPS_DIR, "install", "ifcopenshell", f"python-{python_version}")])
|
||||
|
||||
logger.info("\rBuilt IfcOpenShell...\n\n")
|
||||
|
||||
@@ -68,6 +68,7 @@ def unpack_dependencies(install_dir: Path) -> None:
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
action = None
|
||||
if len(sys.argv) != 2 or (action := sys.argv[1].lower()) not in ("pack", "unpack"):
|
||||
print(__doc__)
|
||||
sys.exit(1)
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
# This file was generated with the assistance of an AI coding tool.
|
||||
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||
index 42e403b..764562f 100644
|
||||
--- a/CMakeLists.txt
|
||||
+++ b/CMakeLists.txt
|
||||
@@ -249,11 +249,6 @@ set_source_files_properties(
|
||||
PROPERTIES GENERATED TRUE
|
||||
)
|
||||
|
||||
-# If it's an EMSCRIPTEN build, we're done
|
||||
-if(EMSCRIPTEN)
|
||||
- return()
|
||||
-endif()
|
||||
-
|
||||
# CMake exports
|
||||
configure_file(
|
||||
cmake/manifoldConfig.cmake.in
|
||||
+1
-1
@@ -7,7 +7,7 @@ index d765b108..6800c2a7 100644
|
||||
|
||||
# Set the library variable
|
||||
-if(UNIX)
|
||||
+if(@USE_SHARED@)
|
||||
+if(0)
|
||||
set(OPENCOLLADA_LIBRARIES
|
||||
ftoa_shared
|
||||
buffer_shared
|
||||
@@ -34,6 +34,7 @@ environments:
|
||||
- conda: https://conda.anaconda.org/conda-forge/linux-64/gxx-13.3.0-h9576a4e_1.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_impl_linux-64-13.3.0-hdbfa832_1.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_linux-64-13.3.0-h6834431_7.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.14.4-nompi_h2d575fe_105.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/linux-64/icu-75.1-he02047a_0.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/linux-64/imath-3.1.12-h7955e40_0.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/linux-64/jxrlib-1.1-hd590300_3.conda
|
||||
@@ -148,6 +149,7 @@ environments:
|
||||
- conda: https://conda.anaconda.org/conda-forge/osx-64/freeimage-3.18.0-h7cd8ba8_22.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/osx-64/freetype-2.12.1-h60636b9_2.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/osx-64/gmp-6.3.0-hf036a51_2.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/osx-64/hdf5-1.14.4-nompi_h1607680_105.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/osx-64/icu-75.1-h120a0e1_0.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/osx-64/imath-3.1.12-h2016aa1_0.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/osx-64/jxrlib-1.1-h10d778d_3.conda
|
||||
@@ -241,6 +243,7 @@ environments:
|
||||
- conda: https://conda.anaconda.org/conda-forge/win-64/freeimage-3.18.0-h8310ca0_22.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/win-64/freetype-2.12.1-hdaf720e_2.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/win-64/gmp-6.3.0-hfeafd45_2.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/win-64/hdf5-1.14.4-nompi_hd5d9e70_105.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/win-64/imath-3.1.12-hbb528cf_0.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/win-64/intel-openmp-2024.2.1-h57928b3_1083.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/win-64/jxrlib-1.1-hcfcfb64_3.conda
|
||||
@@ -346,6 +349,7 @@ environments:
|
||||
- conda: https://conda.anaconda.org/conda-forge/linux-64/gxx-13.3.0-h9576a4e_1.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_impl_linux-64-13.3.0-hdbfa832_1.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_linux-64-13.3.0-h6834431_7.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.14.4-nompi_h2d575fe_105.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/linux-64/icu-75.1-he02047a_0.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/linux-64/imath-3.1.12-h7955e40_0.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/linux-64/jxrlib-1.1-hd590300_3.conda
|
||||
@@ -460,6 +464,7 @@ environments:
|
||||
- conda: https://conda.anaconda.org/conda-forge/osx-64/freeimage-3.18.0-h7cd8ba8_22.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/osx-64/freetype-2.12.1-h60636b9_2.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/osx-64/gmp-6.3.0-hf036a51_2.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/osx-64/hdf5-1.14.4-nompi_h1607680_105.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/osx-64/icu-75.1-h120a0e1_0.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/osx-64/imath-3.1.12-h2016aa1_0.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/osx-64/jxrlib-1.1-h10d778d_3.conda
|
||||
@@ -553,6 +558,7 @@ environments:
|
||||
- conda: https://conda.anaconda.org/conda-forge/win-64/freeimage-3.18.0-h8310ca0_22.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/win-64/freetype-2.12.1-hdaf720e_2.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/win-64/gmp-6.3.0-hfeafd45_2.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/win-64/hdf5-1.14.4-nompi_hd5d9e70_105.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/win-64/imath-3.1.12-hbb528cf_0.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/win-64/intel-openmp-2024.2.1-h57928b3_1083.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/win-64/jxrlib-1.1-hcfcfb64_3.conda
|
||||
@@ -737,6 +743,7 @@ environments:
|
||||
- conda: https://conda.anaconda.org/conda-forge/linux-64/gxx-13.3.0-h9576a4e_1.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_impl_linux-64-13.3.0-hdbfa832_1.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_linux-64-13.3.0-h6834431_7.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.14.4-nompi_h2d575fe_105.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/linux-64/icu-75.1-he02047a_0.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/linux-64/imath-3.1.12-h7955e40_0.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/linux-64/jxrlib-1.1-hd590300_3.conda
|
||||
@@ -851,6 +858,7 @@ environments:
|
||||
- conda: https://conda.anaconda.org/conda-forge/osx-64/freeimage-3.18.0-h7cd8ba8_22.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/osx-64/freetype-2.12.1-h60636b9_2.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/osx-64/gmp-6.3.0-hf036a51_2.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/osx-64/hdf5-1.14.4-nompi_h1607680_105.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/osx-64/icu-75.1-h120a0e1_0.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/osx-64/imath-3.1.12-h2016aa1_0.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/osx-64/jxrlib-1.1-h10d778d_3.conda
|
||||
@@ -944,6 +952,7 @@ environments:
|
||||
- conda: https://conda.anaconda.org/conda-forge/win-64/freeimage-3.18.0-h8310ca0_22.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/win-64/freetype-2.12.1-hdaf720e_2.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/win-64/gmp-6.3.0-hfeafd45_2.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/win-64/hdf5-1.14.4-nompi_hd5d9e70_105.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/win-64/imath-3.1.12-hbb528cf_0.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/win-64/intel-openmp-2024.2.1-h57928b3_1083.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/win-64/jxrlib-1.1-hcfcfb64_3.conda
|
||||
@@ -1059,6 +1068,7 @@ environments:
|
||||
- conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_impl_linux-64-14.3.0-he663afc_4.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_linux-64-14.3.0-ha7acb78_11.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.14.6-nompi_h6e4c0c1_103.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/linux-64/icu-75.1-he02047a_0.conda
|
||||
@@ -1229,6 +1239,7 @@ environments:
|
||||
- conda: https://conda.anaconda.org/conda-forge/osx-64/geos-3.13.1-h502464c_0.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/osx-64/gmp-6.3.0-hf036a51_2.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/osx-64/hdf5-1.14.6-nompi_hc8237f9_103.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/osx-64/icu-75.1-h120a0e1_0.conda
|
||||
@@ -1369,6 +1380,7 @@ environments:
|
||||
- conda: https://conda.anaconda.org/conda-forge/win-64/geos-3.13.1-h9ea8674_0.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/win-64/gmp-6.3.0-hfeafd45_2.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/win-64/hdf5-1.14.6-nompi_he30205f_103.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.10-pyhd8ed1ab_1.conda
|
||||
@@ -2978,6 +2990,106 @@ packages:
|
||||
- pkg:pypi/h2?source=compressed-mapping
|
||||
size: 95967
|
||||
timestamp: 1756364871835
|
||||
- conda: https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.14.4-nompi_h2d575fe_105.conda
|
||||
sha256: 93d2bfc672f3ee0988d277ce463330a467f3686d3f7ee37812a3d8ca11776d77
|
||||
md5: d76fff0092b6389a12134ddebc0929bd
|
||||
depends:
|
||||
- __glibc >=2.17,<3.0.a0
|
||||
- libaec >=1.1.3,<2.0a0
|
||||
- libcurl >=8.10.1,<9.0a0
|
||||
- libgcc >=13
|
||||
- libgfortran
|
||||
- libgfortran5 >=13.3.0
|
||||
- libstdcxx >=13
|
||||
- libzlib >=1.3.1,<2.0a0
|
||||
- openssl >=3.4.0,<4.0a0
|
||||
license: BSD-3-Clause
|
||||
license_family: BSD
|
||||
size: 3950601
|
||||
timestamp: 1733003331788
|
||||
- conda: https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.14.6-nompi_h6e4c0c1_103.conda
|
||||
sha256: 4f173af9e2299de7eee1af3d79e851bca28ee71e7426b377e841648b51d48614
|
||||
md5: c74d83614aec66227ae5199d98852aaf
|
||||
depends:
|
||||
- __glibc >=2.17,<3.0.a0
|
||||
- libaec >=1.1.4,<2.0a0
|
||||
- libcurl >=8.14.1,<9.0a0
|
||||
- libgcc >=14
|
||||
- libgfortran
|
||||
- libgfortran5 >=14.3.0
|
||||
- libstdcxx >=14
|
||||
- libzlib >=1.3.1,<2.0a0
|
||||
- openssl >=3.5.1,<4.0a0
|
||||
license: BSD-3-Clause
|
||||
license_family: BSD
|
||||
purls: []
|
||||
size: 3710057
|
||||
timestamp: 1753357500665
|
||||
- conda: https://conda.anaconda.org/conda-forge/osx-64/hdf5-1.14.4-nompi_h1607680_105.conda
|
||||
sha256: 56500937894b1ca917e1ae1bea64b873a9eec57d581173579189d0b1f590db26
|
||||
md5: 12ebafc40b10d4bf519e4c2074c52aef
|
||||
depends:
|
||||
- __osx >=10.13
|
||||
- libaec >=1.1.3,<2.0a0
|
||||
- libcurl >=8.10.1,<9.0a0
|
||||
- libcxx >=18
|
||||
- libgfortran 5.*
|
||||
- libgfortran5 >=13.2.0
|
||||
- libzlib >=1.3.1,<2.0a0
|
||||
- openssl >=3.4.0,<4.0a0
|
||||
license: BSD-3-Clause
|
||||
license_family: BSD
|
||||
size: 3732340
|
||||
timestamp: 1733003702265
|
||||
- conda: https://conda.anaconda.org/conda-forge/osx-64/hdf5-1.14.6-nompi_hc8237f9_103.conda
|
||||
sha256: e41d22f672b1fbe713d22cf69630abffaee68bdb38a500a708fc70e6f639357f
|
||||
md5: 3f1df98f96e0c369d94232712c9b87d0
|
||||
depends:
|
||||
- __osx >=10.13
|
||||
- libaec >=1.1.4,<2.0a0
|
||||
- libcurl >=8.14.1,<9.0a0
|
||||
- libcxx >=19
|
||||
- libgfortran
|
||||
- libgfortran5 >=14.3.0
|
||||
- libgfortran5 >=15.1.0
|
||||
- libzlib >=1.3.1,<2.0a0
|
||||
- openssl >=3.5.1,<4.0a0
|
||||
license: BSD-3-Clause
|
||||
license_family: BSD
|
||||
purls: []
|
||||
size: 3522832
|
||||
timestamp: 1753358062940
|
||||
- conda: https://conda.anaconda.org/conda-forge/win-64/hdf5-1.14.4-nompi_hd5d9e70_105.conda
|
||||
sha256: e8ced65c604a3b9e4803758a25149d71d8096f186fe876817a0d1d97190550c0
|
||||
md5: 4381be33460283890c34341ecfa42d97
|
||||
depends:
|
||||
- libaec >=1.1.3,<2.0a0
|
||||
- libcurl >=8.10.1,<9.0a0
|
||||
- libzlib >=1.3.1,<2.0a0
|
||||
- openssl >=3.4.0,<4.0a0
|
||||
- ucrt >=10.0.20348.0
|
||||
- vc >=14.2,<15
|
||||
- vc14_runtime >=14.29.30139
|
||||
license: BSD-3-Clause
|
||||
license_family: BSD
|
||||
size: 2048450
|
||||
timestamp: 1733003052575
|
||||
- conda: https://conda.anaconda.org/conda-forge/win-64/hdf5-1.14.6-nompi_he30205f_103.conda
|
||||
sha256: 0a90263b97e9860cec6c2540160ff1a1fff2a609b3d96452f8716ae63489dac5
|
||||
md5: f1f7aaf642cefd2190582550eaca4658
|
||||
depends:
|
||||
- libaec >=1.1.4,<2.0a0
|
||||
- libcurl >=8.14.1,<9.0a0
|
||||
- libzlib >=1.3.1,<2.0a0
|
||||
- openssl >=3.5.1,<4.0a0
|
||||
- ucrt >=10.0.20348.0
|
||||
- vc >=14.3,<15
|
||||
- vc14_runtime >=14.44.35208
|
||||
license: BSD-3-Clause
|
||||
license_family: BSD
|
||||
purls: []
|
||||
size: 2031491
|
||||
timestamp: 1753357255237
|
||||
- conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda
|
||||
sha256: 6ad78a180576c706aabeb5b4c8ceb97c0cb25f1e112d76495bff23e3779948ba
|
||||
md5: 0a802cb9888dd14eeefc611f05c40b6e
|
||||
|
||||
@@ -31,6 +31,7 @@ occt = { version = "*", build = "*novtk*" }
|
||||
cgal-cpp = "*"
|
||||
numpy = "*"
|
||||
lark = "*"
|
||||
hdf5 = "*"
|
||||
eigen = "*"
|
||||
mpfr = "*"
|
||||
gmp = "*"
|
||||
|
||||
@@ -30,8 +30,3 @@ since it's pure cmake without any additional moving parts.
|
||||
- it will produce Python package in `IfcOpenShell/ifcopenshell`
|
||||
- run `pyodide build`
|
||||
- it will produce a wheel in `IfcOpenShell/dist`
|
||||
- testing:
|
||||
- ensure you're in pyodide environment
|
||||
- `cd IfcOpenshell/pyodide`
|
||||
- `./run_pytest.py setup`
|
||||
- `./run_pytest.py run`
|
||||
|
||||
@@ -22,8 +22,7 @@ uv run pyodide xbuildenv install "${PYODIDE_VERSION}"
|
||||
uv run pyodide xbuildenv install-emscripten
|
||||
|
||||
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"
|
||||
source "${EMSDK_ROOT}/emsdk_env.sh"
|
||||
which emcc
|
||||
emcc --version
|
||||
|
||||
@@ -36,12 +35,6 @@ sed -i s/0.8.0/$VERSION/g packages/ifcopenshell/meta.yaml
|
||||
# Otherwise pyodide build path typically includes package version, so cached cmake configs might break.
|
||||
export BUILD_DIR=`readlink -f ifcopenshell_build`
|
||||
|
||||
# Sat, 25 Apr 2026 12:11:39 GMT 2026-04-25 12:11:39,173 - DEBUG - running
|
||||
# command `make -j5 ifcopenshell_wrapper VERBOSE=1` in directory
|
||||
# '/home/runner/work/IfcOpenShell/IfcOpenShell/ifcopenshell_build/Linux/wasm/build/ifcopenshell/build'
|
||||
# Sat, 25 Apr 2026 12:18:01 GMT Error: Process completed with exit code 143.
|
||||
export IFCOS_NUM_BUILD_PROCS=1
|
||||
|
||||
# Use build-recipes-no-deps first, so logs would be printed to stdout.
|
||||
pyodide build-recipes-no-deps ifcopenshell
|
||||
pyodide build-recipes ifcopenshell --install
|
||||
|
||||
@@ -1,143 +0,0 @@
|
||||
#!/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.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import re
|
||||
import tempfile
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
SCHEMA_ORDER = {
|
||||
"ifc2x3": 0,
|
||||
"ifc4": 1,
|
||||
"ifc4x1": 2,
|
||||
"ifc4x2": 3,
|
||||
"ifc4x3": 4,
|
||||
"ifc4x3_add1": 5,
|
||||
"ifc4x3_add2": 6,
|
||||
}
|
||||
|
||||
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\.[^.]+\.([^.]+)\.so$")
|
||||
GEOMETRY_SERIALIZATION_PLUGIN_RE = re.compile(r"^ifcopenshell\.geometry\.serialization\.([^.]+)\.so$")
|
||||
|
||||
|
||||
def schema_key(schema: str) -> tuple[int, str]:
|
||||
schema = schema.lower()
|
||||
return SCHEMA_ORDER.get(schema, len(SCHEMA_ORDER)), schema
|
||||
|
||||
|
||||
def shared_object_sort_key(filename: str, index: int) -> tuple[int, tuple[int, str], str, int]:
|
||||
basename = Path(filename).name
|
||||
if MAIN_SHARED_OBJECT_RE.match(basename):
|
||||
return 0, schema_key(""), basename, index
|
||||
|
||||
if match := SCHEMA_PLUGIN_RE.match(basename):
|
||||
return 1, schema_key(match.group(1)), basename, index
|
||||
|
||||
if match := MAPPING_PLUGIN_RE.match(basename):
|
||||
return 2, schema_key(match.group(1)), basename, index
|
||||
|
||||
if match := DOCUMENT_PLUGIN_RE.match(basename):
|
||||
return 3, schema_key(match.group(1)), basename, index
|
||||
|
||||
if match := GEOMETRY_SERIALIZATION_PLUGIN_RE.match(basename):
|
||||
return 4, schema_key(match.group(1)), basename, index
|
||||
|
||||
return 5, schema_key(""), basename, index
|
||||
|
||||
|
||||
def ordered_infos(infos: list[zipfile.ZipInfo]) -> list[zipfile.ZipInfo]:
|
||||
shared_infos = [(index, info) for index, info in enumerate(infos) if info.filename.endswith(".so")]
|
||||
ordered_shared_infos = [
|
||||
info for index, info in sorted(shared_infos, key=lambda item: shared_object_sort_key(item[1].filename, item[0]))
|
||||
]
|
||||
ordered_shared_iter = iter(ordered_shared_infos)
|
||||
return [next(ordered_shared_iter) if info.filename.endswith(".so") else info for info in infos]
|
||||
|
||||
|
||||
def zip_info_for_write(source: zipfile.ZipInfo) -> zipfile.ZipInfo:
|
||||
info = zipfile.ZipInfo(source.filename)
|
||||
info.date_time = source.date_time
|
||||
info.compress_type = source.compress_type
|
||||
info.comment = source.comment
|
||||
info.create_system = source.create_system
|
||||
info.external_attr = source.external_attr
|
||||
info.extra = source.extra
|
||||
return info
|
||||
|
||||
|
||||
def shared_object_names(infos: list[zipfile.ZipInfo]) -> list[str]:
|
||||
return [info.filename for info in infos if info.filename.endswith(".so")]
|
||||
|
||||
|
||||
def rewrite_wheel(wheel: Path, ordered: list[zipfile.ZipInfo]) -> None:
|
||||
fd, temp_name = tempfile.mkstemp(prefix=f".{wheel.name}.", suffix=".tmp", dir=wheel.parent)
|
||||
os.close(fd)
|
||||
temp_path = Path(temp_name)
|
||||
try:
|
||||
with zipfile.ZipFile(wheel) as zin, zipfile.ZipFile(
|
||||
temp_path, "w", compression=zipfile.ZIP_DEFLATED, compresslevel=9
|
||||
) as zout:
|
||||
for info in ordered:
|
||||
zout.writestr(zip_info_for_write(info), zin.read(info))
|
||||
os.replace(temp_path, wheel)
|
||||
finally:
|
||||
if temp_path.exists():
|
||||
temp_path.unlink()
|
||||
|
||||
|
||||
def order_wheel(wheel: Path, check: bool) -> bool:
|
||||
wheel = wheel.resolve()
|
||||
if wheel.suffix != ".whl":
|
||||
raise ValueError(f"not a wheel: {wheel}")
|
||||
|
||||
with zipfile.ZipFile(wheel) as zf:
|
||||
infos = zf.infolist()
|
||||
|
||||
ordered = ordered_infos(infos)
|
||||
changed = shared_object_names(infos) != shared_object_names(ordered)
|
||||
if check:
|
||||
if changed:
|
||||
print(f"{wheel}: shared object order needs updating")
|
||||
return False
|
||||
print(f"{wheel}: shared object order is already valid")
|
||||
return True
|
||||
|
||||
if changed:
|
||||
rewrite_wheel(wheel, ordered)
|
||||
print(f"{wheel}: reordered shared objects")
|
||||
else:
|
||||
print(f"{wheel}: shared object order is already valid")
|
||||
return True
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("wheel", type=Path, help="Wheel to rewrite in place")
|
||||
parser.add_argument("--check", action="store_true", help="Only validate the current shared object order")
|
||||
args = parser.parse_args()
|
||||
|
||||
return 0 if order_wheel(args.wheel, args.check) else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -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,311 +0,0 @@
|
||||
#!/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`
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import base64
|
||||
import csv
|
||||
import hashlib
|
||||
import io
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
import zipfile
|
||||
from email.parser import Parser
|
||||
from pathlib import Path
|
||||
|
||||
MAIN_SHARED_OBJECT_RE = re.compile(r"(^|/)_ifcopenshell_wrapper(?:\.|$)")
|
||||
PURE_PYTHON_PACKAGE_NAME = "ifcopenshell-pure-python"
|
||||
PURE_PYTHON_PREFIXES = (
|
||||
"ifcopenshell/api/",
|
||||
"ifcopenshell/express/",
|
||||
"ifcopenshell/mvd/",
|
||||
"ifcopenshell/simple_spf/",
|
||||
)
|
||||
|
||||
|
||||
def wheel_parts(path: Path) -> tuple[str, str, str, str, str]:
|
||||
if path.suffix != ".whl":
|
||||
raise ValueError(f"not a wheel: {path}")
|
||||
stem = path.name[:-4]
|
||||
left, py_tag, abi_tag, platform_tag = stem.rsplit("-", 3)
|
||||
dist, version = left.rsplit("-", 1)
|
||||
return dist, version, py_tag, abi_tag, platform_tag
|
||||
|
||||
|
||||
def safe_name(name: str) -> str:
|
||||
return re.sub(r"[-_.]+", "-", name).lower().strip("-")
|
||||
|
||||
|
||||
def wheel_escape(value: str) -> str:
|
||||
return re.sub(r"[^\w\d.]+", "_", value, flags=re.UNICODE)
|
||||
|
||||
|
||||
def wheel_version_escape(value: str) -> str:
|
||||
return re.sub(r"[^\w\d.+]+", "_", value, flags=re.UNICODE)
|
||||
|
||||
|
||||
def dist_info_dir(name: str, version: str) -> str:
|
||||
return f"{wheel_escape(name)}-{wheel_version_escape(version)}.dist-info"
|
||||
|
||||
|
||||
def sha256_record_value(data: bytes) -> str:
|
||||
digest = hashlib.sha256(data).digest()
|
||||
return "sha256=" + base64.urlsafe_b64encode(digest).decode("ascii").rstrip("=")
|
||||
|
||||
|
||||
def make_info(name: str, *, source: zipfile.ZipInfo | None = None, mode: int | None = None) -> zipfile.ZipInfo:
|
||||
info = zipfile.ZipInfo(name)
|
||||
if source is not None:
|
||||
info.date_time = source.date_time
|
||||
info.external_attr = source.external_attr
|
||||
info.comment = source.comment
|
||||
info.create_system = source.create_system
|
||||
else:
|
||||
info.date_time = time.localtime(time.time())[:6]
|
||||
info.external_attr = ((mode if mode is not None else 0o644) & 0xFFFF) << 16
|
||||
info.create_system = 3
|
||||
info.compress_type = zipfile.ZIP_DEFLATED
|
||||
return info
|
||||
|
||||
|
||||
def write_record(zf: zipfile.ZipFile, entries: dict[str, bytes | None], record_name: str) -> None:
|
||||
rows: list[list[str]] = []
|
||||
for name in sorted(entries):
|
||||
data = entries[name]
|
||||
if name == record_name:
|
||||
rows.append([name, "", ""])
|
||||
elif data is None:
|
||||
raise ValueError(f"missing bytes for RECORD entry {name}")
|
||||
else:
|
||||
rows.append([name, sha256_record_value(data), str(len(data))])
|
||||
|
||||
buf = io.StringIO(newline="")
|
||||
writer = csv.writer(buf, lineterminator="\n")
|
||||
writer.writerows(rows)
|
||||
zf.writestr(make_info(record_name), buf.getvalue().encode("utf-8"))
|
||||
|
||||
|
||||
def read_original_metadata(zf: zipfile.ZipFile) -> tuple[str, str, str]:
|
||||
metadata_names = [n for n in zf.namelist() if n.endswith(".dist-info/METADATA")]
|
||||
wheel_names = [n for n in zf.namelist() if n.endswith(".dist-info/WHEEL")]
|
||||
record_names = [n for n in zf.namelist() if n.endswith(".dist-info/RECORD")]
|
||||
if len(metadata_names) != 1 or len(wheel_names) != 1 or len(record_names) != 1:
|
||||
raise ValueError("expected exactly one METADATA, WHEEL, and RECORD in the source wheel")
|
||||
return metadata_names[0], wheel_names[0], record_names[0]
|
||||
|
||||
|
||||
def shared_package_name(so_path: str) -> str:
|
||||
stem = Path(so_path).name.removesuffix(".so")
|
||||
stem = re.sub(r"[^A-Za-z0-9]+", "-", stem).strip("-")
|
||||
return safe_name(stem)
|
||||
|
||||
|
||||
def is_pure_python_split_path(path: str) -> bool:
|
||||
return any(path.startswith(prefix) for prefix in PURE_PYTHON_PREFIXES)
|
||||
|
||||
|
||||
def build_wheel(
|
||||
output_dir: Path,
|
||||
package_name: str,
|
||||
version: str,
|
||||
tag: str,
|
||||
root_is_purelib: bool,
|
||||
summary: str,
|
||||
payloads: list[tuple[zipfile.ZipInfo, bytes]],
|
||||
license_files: dict[str, bytes],
|
||||
) -> Path:
|
||||
di = dist_info_dir(package_name, version)
|
||||
wheel_name = f"{wheel_escape(package_name)}-{wheel_version_escape(version)}-{tag}.whl"
|
||||
out = output_dir / wheel_name
|
||||
record_name = f"{di}/RECORD"
|
||||
entries: dict[str, bytes | None] = {}
|
||||
|
||||
metadata = (
|
||||
"Metadata-Version: 2.4\n"
|
||||
f"Name: {package_name}\n"
|
||||
f"Version: {version}\n"
|
||||
f"Summary: {summary}\n"
|
||||
"License-File: COPYING\n"
|
||||
"License-File: COPYING.LESSER\n"
|
||||
"\n"
|
||||
).encode()
|
||||
wheel = (
|
||||
"Wheel-Version: 1.0\n"
|
||||
"Generator: split_pyodide_ifcopenshell_wheel.py\n"
|
||||
f"Root-Is-Purelib: {str(root_is_purelib).lower()}\n"
|
||||
f"Tag: {tag}\n"
|
||||
"\n"
|
||||
).encode()
|
||||
|
||||
with zipfile.ZipFile(out, "w", compression=zipfile.ZIP_DEFLATED, compresslevel=9) as zf:
|
||||
for info, data in payloads:
|
||||
zf.writestr(make_info(info.filename, source=info), data)
|
||||
entries[info.filename] = data
|
||||
|
||||
metadata_name = f"{di}/METADATA"
|
||||
wheel_meta_name = f"{di}/WHEEL"
|
||||
zf.writestr(make_info(metadata_name), metadata)
|
||||
zf.writestr(make_info(wheel_meta_name), wheel)
|
||||
entries[metadata_name] = metadata
|
||||
entries[wheel_meta_name] = wheel
|
||||
|
||||
for basename, data in license_files.items():
|
||||
name = f"{di}/licenses/{basename}"
|
||||
zf.writestr(make_info(name), data)
|
||||
entries[name] = data
|
||||
|
||||
entries[record_name] = None
|
||||
write_record(zf, entries, record_name)
|
||||
print(f"Splitting wheel to '{out}'.")
|
||||
return out
|
||||
|
||||
|
||||
def rewrite_main_wheel(source: Path, target: Path, split_paths: set[str]) -> None:
|
||||
with zipfile.ZipFile(source) as zin, zipfile.ZipFile(
|
||||
target, "w", compression=zipfile.ZIP_DEFLATED, compresslevel=9
|
||||
) as zout:
|
||||
_, _, record_name = read_original_metadata(zin)
|
||||
entries: dict[str, bytes | None] = {}
|
||||
for info in zin.infolist():
|
||||
if info.filename in split_paths or info.filename == record_name:
|
||||
continue
|
||||
data = zin.read(info.filename)
|
||||
zout.writestr(make_info(info.filename, source=info), data)
|
||||
entries[info.filename] = data
|
||||
entries[record_name] = None
|
||||
write_record(zout, entries, record_name)
|
||||
|
||||
|
||||
def verify_wheel(path: Path) -> None:
|
||||
with zipfile.ZipFile(path) as zf:
|
||||
zf.testzip()
|
||||
metadata_name, wheel_name, record_name = read_original_metadata(zf)
|
||||
Parser().parsestr(zf.read(metadata_name).decode("utf-8"))
|
||||
wheel_text = zf.read(wheel_name).decode("utf-8")
|
||||
if "Wheel-Version:" not in wheel_text or "Tag:" not in wheel_text:
|
||||
raise ValueError(f"invalid WHEEL metadata in {path}")
|
||||
|
||||
record_rows = list(csv.reader(io.StringIO(zf.read(record_name).decode("utf-8"))))
|
||||
names = {row[0] for row in record_rows}
|
||||
missing = set(zf.namelist()) - names
|
||||
if missing:
|
||||
raise ValueError(f"{path} RECORD is missing entries: {sorted(missing)[:5]}")
|
||||
for name, digest, size in record_rows:
|
||||
if name == record_name:
|
||||
continue
|
||||
data = zf.read(name)
|
||||
if digest != sha256_record_value(data) or size != str(len(data)):
|
||||
raise ValueError(f"{path} RECORD mismatch for {name}")
|
||||
|
||||
|
||||
def split_wheel(wheel_path: Path, output_dir: Path) -> None:
|
||||
wheel_path = wheel_path.expanduser().resolve()
|
||||
if not wheel_path.exists():
|
||||
raise FileNotFoundError(wheel_path)
|
||||
|
||||
output_dir = output_dir.expanduser().resolve()
|
||||
output_dir.mkdir(parents=True, exist_ok=True)
|
||||
main_wheel_path = output_dir / wheel_path.name
|
||||
if main_wheel_path.resolve(strict=False) == wheel_path:
|
||||
raise ValueError("output directory must not point to the input wheel location")
|
||||
|
||||
_, version, py_tag, abi_tag, platform_tag = wheel_parts(wheel_path)
|
||||
binary_tag = f"{py_tag}-{abi_tag}-{platform_tag}"
|
||||
pure_tag = "py3-none-any"
|
||||
|
||||
with zipfile.ZipFile(wheel_path) as zf:
|
||||
file_infos = [info for info in zf.infolist() if not info.is_dir()]
|
||||
so_infos = [info for info in file_infos if info.filename.endswith(".so")]
|
||||
split_so_infos = [info for info in so_infos if not MAIN_SHARED_OBJECT_RE.search(Path(info.filename).name)]
|
||||
pure_python_infos = [info for info in file_infos if is_pure_python_split_path(info.filename)]
|
||||
if not split_so_infos and not pure_python_infos:
|
||||
raise RuntimeError("no secondary .so files or pure Python subpackages found to split")
|
||||
|
||||
license_files = {
|
||||
Path(info.filename).name: zf.read(info.filename)
|
||||
for info in file_infos
|
||||
if ".dist-info/licenses/" in info.filename
|
||||
}
|
||||
split_so_payloads = [(info, zf.read(info.filename)) for info in split_so_infos]
|
||||
pure_python_payloads = [(info, zf.read(info.filename)) for info in pure_python_infos]
|
||||
|
||||
created_wheels: list[Path] = []
|
||||
for info, data in split_so_payloads:
|
||||
package_name = shared_package_name(info.filename)
|
||||
created_wheels.append(
|
||||
build_wheel(
|
||||
output_dir,
|
||||
package_name,
|
||||
version,
|
||||
binary_tag,
|
||||
False,
|
||||
f"Pyodide shared library split from IfcOpenShell ({Path(info.filename).name}).",
|
||||
[(info, data)],
|
||||
license_files,
|
||||
)
|
||||
)
|
||||
|
||||
if pure_python_payloads:
|
||||
created_wheels.append(
|
||||
build_wheel(
|
||||
output_dir,
|
||||
PURE_PYTHON_PACKAGE_NAME,
|
||||
version,
|
||||
pure_tag,
|
||||
True,
|
||||
"Pure Python subpackages split from IfcOpenShell.",
|
||||
pure_python_payloads,
|
||||
license_files,
|
||||
)
|
||||
)
|
||||
|
||||
temp_main_wheel = output_dir / f".{wheel_path.name}.tmp"
|
||||
try:
|
||||
rewrite_main_wheel(
|
||||
wheel_path,
|
||||
temp_main_wheel,
|
||||
{info.filename for info, _ in split_so_payloads + pure_python_payloads},
|
||||
)
|
||||
verify_wheel(temp_main_wheel)
|
||||
for created in created_wheels:
|
||||
verify_wheel(created)
|
||||
os.replace(temp_main_wheel, main_wheel_path)
|
||||
finally:
|
||||
if temp_main_wheel.exists():
|
||||
temp_main_wheel.unlink()
|
||||
|
||||
|
||||
def parse_args(argv: list[str]) -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Extract optional IfcOpenShell Pyodide payloads into separate wheel artifacts."
|
||||
)
|
||||
parser.add_argument("wheel", help="IfcOpenShell Pyodide wheel to split")
|
||||
parser.add_argument("output_dir", help="Directory for generated wheels")
|
||||
return parser.parse_args(argv)
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
args = parse_args(sys.argv[1:] if argv is None else argv)
|
||||
split_wheel(Path(args.wheel), Path(args.output_dir))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -1,2 +0,0 @@
|
||||
pyodide
|
||||
pyodide-modular
|
||||
+11
-24
@@ -1,33 +1,19 @@
|
||||
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)])
|
||||
ifc_file = ifcopenshell.file()
|
||||
wall = ifc_file.create_entity("IfcWall")
|
||||
wall1 = ifc_file.by_type("IfcWall")[0]
|
||||
@@ -36,4 +22,5 @@ def test_ifcopenshell_import(selenium, request):
|
||||
wall.Name = "Test"
|
||||
assert wall.Name == "Test", f"Entity name wasn't changed: {{wall}}"
|
||||
print(wall)
|
||||
""")
|
||||
"""
|
||||
)
|
||||
|
||||
+1
-2
@@ -9,7 +9,6 @@ line-length = 120
|
||||
include = '''
|
||||
src/.*.pyi?$
|
||||
|nix/.*.pyi?$
|
||||
|pyodide/.*.pyi?$
|
||||
'''
|
||||
extend-exclude = '''
|
||||
src/ifcopenshell-python/ifcopenshell/express/rules/*
|
||||
@@ -70,7 +69,7 @@ select = [
|
||||
ignore = [
|
||||
"FA100", # Conflicts with Blender using annotations for props definitions.
|
||||
# Maybe will enable later:
|
||||
"UP007", # Union[X,Y] to X | Y
|
||||
"UP007", # Optional to X | Y
|
||||
"UP045", # Optional to X | None
|
||||
"UP015", # Unnecessary mode argument
|
||||
"UP028", # yield for -> yield from
|
||||
|
||||
@@ -35,7 +35,7 @@ def camera_vectors_from_target_position(
|
||||
"""
|
||||
camera_offset = np.array((5, 5, 5)) if offset is None else offset
|
||||
camera_position = target_position + camera_offset
|
||||
camera_direction = unit_vector(-camera_offset)
|
||||
camera_direction = unit_vector(-camera_offset) # pylint: disable=invalid-unary-operand-type
|
||||
camera_right = unit_vector(np.cross(np.array([0.0, 0.0, 1.0]), camera_direction))
|
||||
camera_up = unit_vector(np.cross(camera_direction, camera_right))
|
||||
return camera_position.tolist(), camera_direction.tolist(), camera_up.tolist()
|
||||
|
||||
@@ -316,7 +316,7 @@ def build_viewpoint(element: entity_instance) -> mdl.VisualizationInfo:
|
||||
Returns:
|
||||
The BCF viewpoint definition.
|
||||
"""
|
||||
ifc_file = element.file
|
||||
ifc_file = element.wrapped_data.file
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(ifc_file)
|
||||
elem_placement = ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement)
|
||||
elem_placement[:3, 3] *= unit_scale
|
||||
|
||||
@@ -96,10 +96,10 @@ class FoundationClient:
|
||||
webbrowser.open(f"{auth_endpoint}?{query}")
|
||||
server.timeout = 100
|
||||
server.handle_request()
|
||||
if server.auth_code and server.auth_state == state:
|
||||
if server.auth_code and server.auth_state == state: # pylint: disable=E1101
|
||||
data = {
|
||||
"grant_type": "authorization_code",
|
||||
"code": server.auth_code,
|
||||
"code": server.auth_code, # pylint: disable=E1101 type:ignore
|
||||
"redirect_uri": f"http://localhost:{server.server_address[1]}/{self.redirect_subdir}",
|
||||
}
|
||||
headers = self._get_access_token_headers()
|
||||
|
||||
@@ -316,7 +316,7 @@ def build_viewpoint(element: entity_instance) -> mdl.VisualizationInfo:
|
||||
Returns:
|
||||
The BCF viewpoint definition.
|
||||
"""
|
||||
ifc_file = element.file
|
||||
ifc_file = element.wrapped_data.file
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(ifc_file)
|
||||
elem_placement = ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement)
|
||||
elem_placement[:3, 3] *= unit_scale
|
||||
|
||||
+79
-1
@@ -31,6 +31,14 @@ classifiers = [
|
||||
Source = "https://github.com/IfcOpenShell/IfcOpenShell"
|
||||
Issues = "https://github.com/IfcOpenShell/IfcOpenShell/issues"
|
||||
|
||||
[tool.black]
|
||||
line-length = 120
|
||||
extend-exclude = "model"
|
||||
|
||||
[tool.isort]
|
||||
profile = "black"
|
||||
extend_skip_glob = ["src/bcf/*/model/*"]
|
||||
|
||||
[tool.coverage.paths]
|
||||
source = ["src"]
|
||||
|
||||
@@ -52,7 +60,7 @@ exclude_lines = [
|
||||
[tool.tox]
|
||||
legacy_tox_ini = """
|
||||
[tox]
|
||||
env_list = py3{10,11}
|
||||
env_list = lint, type, py3{10,11}
|
||||
skip_missing_interpreters = true
|
||||
|
||||
[testenv]
|
||||
@@ -61,8 +69,78 @@ deps =
|
||||
pytest-cov
|
||||
coverage
|
||||
commands = pytest --cov --cov-report=term tests
|
||||
|
||||
[testenv:lint]
|
||||
description = run linters
|
||||
skip_install = true
|
||||
deps =
|
||||
black
|
||||
isort
|
||||
pylint
|
||||
commands =
|
||||
black {posargs:.}
|
||||
isort {posargs:.}
|
||||
pylint {posargs:.} --output-format=colorized
|
||||
|
||||
[testenv:type]
|
||||
description = run type checks
|
||||
deps =
|
||||
mypy>=0.991
|
||||
commands =
|
||||
- mypy {posargs:src}
|
||||
"""
|
||||
|
||||
[tool.mypy]
|
||||
check_untyped_defs = true
|
||||
disallow_any_generics = true
|
||||
disallow_incomplete_defs = true
|
||||
disallow_subclassing_any = true
|
||||
disallow_untyped_calls = true
|
||||
disallow_untyped_defs = true
|
||||
no_implicit_optional = true
|
||||
#no_implicit_reexport = true
|
||||
show_column_numbers = true
|
||||
show_error_codes = true
|
||||
show_error_context = true
|
||||
strict_equality = true
|
||||
strict_optional = true
|
||||
warn_redundant_casts = true
|
||||
#warn_return_any = true
|
||||
warn_unreachable = true
|
||||
warn_unused_configs = true
|
||||
warn_unused_ignores = true
|
||||
exclude= "src/bcf/v(2|3)/model"
|
||||
plugins = "numpy.typing.mypy_plugin"
|
||||
|
||||
[[tool.mypy.overrides]]
|
||||
module = "tests"
|
||||
disallow_untyped_decorators = false
|
||||
disallow_untyped_defs = false
|
||||
|
||||
[[tool.mypy.overrides]]
|
||||
module = [
|
||||
"pytest",
|
||||
"pytest_mock",
|
||||
"ifcopenshell",
|
||||
"ifcopenshell.*",
|
||||
]
|
||||
ignore_missing_imports = true
|
||||
|
||||
[tool.pylint.main]
|
||||
ignore = ["model"]
|
||||
ignored-modules = ["bcf.v2.model", "bcf.v3.model", "xsdata"]
|
||||
jobs = 0
|
||||
disable="all"
|
||||
enable="E" # B,B9,BLK,C,D,E,F,I,N,S,W
|
||||
|
||||
[tool.pylint.design]
|
||||
max-args = 10
|
||||
max-attributes = 10
|
||||
|
||||
[tool.pylint.format]
|
||||
expected-line-ending-format = "LF"
|
||||
max-line-length = 120
|
||||
|
||||
[tool.ruff]
|
||||
extend = "../../pyproject.toml"
|
||||
lint.select = [
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
black
|
||||
mypy
|
||||
pylint
|
||||
isort
|
||||
xsdata
|
||||
tox==3.27.1
|
||||
@@ -370,6 +370,11 @@ test-modal:
|
||||
test-reregister:
|
||||
blender --python scripts/reregister_bonsai.py
|
||||
|
||||
.PHONY: qa
|
||||
qa:
|
||||
black .
|
||||
pylint ./* --output-format=colorized --disable all --enable E --disable import-error
|
||||
|
||||
.PHONY: coverage
|
||||
coverage:
|
||||
coverage run --source bonsai.core -m pytest -p no:pytest-blender test/core
|
||||
|
||||
+1
@@ -0,0 +1 @@
|
||||
This cache folder contains .h5 files. These files cache IFC geometry for performance only. You may safely clear the contents of this cache folder without losing data.
|
||||
@@ -43,6 +43,7 @@ class IfcExporter:
|
||||
def export(self):
|
||||
self.file = tool.Ifc.get()
|
||||
self.set_header()
|
||||
IfcStore.update_cache()
|
||||
self.sync_all_objects()
|
||||
extension = self.ifc_export_settings.output_file.split(".")[-1].lower()
|
||||
if extension == "ifczip":
|
||||
|
||||
@@ -187,7 +187,7 @@ def import_attributes(
|
||||
info = {a.name(): None for a in attributes}
|
||||
info["type"] = element
|
||||
else:
|
||||
assert (entity := element.declaration.as_entity())
|
||||
assert (entity := element.wrapped_data.declaration().as_entity())
|
||||
attributes = entity.all_attributes()
|
||||
info = element.get_info()
|
||||
for attribute in attributes:
|
||||
|
||||
@@ -110,6 +110,8 @@ class IfcStore:
|
||||
"""Should be set only using ``tool.Ifc.set``."""
|
||||
|
||||
schema: Optional[ifcopenshell.ifcopenshell_wrapper.schema_definition] = None
|
||||
cache: Optional[ifcopenshell.ifcopenshell_wrapper.HdfSerializer] = None
|
||||
cache_path: Optional[str] = None
|
||||
id_map: dict[int, IFC_CONNECTED_TYPE] = {}
|
||||
guid_map: dict[str, IFC_CONNECTED_TYPE] = {}
|
||||
edited_objs: set[bpy.types.Object] = set()
|
||||
@@ -131,6 +133,8 @@ class IfcStore:
|
||||
IfcStore.path = ""
|
||||
IfcStore.file = None
|
||||
IfcStore.schema = None
|
||||
IfcStore.cache = None
|
||||
IfcStore.cache_path = None
|
||||
IfcStore.id_map = {}
|
||||
IfcStore.guid_map = {}
|
||||
IfcStore.edited_objs = set()
|
||||
@@ -246,7 +250,7 @@ class IfcStore:
|
||||
IfcStore.file = ifcopenshell.open(filename)
|
||||
return
|
||||
elif extension.lower() == "ifcxml":
|
||||
raise NotImplementedError("Reading .ifcXML files is not currently supported.")
|
||||
IfcStore.file = ifcopenshell.file(ifcopenshell.ifcopenshell_wrapper.parse_ifcxml(path))
|
||||
elif prefs.should_stream:
|
||||
IfcStore.file = ifcopenshell.open(path, should_stream=True)
|
||||
else:
|
||||
|
||||
@@ -718,6 +718,10 @@ class IfcImporter:
|
||||
iterator = ifcopenshell.geom.iterator(
|
||||
settings, self.file, include=products, geometry_library=self.ifc_import_settings.geometry_library
|
||||
)
|
||||
if self.ifc_import_settings.should_cache:
|
||||
cache = IfcStore.get_cache()
|
||||
if cache:
|
||||
iterator.set_cache(cache)
|
||||
valid_file = iterator.initialize()
|
||||
if not valid_file:
|
||||
return results
|
||||
@@ -1277,6 +1281,7 @@ class IfcImportSettings:
|
||||
self.should_merge_materials_by_colour = False
|
||||
self.should_load_geometry = True
|
||||
self.should_clean_mesh = False
|
||||
self.should_cache = True
|
||||
self.deflection_tolerance = 0.05 # Default is 0.001, but I find this to be more practical
|
||||
self.angular_tolerance = 0.5
|
||||
self.void_limit = 30
|
||||
@@ -1304,6 +1309,7 @@ class IfcImportSettings:
|
||||
context=None, input_file: Optional[str] = None, logger: Optional[logging.Logger] = None
|
||||
) -> IfcImportSettings:
|
||||
scene_diff = tool.Blender.get_diff_props()
|
||||
prefs = tool.Blender.get_addon_preferences()
|
||||
props = tool.Project.get_project_props()
|
||||
settings = IfcImportSettings()
|
||||
settings.input_file = input_file
|
||||
@@ -1316,6 +1322,7 @@ class IfcImportSettings:
|
||||
settings.should_merge_materials_by_colour = props.should_merge_materials_by_colour
|
||||
settings.should_load_geometry = props.should_load_geometry
|
||||
settings.should_clean_mesh = props.should_clean_mesh
|
||||
settings.should_cache = prefs.should_always_cache or props.should_cache
|
||||
settings.deflection_tolerance = props.deflection_tolerance
|
||||
settings.angular_tolerance = props.angular_tolerance
|
||||
settings.void_limit = props.void_limit
|
||||
|
||||
@@ -1059,6 +1059,7 @@ class AddBoundary(bpy.types.Operator, tool.Ifc.Operator):
|
||||
return tool.Ifc.get().createIfcConnectionSurfaceGeometry(surface)
|
||||
|
||||
def export_surface(self, polygon, target_face_matrix):
|
||||
ifc_file = tool.Ifc.get()
|
||||
x_axis = target_face_matrix.col[0][:3]
|
||||
z_axis = target_face_matrix.col[2][:3]
|
||||
p1 = target_face_matrix.translation
|
||||
@@ -1071,19 +1072,20 @@ class AddBoundary(bpy.types.Operator, tool.Ifc.Operator):
|
||||
placement = builder.create_axis2_placement_3d([o / self.unit_scale for o in p1], z_axis, x_axis)
|
||||
surface.BasisSurface = tool.Ifc.get().create_entity("IfcPlane", placement)
|
||||
|
||||
if tool.Ifc.get().schema != "IFC2X3":
|
||||
schema = ifc_file.schema
|
||||
if schema != "IFC2X3":
|
||||
points = [tool.Model.convert_si_to_unit(list(co)) for co in polygon.exterior.coords]
|
||||
point_list = tool.Ifc.get().createIfcCartesianPointList2D(points)
|
||||
outer_boundary = tool.Ifc.get().createIfcIndexedPolyCurve(point_list, None, False)
|
||||
|
||||
inner_boundaries = []
|
||||
inner_boundaries: list[ifcopenshell.entity_instance] = []
|
||||
for interior in polygon.interiors:
|
||||
points = [tool.Model.convert_si_to_unit(list(co)) for co in interior.coords]
|
||||
point_list = tool.Ifc.get().createIfcCartesianPointList2D(points)
|
||||
inner_boundaries.append(tool.Ifc.get().createIfcIndexedPolyCurve(point_list, None, False))
|
||||
else:
|
||||
# TODO:
|
||||
raise NotImplementedError(tool.Ifc.get().schema)
|
||||
raise NotImplementedError(schema)
|
||||
|
||||
surface.OuterBoundary = outer_boundary
|
||||
surface.InnerBoundaries = inner_boundaries
|
||||
|
||||
@@ -208,7 +208,12 @@ class CostSchedulesData:
|
||||
data["UnitSymbol"] = ifcopenshell.util.unit.get_unit_symbol(unit)
|
||||
if quantity.is_a("IfcPhysicalSimpleQuantity"):
|
||||
measure_class = (
|
||||
quantity.declaration.as_entity().attribute_by_index(3).type_of_attribute().declared_type().name()
|
||||
quantity.wrapped_data.declaration()
|
||||
.as_entity()
|
||||
.attribute_by_index(3)
|
||||
.type_of_attribute()
|
||||
.declared_type()
|
||||
.name()
|
||||
)
|
||||
if "Count" in measure_class:
|
||||
data["UnitSymbol"] = "U"
|
||||
|
||||
@@ -37,6 +37,7 @@ classes = (
|
||||
operator.PrintObjectPlacement,
|
||||
operator.PrintUnusedElementStats,
|
||||
operator.ProfileImportIFC,
|
||||
operator.PurgeHdf5Cache,
|
||||
operator.PurgeUnusedElementsByClass,
|
||||
operator.PurgeUnusedObjects,
|
||||
operator.RestartBlender,
|
||||
|
||||
@@ -90,7 +90,7 @@ class PrintIfcFile(bpy.types.Operator):
|
||||
return tool.Ifc.get()
|
||||
|
||||
def execute(self, context):
|
||||
print(tool.Ifc.get().to_string())
|
||||
print(tool.Ifc.get().wrapped_data.to_string())
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -570,6 +570,17 @@ class SelectExpressFile(bpy.types.Operator, ImportHelper):
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class PurgeHdf5Cache(bpy.types.Operator):
|
||||
bl_idname = "bim.purge_hdf5_cache"
|
||||
bl_label = "Purge HDF5 Cache"
|
||||
bl_description = "Clean up HDF5 cache files except the ones that currently loaded"
|
||||
|
||||
def execute(self, context):
|
||||
core.purge_hdf5_cache(tool.Debug)
|
||||
self.report({"INFO"}, "HDF5 cache purged.")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class OverrideDisplayType(bpy.types.Operator):
|
||||
bl_idname = "bim.override_display_type"
|
||||
bl_label = "Override Display Type"
|
||||
|
||||
@@ -64,6 +64,9 @@ class BIM_PT_debug(Panel):
|
||||
row = layout.row()
|
||||
row.operator("bim.copy_debug_information")
|
||||
|
||||
row = layout.row()
|
||||
row.operator("bim.purge_hdf5_cache")
|
||||
|
||||
row = layout.row()
|
||||
row.operator("bim.update_representation", text="Manually Save Representation")
|
||||
|
||||
|
||||
@@ -652,7 +652,7 @@ class DecoratorData:
|
||||
try:
|
||||
matrix = ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement)
|
||||
if matrix is not None:
|
||||
ifc_file = element.file
|
||||
ifc_file = element.wrapped_data.file
|
||||
project = ifc_file.by_type("IfcProject")[0] if ifc_file.by_type("IfcProject") else None
|
||||
|
||||
if project:
|
||||
|
||||
@@ -1275,7 +1275,6 @@ class SectionLevelDecorator(BaseDecorator):
|
||||
add_verts_sequence([v0 + gap, v1 - gap], start_i, **out_kwargs)
|
||||
|
||||
self.draw_lines(context, obj, output_verts, output_edges)
|
||||
assert text_position is not None and text_dir is not None
|
||||
self.draw_labels(context, obj, self.get_splines(obj), text_position.to_2d(), text_dir.to_2d())
|
||||
|
||||
def draw_labels(self, context, obj, splines, text_position, text_dir):
|
||||
@@ -1599,7 +1598,6 @@ class SectionDecorator(BaseDecorator):
|
||||
start_i = add_verts_sequence([v + v1 for v in circle_head], start_i, **out_kwargs, closed=True)
|
||||
# circle middle divider
|
||||
if not display_end_symbol:
|
||||
assert divider_offset is not None
|
||||
start_i = add_verts_sequence(
|
||||
[v1 + divider_offset[1], v1 - divider_offset[0]], start_i, **out_kwargs
|
||||
)
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
import logging
|
||||
import multiprocessing
|
||||
@@ -807,7 +808,6 @@ class CreateDrawing(bpy.types.Operator):
|
||||
bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.000001)
|
||||
bmesh.ops.triangle_fill(bm, use_dissolve=True, edges=bm.edges)
|
||||
|
||||
prev_co = None
|
||||
if not usage:
|
||||
sense_factor = 1 # Assume the extrusion vector points in the direction sense
|
||||
no = tool.Drawing.get_extrusion_vector(element).normalized()
|
||||
@@ -1003,7 +1003,10 @@ class CreateDrawing(bpy.types.Operator):
|
||||
# All very hackish whilst prototyping
|
||||
exporter = bonsai.bim.export_ifc.IfcExporter(None)
|
||||
exporter.file = tool.Ifc.get()
|
||||
exporter.sync_all_objects()
|
||||
invalidated_elements = exporter.sync_all_objects()
|
||||
invalidated_guids = [e.GlobalId for e in invalidated_elements if hasattr(e, "GlobalId")]
|
||||
if cache := IfcStore.get_cache():
|
||||
[cache.remove(guid) for guid in invalidated_guids]
|
||||
|
||||
# If we have already calculated it in the SVG in the past, don't recalculate
|
||||
edited_guids = set()
|
||||
@@ -1047,6 +1050,10 @@ class CreateDrawing(bpy.types.Operator):
|
||||
|
||||
for ifc_path, (ifc, link_matrix) in files.items():
|
||||
# Don't use draw.main() just whilst we're prototyping and experimenting
|
||||
# TODO: hash paths are never used
|
||||
ifc_hash = hashlib.md5(ifc_path.encode("utf-8")).hexdigest()
|
||||
ifc_cache_path = os.path.join(prefs.cache_dir, f"{ifc_hash}.h5")
|
||||
|
||||
self.serialiser.setFile(ifc)
|
||||
drawing_elements = tool.Drawing.get_drawing_elements(self.camera_element, ifc_file=ifc)
|
||||
|
||||
@@ -1407,30 +1414,30 @@ class CreateDrawing(bpy.types.Operator):
|
||||
pass
|
||||
self.svg_buffer = ifcopenshell.geom.serializers.buffer()
|
||||
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.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.serialiser_settings.set("svg-mirror-y", True)
|
||||
self.serialiser = ifcopenshell.geom.serializers.svg(
|
||||
self.svg_buffer, self.svg_settings, self.serialiser_settings
|
||||
)
|
||||
self.serialiser.setWithoutStoreys(True)
|
||||
self.serialiser.setPolygonal(True)
|
||||
self.serialiser.setUseHlrPoly(True)
|
||||
# Objects with more than these edges are rendered as wireframe instead of HLR for optimisation
|
||||
self.serialiser.setProfileThreshold(10000)
|
||||
self.serialiser.setUseNamespace(True)
|
||||
self.serialiser.setAlwaysProject(True)
|
||||
self.serialiser.setAutoElevation(False)
|
||||
self.serialiser.setAutoSection(False)
|
||||
self.serialiser.setPrintSpaceNames(False)
|
||||
self.serialiser.setPrintSpaceAreas(False)
|
||||
self.serialiser.setDrawDoorArcs(False)
|
||||
self.serialiser.setNoCSS(True)
|
||||
self.serialiser.setElevationRefGuid(self.camera_element.GlobalId)
|
||||
self.serialiser.setScale(self.scale)
|
||||
self.serialiser.setSubtractionSettings(ifcopenshell.ifcopenshell_wrapper.ALWAYS)
|
||||
self.serialiser.setUsePrefiltering(True) # See #3359
|
||||
self.serialiser.setUnifyInputs(True)
|
||||
self.serialiser.setSegmentProjection(True)
|
||||
if target_view == "REFLECTED_PLAN_VIEW":
|
||||
self.serialiser.setMirrorY(True)
|
||||
# tree = ifcopenshell.geom.tree()
|
||||
# This instructs the tree to explode BReps into faces and return
|
||||
# the style of the face when running tree.select_ray()
|
||||
|
||||
@@ -102,16 +102,16 @@ void angle_circle_head(
|
||||
in vec4 circle_start, in float circle_angle,
|
||||
in bool counterclockwise,
|
||||
out vec4 head[CIRCLE_SEGS+1], out float angle_segs) {
|
||||
|
||||
|
||||
// 1 added to CIRCLE_SEGS because we're number of vertices
|
||||
// for n segments is n+1
|
||||
|
||||
|
||||
float angle_d;
|
||||
angle_d = PI * 2 / CIRCLE_SEGS; // 30d
|
||||
// need to bottom clamp it to 1, otherwise it causes Blender crash at extruding the curve
|
||||
angle_segs = max(1, ceil(circle_angle / angle_d));
|
||||
angle_d = circle_angle / angle_segs;
|
||||
|
||||
|
||||
for(int i = 0; i < (angle_segs + 1); i++) {
|
||||
float angle = angle_d * i;
|
||||
if (counterclockwise) {
|
||||
@@ -143,7 +143,7 @@ void cross_head(in vec4 dir, in float size, out vec4 head[3]) {
|
||||
#define do_vertex(pos, e) (do_vertex_util(pos, vec2(-(e).y, (e).x) / winsize.xy))
|
||||
#define do_vertex_win(pos, e) ( do_vertex( WIN2CLIP( pos ), e ) )
|
||||
|
||||
// if vertex is shared by two segments of the line still need to emit it twice
|
||||
// if vertex is shared by two segments of the line still need to emit it twice
|
||||
// to avoid smoothing artifacts
|
||||
// don't forget to initialize `vec2 EDGE_DIR` for macro to work
|
||||
// `pos0` / `pos1` - vertex position in clip space
|
||||
@@ -203,6 +203,7 @@ def add_verts_sequence(verts, start_i, output_verts, output_edges, closed=False)
|
||||
output_edges.append((i, i + 1))
|
||||
output_verts.append(verts[-1])
|
||||
assert i is not None
|
||||
|
||||
if closed:
|
||||
output_edges.append((i + 1, start_i))
|
||||
return i + 2
|
||||
@@ -275,7 +276,7 @@ class BaseShader:
|
||||
FRAG_GLSL = """
|
||||
uniform vec4 color;
|
||||
uniform float lineWidth;
|
||||
|
||||
|
||||
in float smoothline;
|
||||
out vec4 fragColor;
|
||||
void main() {
|
||||
|
||||
@@ -1445,12 +1445,11 @@ class SvgWriter:
|
||||
O = A.copy()
|
||||
O.z = B.z
|
||||
run = (B - O).length
|
||||
|
||||
angle_tg = None
|
||||
if run != 0:
|
||||
angle_tg = rise / run
|
||||
angle = round(degrees(atan(angle_tg)))
|
||||
else:
|
||||
angle_tg = None
|
||||
angle = 90
|
||||
|
||||
# ues SLOPE_ANGLE as default
|
||||
|
||||
@@ -1031,11 +1031,9 @@ class BIM_UL_sheets(bpy.types.UIList):
|
||||
|
||||
if self.filter_name:
|
||||
filter_name = self.filter_name.lower()
|
||||
active_sheet = None
|
||||
active_sheet_index = None
|
||||
for sheet in data.sheets:
|
||||
if sheet.is_sheet:
|
||||
active_sheet = sheet
|
||||
active_sheet_index = len(flt_flags)
|
||||
if filter_name in sheet.name.lower() or filter_name in sheet.identification.lower():
|
||||
flt_flags.append(self.bitflag_filter_item)
|
||||
|
||||
@@ -588,6 +588,9 @@ class UpdateRepresentation(bpy.types.Operator, tool.Ifc.Operator):
|
||||
)
|
||||
return
|
||||
|
||||
if not product.is_a("IfcGridAxis"):
|
||||
tool.Geometry.clear_cache(product)
|
||||
|
||||
if product.is_a("IfcGridAxis"):
|
||||
# Grid geometry does not follow the "representation" paradigm and needs to be treated specially
|
||||
tool.Model.create_axis_curve(obj, product)
|
||||
@@ -799,7 +802,7 @@ def lock_error_message(name: str) -> str:
|
||||
|
||||
|
||||
def calc_delete_is_batch(ifc_file: ifcopenshell.file, context: bpy.types.Context) -> bool:
|
||||
total_elements = len(tool.Ifc.get().entity_names())
|
||||
total_elements = len(tool.Ifc.get().wrapped_data.entity_names())
|
||||
total_polygons = sum([len(o.data.polygons) for o in context.selected_objects if o.type == "MESH"])
|
||||
# These numbers are a bit arbitrary, but basically batching is only
|
||||
# really necessary on large models and large geometry removals.
|
||||
@@ -3531,10 +3534,11 @@ class EditRepresentationItemShapeAspect(bpy.types.Operator, tool.Ifc.Operator):
|
||||
for representation_map in element.RepresentationMaps:
|
||||
if representation_map.MappedRepresentation == active_representation:
|
||||
product_shape = representation_map
|
||||
assert product_shape is not None
|
||||
|
||||
previous_shape_aspect_id = props.active_item.shape_aspect_id
|
||||
# will be None if item didn't had a shape aspect
|
||||
previous_shape_aspect = tool.Ifc.get_entity_by_id(previous_shape_aspect_id)
|
||||
assert product_shape is not None
|
||||
shape_aspect = tool.Geometry.create_shape_aspect(
|
||||
product_shape, active_representation, [representation_item], previous_shape_aspect
|
||||
)
|
||||
|
||||
@@ -156,7 +156,7 @@ class RadianceRender(bpy.types.Operator):
|
||||
print(f"Quality: {quality}, Detail: {detail}, Variability: {variability}")
|
||||
print(f"Output directory: {output_dir}")
|
||||
|
||||
hdr_image_path, hdr_mask_path, sky_map_cal_path = None, None, None
|
||||
hdr_image_path, hdr_mask_path, sky_map_cal_path = None, None
|
||||
if use_hdr:
|
||||
hdr_image = "noon_grass_2k.hdr"
|
||||
hdr_mask = "noon_grass_2k_mask.hdr"
|
||||
|
||||
@@ -484,8 +484,8 @@ class MEPGenerator:
|
||||
if tool.Cad.is_x(port_local_position.length, 0.0):
|
||||
start_port = port
|
||||
break
|
||||
|
||||
assert start_port is not None
|
||||
|
||||
connected_port = tool.System.get_connected_port(start_port)
|
||||
connected_element = tool.System.get_port_relating_element(connected_port)
|
||||
element_type = ifcopenshell.util.element.get_type(connected_element)
|
||||
|
||||
@@ -1576,6 +1576,7 @@ class LoadLink(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
def link_ifc(self) -> Union[set[str], None]:
|
||||
blend_filepath = self.filepath_.with_suffix(".ifc.cache.blend")
|
||||
h5_filepath = self.filepath_.with_suffix(".ifc.cache.h5")
|
||||
json_filepath = self.filepath_.with_suffix(".ifc.cache.json")
|
||||
|
||||
def should_clear_cache() -> bool:
|
||||
|
||||
@@ -374,6 +374,14 @@ class BIMProjectProperties(PropertyGroup):
|
||||
),
|
||||
default=False,
|
||||
)
|
||||
should_cache: BoolProperty(
|
||||
name="Cache",
|
||||
description=(
|
||||
"Cache loaded geometry to .h5 file in your cache directory (see in preferences) "
|
||||
"for faster imports and geometry reloads."
|
||||
),
|
||||
default=False,
|
||||
)
|
||||
deflection_tolerance: FloatProperty(name="Deflection Tolerance", default=0.05)
|
||||
angular_tolerance: FloatProperty(name="Angular Tolerance", default=0.5)
|
||||
void_limit: IntProperty(
|
||||
@@ -544,6 +552,7 @@ class BIMProjectProperties(PropertyGroup):
|
||||
should_merge_materials_by_colour: bool
|
||||
should_load_geometry: bool
|
||||
should_clean_mesh: bool
|
||||
should_cache: bool
|
||||
deflection_tolerance: float
|
||||
angular_tolerance: float
|
||||
void_limit: int
|
||||
|
||||
@@ -28,9 +28,8 @@ from bpy.types import Menu, Panel, UIList
|
||||
import bonsai.bim
|
||||
import bonsai.tool as tool
|
||||
from bonsai.bim.helper import draw_attributes, prop_with_search
|
||||
from bonsai.bim.ifc import IfcStore, is_cache_locked_by_other_process
|
||||
from bonsai.bim.ifc import IfcStore
|
||||
from bonsai.bim.module.project.data import LinksData, ProjectData
|
||||
from bonsai.bim.ui import draw_multiline_text
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from bonsai.bim.module.project.prop import (
|
||||
@@ -167,20 +166,6 @@ class BIM_PT_project(Panel):
|
||||
if pprops.is_loading:
|
||||
self.draw_advanced_loading_ui(context)
|
||||
elif self.file or props.ifc_file:
|
||||
if is_cache_locked_by_other_process():
|
||||
box = self.layout.box()
|
||||
box.alert = True
|
||||
row = box.row(align=True)
|
||||
row.label(text="IFC Already Open in Another Blender Instance", icon="ERROR")
|
||||
row.operator("bim.dismiss_multi_instance_warning", text="", icon="CANCEL")
|
||||
draw_multiline_text(
|
||||
box.column(align=True),
|
||||
"This file is open in another Blender instance. Editing the same "
|
||||
"IFC from two instances at once can lose your work or display "
|
||||
"outdated geometry. Close the other Blender instances to continue safely.",
|
||||
context=context,
|
||||
)
|
||||
|
||||
if props.has_blend_warning:
|
||||
box = self.layout.box()
|
||||
box.alert = True
|
||||
@@ -190,21 +175,6 @@ class BIM_PT_project(Panel):
|
||||
op.uri = "https://docs.bonsaibim.org/guides/troubleshooting.html#saving-and-loading-blend-files"
|
||||
row.operator("bim.close_blend_warning", text="", icon="CANCEL")
|
||||
|
||||
if pending := pprops.pending_opening_recut:
|
||||
box = self.layout.box()
|
||||
box.alert = True
|
||||
box.label(text="Opening Cuts Skipped", icon="ERROR")
|
||||
draw_multiline_text(
|
||||
box.column(align=True),
|
||||
f"{len(pending)} element(s) had too many openings to cut during load. "
|
||||
f"Apply to recompute their meshes, or dismiss to leave them as they are.",
|
||||
context=context,
|
||||
)
|
||||
row = box.row(align=True)
|
||||
row.operator("bim.select_pending_opening_cuts", text="Select Elements", icon="RESTRICT_SELECT_OFF")
|
||||
row.operator("bim.apply_pending_opening_cuts", text="Apply Openings", icon="PLAY")
|
||||
row.operator("bim.dismiss_pending_opening_cuts", text="", icon="CANCEL")
|
||||
|
||||
if props.ifc_file:
|
||||
self.draw_loaded_project_ui(context)
|
||||
else:
|
||||
@@ -237,6 +207,8 @@ class BIM_PT_project(Panel):
|
||||
row = self.layout.row()
|
||||
row.prop(pprops, "should_clean_mesh")
|
||||
row = self.layout.row()
|
||||
row.prop(pprops, "should_cache")
|
||||
row = self.layout.row()
|
||||
row.prop(pprops, "should_load_geometry")
|
||||
row = self.layout.row()
|
||||
row.prop(pprops, "should_merge_materials_by_colour")
|
||||
|
||||
@@ -631,12 +631,13 @@ class AddElement(bpy.types.Operator, tool.Ifc.Operator):
|
||||
local_z = wall_matrix.to_3x3() @ Vector((0, 0, 1))
|
||||
direction_sense = getattr(usage, "DirectionSense", "POSITIVE")
|
||||
|
||||
if usage.LayerSetDirection == "AXIS2":
|
||||
layer_set_direction = usage.LayerSetDirection
|
||||
if layer_set_direction == "AXIS2":
|
||||
z_axis = tuple(local_y) if direction_sense == "POSITIVE" else tuple(-local_y)
|
||||
elif usage.LayerSetDirection == "AXIS3":
|
||||
elif layer_set_direction == "AXIS3":
|
||||
z_axis = tuple(local_z) if direction_sense == "POSITIVE" else tuple(-local_z)
|
||||
else:
|
||||
assert False, usage.LayerSetDirection
|
||||
assert False, layer_set_direction
|
||||
|
||||
item = builder.extrude(
|
||||
profile,
|
||||
|
||||
@@ -95,7 +95,7 @@ class ColourByPropertyData:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element:
|
||||
return default
|
||||
keys = [a.name() for a in element.declaration.as_entity().all_attributes()]
|
||||
keys = [a.name() for a in element.wrapped_data.declaration().as_entity().all_attributes()]
|
||||
psets = ifcopenshell.util.element.get_psets(element)
|
||||
for pset, properties in psets.items():
|
||||
if pset.endswith("Common"):
|
||||
@@ -126,7 +126,7 @@ class SelectSimilarData:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element:
|
||||
return []
|
||||
keys = [a.name() for a in element.declaration.as_entity().all_attributes()]
|
||||
keys = [a.name() for a in element.wrapped_data.declaration().as_entity().all_attributes()]
|
||||
psets = ifcopenshell.util.element.get_psets(element, psets_only=True)
|
||||
for pset, properties in psets.items():
|
||||
if pset.endswith("Common"):
|
||||
|
||||
@@ -849,15 +849,16 @@ class ShaderInfo:
|
||||
v = l1[1] + fac * (pos - l1[0])
|
||||
return v
|
||||
|
||||
def interpolate(
|
||||
self, pos: float, loadinfo: list[LoadConfigItem], start: int, end: int, key: Literal["load values"]
|
||||
) -> np.ndarray:
|
||||
def interpolate(self, pos: float, loadinfo: list[LoadConfigItem], start: int, end: int) -> np.ndarray:
|
||||
"""interpolate the result vectors between load poits"""
|
||||
result = np.zeros(6)
|
||||
for i in range(6):
|
||||
value1 = [loadinfo[start]["pos"], loadinfo[start][key][i]] # [position, force_component]
|
||||
value2 = [loadinfo[end]["pos"], loadinfo[end][key][i]] # [position, force_component]
|
||||
result[i] = self.interp1d(value1, value2, pos) # interpolated [position, force_component]
|
||||
# [position, force_component]
|
||||
value1 = [loadinfo[start]["pos"], loadinfo[start]["load values"][i]]
|
||||
# [position, force_component]
|
||||
value2 = [loadinfo[end]["pos"], loadinfo[end]["load values"][i]]
|
||||
# interpolated [position, force_component]
|
||||
result[i] = self.interp1d(value1, value2, pos)
|
||||
return result
|
||||
|
||||
def get_before_and_after(self, pos: float, load_config_list: list[list[LoadConfigItem]]) -> dict[str, list[float]]:
|
||||
@@ -901,8 +902,8 @@ class ShaderInfo:
|
||||
load_before += config[end]["load values"]
|
||||
|
||||
elif end - start == 1:
|
||||
load_before += self.interpolate(pos, config, start, end, "load values")
|
||||
load_after += self.interpolate(pos, config, start, end, "load values")
|
||||
load_before += self.interpolate(pos, config, start, end)
|
||||
load_after += self.interpolate(pos, config, start, end)
|
||||
start += 1
|
||||
end -= 1
|
||||
return_value = {"before": load_before.tolist(), "after": load_after.tolist()}
|
||||
|
||||
@@ -230,6 +230,7 @@ class RemoveOpening(bpy.types.Operator, tool.Ifc.Operator):
|
||||
assert representation
|
||||
tool.Geometry.recut_host(building_obj, representation)
|
||||
tool.Geometry.unlock_scale_object_with_openings(obj)
|
||||
tool.Geometry.clear_cache(element)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
|
||||
@@ -620,6 +620,10 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
|
||||
default="PROMPT",
|
||||
)
|
||||
should_stream: BoolProperty(name="Stream Data From IFC-SPF (Only for advanced users)", default=False)
|
||||
should_always_cache: BoolProperty(
|
||||
name="Always Cache Geometry",
|
||||
description="Whether to always cache geometry regardless of 'Cache' setting during Advanced Project Load.",
|
||||
)
|
||||
occurrence_name_style: bpy.props.EnumProperty(
|
||||
items=[("CLASS", "By Class", ""), ("TYPE", "By Type", ""), ("CUSTOM", "Custom", "")],
|
||||
name="Occurrence Name Style",
|
||||
@@ -731,6 +735,7 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
|
||||
autosave_interval_minutes: int
|
||||
autosave_mode: Literal["PROMPT", "BACKUP"]
|
||||
should_stream: bool
|
||||
should_always_cache: bool
|
||||
occurrence_name_style: Literal["CLASS", "TYPE", "CUSTOM"]
|
||||
occurrence_name_function: str
|
||||
gizmos: GizmoPreferences
|
||||
@@ -884,6 +889,7 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
|
||||
layout.prop(self, "autosave_interval_minutes")
|
||||
layout.prop(self, "autosave_mode")
|
||||
layout.prop(self, "should_stream")
|
||||
layout.prop(self, "should_always_cache")
|
||||
layout.label(text="bSDD:")
|
||||
layout.prop(self, "bsdd_load_preview_dictionaries")
|
||||
layout.prop(self, "bsdd_load_inactive_dictionaries")
|
||||
|
||||
@@ -76,9 +76,9 @@ def add_instance_ceiling_covering_from_cursor(
|
||||
|
||||
ceiling_height = None
|
||||
if selected_objects and active_obj:
|
||||
x, y, z, h, mat = spatial.get_x_y_z_h_mat_from_obj(active_obj)
|
||||
x, y, z, _, _ = spatial.get_x_y_z_h_mat_from_obj(active_obj)
|
||||
else:
|
||||
x, y, z, h, mat = spatial.get_x_y_z_h_mat_from_cursor()
|
||||
x, y, z, _, _ = spatial.get_x_y_z_h_mat_from_cursor()
|
||||
ceiling_height = covering.get_z_from_ceiling_height()
|
||||
|
||||
space_polygon = spatial.get_space_polygon_from_context_visible_objects(x, y)
|
||||
@@ -102,7 +102,7 @@ def regen_selected_covering_object(root: type[tool.Root], spatial: type[tool.Spa
|
||||
selected_objects = spatial.get_selected_objects()
|
||||
|
||||
if selected_objects and active_obj:
|
||||
x, y, z, h, mat = spatial.get_x_y_z_h_mat_from_obj(active_obj)
|
||||
x, y, _, _, _ = spatial.get_x_y_z_h_mat_from_obj(active_obj)
|
||||
else:
|
||||
assert False, "Object has to be active and selected."
|
||||
|
||||
|
||||
@@ -30,6 +30,10 @@ def parse_express(debug: type[tool.Debug], filename: str) -> None:
|
||||
debug.add_schema_identifier(debug.load_express(filename))
|
||||
|
||||
|
||||
def purge_hdf5_cache(debug: type[tool.Debug]) -> None:
|
||||
debug.purge_hdf5_cache()
|
||||
|
||||
|
||||
def purge_unused_elements(ifc: type[tool.Ifc], debug: type[tool.Debug], ifc_class: str) -> int:
|
||||
ifc_file = ifc.get()
|
||||
unused_elements = [i for i in ifc_file.by_type(ifc_class) if ifc_file.get_total_inverses(i) == 0]
|
||||
|
||||
@@ -526,9 +526,9 @@ def add_annotation(
|
||||
context = drawing_tool.create_annotation_context(target_view, object_type)
|
||||
|
||||
drawing_tool.show_decorations()
|
||||
relating_type_rep = None
|
||||
obj = drawing_tool.create_annotation_object(drawing, object_type)
|
||||
element = ifc.get_entity(obj)
|
||||
relating_type_rep = None
|
||||
if not element: # Brand new annotation
|
||||
relating_type_rep = drawing_tool.get_annotation_representation(relating_type) if relating_type else None
|
||||
element = drawing_tool.run_root_assign_class(
|
||||
|
||||
@@ -44,6 +44,7 @@ def edit_object_placement(
|
||||
element = ifc.get_entity(obj)
|
||||
if not element:
|
||||
return
|
||||
geometry.clear_cache(element)
|
||||
if apply_scale:
|
||||
geometry.clear_scale(obj)
|
||||
geometry.get_blender_offset_type(obj)
|
||||
@@ -124,6 +125,7 @@ def switch_representation(
|
||||
|
||||
element = ifc.get_entity(obj)
|
||||
assert element
|
||||
geometry.clear_cache(element)
|
||||
geometry.reimport_element_representations(obj, representation, apply_openings=apply_openings)
|
||||
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ import inspect
|
||||
from typing import Optional
|
||||
|
||||
# fmt: off
|
||||
# pylint: skip-file
|
||||
|
||||
# This interface class and decorator is magic syntactic sugar to allow concise interface definitions
|
||||
# If we didn't do this, Python is unnecessarily verbose, which I find distracting. Don't black this file :)
|
||||
@@ -289,6 +290,7 @@ class Cost:
|
||||
class Debug:
|
||||
def add_schema_identifier(cls, schema): pass
|
||||
def load_express(cls, filename): pass
|
||||
def purge_hdf5_cache(cls): pass
|
||||
|
||||
|
||||
@interface
|
||||
@@ -444,6 +446,7 @@ class Feature:
|
||||
@interface
|
||||
class Geometry:
|
||||
def change_object_data(cls, obj, data, is_global=False): pass
|
||||
def clear_cache(cls, element): pass
|
||||
def clear_modifiers(cls, obj): pass
|
||||
def clear_scale(cls, obj): pass
|
||||
def copy_data_links(cls, data, copied_entities) -> None: pass
|
||||
|
||||
@@ -2581,65 +2581,6 @@ class Blender(bonsai.core.tool.Blender):
|
||||
t += dash_period
|
||||
return new_verts, new_edges
|
||||
|
||||
@classmethod
|
||||
def draw_bmesh_face_tris(
|
||||
cls,
|
||||
bm: bmesh.types.BMesh,
|
||||
world_vert_coords: list,
|
||||
color: Any,
|
||||
draw_batch: Callable[[str, list, Any, list], None],
|
||||
) -> None:
|
||||
"""Submit a non-mutating beauty-triangulated TRIS batch for ``bm``'s faces.
|
||||
|
||||
``world_vert_coords`` must be indexed by ``bm.verts`` index. Never call
|
||||
``bmesh.ops.triangulate`` on a live bmesh to compute draw indices — it
|
||||
mutates the input and produces ear-clip fans that render as visible
|
||||
streaks at low alpha.
|
||||
"""
|
||||
tris = [[loop.vert.index for loop in tri] for tri in bm.calc_loop_triangles()]
|
||||
draw_batch("TRIS", world_vert_coords, color, tris)
|
||||
|
||||
@classmethod
|
||||
def build_dashed_line_segments(
|
||||
cls,
|
||||
world_verts: Sequence[Sequence[float]],
|
||||
edges_indices: Sequence[Sequence[int]],
|
||||
dash_period: float,
|
||||
dash_width: float,
|
||||
) -> tuple[list[tuple[float, float, float]], list[tuple[int, int]]]:
|
||||
"""Pre-segment edges into world-space dash chunks for a vanilla LINES batch.
|
||||
|
||||
Each input edge is sliced into segments of length ``dash_width`` spaced
|
||||
``dash_period`` apart (dash phase resets per-edge). The result is a fresh
|
||||
``(verts, edges)`` pair that draws as dashes through any standard line
|
||||
shader — letting both passes of a visible/occluded outline reuse the
|
||||
same shader so depth values match exactly across passes.
|
||||
"""
|
||||
new_verts: list[tuple[float, float, float]] = []
|
||||
new_edges: list[tuple[int, int]] = []
|
||||
if dash_period <= 0 or dash_width <= 0:
|
||||
return new_verts, new_edges
|
||||
n = len(world_verts)
|
||||
for i, j in edges_indices:
|
||||
if not (0 <= i < n and 0 <= j < n) or i == j:
|
||||
continue
|
||||
v0 = world_verts[i]
|
||||
v1 = world_verts[j]
|
||||
dx, dy, dz = v1[0] - v0[0], v1[1] - v0[1], v1[2] - v0[2]
|
||||
edge_length = math.sqrt(dx * dx + dy * dy + dz * dz)
|
||||
if edge_length == 0.0:
|
||||
continue
|
||||
ux, uy, uz = dx / edge_length, dy / edge_length, dz / edge_length
|
||||
t = 0.0
|
||||
while t < edge_length:
|
||||
t_end = min(t + dash_width, edge_length)
|
||||
idx = len(new_verts)
|
||||
new_verts.append((v0[0] + ux * t, v0[1] + uy * t, v0[2] + uz * t))
|
||||
new_verts.append((v0[0] + ux * t_end, v0[1] + uy * t_end, v0[2] + uz * t_end))
|
||||
new_edges.append((idx, idx + 1))
|
||||
t += dash_period
|
||||
return new_verts, new_edges
|
||||
|
||||
@classmethod
|
||||
def extract_error_reports(cls, exception: RuntimeError) -> list[str]:
|
||||
"""Extracts error report lines from a runtime exception during operator execution.
|
||||
|
||||
@@ -170,7 +170,7 @@ class Bsdd(bonsai.core.tool.Bsdd):
|
||||
psets.setdefault(pset, {})
|
||||
|
||||
predefined_value = prop.get("predefinedValue")
|
||||
if predefined_value:
|
||||
if predefined_value and predefined_value != "None":
|
||||
possible_values = [predefined_value]
|
||||
else:
|
||||
possible_values = prop.get("allowedValues", []) or []
|
||||
@@ -369,7 +369,7 @@ class Bsdd(bonsai.core.tool.Bsdd):
|
||||
data = cls.bsdd_properties[bsdd_prop.uri]
|
||||
|
||||
predefined_value = data.get("predefinedValue")
|
||||
if predefined_value:
|
||||
if predefined_value and predefined_value != "None":
|
||||
possible_values = [predefined_value]
|
||||
else:
|
||||
possible_values = data.get("allowedValues", []) or []
|
||||
@@ -401,7 +401,13 @@ class Bsdd(bonsai.core.tool.Bsdd):
|
||||
uris.add(uri)
|
||||
psets = set()
|
||||
for uri in uris:
|
||||
if not (bsdd_class := cls.bsdd_classes.get(uri, None)):
|
||||
try:
|
||||
# Cache may not be populated yet (e.g. a fresh session that never
|
||||
# browsed this class), so fetch on a cache miss instead of skipping.
|
||||
bsdd_class = cls.get_bsdd_class(uri)
|
||||
except Exception:
|
||||
continue
|
||||
if not bsdd_class:
|
||||
continue
|
||||
for class_pset in bsdd_class.get("classProperties", []):
|
||||
if not (pset_name := class_pset.get("propertySet", None)):
|
||||
@@ -409,6 +415,37 @@ class Bsdd(bonsai.core.tool.Bsdd):
|
||||
psets.add((uri, bsdd_class["name"], pset_name))
|
||||
return psets
|
||||
|
||||
@classmethod
|
||||
def get_bsdd_pset_property_values(
|
||||
cls, element: ifcopenshell.entity_instance, pset_name: str
|
||||
) -> dict[str, list[str]]:
|
||||
"""Map bSDD property code -> allowed/predefined values for properties bSDD says
|
||||
are applicable to `element` (via its classification references) under `pset_name`.
|
||||
|
||||
Used to recognise a Pset property as bSDD-sourced even outside the dedicated
|
||||
bSDD add-property flow (e.g. a Pset created in a previous session), so it can
|
||||
still be edited as a picklist.
|
||||
"""
|
||||
result: dict[str, list[str]] = {}
|
||||
for uri, class_name, applicable_pset_name in cls.get_applicable_psets(element):
|
||||
if applicable_pset_name != pset_name:
|
||||
continue
|
||||
bsdd_class = cls.get_bsdd_class(uri)
|
||||
for class_prop in bsdd_class.get("classProperties", []):
|
||||
if class_prop.get("propertySet") != pset_name:
|
||||
continue
|
||||
code = class_prop.get("propertyCode")
|
||||
if not code or code in result:
|
||||
continue
|
||||
predefined_value = class_prop.get("predefinedValue")
|
||||
if predefined_value and predefined_value != "None":
|
||||
result[code] = [predefined_value]
|
||||
continue
|
||||
allowed_values = class_prop.get("allowedValues", []) or []
|
||||
if allowed_values:
|
||||
result[code] = [v["value"] for v in allowed_values]
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
def is_applicable(cls, pset_uri: str, element: ifcopenshell.entity_instance) -> bool:
|
||||
uris = set()
|
||||
|
||||
@@ -1085,8 +1085,8 @@ class Cad:
|
||||
break
|
||||
|
||||
v1 = v2
|
||||
|
||||
assert new_verts is not None
|
||||
|
||||
return new_verts
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
from collections import defaultdict
|
||||
from collections.abc import Iterable
|
||||
@@ -58,6 +59,17 @@ class Debug(bonsai.core.tool.Debug):
|
||||
ifcopenshell.register_schema(schema)
|
||||
return schema.schema
|
||||
|
||||
@classmethod
|
||||
def purge_hdf5_cache(cls) -> None:
|
||||
prefs = tool.Blender.get_addon_preferences()
|
||||
cache_dir = prefs.cache_dir
|
||||
filelist = [f for f in os.listdir(cache_dir) if f.endswith(".h5")]
|
||||
for f in filelist:
|
||||
try:
|
||||
os.remove(os.path.join(cache_dir, f))
|
||||
except PermissionError:
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def debug_bmesh(cls, bm: bmesh.types.BMesh, name: str = "Debug") -> bpy.types.Object:
|
||||
mesh = bpy.data.meshes.new("Debug")
|
||||
@@ -152,7 +164,7 @@ class Debug(bonsai.core.tool.Debug):
|
||||
return name
|
||||
|
||||
def get_hash(element: ifcopenshell.entity_instance) -> int:
|
||||
data = element.get_info(include_identifier=False, recursive=True)
|
||||
data = element.get_info_2(include_identifier=False, recursive=True)
|
||||
if object_type == "APPLICATION":
|
||||
# To avoid disruption let user merge organizations separately.
|
||||
data["ApplicationDeveloper"] = element.ApplicationDeveloper.id()
|
||||
|
||||
@@ -2609,12 +2609,10 @@ class Drawing(bonsai.core.tool.Drawing):
|
||||
subcontext = current_representation.ContextOfItems
|
||||
current_representation_subcontext = tool.Geometry.get_subcontext_parameters(subcontext)
|
||||
|
||||
has_context = False
|
||||
for subcontext in subcontexts:
|
||||
# prioritize already active representation if it matches the subcontext
|
||||
# (element could have multiple representations in the same subcontext)
|
||||
if current_representation and subcontext == current_representation_subcontext:
|
||||
has_context = True
|
||||
if current_representation_subcontext and subcontext == current_representation_subcontext:
|
||||
break
|
||||
priority_representation = ifcopenshell.util.representation.get_representation(element, *subcontext)
|
||||
if priority_representation:
|
||||
@@ -2624,7 +2622,6 @@ class Drawing(bonsai.core.tool.Drawing):
|
||||
obj=obj,
|
||||
representation=priority_representation,
|
||||
)
|
||||
has_context = True
|
||||
break
|
||||
|
||||
linked_handles: set[bpy.types.Object] = set()
|
||||
|
||||
@@ -23,7 +23,6 @@ from typing import TYPE_CHECKING
|
||||
|
||||
import bpy
|
||||
import ifcopenshell.api.feature
|
||||
import ifcopenshell.util.representation
|
||||
|
||||
import bonsai.core.geometry
|
||||
import bonsai.core.tool
|
||||
@@ -45,12 +44,12 @@ class Feature(bonsai.core.tool.Feature):
|
||||
featured_element = tool.Ifc.get_entity(featured_obj)
|
||||
|
||||
has_visible_openings = False
|
||||
element_had_openings = None
|
||||
for opening in [r.RelatedOpeningElement for r in featured_element.HasOpenings]:
|
||||
if tool.Ifc.get_object(opening):
|
||||
has_visible_openings = True
|
||||
break
|
||||
|
||||
element_had_openings = None
|
||||
for feature_obj in feature_objs:
|
||||
feature_element = tool.Ifc.get_entity(feature_obj)
|
||||
|
||||
@@ -59,7 +58,6 @@ class Feature(bonsai.core.tool.Feature):
|
||||
bonsai.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=featured_obj)
|
||||
|
||||
element_had_openings = tool.Geometry.has_openings(featured_element)
|
||||
body_context = ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model", "Body")
|
||||
ifcopenshell.api.feature.add_feature(tool.Ifc.get(), feature=feature_element, element=featured_element)
|
||||
|
||||
if tool.Ifc.is_moved(feature_obj):
|
||||
|
||||
@@ -74,7 +74,7 @@ import bonsai.core.style
|
||||
import bonsai.core.system
|
||||
import bonsai.core.tool
|
||||
import bonsai.tool as tool
|
||||
from bonsai.bim.ifc import get_cache_or_detect_lock
|
||||
from bonsai.bim.ifc import IfcStore, get_cache_or_detect_lock
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from bonsai.bim.module.geometry.prop import (
|
||||
@@ -111,18 +111,8 @@ class Geometry(bonsai.core.tool.Geometry):
|
||||
old_data.user_remap(new_data)
|
||||
|
||||
@classmethod
|
||||
def has_axis_representation(cls, element: ifcopenshell.entity_instance) -> bool:
|
||||
"""True if the element carries a shape representation whose
|
||||
RepresentationIdentifier is 'Axis'. Elements without one cannot be
|
||||
projected to an unambiguous 1D path; callers that draw schematic axis
|
||||
overlays must skip them rather than fall back to mesh-derived geometry."""
|
||||
product_rep = getattr(element, "Representation", None)
|
||||
if product_rep is None:
|
||||
return False
|
||||
for rep in product_rep.Representations:
|
||||
if getattr(rep, "RepresentationIdentifier", None) == "Axis":
|
||||
return True
|
||||
return False
|
||||
def get_cache(cls) -> Union[ifcopenshell.geom.serializers.hdf5, None]:
|
||||
return IfcStore.get_cache()
|
||||
|
||||
@classmethod
|
||||
def clear_cache(cls, element: ifcopenshell.entity_instance) -> None:
|
||||
@@ -1110,6 +1100,7 @@ class Geometry(bonsai.core.tool.Geometry):
|
||||
if not cls.has_data_users(old_data):
|
||||
cls.delete_data(old_data)
|
||||
cls.clear_modifiers(obj)
|
||||
cls.clear_cache(element)
|
||||
|
||||
# Import swept disk solids as Blender curves if possible.
|
||||
elements_without_openings = {e for e in elements if not getattr(e, "HasOpenings", False)}
|
||||
@@ -1916,11 +1907,11 @@ class Geometry(bonsai.core.tool.Geometry):
|
||||
previous_shape_aspect = inverse.OfShapeAspect[0]
|
||||
else:
|
||||
base_representation = inverse
|
||||
assert base_representation
|
||||
|
||||
# remove item from previous shape aspect
|
||||
if previous_shape_aspect:
|
||||
cls.remove_representation_items_from_shape_aspect(representation_items, previous_shape_aspect)
|
||||
assert base_representation
|
||||
shape_aspect_representation = cls.get_shape_aspect_representation(
|
||||
shape_aspect, base_representation, create_new=True
|
||||
)
|
||||
@@ -2179,7 +2170,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.wrapped_data.declaration().as_entity().all_attributes()
|
||||
if a.type_of_attribute()._is("IfcLengthMeasure")
|
||||
]
|
||||
|
||||
|
||||
@@ -548,9 +548,7 @@ class IfcGit(bonsai.core.tool.IfcGit):
|
||||
@classmethod
|
||||
def config_push(cls, repo: git.Repo) -> None:
|
||||
"""Set push.autoSetupRemote"""
|
||||
# Repository level only - a global push.* setting must not stop us
|
||||
# configuring this repo.
|
||||
config_reader = repo.config_reader("repository")
|
||||
config_reader = repo.config_reader()
|
||||
if not config_reader.has_section("push"):
|
||||
with repo.config_writer() as config_writer:
|
||||
config_writer.set_value("push", "default", "current")
|
||||
|
||||
@@ -1087,20 +1087,21 @@ class Loader(bonsai.core.tool.Loader):
|
||||
bm = bmesh.new()
|
||||
bm.from_mesh(mesh)
|
||||
prev_co = None
|
||||
if usage.LayerSetDirection == "AXIS2":
|
||||
layer_set_direction = usage.LayerSetDirection
|
||||
if layer_set_direction == "AXIS2":
|
||||
co = Vector((0.0, offset, 0.0))
|
||||
no = cls.get_extrusion_vector(element).normalized()
|
||||
no = no.cross(Vector([1.0, 0.0, 0.0]))
|
||||
elif usage.LayerSetDirection == "AXIS3":
|
||||
elif layer_set_direction == "AXIS3":
|
||||
co = Vector((0.0, 0.0, offset))
|
||||
no = cls.get_extrusion_vector(element).normalized()
|
||||
no = Vector([0.0, 0.0, 1.0])
|
||||
elif usage.LayerSetDirection == "AXIS1":
|
||||
elif layer_set_direction == "AXIS1":
|
||||
co = Vector((0.0, 0.0, offset))
|
||||
no = cls.get_extrusion_vector(element).normalized()
|
||||
no = Vector([1.0, 0.0, 0.0])
|
||||
else:
|
||||
assert False, usage.LayerSetDirection
|
||||
assert False, layer_set_direction
|
||||
no *= sense_factor
|
||||
# Cache this
|
||||
body = ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model", "Body", "MODEL_VIEW")
|
||||
|
||||
@@ -370,20 +370,21 @@ class Project(bonsai.core.tool.Project):
|
||||
props = cls.get_project_props()
|
||||
active_library_breadcrumb = props.get_active_library_breadcrumb()
|
||||
change_back = False
|
||||
name = breadcrumb_type = library_id = None
|
||||
breadcrumb = None
|
||||
if active_library_breadcrumb:
|
||||
name = active_library_breadcrumb.name
|
||||
breadcrumb_type = active_library_breadcrumb.breadcrumb_type
|
||||
library_id = active_library_breadcrumb.library_id
|
||||
breadcrumb = (name, breadcrumb_type, library_id)
|
||||
change_back = True
|
||||
|
||||
bpy.ops.bim.rewind_library()
|
||||
if change_back:
|
||||
assert name is not None and breadcrumb_type is not None and library_id is not None
|
||||
assert breadcrumb
|
||||
bpy.ops.bim.change_library_element(
|
||||
element_name=name,
|
||||
breadcrumb_type=breadcrumb_type,
|
||||
library_id=library_id,
|
||||
element_name=breadcrumb[0],
|
||||
breadcrumb_type=breadcrumb[1],
|
||||
library_id=breadcrumb[2],
|
||||
)
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -223,6 +223,18 @@ class Pset(bonsai.core.tool.Pset):
|
||||
elif pset.is_a("IfcMaterialProperties") or pset.is_a("IfcProfileProperties"):
|
||||
pset_props = pset.Properties
|
||||
|
||||
# If this Pset's owning element is classified against a bSDD class that defines
|
||||
# this Pset, recognise properties matching bSDD property codes as picklists,
|
||||
# even though the Pset wasn't necessarily created through the bSDD add-property UI.
|
||||
bsdd_allowed_values: dict[str, list[str]] = {}
|
||||
if pset.is_a("IfcPropertySet"):
|
||||
elements = ifcopenshell.util.element.get_elements_by_pset(pset)
|
||||
if elements:
|
||||
try:
|
||||
bsdd_allowed_values = tool.Bsdd.get_bsdd_pset_property_values(next(iter(elements)), pset.Name)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
prop_templates: dict[str, ifcopenshell.entity_instance] = {}
|
||||
if pset_template:
|
||||
prop_templates = {prop.Name: prop for prop in pset_template.HasPropertyTemplates}
|
||||
@@ -286,6 +298,17 @@ class Pset(bonsai.core.tool.Pset):
|
||||
metadata.set_value(metadata.get_value_default() if metadata.is_null else value)
|
||||
process_prop_description(metadata)
|
||||
|
||||
if prop.is_a("IfcPropertySingleValue") and (possible_values := bsdd_allowed_values.get(prop.Name)):
|
||||
str_value = None if value is None else str(value)
|
||||
if str_value is not None and str_value not in possible_values:
|
||||
# Preserve a legacy/imported value that doesn't match the current
|
||||
# bSDD enumeration instead of silently dropping it.
|
||||
possible_values = [*possible_values, str_value]
|
||||
metadata.enum_items = json.dumps(possible_values)
|
||||
metadata.data_type = "enum"
|
||||
if str_value is not None:
|
||||
metadata.enum_value = str_value
|
||||
|
||||
@classmethod
|
||||
def get_prop_template_primitive_type(cls, prop_template: ifcopenshell.entity_instance) -> str:
|
||||
if prop_template.TemplateType in ["Q_LENGTH", "Q_AREA", "Q_VOLUME", "Q_WEIGHT", "Q_TIME"]:
|
||||
|
||||
@@ -23,7 +23,7 @@ import re
|
||||
from collections.abc import Iterable
|
||||
from datetime import datetime
|
||||
from datetime import time as datetime_time
|
||||
from typing import TYPE_CHECKING, Any, Literal, Optional, Union
|
||||
from typing import TYPE_CHECKING, Any, Literal, Optional, Union, assert_never
|
||||
|
||||
import bpy
|
||||
import ifcopenshell
|
||||
@@ -1127,7 +1127,8 @@ class Sequence(bonsai.core.tool.Sequence):
|
||||
|
||||
@classmethod
|
||||
def load_default_animation_color_scheme(cls):
|
||||
groups = {
|
||||
GroupType = Literal["CREATION", "OPERATION", "MOVEMENT_TO", "DESTRUCTION", "MOVEMENT_FROM", "USERDEFINED"]
|
||||
groups: dict[GroupType, dict[str, Any]] = {
|
||||
"CREATION": {
|
||||
"PredefinedType": ["CONSTRUCTION", "INSTALLATION"],
|
||||
"Color": (0.0, 1.0, 0.0),
|
||||
@@ -1158,7 +1159,6 @@ class Sequence(bonsai.core.tool.Sequence):
|
||||
props.task_input_colors.clear()
|
||||
for group, data in groups.items():
|
||||
for predefined_type in data["PredefinedType"]:
|
||||
predefined_type_item = None
|
||||
if group in ("CREATION", "OPERATION", "MOVEMENT_TO"):
|
||||
predefined_type_item = props.task_output_colors.add()
|
||||
elif group in ("MOVEMENT_FROM",):
|
||||
@@ -1168,8 +1168,9 @@ class Sequence(bonsai.core.tool.Sequence):
|
||||
predefined_type_item2 = props.task_output_colors.add()
|
||||
predefined_type_item2.name = predefined_type
|
||||
predefined_type_item2.color = data["Color"]
|
||||
else:
|
||||
assert_never(group)
|
||||
# TO DO: consider cases where users confuses inputs and outputs
|
||||
assert predefined_type_item is not None
|
||||
predefined_type_item.name = predefined_type
|
||||
predefined_type_item.color = data["Color"]
|
||||
|
||||
|
||||
@@ -224,8 +224,8 @@ class Snap(bonsai.core.tool.Snap):
|
||||
|
||||
# Get axis that are closer than the stick factor threshold
|
||||
elegible_axis = []
|
||||
axis = None
|
||||
|
||||
axis = None
|
||||
for axis in snap_axis:
|
||||
if not axis:
|
||||
continue
|
||||
|
||||
@@ -979,24 +979,25 @@ class Spatial(bonsai.core.tool.Spatial):
|
||||
def get_purged_inner_holes_poly(cls, union_geom: Polygon, min_area: float) -> Polygon:
|
||||
interiors_list = []
|
||||
|
||||
new_poly = None
|
||||
poly = None
|
||||
if union_geom.geom_type == "MultiPolygon":
|
||||
poly = None
|
||||
for poly in union_geom.geoms:
|
||||
interiors_list = cls.get_poly_valid_interior_list(
|
||||
poly=poly, min_area=min_area, interiors_list=interiors_list
|
||||
)
|
||||
|
||||
assert poly is not None
|
||||
assert poly
|
||||
new_poly = Polygon(poly.exterior.coords, holes=interiors_list)
|
||||
|
||||
if union_geom.geom_type == "Polygon":
|
||||
elif union_geom.geom_type == "Polygon":
|
||||
interiors_list = cls.get_poly_valid_interior_list(
|
||||
poly=union_geom, min_area=min_area, interiors_list=interiors_list
|
||||
)
|
||||
new_poly = Polygon(union_geom.exterior.coords, holes=interiors_list)
|
||||
|
||||
assert new_poly is not None
|
||||
else:
|
||||
assert False, union_geom.geom_type
|
||||
|
||||
return new_poly
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -360,6 +360,10 @@ class Style(bonsai.core.tool.Style):
|
||||
material_output = tool.Blender.get_material_node(obj, "OUTPUT_MATERIAL", {"is_active_output": True})
|
||||
surface_output = get_input_node(material_output, "Surface")
|
||||
|
||||
# TODO: this variable is not really needed,
|
||||
# just workaround a for ty issue detecting unresolved refs.
|
||||
bsdf = None
|
||||
|
||||
if surface_output and surface_output.type == "MIX_SHADER":
|
||||
mix_shader = surface_output
|
||||
if (
|
||||
@@ -388,6 +392,7 @@ class Style(bonsai.core.tool.Style):
|
||||
and (bsdf := get_input_node(surface_output, input_index=1, of_type="BSDF_PRINCIPLED"))
|
||||
)
|
||||
):
|
||||
assert bsdf
|
||||
report(f"Because of {BLUE}BSDF_PRINCIPLED{R} node reflectance method identified as {BLUE}PHYSICAL{R}")
|
||||
attributes["ReflectanceMethod"] = "NOTDEFINED" if tool.Ifc.get_schema() != "IFC4X3" else "PHYSICAL"
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
# fmt: off
|
||||
# pylint: skip-file
|
||||
|
||||
import bpy
|
||||
import ifcopenshell.api
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
# fmt: off
|
||||
# pylint: skip-file
|
||||
|
||||
from math import pi
|
||||
from pathlib import Path
|
||||
|
||||
@@ -22,6 +22,9 @@ props = tool.Georeference.get_georeference_props()
|
||||
|
||||
props = tool.Project.get_project_props()
|
||||
|
||||
# Generally recommended to disable caching for stability right now
|
||||
props.should_cache = False
|
||||
|
||||
# If you are not authoring, it is recommended to enable this.
|
||||
# When enabled, types, openings, and non geometric elements are not loaded.
|
||||
props.is_coordinating = True
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user