Compare commits

..

20 Commits

Author SHA1 Message Date
Thomas Krijnen e42acdfa86 ifcquery plot subcommand 2026-03-01 21:01:09 +01:00
Bruno Postle 1801a768c1 Fix clipping when generating a wall 2026-03-01 17:07:50 +00:00
Bruno Postle 66f0b71c65 Add ifcmcp ifc_render tool and ifcquery render subcommand
Renders IFC model geometry to a PNG image using pyvista (off_screen).

ifcquery CLI:
  ifcquery <file> render [-o out.png] [--selector QUERY]
                         [--element ID[,ID...]] [--view iso|top|south|north|east|west]

ifcmcp MCP tool:
  ifc_render(selector, element_ids, view) -> list[ImageContent]
  Returns base64-encoded PNG as an MCP ImageContent block so the agent
  can inspect the model geometry inline.

Generated with the assistance of an AI coding tool.
2026-03-01 13:23:00 +00:00
Bruno Postle f4d7fda255 Support file paths in ifcedit project.append_asset
This now works:

  ifcedit run model.ifc project.append_asset --library library.ifc --element 5711

Generated with the assistance of an AI coding tool.
2026-02-28 07:19:51 +00:00
Bruno Postle c112c115ce Update README files
Generated with the assistance of an AI coding tool.
2026-02-23 23:30:07 +00:00
Bruno Postle 2a2ba45676 Add new ifcmcp tools to ifcchat tool schema
Adds ifc_validate, ifc_schedule, ifc_cost, ifc_schema, and ifc_quantify
to the hardcoded tool list in app.js to match the extended ifcmcp session.

Generated with the assistance of an AI coding tool.
2026-02-23 23:19:06 +00:00
Bruno Postle fd69c0534b Add validate, schedule, cost, schema, quantify to ifcquery/ifcedit/ifcmcp
ifcquery: validate [--rules], schedule [--depth N], cost [--depth N],
schema <EntityType>. ifcedit: quantify list/run subcommands using ifc5d
QTO rules. schedule and cost support max_depth to limit tree expansion,
replacing truncated levels with {truncated, count}. ifcmcp gains matching
session methods, @server.tool() decorators, and OpenAI tool schemas.

Generated with the assistance of an AI coding tool.
2026-02-23 23:11:35 +00:00
Thomas Krijnen 1226370b9e re-add AI generation disclosure 2026-02-21 09:37:29 +01:00
Thomas Krijnen 6e3eaa9b39 mv chat/ ifcchat 2026-02-21 09:34:42 +01:00
Bruno Postle 3ed2da23be Create missing CLI tools
Actually create the ifcquery, ifcedit and ifcmcp executables on pip
install instead of relying on `python3 -m ifcquery` style usage
2026-02-20 22:22:29 +00:00
Bruno Postle 249c498112 Add AGENTS.md contributor guide
Guidelines for external contributors using AI coding tools,
covering licensing, AI disclosure requirements, PR scope,
commit style, code formatting, and testing expectations.

Generated with the assistance of an AI coding tool.
2026-02-20 21:43:22 +00:00
Thomas Krijnen 2dde62803d Package ifcmcp as a HTML client-side chat app (#7697) 2026-02-20 20:54:16 +00:00
Bruno Postle d4ac3c36ce Add AI generation disclosure 2026-02-14 08:10:55 +00:00
Bruno Postle fecd4a810a black and ruff 2026-02-11 18:22:45 +00:00
Bruno Postle 060afb94b5 MCP wrapper for ifcquery and ifcedit
Model Context Protocol server implementation for AI agent access to IFC
projects
2026-02-10 22:32:43 +00:00
Bruno Postle b1f1de954d Rename ifcapi to ifcedit, README and black 2026-02-09 22:59:11 +00:00
Bruno Postle 12ed2d1ec3 Add ifcquery clash subcommand for geometric clash detection
Uses ifcopenshell.geom.tree API directly to check an element for
intersections and clearance violations against sibling elements
(--scope storey) or the entire model (--scope all).
2026-02-09 18:20:41 +00:00
Bruno Postle c77983ffe5 Add ifcquery relations subcommand for IFC relationship traversal
New subcommand: ifcquery <file> relations <element_id>
Returns all relationships for an element organised by category:
hierarchy (parent, container, aggregate, nest), children (contained,
parts, components, openings), type relationships, groups, systems,
zones, material, referenced structures, and connections/ports.
Empty categories are omitted from output.

Optional --traverse up flag walks the hierarchy from the element
up to IfcProject, returning the chain as a list.
2026-02-09 07:20:37 +00:00
Bruno Postle 78f26c2432 Add ifcapi CLI wrapper for ifcopenshell.api editor functions
Runtime introspection auto-discovers all API functions. Subcommands:
list (modules/functions), docs (parameter documentation), run (execute
with type-coerced CLI arguments). Output is JSON to stdout, supports
--dry-run validation and -o alternate output path.
2026-02-09 06:27:00 +00:00
Bruno Postle 39161e7256 Add ifcquery CLI tool for IFC model interrogation
Provides four subcommands for querying IFC models with JSON output:
- summary: schema, entity counts, project info
- tree: spatial hierarchy (Project > Site > Building > Storey > elements)
- info: deep element inspection (attributes, psets, type, material, container)
- select: filter elements using selector syntax
2026-02-08 23:24:48 +00:00
666 changed files with 7883 additions and 44879 deletions
+2 -3
View File
@@ -1,8 +1,7 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/BlankSpruce/gersemi/0.24.0/gersemi/configuration.schema.json
# Gersemi doesn't support autodetection of macros/functions from other files or from the current one
# and requires to explicitly list directories/cmake files that define them.
definitions: ["./cmake", "./src"]
# Needed for gersemi to detect custom functions and macros.
definitions: ["./cmake"]
disable_formatting: false
extensions: []
indent: 4
+7 -7
View File
@@ -40,8 +40,6 @@ jobs:
# preinstalled: xz, cmake
brew install git bison autoconf automake libffi findutils
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
- name: Install aws cli
run: |
@@ -49,11 +47,11 @@ jobs:
- name: Unpack Dependencies
run: |
cd build
python ../nix/cache_dependencies.py unpack
install_root=$(find ./build -maxdepth 4 -type d -name install 2>/dev/null | head -n 1 || true)
[ -n "$install_root" ] && find "$install_root" -type f -name 'cache-*.tar.gz' -maxdepth 1 -exec tar -xzf {} -C "$install_root" \; || true
- name: ccache
uses: hendrikmuhs/ccache-action@v1.2.22
uses: hendrikmuhs/ccache-action@v1.2
with:
key: mac-${{ matrix.arch }}
@@ -83,7 +81,7 @@ jobs:
- name: Upload Build Logs
if: always()
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@v6
with:
name: build-logs-osx-${{ matrix.arch }}
path: |
@@ -95,7 +93,9 @@ jobs:
- name: Pack Dependencies
run: |
cd build
python ../nix/cache_dependencies.py pack
for install_dir in $(find $(find . -maxdepth 4 -name install) -mindepth 1 -maxdepth 1 -type d); do
test -f $(dirname "$install_dir")/cache-$(basename "$install_dir").tar.gz || tar -czf $(dirname "$install_dir")/cache-$(basename "$install_dir").tar.gz -C $(dirname "$install_dir") $(basename "$install_dir");
done
- name: Commit and Push Changes to Build Repository
run: |
+4 -4
View File
@@ -26,10 +26,10 @@ jobs:
- name: Unpack Dependencies
run: |
cd ifcopenshell_build
python ../IfcOpenShell/nix/cache_dependencies.py unpack
python ../IfcOpenShell/pyodide/cache_dependencies.py unpack
- name: ccache
uses: hendrikmuhs/ccache-action@v1.2.22
uses: hendrikmuhs/ccache-action@v1.2
with:
key: ubuntu-22.04-${{ runner.arch }}
@@ -42,7 +42,7 @@ jobs:
- name: Upload Build Logs
if: always()
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@v6
with:
name: build-logs-pyodide
path: |
@@ -65,7 +65,7 @@ jobs:
- name: Pack Dependencies
run: |
cd ifcopenshell_build
python ../IfcOpenShell/nix/cache_dependencies.py pack
python ../IfcOpenShell/pyodide/cache_dependencies.py pack
- name: Commit and Push Changes to Build Repository
run: |
+13 -10
View File
@@ -6,13 +6,13 @@ on:
jobs:
build_ifcopenshell:
runs-on: ubuntu-22.04
container: rockylinux:9
container: rockylinux:8
steps:
- name: Install Dependencies
run: |
dnf update -y
dnf install -y gcc gcc-c++ git autoconf automake bison make zip cmake python3 python3-pip \
yum update -y
yum install -y gcc gcc-c++ git autoconf automake bison make zip cmake python3 \
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 \
@@ -38,19 +38,20 @@ jobs:
with:
repository: IfcOpenShell/build-outputs
path: ./build
ref: rockylinux9-x64
ref: rockylinux8-x64
lfs: true
token: ${{ secrets.BUILD_REPO_TOKEN }}
- name: Unpack Dependencies
run: |
cd build
python3 ../nix/cache_dependencies.py unpack
install_root=$(find ./build -maxdepth 4 -type d -name install 2>/dev/null | head -n 1 || true)
[ -n "$install_root" ] && find "$install_root" -type f -name 'cache-*.tar.gz' -maxdepth 1 -exec tar -xzf {} -C "$install_root" \; || true
- name: ccache
uses: hendrikmuhs/ccache-action@v1.2.22
# TODO: Use tag after 1.2.20 releases.
uses: hendrikmuhs/ccache-action@5ebbd400eff9e74630f759d94ddd7b6c26299639
with:
key: ubuntu-22.04-${{ runner.arch }}-rockylinux9
key: ubuntu-22.04-${{ runner.arch }}-rockylinux8
- name: Run Build Script
shell: bash
@@ -60,7 +61,7 @@ jobs:
- name: Upload Build Logs
if: always()
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@v6
with:
name: build-logs-rocky
path: |
@@ -71,7 +72,9 @@ jobs:
- name: Pack Dependencies
run: |
cd build
python3 ../nix/cache_dependencies.py pack
for install_dir in $(find $(find . -maxdepth 4 -name install) -mindepth 1 -maxdepth 1 -type d); do
test -f $(dirname "$install_dir")/cache-$(basename "$install_dir").tar.gz || tar -czf $(dirname "$install_dir")/cache-$(basename "$install_dir").tar.gz -C $(dirname "$install_dir") $(basename "$install_dir");
done
- name: Commit and Push Changes to Build Repository
run: |
+13 -10
View File
@@ -6,13 +6,13 @@ on:
jobs:
build_ifcopenshell:
runs-on: ubuntu-22.04-arm
container: arm64v8/rockylinux:9
container: arm64v8/rockylinux:8
steps:
- name: Install Dependencies
run: |
dnf update -y
dnf install -y gcc gcc-c++ git autoconf automake bison make zip cmake python3 python3-pip \
yum update -y
yum install -y gcc gcc-c++ git autoconf automake bison make zip cmake python3 \
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 \
@@ -38,19 +38,20 @@ jobs:
with:
repository: IfcOpenShell/build-outputs
path: ./build
ref: rockylinux9-arm64
ref: rockylinux8-arm64
lfs: true
token: ${{ secrets.BUILD_REPO_TOKEN }}
- name: Unpack Dependencies
run: |
cd build
python3 ../nix/cache_dependencies.py unpack
install_root=$(find ./build -maxdepth 4 -type d -name install 2>/dev/null | head -n 1 || true)
[ -n "$install_root" ] && find "$install_root" -type f -name 'cache-*.tar.gz' -maxdepth 1 -exec tar -xzf {} -C "$install_root" \; || true
- name: ccache
uses: hendrikmuhs/ccache-action@v1.2.22
# TODO: Use tag after 1.2.20 releases.
uses: hendrikmuhs/ccache-action@5ebbd400eff9e74630f759d94ddd7b6c26299639
with:
key: ubuntu-22.04-${{ runner.arch }}-rockylinux9
key: ubuntu-22.04-${{ runner.arch }}-rockylinux8
- name: Run Build Script
shell: bash
@@ -60,7 +61,7 @@ jobs:
- name: Upload Build Logs
if: always()
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@v6
with:
name: build-logs-rocky-arm64
path: |
@@ -71,7 +72,9 @@ jobs:
- name: Pack Dependencies
run: |
cd build
python3 ../nix/cache_dependencies.py pack
for install_dir in $(find $(find . -maxdepth 4 -name install) -mindepth 1 -maxdepth 1 -type d); do
test -f $(dirname "$install_dir")/cache-$(basename "$install_dir").tar.gz || tar -czf $(dirname "$install_dir")/cache-$(basename "$install_dir").tar.gz -C $(dirname "$install_dir") $(basename "$install_dir");
done
- name: Commit and Push Changes to Build Repository
run: |
+11 -28
View File
@@ -5,26 +5,11 @@ on:
jobs:
build_ifcopenshell:
runs-on: windows-2022
strategy:
fail-fast: false
matrix:
include:
- arch: x64
runs_on: windows-2022
deps_dir: _deps-vs2022-x64-installed
vcvars: '"C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat"'
build_branch: windows-x64
zip_suffix: win64
- arch: ARM64
runs_on: windows-11-arm
deps_dir: _deps-vs2022-ARM64-installed
vcvars: '"C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvarsarm64.bat"'
build_branch: windows-arm64
zip_suffix: win-arm64
runs-on: ${{ matrix.runs_on }}
arch: ['x64']
steps:
- name: Checkout Repository
uses: actions/checkout@v6
@@ -35,8 +20,8 @@ jobs:
uses: actions/checkout@v6
with:
repository: IfcOpenShell/build-outputs
path: ${{ matrix.deps_dir }}
ref: ${{ matrix.build_branch }}
path: _deps-vs2022-x64-installed
ref: windows-${{ matrix.arch }}
lfs: true
token: ${{ secrets.BUILD_REPO_TOKEN }}
@@ -46,13 +31,14 @@ jobs:
- name: Unpack Dependencies
run: |
cd ${{ matrix.deps_dir }}
cd _deps-vs2022-x64-installed
Get-ChildItem -Path . -Filter 'cache-*.zip' | ForEach-Object {
7z x $_.FullName
}
- name: ccache
uses: hendrikmuhs/ccache-action@v1.2.22
# TODO: Use tag after 1.2.20 releases.
uses: hendrikmuhs/ccache-action@5ebbd400eff9e74630f759d94ddd7b6c26299639
with:
key: win-${{ matrix.arch }}
# Windows ccache needs ~1GB
@@ -61,16 +47,14 @@ jobs:
- name: Run Build Script And Pack .zip Archives
shell: cmd
env:
TARGET_ARCH: ${{ matrix.arch }} # lets the Python script know which arch to target (optional override)
run: |
call ${{ matrix.vcvars }}
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat"
cd win
python build-all-win.py
- name: Pack Dependencies
run: |
cd ${{ matrix.deps_dir }}
cd _deps-vs2022-x64-installed
Get-ChildItem -Path . -Directory | ForEach-Object {
$cacheFile = "cache-$($_.Name).zip"
echo $cacheFile
@@ -81,13 +65,12 @@ jobs:
- name: Commit and Push Changes to Build Repository
run: |
cd ${{ matrix.deps_dir }}
cd _deps-vs2022-x64-installed
git config user.name "IfcOpenBot"
git config user.email "ifcopenbot@ifcopenshell.org"
git checkout -B ${{ matrix.build_branch }}
git add *.zip
git commit -m "Update build artifacts [skip ci]" || echo "No changes to commit"
git push --set-upstream origin ${{ matrix.build_branch }} || echo "Push failed"
git push || echo "Push failed"
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v6
+4 -9
View File
@@ -7,9 +7,6 @@ on:
jobs:
lint-formatting:
runs-on: ubuntu-latest
env:
MIN_IOS_PY_VERSION: "3.10"
MIN_BLENDER_PY_VERSION: "3.11"
steps:
- name: Action - checkout repository
uses: actions/checkout@v6
@@ -17,12 +14,12 @@ jobs:
- name: Action - install python
uses: actions/setup-python@v6
with:
python-version: ${{ env.MIN_IOS_PY_VERSION }}
python-version: "3.10"
- name: Action - install python
uses: actions/setup-python@v6
with:
python-version: ${{ env.MIN_BLENDER_PY_VERSION }}
python-version: "3.11"
- name: Install dependencies
run: |
@@ -36,10 +33,8 @@ jobs:
id: syntax-errors
run: |
ERROR=0
# Using 2 Python versions - one minimum required for IfcOpenShell
# and other that's used by Blender currently.
python${{ env.MIN_IOS_PY_VERSION }} -W error -m compileall -q src/ifcopenshell-python || ERROR=1
python${{ env.MIN_BLENDER_PY_VERSION }} -W error -m compileall -q src/bonsai || ERROR=1
python3.10 -W error -m compileall -q src/ifcopenshell-python || ERROR=1
python3.11 -W error -m compileall -q src/bonsai || ERROR=1
exit $ERROR
continue-on-error: true
+11 -11
View File
@@ -24,15 +24,9 @@ jobs:
runs-on: ubuntu-latest
if: |
github.repository == 'IfcOpenShell/IfcOpenShell'
outputs:
timestamp: ${{ steps.timestamp.outputs.timestamp }}
steps:
- name: Get current timestamp
id: timestamp
# Include hours and minutes to release tag
# to avoid possibility of unstable repo's index.json
# pointing to the new file when index.json itself wasn't yet updated.
run: echo "timestamp=$(date +'%y%m%d%H%M')" >> $GITHUB_OUTPUT
- name: Set env
run: echo ok go
build:
needs: activate
@@ -73,6 +67,12 @@ jobs:
- name: Get current version
id: version
run: echo "version=$(cat VERSION)" >> $GITHUB_OUTPUT
- name: Get current date
id: date
# Include hours and minutes to release tag
# to avoid possibility of unstable repo's index.json
# pointing to the new file when index.json itself wasn't yet updated.
run: echo "date=$(date +'%y%m%d%H%M')" >> $GITHUB_OUTPUT
- name: Compile
run: |
cd src/bonsai && make dist PLATFORM=${{ matrix.config.short_name }} PYVERSION=${{ matrix.pyver }}
@@ -88,8 +88,8 @@ jobs:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: ${{ steps.find_zip.outputs.filepath }}
asset_name: ${{ steps.find_zip.outputs.filename }}
release_name: "bonsai-${{steps.version.outputs.version}}-alpha${{ needs.activate.outputs.timestamp }} (unstable)"
tag: "bonsai-${{steps.version.outputs.version}}-alpha${{ needs.activate.outputs.timestamp }}"
release_name: "bonsai-${{steps.version.outputs.version}}-alpha${{steps.date.outputs.date}} (unstable)"
tag: "bonsai-${{steps.version.outputs.version}}-alpha${{steps.date.outputs.date}}"
overwrite: true
body: "See README in https://github.com/IfcOpenShell/bonsai_unstable_repo/ on how to setup autoupdates for daily Bonsai builds."
@@ -109,7 +109,7 @@ jobs:
# Ensure Bonsai and ifcsverchok enable/disable works before uploading to extensions repo.
# Download Blender.
wget -q -O blender.tar.xz https://download.blender.org/release/Blender5.0/blender-5.1.0-linux-x64.tar.xz
wget -q -O blender.tar.xz https://download.blender.org/release/Blender5.0/blender-5.0.1-linux-x64.tar.xz
tar -xf blender.tar.xz
# Setup Blender.
-35
View File
@@ -1,35 +0,0 @@
name: ci-ifcedit-pypi
on:
workflow_dispatch:
jobs:
activate:
runs-on: ubuntu-latest
if: |
github.repository == 'IfcOpenShell/IfcOpenShell'
steps:
- name: Set env
run: echo ok go
build:
needs: activate
runs-on: ubuntu-latest
strategy:
fail-fast: false
steps:
- uses: actions/checkout@v6
- uses: actions/setup-python@v6
with:
python-version: '3.11'
- name: Compile
run: |
pip install build
cd src/ifcedit &&
make dist IS_STABLE=TRUE
- name: Publish a Python distribution to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
user: __token__
password: ${{ secrets.PYPI_API_TOKEN }}
packages_dir: src/ifcedit/dist
-36
View File
@@ -1,36 +0,0 @@
name: ci-ifcmcp-pypi
on:
workflow_dispatch:
jobs:
activate:
runs-on: ubuntu-latest
if: |
github.repository == 'IfcOpenShell/IfcOpenShell'
steps:
- name: Set env
run: echo ok go
build:
needs: activate
runs-on: ubuntu-latest
strategy:
fail-fast: false
steps:
- uses: actions/checkout@v6
- uses: actions/setup-python@v6
with:
python-version: '3.11'
- name: Compile
run: |
pip install build
cd src/ifcmcp &&
make dist IS_STABLE=TRUE
- name: Publish a Python distribution to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
user: __token__
password: ${{ secrets.PYPI_API_TOKEN }}
packages_dir: src/ifcmcp/dist
verbose: true
@@ -24,7 +24,7 @@ jobs:
if: |
github.repository == 'IfcOpenShell/IfcOpenShell'
steps:
- uses: mamba-org/setup-micromamba@v3 # https://github.com/mamba-org/setup-micromamba
- uses: mamba-org/setup-micromamba@v2 # https://github.com/mamba-org/setup-micromamba
with:
environment-name: test-env
create-args: >-
@@ -84,7 +84,7 @@ jobs:
run: |
curl -L https://github.com/phracker/MacOSX-SDKs/releases/download/11.3/MacOSX10.13.sdk.tar.xz | tar -xvJf - -C /Users/runner/work/
- uses: mamba-org/setup-micromamba@v3 # https://github.com/mamba-org/setup-micromamba
- uses: mamba-org/setup-micromamba@v2 # https://github.com/mamba-org/setup-micromamba
with:
environment-name: test-env
create-args: >-
+7 -7
View File
@@ -35,7 +35,7 @@ jobs:
-
name: ccache
uses: hendrikmuhs/ccache-action@v1.2.22
uses: hendrikmuhs/ccache-action@v1.2
-
name: Build ifcopenshell
@@ -73,7 +73,7 @@ jobs:
make package
working-directory: build
- name: Upload
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@v6
with:
# Artifact name
name: ifcos-artifacts
@@ -91,26 +91,26 @@ jobs:
lfs: true
- name: Download
uses: actions/download-artifact@v8.0.1
uses: actions/download-artifact@v7.0.0
with:
# Artifact name
name: ifcos-artifacts
path: artifacts/
-
name: Set up QEMU
uses: docker/setup-qemu-action@v4
uses: docker/setup-qemu-action@v3
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
uses: docker/setup-buildx-action@v3
-
name: Login to Dockerhub
uses: docker/login-action@v4
uses: docker/login-action@v3
with:
username: aecgeeks
password: ${{ secrets.DOCKER_HUB_TOKEN }}
-
name: Build container image
uses: docker/build-push-action@v7
uses: docker/build-push-action@v6
with:
context: artifacts
repository: aecgeeks/ifcopenshell
@@ -24,7 +24,7 @@ jobs:
strategy:
fail-fast: false
matrix:
pyver: [py310, py311, py312, py313, py314]
pyver: [py39, py310, py311, py312, py313, py314]
config:
- {
name: "Windows 64bit",
+1 -1
View File
@@ -19,7 +19,7 @@ jobs:
strategy:
fail-fast: false
matrix:
pyver: [py310, py311, py312, py313, py314]
pyver: [py39, py310, py311, py312, py313, py314]
config:
- {
name: "Windows 64bit",
-35
View File
@@ -1,35 +0,0 @@
name: ci-ifcquery-pypi
on:
workflow_dispatch:
jobs:
activate:
runs-on: ubuntu-latest
if: |
github.repository == 'IfcOpenShell/IfcOpenShell'
steps:
- name: Set env
run: echo ok go
build:
needs: activate
runs-on: ubuntu-latest
strategy:
fail-fast: false
steps:
- uses: actions/checkout@v6
- uses: actions/setup-python@v6
with:
python-version: '3.11'
- name: Compile
run: |
pip install build
cd src/ifcquery &&
make dist IS_STABLE=TRUE
- name: Publish a Python distribution to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
user: __token__
password: ${{ secrets.PYPI_API_TOKEN }}
packages_dir: src/ifcquery/dist
+4 -3
View File
@@ -79,7 +79,10 @@ jobs:
libhdf5-dev libcgal-dev libeigen3-dev
- name: ccache
uses: hendrikmuhs/ccache-action@v1.2.22
# TODO: temporarily pointing to 1.2.19 to get notified by dependabot when 1.2.20 is released
# to update hardcoded references to commits in some other workflows.
# Then we can switch back to 1.2 in all actions.
uses: hendrikmuhs/ccache-action@v1.2.19
with:
key: ubuntu-22.04-${{ runner.arch }}
@@ -181,7 +184,6 @@ jobs:
"-DSCHEMA_VERSIONS=2x3;4;4x3_add2" \
-DGLTF_SUPPORT=On \
-DWITH_ROCKSDB=On \
-DBUILD_EXAMPLES=ON \
../cmake
sudo make -j $(nproc)
sudo make install
@@ -254,7 +256,6 @@ jobs:
cd ../ifcpatch && make test || ERROR=1
pip install -e ../ifctester --no-deps
cd ../ifctester && make test || ERROR=1
make build-ids-docs || ERROR=1
# Run mathutils related tests at the end to ensure no other code is relying on mathutils.
cd ../ifcopenshell-python
pip install mathutils
-65
View File
@@ -1,65 +0,0 @@
name: Deploy AI chat App to static page repo
permissions:
id-token: write
pages: write
on:
push:
paths:
- 'src/ifcchat/**'
- '.github/workflows/publish-aichat-app.yaml'
branches:
- v0.8.0
workflow_dispatch:
jobs:
activate:
runs-on: ubuntu-latest
if: |
github.repository == 'IfcOpenShell/IfcOpenShell'
steps:
- name: Set env
run: echo ok go
build:
needs: activate
runs-on: ubuntu-latest
steps:
- name: Checkout (recursive)
uses: actions/checkout@v6
with:
submodules: recursive
fetch-depth: 0
- name: Checkout intermediate Pages repo
uses: actions/checkout@v4
with:
repository: IfcOpenShell/aichat_ifcopenshell_org_static_html
ref: gh-pages
path: output
token: ${{ secrets.WEBSITE_PUBLISH }}
- name: Sync demo app into target subfolder
run: |
rsync -av --delete --exclude='.git/' src/ifcchat/ output/
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: "3.x"
- name: Download wheels
working-directory: output/
run: |
pip download ifcquery==0.8.5 ifcopenshell-mcp==0.8.5 ifcedit==0.8.5 lark==1.3.1 isodate==0.7.2 --no-deps -d ./dist
- name: Commit and push if changed
working-directory: output
run: |
git config --global user.name 'IfcOpenBot'
git config --global user.email 'IfcOpenBot@users.noreply.github.com'
git add .
if git diff --cached --quiet; then
echo "No changes to commit"
exit 0
fi
git commit -m "$(git log --oneline -1)"
git push origin gh-pages
+17 -24
View File
@@ -1,4 +1,4 @@
name: Deploy Pyodide Demo App to static page repo
name: Deploy Pyodide Demo App to GitHub Pages
permissions:
id-token: write
@@ -11,7 +11,6 @@ on:
- '.github/workflows/publish-pyodide-demo-app.yml'
branches:
- v0.8.0
workflow_dispatch:
jobs:
activate:
@@ -31,27 +30,21 @@ jobs:
with:
submodules: recursive
fetch-depth: 0
- name: Checkout intermediate Pages repo
uses: actions/checkout@v4
- name: Setup Pages
uses: actions/configure-pages@v5
- name: Upload static files as artifact
id: deployment
uses: actions/upload-pages-artifact@v4
with:
repository: IfcOpenShell/wasm_ifcopenshell_org_static_html
ref: gh-pages
path: output
token: ${{ secrets.WEBSITE_PUBLISH }}
- name: Sync demo app into target subfolder
run: |
rsync -av --delete --exclude='.git/' src/pyodide/demo-app/ output/
- name: Commit and push if changed
working-directory: output
run: |
git config --global user.name 'IfcOpenBot'
git config --global user.email 'IfcOpenBot@users.noreply.github.com'
path: src/pyodide/demo-app/
git add .
if git diff --cached --quiet; then
echo "No changes to commit"
exit 0
fi
git commit -m "$(git log --oneline -1)"
git push origin gh-pages
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
needs: build
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
+1 -16
View File
@@ -5,8 +5,6 @@
/_installed-vs*-x*/
/build/
/src/examples/build/
# ifctester docs output
/src/ifctester/test/build/
# output directories
/cmake/out/
@@ -14,7 +12,6 @@
/src/ifcmax/out/
/src/ifcwrap/out/
/src/qtviewer/out/
/src/ifctester/webapp/public/pyodide/
/win/BuildDepsCache*.txt
@@ -83,14 +80,8 @@ src/ifcopenshell-python/test/build
# bonsai i18n
src/bonsai/bonsai/translations.py
# bonsai external dependencies (cloned for just ty checks)
src/bonsai/external_dependencies/
# bonsai test temp/cache files
# bonsai test temp files
src/bonsai/test/files/temp
src/bonsai/test/files/*.cache.blend
src/bonsai/test/files/*.cache.json
src/bonsai/test/files/*.cache.sqlite
# bonsai data
src/bonsai/bonsai/bim/data/build/
@@ -122,9 +113,3 @@ dev_environment.bat
src/ifcopenshell-python/ifcopenshell/express/*.exp
src/ifcopenshell-python/ifcopenshell/express/*.exp.cache.dat
# temp files from AI coding tools
*.claude
*.py.tmp*
*.json.tmp*
-3
View File
@@ -50,13 +50,10 @@ Contents
| [ifcconvert](https://docs.ifcopenshell.org/ifcconvert.html) | CLI app to convert IFC to many other formats | LGPL-3.0-or-later\* | [![Official](https://img.shields.io/badge/IfcOpenShell.org-Download-70ba35)](https://docs.ifcopenshell.org/ifcconvert/installation.html) [![GitHub](https://img.shields.io/github/v/release/ifcopenshell/ifcopenshell?filter=ifcconvert-*&label=GitHub&color=f6f8fa)](https://github.com/IfcOpenShell/IfcOpenShell/releases?q=ifcconvert&expanded=true)
| [ifccsv](https://docs.ifcopenshell.org/ifccsv.html) | Library and CLI app to export and import schedules from IFC | LGPL-3.0-or-later | [![PyPI](https://img.shields.io/pypi/v/ifccsv?label=PyPI&color=006dad)](https://pypi.org/project/ifccsv/) |
| [ifcdiff](https://docs.ifcopenshell.org/ifcdiff.html) | Compare changes between IFC models | LGPL-3.0-or-later | [![PyPI](https://img.shields.io/pypi/v/ifcdiff?label=PyPI&color=006dad)](https://pypi.org/project/ifcdiff/) |
| [ifcedit](https://docs.ifcopenshell.org/ifcedit.html) | CLI wrapper for ifcopenshell.api IFC model mutation functions | LGPL-3.0-or-later | [![PyPI](https://img.shields.io/pypi/v/ifcedit?label=PyPI&color=006dad)](https://pypi.org/project/ifcedit/) |
| [ifcfm](https://docs.ifcopenshell.org/ifcfm.html) | Extract IFC data for FM handover requirements | LGPL-3.0-or-later | [![PyPI](https://img.shields.io/pypi/v/ifcfm?label=PyPI&color=006dad)](https://pypi.org/project/ifcfm/) |
| [ifcmax](https://docs.ifcopenshell.org/ifcmax.html) | Historic extension for IFC support in 3DS Max | LGPL-3.0-or-later\* | [![Official](https://img.shields.io/badge/IfcOpenShell.org-Download-70ba35)](https://docs.ifcopenshell.org/ifcmax.html)
| [ifcmcp](https://docs.ifcopenshell.org/ifcmcp.html) | MCP server for querying and editing IFC building models | LGPL-3.0-or-later | [![PyPI](https://img.shields.io/pypi/v/ifcmcp?label=PyPI&color=006dad)](https://pypi.org/project/ifcmcp/) |
| [ifcopenshell-python](https://docs.ifcopenshell.org/ifcopenshell-python.html) | Python library for IFC manipulation | LGPL-3.0-or-later\* | [![Official](https://img.shields.io/badge/IfcOpenShell.org-Download-70ba35)](https://docs.ifcopenshell.org/ifcopenshell-python/installation.html) [![GitHub](https://img.shields.io/github/v/release/ifcopenshell/ifcopenshell?filter=ifcopenshell-python-*&label=GitHub&color=f6f8fa)](https://github.com/IfcOpenShell/IfcOpenShell/releases?q=ifcopenshell-python&expanded=true) [![PyPI](https://img.shields.io/pypi/v/ifcopenshell?label=PyPI&color=006dad)](https://pypi.org/project/ifcopenshell/) [![Anaconda](https://img.shields.io/conda/vn/conda-forge/ifcopenshell?label=Anaconda&color=43b02a)](https://anaconda.org/conda-forge/ifcopenshell) [![Anaconda](https://img.shields.io/conda/vn/ifcopenshell/ifcopenshell?label=Anaconda-Unstable&color=43b02a)](https://anaconda.org/ifcopenshell/ifcopenshell) [![Docker](https://img.shields.io/docker/pulls/aecgeeks/ifcopenshell?label=Docker&color=1D63ED)](https://hub.docker.com/r/aecgeeks/ifcopenshell) [![AUR](https://img.shields.io/aur/version/ifcopenshell?label=AUR&color=1793d1)](https://aur.archlinux.org/packages/ifcopenshell) [![AUR Unstable](https://img.shields.io/aur/version/ifcopenshell-git?label=AUR-Unstable&color=1793d1)](https://aur.archlinux.org/packages/ifcopenshell-git) [Pyodide WASM Wheels](https://github.com/IfcOpenShell/wasm-wheels#pyodide-test-wheels) |
| [ifcpatch](https://docs.ifcopenshell.org/ifcpatch.html) | Utility to run pre-packaged scripts to manipulate IFCs | LGPL-3.0-or-later | [![PyPI](https://img.shields.io/pypi/v/ifcpatch?label=PyPI&color=006dad)](https://pypi.org/project/ifcpatch/) |
| [ifcquery](https://docs.ifcopenshell.org/ifcquery.html) | CLI tool for querying and inspecting IFC building models | LGPL-3.0-or-later | [![PyPI](https://img.shields.io/pypi/v/ifcquery?label=PyPI&color=006dad)](https://pypi.org/project/ifcquery/) |
| [ifcsverchok](https://docs.ifcopenshell.org/ifcsverchok.html) | Blender Add-on for visual node programming with IFC | GPL-3.0-or-later | [![GitHub Unstable](https://img.shields.io/github/v/release/ifcopenshell/ifcopenshell?filter=ifcsverchok-*.*.*.*&label=GitHub-Unstable&color=f6f8fa)](https://github.com/IfcOpenShell/IfcOpenShell/releases?q=ifcsverchok&expanded=true)
| [ifctester](https://docs.ifcopenshell.org/ifctester.html) | Library, CLI and webapp for IDS model auditing | LGPL-3.0-or-later | [![PyPI](https://img.shields.io/pypi/v/ifctester?label=PyPI&color=006dad)](https://pypi.org/project/ifctester/) |
+10 -19
View File
@@ -13,7 +13,6 @@ import hashlib
import os
import pathlib
import re
import subprocess
from typing import NoReturn
from urllib import request
@@ -21,7 +20,7 @@ from github import Github
def get_repo_tag_names() -> list[str]:
git_return = subprocess.check_output("git tag -l", text=True)
git_return = os.popen("git tag -l").read()
tag_names = [tag_name for tag_name in git_return.split("\n") if tag_name]
print(f"{len(tag_names)} tag_names found in repo")
return tag_names
@@ -79,10 +78,6 @@ def get_release_zip(tag: str) -> tuple[str, str]:
raise Exception(f"Couldn't find the release matching '{python_version}' and '{TARGET_OS}' in tag '{tag}'.")
def run(command: str) -> None:
subprocess.check_output(command)
start = datetime.datetime.now()
URL_CHOCO_PACKAGE = "https://community.chocolatey.org/packages/blender"
@@ -102,7 +97,7 @@ should_release = False
target_release_tag = ""
TARGET_OS = "windows-x64"
git_status = subprocess.check_output("git status", text=True)
git_status = os.popen("git status").read()
print(git_status)
for tag_name in get_repo_tag_names():
@@ -152,7 +147,7 @@ blenderbim_build_version = target_release_tag.replace("blenderbim-", "")
# url_blenderbim_py3x_win_zip
release_zip_file_name, url_blenderbim_py3x_win_zip = get_release_zip(target_release_tag)
subprocess.check_call(f"wget {url_blenderbim_py3x_win_zip} --no-verbose")
os.popen(f"wget {url_blenderbim_py3x_win_zip} --no-verbose").read()
# sha256sum_blenderbim_py310_win_zip
sha256sum_blenderbim_py3x_win_zip = get_file_sha256_hash(release_zip_file_name)
@@ -206,13 +201,13 @@ print("[INFO] inserting dynamic chocolatey package parameters successful")
print("\n_____ build choco.exe with mono")
choco_version = "1.1.0"
run(f"wget https://github.com/chocolatey/choco/archive/refs/tags/{choco_version}.tar.gz --quiet")
run(f"tar -xzf {choco_version}.tar.gz")
os.popen(f"wget https://github.com/chocolatey/choco/archive/refs/tags/{choco_version}.tar.gz --quiet").read()
os.popen(f"tar -xzf {choco_version}.tar.gz").read()
print("choco tar unpack successful")
os.chdir("choco-1.1.0")
run("./build.sh")
os.popen("./build.sh").read()
run("cp -r build_output/chocolatey /opt/chocolatey")
os.popen("cp -r build_output/chocolatey /opt/chocolatey").read()
os.chdir(BLENDERBIM_DIR)
if pathlib.Path("/opt/chocolatey/choco.exe").exists():
@@ -220,15 +215,11 @@ if pathlib.Path("/opt/chocolatey/choco.exe").exists():
print("\n_____ build choco pack")
run("mono /opt/chocolatey/choco.exe pack --allow-unofficial")
run(
'mono /opt/chocolatey/choco.exe setapikey --key="{choco_token}" --source="https://push.chocolatey.org/" --allow-unofficial'
)
os.popen("mono /opt/chocolatey/choco.exe pack --allow-unofficial").read()
os.popen('mono /opt/chocolatey/choco.exe setapikey --key="{choco_token}" --source="https://push.chocolatey.org/" --allow-unofficial').read()
print("\n_____ build choco push")
run(
'mono /opt/chocolatey/choco.exe push --source="https://push.chocolatey.org/" --key="$CHOCO_TOKEN" --allow-unofficial --verbose'
)
os.popen('mono /opt/chocolatey/choco.exe push --source="https://push.chocolatey.org/" --key="$CHOCO_TOKEN" --allow-unofficial --verbose').read()
print(f"choco push of version: {target_release_tag} successful!")
print(f"it took: {datetime.datetime.now() - start}")
+10 -18
View File
@@ -80,7 +80,7 @@ option(BUILD_IFCGEOM "Build IfcGeom." ON)
option(BUILD_IFCPYTHON "Build IfcPython." ON)
option(BUILD_CONVERT "Build IfcConvert executable." ON)
option(BUILD_DOCUMENTATION "Build IfcOpenShell Documentation." OFF)
option(BUILD_EXAMPLES "Build example applications." OFF)
option(BUILD_EXAMPLES "Build example applications." ON)
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_QTVIEWER "Build IfcOpenShell Qt GUI Viewer" OFF) # QtViewer requires Qt6
@@ -258,10 +258,10 @@ if(WITH_ROCKSDB)
set(ROCKSDB_LIBRARIES "IFCOPENSHELL_RocksDB")
target_compile_definitions(IFCOPENSHELL_RocksDB INTERFACE IFOPSH_WITH_ROCKSDB)
set(SWIG_DEFINES ${SWIG_DEFINES} -DIFOPSH_WITH_ROCKSDB)
# Shared binaries for `rocksdb` only support limited API (only `c.h`), but we use `db.h` API.
# So rocksdb supported only as a static library.
# See https://github.com/facebook/rocksdb/issues/981.
target_link_libraries(IFCOPENSHELL_RocksDB INTERFACE RocksDB::rocksdb)
target_link_libraries(
IFCOPENSHELL_RocksDB
INTERFACE $<IF:$<TARGET_EXISTS:RocksDB::rocksdb-shared>,RocksDB::rocksdb-shared,RocksDB::rocksdb>
)
if(WITH_ZSTD)
# @todo do we actually need the zstd include dir or rather just pass
@@ -368,20 +368,12 @@ if(ENABLE_BUILD_OPTIMIZATIONS)
# Linker
# /OPT:REF enables also /OPT:ICF and disables INCREMENTAL
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(LINKER_FLAGS_RELWITHDEBINFO "/DEBUG /OPT:NOICF")
set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} /LTCG /OPT:REF")
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}"
)
# /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")
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")
-11
View File
@@ -43,17 +43,6 @@ if(NOT OCC_INCLUDE_DIR AND NOT OCC_LIBRARY_DIR)
set_target_properties(TKernel PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${OpenCASCADE_INCLUDE_DIR}")
endif()
if(
OpenCASCADE_VERSION VERSION_LESS "7.9.0"
AND CMAKE_VERSION GREATER_EQUAL "3.24"
AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU"
)
# Before 7.9.0 targets in OCCT cmake configs are not linked to each other
# leading to missing symbols on Unix. Link them as a single group as a workaround.
# Only needed for gcc, because other compilers (e.g. Apple Clang, MSVC) do rescan automatically.
set(OpenCASCADE_LIBRARIES "$<LINK_GROUP:RESCAN,${OpenCASCADE_LIBRARIES}>")
endif()
if(OpenCASCADE_VERSION VERSION_LESS "7.9.0" AND WIN32)
# Bug in OCCT cmake configs < 7.9.0 - missing linked library.
list(APPEND OpenCASCADE_LIBRARIES WSOCK32.lib)
+77 -60
View File
@@ -75,30 +75,27 @@ Used environment variables:
# #
# for python37 to install correctly additionally: #
# * libffi(-dev[el]) #
# for Python build we also needs ssl and zlib #
# for Python build we also needs ssl #
# (since we do `pip install numpy` at the end) #
# * libssl-dev #
# #
# on debian 7.8 these can be obtained with: #
# $ apt-get install git gcc g++ autoconf bison bzip2 cmake #
# mesa-common-dev libffi-dev libfontconfig1-dev #
# libssl-dev xz zlib1g-dev #
# libssl-dev xz #
# #
# on ubuntu 14.04: #
# $ apt-get install git gcc g++ autoconf bison make cmake #
# mesa-common-dev libffi-dev libfontconfig1-dev #
# libssl-dev xz-utils zlib1g-dev #
# libssl-dev xz-utils #
# #
# on OS X El Capitan with homebrew: #
# $ brew install git bison autoconf automake libffi cmake #
# $ # `bison` shipped with Mac is too old for swig build, #
# $ # so we use `brew`. #
# $ export PATH=$(brew --prefix bison)/bin:$PATH #
# #
# on RHEL-related distros: #
# $ dnf install git gcc gcc-c++ autoconf bison make cmake #
# $ yum install git gcc gcc-c++ autoconf bison make cmake #
# mesa-libGL-devel libffi-devel fontconfig-devel bzip2 #
# automake patch byacc xz zlib-devel openssl-devel #
# automake patch byacc xz #
"""
@@ -150,8 +147,9 @@ OCCT_VERSION = "7.8.1"
BOOST_VERSION = "1.86.0"
EIGEN_VERSION = "3.4.0"
PCRE_VERSION = "8.41"
PCRE2_VERSION = "10.32"
LIBXML2_VERSION = "2.13.8"
SWIG_VERSION = "4.2.1"
SWIG_VERSION = "4.1.0"
OPENCOLLADA_VERSION = "v1.6.68"
HDF5_VERSION = "1.13.1"
@@ -248,8 +246,15 @@ if WASM:
# https://github.com/pyodide/pyodide-build/pull/249
WASM_CMAKE_IS_USING_INIT_VARS = get_pyodide_build_version() >= (99, 0, 0)
# 0.31 is required for SIDE_MODULE_CXXFLAGS to be provided.
assert get_pyodide_build_version() >= (0, 31)
# pyodide provide empty `CXXFLAGS`, leading to issues using C++ files compiled with `-fexceptions`
# which is used by OCCT.
# https://github.com/pyodide/pyodide-build/issues/251
side_module_cxx_flags = os.environ.get("SIDE_MODULE_CXXFLAGS", "")
if side_module_cxx_flags.strip():
print("SIDE_MODULE_CXXFLAGS are already passed from pyodide build ('{side_module_cxx_flags}').")
print("Maybe it's time to stop overriding them in the script?")
os.environ["SIDE_MODULE_CXXFLAGS"] = os.environ["SIDE_MODULE_CFLAGS"]
# Set defaults for missing empty environment variables
@@ -289,7 +294,6 @@ DEPS_DIR = os.getenv("DEPS_DIR", DEFAULT_DEPS_DIR)
if not os.path.exists(DEPS_DIR):
os.makedirs(DEPS_DIR)
INSTALL_DIR = Path(DEPS_DIR) / "install"
BUILD_CFG = os.getenv("BUILD_CFG", "RelWithDebInfo")
@@ -315,18 +319,24 @@ cecho(f"* Build Directory = {BUILD_DIR}", MAGENTA)
cecho(f"* Dependency Directory = {DEPS_DIR}", MAGENTA)
cecho(f" - The directory where {PROJECT_NAME} dependencies are installed.")
cecho(f"* Build Config Type = {BUILD_CFG}", MAGENTA)
cecho(""" - The used build configuration type for the dependencies.
Defaults to RelWithDebInfo if not specified.""")
cecho(
""" - The used build configuration type for the dependencies.
Defaults to RelWithDebInfo if not specified."""
)
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(
""" - 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.
""")
cecho(
""" - IFC Schemas to compile. If not provided, fallback to default provided in cmake.
"""
)
dependency_tree: "dict[str, tuple[str, ...]]" = {
"IfcParse": ("boost", "libxml2", "hdf5", "rocksdb"),
@@ -335,12 +345,13 @@ dependency_tree: "dict[str, tuple[str, ...]]" = {
"OpenCOLLADA": ("libxml2", "pcre"),
"IfcGeomServer": ("IfcGeom",),
"IfcOpenShell-Python": ("python", "swig", "IfcGeom"),
"swig": (),
"swig": ("pcre2",),
"boost": (),
"libxml2": (),
"python": (),
"occ": (),
"pcre": (),
"pcre2": (),
"json": (),
"hdf5": (),
"cgal": (),
@@ -407,6 +418,7 @@ if WASM:
"opencollada",
"swig",
"pcre",
"pcre2",
"IfcGeom",
"IfcConvert",
"IfcGeomServer",
@@ -421,16 +433,13 @@ print("Building:", *sorted(targets, key=lambda t: len(list(gather_dependencies(t
# Check that required tools are in PATH
yacc = "yacc" # Used during swig building process, installed with `bison` on Debian / `byacc` on Red Hat.
bison = "bison"
missing_commands: "list[str]" = []
required_commands = [git, bunzip2, tar, cc, cplusplus, autoconf, automake, make, "patch", "cmake", yacc, xz, bison]
required_commands = [git, bunzip2, tar, cc, cplusplus, autoconf, automake, make, "patch", "cmake", yacc, xz]
if "wasm" in flags:
# Skip swig build for WASM.
required_commands.append("swig")
required_commands.append("pyodide")
required_commands.remove(yacc)
required_commands.remove(bison)
for cmd in required_commands:
if shutil.which(cmd) is None:
@@ -489,7 +498,7 @@ def run(cmds: "Sequence[str]", cwd: "Union[str, None]" = None, can_fail: bool =
collector.append(line)
pipe.close()
logger.debug(f"running command `{' '.join(cmds)}` in directory '{cwd}'")
logger.debug(f"running command {' '.join(cmds)} in directory {cwd}")
stdout: list[str] = []
stderr: list[str] = []
@@ -535,14 +544,14 @@ 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(arg1: 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(
[bash, "./autogen.sh"], cwd=os.path.realpath(os.path.join(cwd, ".."))
) # only run autogen.sh in the directory it is located and use cwd to achieve that in order to not mess up things
# Using `sh` over `bash` fixes issues with building swig
prefix = os.path.realpath(f"{DEPS_DIR}/install/{dependency_name}")
prefix = os.path.realpath(f"{DEPS_DIR}/install/{arg1}")
wasm = []
if "wasm" in flags:
@@ -921,15 +930,20 @@ if "pcre" in targets:
restore_env("CC", OLD_CC)
restore_env("CXX", OLD_CXX)
if "swig" in targets:
dependency_name = f"swig-{SWIG_VERSION}"
if "pcre2" in targets:
build_dependency(
name=dependency_name,
mode="cmake",
build_tool_args=[
"-DWITH_PCRE=OFF",
f"-DCMAKE_INSTALL_PREFIX={DEPS_DIR}/install/{dependency_name}",
],
name=f"pcre2-{PCRE2_VERSION}",
mode="autoconf",
build_tool_args=[DISABLE_FLAG],
download_url=f"https://downloads.sourceforge.net/project/pcre/pcre2/{PCRE2_VERSION}/",
download_name=f"pcre2-{PCRE2_VERSION}.tar.bz2",
)
if "swig" in targets:
build_dependency(
name=f"swig-{SWIG_VERSION}",
mode="autoconf",
build_tool_args=["--disable-ccache", f"--with-pcre2-prefix={DEPS_DIR}/install/pcre2-{PCRE2_VERSION}"],
download_url="https://github.com/swig/swig.git",
download_name="swig",
download_tool=download_tool_git,
@@ -937,18 +951,21 @@ if "swig" in targets:
)
if USE_OCCT and "occ" in targets:
occt_args: "list[str]" = []
patches: "list[str]" = []
patches = []
if OCCT_VERSION < "7.4":
patches.append("./patches/occt/enable-exception-handling.patch")
# Skip ExpToCasExe as we don't need it and it requires additional dependencies.
# Before 7.7.2 ExpToCasExe is part of DataExchange, DETools doesn't exist yet.
# Since we do need DataExchange (used for IgesSerializer), we use a patch to skip only ExpToCasExe.
if "7.7.2" > OCCT_VERSION >= "7.7":
if OCCT_VERSION == "7.7.1":
patches.append("./patches/occt/no_ExpToCasExe.patch")
elif OCCT_VERSION >= "7.7.2":
occt_args.append("-DBUILD_MODULE_DETools=OFF")
if OCCT_VERSION == "7.7.2":
patches.append("./patches/occt/no_ExpToCasExe_7_7_2.patch")
if OCCT_VERSION == "7.8.1":
patches.append("./patches/occt/no_ExpToCasExe_7_8_1.patch")
if OCCT_VERSION == "7.9.1":
patches.append("./patches/occt/no_ExpToCasExe_7_9_1.patch")
if "wasm" in flags:
patches.append("./patches/occt/no_em_js.patch")
@@ -969,7 +986,6 @@ if USE_OCCT and "occ" in targets:
f"-DUSE_GLES2=OFF",
f"-DCMAKE_POLICY_VERSION_MINIMUM=3.5",
*MAC_CROSS_COMPILE_INTEL_ARGS,
*occt_args,
],
download_url="https://github.com/Open-Cascade-SAS/OCCT",
download_name="occt",
@@ -1085,19 +1101,23 @@ if "python" in targets and not USE_CURRENT_PYTHON_VERSION and "wasm" not in flag
PYTHON_CONFIGURE_ARGS.extend(["--with-universal-archs=intel-64", "--enable-universalsdk"])
for PYTHON_VERSION in PYTHON_VERSIONS:
# Don't fail silently on missing Python dependencies (e.g. openssl or zlib),
# because later ifcopenshell-python build will fail too but in a more confusing way.
build_dependency(
f"python-{PYTHON_VERSION}",
"autoconf",
PYTHON_CONFIGURE_ARGS,
f"http://www.python.org/ftp/python/{PYTHON_VERSION}/",
f"Python-{PYTHON_VERSION}.tgz",
)
python_bin = INSTALL_DIR / f"python-{PYTHON_VERSION}" / "bin" / "python3"
# `_ssl` module is present -> we will be able to install `numpy` later
# to verify IfcOpenShell installation
run([str(python_bin), "-c", "import _ssl"])
try:
build_dependency(
f"python-{PYTHON_VERSION}",
"autoconf",
PYTHON_CONFIGURE_ARGS,
f"http://www.python.org/ftp/python/{PYTHON_VERSION}/",
f"Python-{PYTHON_VERSION}.tgz",
)
except RuntimeError as e:
# Sometimes setting up modules such as pip/lzma can cause
# the python installer script to return a non zero exit
# code where actually the headers and dynamic libraries
# are installed correctly. This is all we need so we catch
# the exception and only reraise if a partially successful
# install is not detected.
if not os.path.exists(os.path.join(DEPS_DIR, "install", f"python-{PYTHON_VERSION}")):
raise e
if MAC_CROSS_COMPILE_INTEL:
assert original_path
@@ -1515,16 +1535,13 @@ if "IfcOpenShell-Python" in targets:
)
# Copy setup.py where pyodide build system expects it.
shutil.copy(REPO_PATH / "pyodide" / "setup.py", REPO_PATH)
# Empty pyproject so it's contents won't affect the resulting wheelthe the
# otherwise the wheel will use version and dependencies from toml, not setup.py.
(REPO_PATH / "pyproject.toml").write_text("")
elif USE_CURRENT_PYTHON_VERSION:
python_info = sysconfig.get_paths()
compile_python_wrapper(platform.python_version(), python_info["include"], sys.executable)
else:
for python_version in PYTHON_VERSIONS:
python_path = INSTALL_DIR / f"python-{python_version}"
python_path = Path(DEPS_DIR) / "install" / f"python-{python_version}"
module_dir = compile_python_wrapper(python_version, python_path=python_path)
assert module_dir
# Not sure why, but added after reading this in the logs
+13 -9
View File
@@ -1,9 +1,13 @@
--- a/adm/MODULES
+++ b/adm/MODULES
@@ -3,5 +3,5 @@ ModelingData TKG2d TKG3d TKGeomBase TKBRep
ModelingAlgorithms TKGeomAlgo TKTopAlgo TKPrim TKBO TKBool TKHLR TKFillet TKOffset TKFeat TKMesh TKXMesh TKShHealing
Visualization TKService TKV3d TKOpenGl TKOpenGles TKMeshVS TKIVtk TKD3DHost
ApplicationFramework TKCDF TKLCAF TKCAF TKBinL TKXmlL TKBin TKXml TKStdL TKStd TKTObj TKBinTObj TKXmlTObj TKVCAF
-DataExchange TKXDE TKXSBase TKSTEPBase TKSTEPAttr TKSTEP209 TKSTEP TKIGES TKXCAF TKXDEIGES TKXDESTEP TKSTL TKVRML TKXmlXCAF TKBinXCAF TKRWMesh TKXDECascade TKExpress ExpToCasExe
+DataExchange TKXDE TKXSBase TKSTEPBase TKSTEPAttr TKSTEP209 TKSTEP TKIGES TKXCAF TKXDEIGES TKXDESTEP TKSTL TKVRML TKXmlXCAF TKBinXCAF TKRWMesh TKXDECascade TKExpress
Draw TKDraw TKTopTest TKOpenGlTest TKOpenGlesTest TKD3DHostTest TKViewerTest TKXSDRAW TKDCAF TKXDEDRAW TKTObjDRAW TKQADraw TKIVtkDraw DRAWEXE
diff --git a/CMakeLists.txt b/CMakeLists.txt
index fd17283f77..6cecf9dad3 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -826,6 +826,8 @@ if (EMSCRIPTEN)
list (REMOVE_ITEM BUILD_TOOLKITS ExpToCasExe)
endif()
+list (REMOVE_ITEM BUILD_TOOLKITS ExpToCasExe)
+
# bison
if (BUILD_YACCLEX)
OCCT_INCLUDE_CMAKE_FILE ("adm/cmake/bison")
@@ -0,0 +1,13 @@
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 1bacca1a48..11f931ad39 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -820,6 +820,8 @@ else()
OCCT_CHECK_AND_UNSET ("3RDPARTY_DOT_EXECUTABLE")
endif()
+list (REMOVE_ITEM BUILD_TOOLKITS ExpToCasExe)
+
# bison
if (BUILD_YACCLEX)
OCCT_INCLUDE_CMAKE_FILE ("adm/cmake/bison")
@@ -0,0 +1,13 @@
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 86905287dc..9d0bce984c 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -828,6 +828,8 @@ else()
OCCT_CHECK_AND_UNSET ("3RDPARTY_DOT_EXECUTABLE")
endif()
+list (REMOVE_ITEM BUILD_TOOLKITS ExpToCasExe)
+
# bison
if (BUILD_YACCLEX)
OCCT_INCLUDE_CMAKE_FILE ("adm/cmake/bison")
@@ -0,0 +1,13 @@
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 34300d41ad..09b2e0d45f 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -721,6 +721,8 @@ else()
OCCT_CHECK_AND_UNSET ("3RDPARTY_DOT_EXECUTABLE")
endif()
+list (REMOVE_ITEM BUILD_TOOLKITS ExpToCasExe)
+
# bison
if (BUILD_YACCLEX)
list (APPEND OCCT_3RDPARTY_CMAKE_LIST "adm/cmake/bison")
+2 -7
View File
@@ -1,12 +1,9 @@
#!/usr/bin/bash
set -ex
# Script is assuming that it will be possible to execute it multiple times
# therefore we're clearing venv each time and ignoring existing 'emsdk' folder.
# Install uv.
curl -LsSf https://astral.sh/uv/install.sh | sh
uv venv --python 3.13 --clear
uv venv --python 3.13
source .venv/bin/activate
# Install pyodide cross build environment.
@@ -16,9 +13,7 @@ uv pip install pyodide-build
uv run pyodide xbuildenv install
# Emscripten doesn't come with xbuildenv.
if [ ! -d emsdk ]; then
git clone https://github.com/emscripten-core/emsdk
fi
git clone https://github.com/emscripten-core/emsdk
pushd emsdk
PYODIDE_EMSCRIPTEN_VERSION=$(pyodide config get emscripten_version)
./emsdk install ${PYODIDE_EMSCRIPTEN_VERSION}
@@ -5,13 +5,9 @@ This script is finding common install directory and either
packs each folder into a tar.gz archive, if it wasn't packed before,
or unpacks existing archives.
Expected to be executed from 'build' directory (e.g. that might contain 'Linux/x86_64/install').
Usage: python cache_dependencies.py [pack|unpack]
"""
import platform
import subprocess
import sys
import tarfile
from pathlib import Path
@@ -21,35 +17,23 @@ CACHE_PREFIX = "cache-"
def get_install_dir() -> Path:
if platform.system() == "Darwin":
pattern = "Darwin/*/*/install"
else:
pattern = "*/*/install"
for data in Path.cwd().glob(pattern):
for data in Path.cwd().glob("*/*/install"):
return data
raise Exception("No install dir found")
def run(cmd: str) -> None:
print(f"Running command: `{cmd}`")
subprocess.check_call(cmd, shell=True)
def pack_dependencies(install_dir: Path) -> None:
# Process each install_dir
for dependency_path in install_dir.iterdir():
if not dependency_path.is_dir():
continue
dependency_name = dependency_path.name
# Skip ifcopenshell - it's a build output, not a dependency to reuse across builds.
if dependency_name == "ifcopenshell":
continue
tar_path = install_dir / f"{CACHE_PREFIX}{dependency_name}.tar.gz"
if tar_path.exists():
print(f"Skipping existing cache: '{tar_path}'")
else:
# Python's `tarfile` is 10x slower than `tar` cli, so we use `tar`.
run(f'tar -czf "{tar_path}" -C "{install_dir}" "{dependency_name}"')
with tarfile.open(tar_path, "w:gz") as tar:
tar.add(dependency_path, arcname=dependency_path.name)
print(f"Created cache: '{tar_path}'")
+4 -185
View File
@@ -2,10 +2,10 @@
name = "IfcOpenShell"
version = "0.0.0"
dependencies = [
"black==26.3.1",
"ruff==0.15.8",
"black==26.1.0",
"ruff==0.15.0",
"poethepoet",
"gersemi==0.26.1",
"gersemi==0.25.4",
]
[tool.black]
@@ -15,8 +15,7 @@ include = '''
|nix/.*.pyi?$
'''
extend-exclude = '''
src/ifcopenshell-python/ifcopenshell/express/rules/*
|src/ifcopenshell-python/ifcopenshell/express/express_parser.py
src/ifcopenshell-python/ifcopenshell/express/*
|src/ifcopenshell-python/ifcopenshell/mvd/*
|src/ifcopenshell-python/ifcopenshell/simple_spf/*
|src/ifc2ca/templates/*
@@ -28,12 +27,6 @@ extend-exclude = '''
reportInvalidTypeForm = false
disableBytesTypePromotions = true
reportUnnecessaryTypeIgnoreComment = true
# Pylance doesn't respect gitignore, so we have to exclude files manually here
# to avoid VS Code slowing down.
# https://github.com/microsoft/pylance-release/issues/5169
exclude = [
"_deps",
]
# Define here general ruff settings,
# then they will be inherited by projects' .toml files.
@@ -78,139 +71,6 @@ ignore = [
"UP032", # Replace .format with f-string
]
[tool.ty.rules]
all = "ignore"
# Structural rules (no deep type inference needed, easier to adapt).
abstract-method-in-final-class = "error"
ambiguous-protocol-member = "error"
byte-string-type-annotation = "error"
conflicting-declarations = "error"
conflicting-metaclass = "error"
cyclic-class-definition = "error"
cyclic-type-alias-definition = "error"
dataclass-field-order = "error"
duplicate-base = "error"
duplicate-kw-only = "error"
empty-body = "error"
escape-character-in-forward-annotation = "error"
final-on-non-method = "error"
final-without-value = "error"
fstring-type-annotation = "error"
ignore-comment-unknown-rule = "error"
implicit-concatenated-string-type-annotation = "error"
inconsistent-mro = "error"
ineffective-final = "error"
instance-layout-conflict = "error"
invalid-dataclass = "error"
invalid-dataclass-override = "error"
invalid-enum-member-annotation = "error"
invalid-explicit-override = "error"
invalid-frozen-dataclass-subclass = "error"
invalid-generic-class = "error"
invalid-generic-enum = "error"
invalid-ignore-comment = "error"
invalid-legacy-positional-parameter = "error"
invalid-legacy-type-variable = "error"
invalid-named-tuple = "error"
invalid-newtype = "error"
invalid-overload = "error"
invalid-paramspec = "error"
invalid-protocol = "error"
invalid-syntax-in-forward-annotation = "error"
invalid-total-ordering = "error"
invalid-type-alias-type = "error"
invalid-type-checking-constant = "error"
invalid-type-guard-definition = "error"
invalid-type-variable-bound = "error"
invalid-type-variable-constraints = "error"
invalid-typed-dict-header = "error"
invalid-typed-dict-statement = "error"
override-of-final-method = "error"
override-of-final-variable = "error"
possibly-missing-import = "error"
possibly-missing-submodule = "error"
# Has false positives due to ty walrus operator bug.
# possibly-unresolved-reference = "error"
raw-string-type-annotation = "error"
redundant-final-classvar = "error"
shadowed-type-variable = "error"
subclass-of-final-class = "error"
super-call-in-named-tuple-method = "error"
unavailable-implicit-super-arguments = "error"
unbound-type-variable = "error"
undefined-reveal = "error"
unresolved-global = "error"
unresolved-import = "error"
unresolved-reference = "error"
unused-ignore-comment = "error"
unused-type-ignore-comment = "error"
useless-overload-body = "error"
# Non-structural rules:
deprecated = "error"
zero-stepsize-in-slice = "error"
possibly-missing-implicit-call = "error"
unused-awaitable = "error"
# Function argument rules:
# Conflicts with `ifcopenshell.api.geometry.add_representation` type of callables we have, confusing them with a module.
# call-non-callable = "error"
conflicting-argument-forms = "error"
# Too many false positives.
# invalid-argument-type = "error"
missing-argument = "error"
parameter-already-assigned = "error"
positional-only-parameter-as-kwarg = "error"
too-many-positional-arguments = "error"
unknown-argument = "error"
# Has a lot of warnings due to current ty walrus operator issues.
# index-out-of-bounds = "error"
# unresolved-attribute = "error"
[tool.ty.environment]
extra-paths = [
"src/bonsai/external_dependencies",
"src/bcf",
"src/bsdd",
"src/bonsai",
"src/ifc4d",
"src/ifc5d",
"src/ifccityjson",
"src/ifcclash",
"src/ifccsv",
"src/ifcdiff",
"src/ifcfm",
"src/ifcopenshell-python",
"src/ifcpatch",
"src/ifctester",
]
[tool.ty.src]
exclude = [
# External dependencies cloned for type checking only.
"src/bonsai/external_dependencies",
# Submodules.
"src/ifcopenshell-python/ifcopenshell/express",
"src/ifcopenshell-python/ifcopenshell/mvd",
"src/ifcopenshell-python/ifcopenshell/simple_spf",
"src/svgfill/3rdparty",
# Has special dependencies.
"src/ifcopenshell-python/ifcopenshell/geom/app.py",
"src/ifcopenshell-python/ifcopenshell/geom/code_editor_pane.py",
"src/ifcopenshell-python/ifcopenshell/util/doc.py",
"src/ifcopenshell-python/ifcopenshell/util/generate_pset_templates.py",
"src/ifcopenshell-python/ifcopenshell/util/ifc4x3dev_scrape_data_for_docs.py",
# Too esoteric.
"src/ifcopenshell-python/ifcopenshell/ifcopenshell_wrapper.py",
"src/ifc2ca/templates",
# Too dev.
"src/bcf/setup.py",
"src/bsdd/yml_to_classes.py",
# Deprecated.
"src/ifc2ca/_deprecated",
]
[tool.poe.tasks]
ruff-main = "ruff check --extend-exclude nix/build-all.py"
@@ -220,47 +80,6 @@ ruff.sequence = ["ruff-main", "ruff-old"]
black = "black ."
ty.sequence = ["ty-bonsai", "ty-ios"]
ty.help = "Run ty type checker. Requires ty-venv to be set up first."
ty-bonsai = "ty check src/bonsai --python=src/bonsai/.venv"
ty-venv.sequence = ["ty-venv-bonsai", "ty-venv-ios"]
ty-venv-bonsai.sequence = [
{cmd = "uv venv src/bonsai/.venv --python=3.11 --allow-existing"},
{cmd = "uv pip install -r src/bonsai/type-check-requirements.txt --python=src/bonsai/.venv"},
]
ty-venv-ios.sequence = [
{cmd = "uv venv src/ifcopenshell-python/.venv --python=3.10 --allow-existing"},
{cmd = "uv pip install -r src/ifcopenshell-python/type-check-requirements.txt --python=src/ifcopenshell-python/.venv"},
]
format.sequence = ["black", "ruff-main", "ruff-old"]
cmake-format = "gersemi . --in-place"
[tool.poe.tasks.ty-ios]
# --ignore unresolved-reference: walrus operator false positives in ty.
cmd = """
ty check
src/bcf
src/bsdd
src/ifc2ca
src/ifc4d
src/ifc5d
src/ifccityjson
src/ifcclash
src/ifccsv
src/ifcdiff
src/ifcfm
src/ifcopenshell-python
src/ifcpatch
src/ifctester
--python=src/ifcopenshell-python/.venv
--ignore unresolved-reference
"""
[tool.poe.tasks.bonsai-deps]
help = "Clone or update Bonsai external dependencies."
cmd = "python src/bonsai/scripts/bonsai_deps.py"
+3 -3
View File
@@ -34,8 +34,8 @@ client_id, client_secret = "", ""
class OAuthReceiver(http.server.BaseHTTPRequestHandler):
def do_GET(self) -> None:
query = urllib.parse.parse_qs(urllib.parse.urlparse(self.path).query)
self.server.auth_code = query.get("code", [""])[0]
self.server.auth_state = query.get("state", [""])[0]
self.server.auth_code = query.get("code", [""])[0] # type:ignore
self.server.auth_state = query.get("state", [""])[0] # type:ignore
self.send_response(200)
self.send_header("Content-type", "text/plain")
self.end_headers()
@@ -255,7 +255,7 @@ class BcfClient:
project_id: str = "",
topics: str = "",
query_string: Optional[str] = None,
) -> None:
) -> list[Any]:
# return self.get(
# f"/projects/{project_id}/topics",
# {
+10 -14
View File
@@ -173,17 +173,16 @@ def assert_viewpoints(viewpoints):
assert viewpoint.snapshot is not None
# TODO: dead code - ported from v2 but buildingSMART/BCF-XML has no v3 MaximumInformation.bcf equivalent
def assert_second_viewpoint(viewpoint, expected_selection, expected_exception, expected_coloring) -> None:
expected_vp = mdl.VisualizationInfo(
components=mdl.Components(
view_setup_hints=mdl.ViewSetupHints(
spaces_visible=False,
space_boundaries_visible=False,
openings_visible=False,
),
selection=expected_selection,
visibility=mdl.ComponentVisibility(
view_setup_hints=mdl.ViewSetupHints(
spaces_visible=False,
space_boundaries_visible=False,
openings_visible=False,
),
exceptions=expected_exception,
default_visibility=False,
),
@@ -194,7 +193,6 @@ def assert_second_viewpoint(viewpoint, expected_selection, expected_exception, e
camera_direction=mdl.Direction(x=0.6745243072509766, y=-0.6599355936050415, z=-0.33091068267822266),
camera_up_vector=mdl.Direction(x=0.2271970510482788, y=-0.24091780185699463, z=0.9435783624649048),
field_of_view=60,
aspect_ratio=1.0,
),
guid="21dd4807-e9af-439e-a980-04d913a6b1ce",
)
@@ -202,17 +200,16 @@ def assert_second_viewpoint(viewpoint, expected_selection, expected_exception, e
assert viewpoint.snapshot is not None
# TODO: dead code - ported from v2 but buildingSMART/BCF-XML has no v3 MaximumInformation.bcf equivalent
def assert_third_viewpoint(viewpoint, expected_selection, expected_exception, expected_coloring) -> None:
expected_vp = mdl.VisualizationInfo(
components=mdl.Components(
view_setup_hints=mdl.ViewSetupHints(
spaces_visible=False,
space_boundaries_visible=False,
openings_visible=True,
),
selection=expected_selection,
visibility=mdl.ComponentVisibility(
view_setup_hints=mdl.ViewSetupHints(
spaces_visible=False,
space_boundaries_visible=False,
openings_visible=True,
),
exceptions=expected_exception,
default_visibility=True,
),
@@ -223,7 +220,6 @@ def assert_third_viewpoint(viewpoint, expected_selection, expected_exception, ex
camera_direction=mdl.Direction(x=0.7232745289802551, y=0.5967116951942444, z=-0.3475759029388428),
camera_up_vector=mdl.Direction(x=0.27662187814712524, y=0.21082592010498047, z=0.937567412853241),
field_of_view=60,
aspect_ratio=1.0,
),
guid="81daa431-bf01-4a49-80a2-1ab07c177717",
)
+28 -10
View File
@@ -48,7 +48,6 @@ VERSION_PATCH:=$(shell cat '../../VERSION' | cut -d '.' -f 3)
VERSION_DATE:=$(shell date '+%y%m%d')
LAST_COMMIT_HASH:=$(shell git rev-parse HEAD)
LAST_COMMIT_DATE:=$(shell git show -s --format=%cI)
LAST_GIT_BRANCH:=$(shell git rev-parse --abbrev-ref HEAD)
PYPI_IMP:=cp
ifdef PYVERSION
@@ -64,7 +63,6 @@ PYNUMBER:=3$(PYMINOR)
PYPI_VERSION:=3.$(PYMINOR)
endif # def PYVERSION
IFCMERGE_VERSION:=2026-04-02
ifdef PLATFORM
SUPPORTED_PLATFORMS := linux macos macosm1 win
@@ -106,7 +104,7 @@ endif
endif # def PLATFORM
# Current build commit hash.
OLD:=1c5b825
OLD:=e8eb5e4
.PHONY: bump
bump:
ifndef NEW
@@ -225,8 +223,19 @@ endif
cd build/bonsai/bim/data/gantt/ && wget https://raw.githubusercontent.com/jsGanttImproved/jsgantt-improved/master/dist/jsgantt.css
# Provides IFCJSON functionality
# TODO: Use official repo, once https://github.com/IFCJSON-Team/IFC2JSON_python/pull/8 is merged.
cd build && . env/$(VENV_ACTIVATE) && $(PYTHON) -m pip wheel "git+https://github.com/Andrej730/IFC2JSON_python.git@pyproject_toml" --no-deps -w wheels/
cd build && wget -O ifc2json.zip https://github.com/IFCJSON-Team/IFC2JSON_python/archive/refs/heads/master.zip
cd build && unzip ifc2json.zip && rm ifc2json.zip
# IFCJSON doesn't have pyproject.toml, so we use python command.
cd build && . env/$(VENV_ACTIVATE) && cd IFC2JSON_python-*/file_converters && \
$(PYTHON) -c "from setuptools import setup; \
setup( \
name='ifcjson', \
version='0.0.1', \
author='Jan Brouwer', \
author_email='jan@brewsky.nl', \
packages=['ifcjson'], \
)" bdist_wheel
cp -r build/IFC2JSON_python-*/file_converters/dist/*.whl build/wheels/
# Brickschema requires pkg_resources which is provided by Blender.
# Provides Brickschema functionality
@@ -234,16 +243,26 @@ endif
cd build/bonsai/bim/data/brick/ && wget https://github.com/BrickSchema/Brick/releases/download/nightly/Brick.ttl
# Required for hipped roof generation
cd build && . env/$(VENV_ACTIVATE) && $(PYTHON) -m pip wheel "git+https://github.com/prochitecture/bpypolyskel" --no-deps -w wheels/
cd build && wget https://github.com/prochitecture/bpypolyskel/archive/refs/heads/master.zip
cd build && unzip master.zip && rm master.zip
cd build && . env/$(VENV_ACTIVATE) && cd bpypolyskel-master && \
$(PYTHON) -c "from setuptools import setup; \
setup( \
name='bpypolyskel', \
version='0.0.0', \
packages=['bpypolyskel'], \
)" bdist_wheel
cp -r build/bpypolyskel-master/dist/*.whl build/wheels/
# folder for executable files
mkdir -p build/bonsai/libs/bin
# required for three-way git merging
ifeq ($(PLATFORM), win)
cd build/bonsai/libs/bin && wget https://github.com/brunopostle/ifcmerge/releases/download/$(IFCMERGE_VERSION)/ifcmerge.exe
cd build/bonsai/libs/bin && wget https://github.com/brunopostle/ifcmerge/releases/download/2025-01-26/ifcmerge.zip
cd build/bonsai/libs/bin && unzip ifcmerge.zip && rm ifcmerge.zip
else
cd build/bonsai/libs/bin && wget https://raw.githubusercontent.com/brunopostle/ifcmerge/$(IFCMERGE_VERSION)/ifcmerge && chmod +x ifcmerge
cd build/bonsai/libs/bin && wget https://raw.githubusercontent.com/brunopostle/ifcmerge/main/ifcmerge && chmod +x ifcmerge
endif
# Generate translations module for Bonsai build
@@ -262,7 +281,6 @@ else
$(SED) "s/0.0.0/$(VERSION)-alpha$(VERSION_DATE)/" build/bonsai/blender_manifest.toml
$(SED) "s/8888888/$(LAST_COMMIT_HASH)/" build/bonsai/__init__.py
$(SED) "s/9999999/$(LAST_COMMIT_DATE)/" build/bonsai/__init__.py
$(SED) "s/7777777/$(LAST_GIT_BRANCH)/" build/bonsai/__init__.py
$(SED) 's/version = "0.0.0"/version = "$(VERSION)-alpha$(VERSION_DATE)"/' build/pyproject.toml
endif
@@ -353,7 +371,7 @@ test-tool:
ifndef MODULE
pytest test/tool
else
pytest test/tool/test_$(MODULE).py --maxfail=1
pytest test/tool/test_$(MODULE).py
endif
# Reregistering test is not added to the standard test suite because during unregister
+37 -51
View File
@@ -34,16 +34,17 @@ IN_PACKAGE = __package__ == "bonsai"
import platform
import re
import shutil
import traceback
import uuid
import webbrowser
from collections import deque
from collections.abc import Generator
from pathlib import Path
from typing import TYPE_CHECKING, Any, Union
from typing import Any, Union
last_commit_hash = "8888888"
last_commit_date = "9999999"
last_git_branch = "7777777"
def get_last_commit_hash() -> Union[str, None]:
@@ -61,15 +62,6 @@ def get_last_commit_date() -> Union[str, None]:
return last_commit_date
def get_git_branch() -> Union[str, None]:
# Using this weird way to write 7777777,
# so makefile won't accidentally replace it here
# we'll be able to distinguish branch from placeholder value.
if last_git_branch == str(7_777777):
return None
return last_git_branch
# Accessed from bonsai extension:
bbim_semver: dict[str, Any] = {}
@@ -81,21 +73,6 @@ REINSTALLED_BBIM_VERSION: Union[str, None] = None
REGISTERED_BBIM_PACKAGE: str
def is_registering() -> bool:
"""
During addon registration ``bpy.context`` and ``bpy.data`` are restricted
and you can't access their properties.
"""
import bpy
if TYPE_CHECKING or bpy.app.version >= (5, 0, 0):
import _bpy_restrict_state as bpy_restrict_state
else:
import bpy_restrict_state
return isinstance(bpy.context, bpy_restrict_state._RestrictContext)
def initialize_bbim_semver():
"""Initialize `bbim_semver` dictionary.
@@ -117,13 +94,9 @@ def initialize_bbim_semver():
bbim_semver["version"] = version_str
def get_debug_info(*, bonsai_failed_to_load: bool = False) -> dict[str, Any]:
import bpy
def get_debug_info():
bbim_version = bbim_semver["version"]
# All data here should be gettable even in case of `bpy.context` and `bpy.data` being inaccessible
# and Bonsai completely failed to load.
debug_info = {
"os": platform.system(),
"os_version": platform.version(),
@@ -135,19 +108,10 @@ def get_debug_info(*, bonsai_failed_to_load: bool = False) -> dict[str, Any]:
"bonsai_version": bbim_version,
"bonsai_commit_hash": get_last_commit_hash(),
"bonsai_commit_date": get_last_commit_date(),
"bonsai_git_branch": get_git_branch(),
"last_actions": last_actions,
"last_error": last_error,
}
# Can't access blend data or context during registration.
# If Bonsai failed to load we cannot safely access any of its properties or its tools
# as they may not be registered yet and acessing them will break Bonsai Fatal Error UI.
if is_registering() or bonsai_failed_to_load:
return debug_info
import bonsai.tool as tool
# Add .blend file save information
if bpy.data.is_saved:
debug_info["blend_file_path"] = bpy.data.filepath
@@ -168,7 +132,7 @@ def get_debug_info(*, bonsai_failed_to_load: bool = False) -> dict[str, Any]:
return debug_info
def format_debug_info(info: dict[str, Any]) -> str:
def format_debug_info(info: dict):
last_actions = ""
for action in info["last_actions"]:
last_actions += f"\n# {action['type']}: {action['name']}"
@@ -186,10 +150,33 @@ def get_binaries(path: Path) -> Generator[Path, None, None]:
yield from path.glob("**/*.so")
# TODO: remove before 0.8.6 release.
# On Windows issues with removing extensions were resolved in Blender 4.3,
# but we removed our workaround that was producing some junk only in 0.8.5 release.
# So we're temporarily keeping the part that's cleaning up outputs from previous releases.
def safe_link_dlls() -> None:
# Blender 4.2+ has a problem on Windows for disabling/enabling/reinstalling extensions
# with loaded binary dependencies (on Windows you can't remove a binary if it's loaded by some program).
# To avoid this issue we temporary hard link dlls to our temp directory on unregister()
# (unregister is executed before Blender will try to uninstall dependencies and the issue will arise).
# Then, Blender won't have a problem unlinking unloaded dlls as they are still linked somewhere.
# On register() we clean up our temp directory with binaries.
#
# TODO: If user uninstalls Bonsai to never use it again, temporary directory won't be cleared.
#
# See: https://projects.blender.org/blender/blender/issues/125049
import bpy
ext_path = Path(bpy.utils.user_resource("EXTENSIONS"))
local_path = ext_path / ".local"
# We use random hash subfolder as user may try to enable/disable addon multiple times.
random_hash = uuid.uuid4().hex[:8]
temp_local = ext_path / ".local_temp" / random_hash
temp_local.mkdir(parents=True)
for filepath in get_binaries(local_path):
dest_path = temp_local / filepath.relative_to(local_path)
dest_path.parent.mkdir(exist_ok=True, parents=True)
os.link(filepath, dest_path)
def clean_up_dlls_safe_links() -> None:
import bpy
@@ -219,8 +206,6 @@ def clean_up_dlls_safe_links() -> None:
if IN_BLENDER:
import bpy
initialize_bbim_semver()
def get_binary_info() -> dict[str, Any]:
@@ -262,12 +247,10 @@ if IN_BLENDER:
global last_commit_hash
global last_commit_date
global last_git_branch
path = Path(__file__).resolve().parent
repo = git.Repo(str(path), search_parent_directories=True)
last_commit_hash = repo.head.object.hexsha
last_commit_date = repo.head.object.committed_datetime.isoformat()
last_git_branch = repo.active_branch.name
except:
pass
@@ -314,6 +297,9 @@ if IN_BLENDER:
purge_cache()
def unregister():
if platform.system() == "Windows":
safe_link_dlls()
import bonsai.bim
bonsai.bim.unregister()
@@ -348,7 +334,7 @@ if IN_BLENDER:
bl_context = "scene"
def draw(self, context):
info = get_debug_info(bonsai_failed_to_load=True)
info = get_debug_info()
layout = self.layout
layout.alert = True
@@ -424,7 +410,7 @@ if IN_BLENDER:
bl_description = "Copies debugging information to your clipboard for use in bugreports"
def execute(self, context):
info = get_debug_info(bonsai_failed_to_load=True)
info = get_debug_info()
info.update(get_binary_info())
info = format_debug_info(info)
context.window_manager.clipboard = info
+11 -1
View File
@@ -24,14 +24,21 @@ import os
import tempfile
import zipfile
from logging import Logger
from math import radians
from typing import Union
import bpy
import ifcopenshell
import ifcopenshell.api
import ifcopenshell.util.element
import ifcopenshell.util.placement
import ifcopenshell.util.unit
from mathutils import Vector
import bonsai.core.aggregate
import bonsai.core.geometry
import bonsai.core.spatial
import bonsai.core.style
import bonsai.tool as tool
from bonsai.bim.ifc import IfcStore
@@ -45,6 +52,7 @@ class IfcExporter:
self.set_header()
IfcStore.update_cache()
self.sync_all_objects()
tool.Project.save_linked_models_to_ifc()
extension = self.ifc_export_settings.output_file.split(".")[-1].lower()
if extension == "ifczip":
with tempfile.TemporaryDirectory() as unzipped_path:
@@ -72,7 +80,9 @@ class IfcExporter:
def set_header(self):
self.file.header.file_name.name = os.path.basename(self.ifc_export_settings.output_file)
self.file.header.file_name.time_stamp = datetime.datetime.now().astimezone().replace(microsecond=0).isoformat()
self.file.header.file_name.time_stamp = (
datetime.datetime.utcnow().replace(tzinfo=datetime.UTC).astimezone().replace(microsecond=0).isoformat()
)
self.file.header.file_name.preprocessor_version = "IfcOpenShell {}".format(ifcopenshell.version)
self.file.header.file_name.originating_system = "{} {}".format(
self.get_application_name(), tool.Blender.get_bonsai_version()
+5 -8
View File
@@ -45,19 +45,16 @@ from bonsai.bim.module.nest.decorator import NestDecorator
cwd = os.path.dirname(os.path.realpath(__file__))
global_subscription_owner = object()
# Separate owner for per-object msgbus subscriptions (name, active_material_index).
# Using a dedicated owner allows clearing all per-object subscriptions at once
# during undo/redo without affecting other global subscriptions.
object_subscription_owner = object()
def name_callback(obj: Union[bpy.types.Object, bpy.types.Material], data: str) -> None:
try:
obj.name
except:
# The object is invalid but somehow still has a callback.
# This can occur during undo/redo when the Python wrapper is stale.
return
# The object is invalid but somehow still has a callback. Clear all
# msgbus subscriptions to prevent useless further triggers.
bpy.msgbus.clear_by_owner(obj)
return # In case the object RNA is gone during an undo / redo operation
# Blender names are up to 63 UTF-8 bytes
if len(bytes(obj.name, "utf-8")) >= 63:
return
@@ -192,7 +189,7 @@ def subscribe_to(obj: bpy.types.ID, data_path: str, callback: Callable[[bpy.type
return
bpy.msgbus.subscribe_rna(
key=subscribe_to,
owner=object_subscription_owner,
owner=obj,
args=(
obj,
data_path,
+1
View File
@@ -28,6 +28,7 @@ import bpy
import ifcopenshell
import ifcopenshell.ifcopenshell_wrapper as W
import ifcopenshell.util.attribute
import ifcopenshell.util.element
import ifcopenshell.util.unit
from ifcopenshell.util.doc import (
get_attribute_doc,
+9 -4
View File
@@ -316,8 +316,11 @@ class IfcStore:
del IfcStore.id_map[data["id"]]
if "guid" in data:
del IfcStore.guid_map[data["guid"]]
# Note: msgbus subscriptions are cleared globally during
# rebuild_element_maps which runs after every undo/redo.
obj = IfcStore.get_object_by_name(data["obj"])
if obj is None:
# obj was just created during this step and didn't existed before.
return
bpy.msgbus.clear_by_owner(obj)
@staticmethod
def commit_link_element(data: OperationData) -> None:
@@ -364,8 +367,10 @@ class IfcStore:
del IfcStore.id_map[data["id"]]
if "guid" in data:
del IfcStore.guid_map[data["guid"]]
# Note: msgbus subscriptions are cleared globally during
# rebuild_element_maps which runs after every undo/redo.
obj = IfcStore.get_object_by_name(data["obj"])
# obj might be removed after unlink.
if not obj:
bpy.msgbus.clear_by_owner(obj)
@staticmethod
def unlink_element(
+48 -10
View File
@@ -32,6 +32,7 @@ import ifcopenshell.api.pset
import ifcopenshell.geom
import ifcopenshell.ifcopenshell_wrapper as W
import ifcopenshell.util.element
import ifcopenshell.util.geolocation
import ifcopenshell.util.placement
import ifcopenshell.util.representation
import ifcopenshell.util.shape
@@ -64,8 +65,8 @@ class MaterialCreator:
mesh: Union[OBJECT_DATA_TYPE, None],
shape_has_openings: bool,
) -> None:
if ((rep := getattr(element, "Representation", ...)) is not ... and not rep) or (
(rep := getattr(element, "RepresentationMaps", ...)) is not ... and not rep
if ((rep := getattr(element, "Representation", ...) is not ...) and not rep) or (
(rep := getattr(element, "RepresentationMaps", ...) is not ...) and not rep
):
return
@@ -84,7 +85,7 @@ class MaterialCreator:
if element.is_a("IfcTypeProduct"):
self.parse_element_type_material_styles(element)
self.parsed_meshes.add(self.mesh.name)
if self.ifc_import_settings.load_indexed_maps:
if not self.ifc_import_settings.load_indexed_maps:
self.load_texture_maps(shape_has_openings)
self.assign_material_slots_to_faces()
tool.Geometry.record_object_materials(obj)
@@ -116,6 +117,7 @@ class MaterialCreator:
for texture in texture_style.Textures or []:
if coords := getattr(texture, "IsMappedBy", None):
coords = coords[0]
# IfcTextureCoordinateGenerator handled in the style shader graph
if coords.is_a("IfcIndexedTextureMap"):
return coords
# TODO: support IfcTextureMap
@@ -133,10 +135,6 @@ class MaterialCreator:
if shape_has_openings and coords.is_a("IfcIndexedTextureMap"):
continue
tool.Loader.load_indexed_map(coords, self.mesh)
elif tool.Style.get_texture_style(material):
# No explicit coordinate mapping (e.g. IFC2X3 has no IsMappedBy,
# and IFC4 COORD uses generated UVs). Bake XY→UV as fallback.
tool.Loader.load_generated_uv_map(self.mesh)
def assign_material_slots_to_faces(self) -> None:
if not self.mesh["ios_materials"]:
@@ -747,7 +745,6 @@ class IfcImporter:
self.update_progress((percent_average / 100 * progress_range) + start_progress)
shape = iterator.get()
if shape:
assert isinstance(shape, W.TriangulationElement)
product = self.file.by_id(shape.id)
self.create_product(product, shape)
results.add(product)
@@ -895,6 +892,48 @@ class IfcImporter:
obj, tool.Loader.apply_blender_offset_to_matrix_world(obj, self.get_element_matrix(element))
)
if element.is_a("IfcAnnotation") and getattr(element, "ObjectType", None) == "IMAGE":
image = None
if obj.data and obj.data.materials and obj.data.materials[0]:
material = obj.data.materials[0]
if material.use_nodes and material.node_tree:
for node in material.node_tree.nodes:
if node.type == "TEX_IMAGE" and node.image:
image = node.image
break
if image:
import bmesh
bm = bmesh.new()
bm.from_mesh(obj.data)
if not bm.loops.layers.uv:
uv_layer = bm.loops.layers.uv.new()
else:
uv_layer = bm.loops.layers.uv.active
if bm.verts:
min_x = min(v.co.x for v in bm.verts)
max_x = max(v.co.x for v in bm.verts)
min_y = min(v.co.y for v in bm.verts)
max_y = max(v.co.y for v in bm.verts)
width = max_x - min_x
height = max_y - min_y
for face in bm.faces:
for loop in face.loops:
vert = loop.vert
u = (vert.co.x - min_x) / width if width > 0 else 0.5
v = (vert.co.y - min_y) / height if height > 0 else 0.5
u = max(0.0, min(1.0, u))
v = max(0.0, min(1.0, v))
loop[uv_layer].uv = (u, v)
bm.to_mesh(obj.data)
bm.free()
obj.data.update()
return obj
def load_existing_meshes(self) -> None:
@@ -1021,8 +1060,7 @@ class IfcImporter:
obj.hide_select = True
obj.hide_viewport = True
self.project["blender"].objects.link(obj)
collection_props = tool.Blender.get_collection_props(self.project["blender"])
collection_props.obj = obj
self.project["blender"].BIMCollectionProperties.obj = obj
props = tool.Blender.get_object_bim_props(obj)
props.collection = self.collections[project.GlobalId] = self.project["blender"]
@@ -19,6 +19,7 @@
import blf
import bpy
import gpu
import ifcopenshell
import ifcopenshell.util.element
from bpy.types import SpaceView3D
from bpy_extras import view3d_utils
@@ -26,6 +27,7 @@ from gpu_extras.batch import batch_for_shader
from mathutils import Vector
import bonsai.tool as tool
from bonsai.bim.module.geometry.decorator import ItemDecorator
def transparent_color(color, alpha=0.1):
@@ -101,7 +103,7 @@ class AggregateDecorator:
cls.is_installed = False
def dotted_line_shader(self):
vert_out = gpu.types.GPUStageInterfaceInfo("my_interface") # ty:ignore[too-many-positional-arguments]
vert_out = gpu.types.GPUStageInterfaceInfo("my_interface")
vert_out.smooth("FLOAT", "v_ArcLength")
shader_info = gpu.types.GPUShaderCreateInfo()
@@ -19,6 +19,8 @@
from typing import TYPE_CHECKING
import bpy
import ifcopenshell
import ifcopenshell.api
import ifcopenshell.api.group
import ifcopenshell.api.pset
import ifcopenshell.api.root
@@ -23,7 +23,12 @@ import ifcopenshell.util.element
from bpy.props import (
BoolProperty,
CollectionProperty,
EnumProperty,
FloatProperty,
FloatVectorProperty,
IntProperty,
PointerProperty,
StringProperty,
)
from bpy.types import PropertyGroup
@@ -32,6 +37,8 @@ from bonsai.bim.module.aggregate.decorator import (
AggregateDecorator,
AggregateModeDecorator,
)
from bonsai.bim.module.spatial.data import SpatialData
from bonsai.bim.prop import Attribute, StrProperty
def can_aggregate(relating_obj: bpy.types.Object, related_obj: bpy.types.Object) -> bool:
@@ -21,6 +21,7 @@ from bpy.types import Panel
import bonsai.tool as tool
from bonsai.bim.ifc import IfcStore
from bonsai.bim.module.aggregate.data import AggregateData
from bonsai.bim.module.group.data import GroupsData, ObjectGroupsData
class BIM_PT_aggregate(Panel):
@@ -18,14 +18,24 @@
# pyright: reportUnnecessaryTypeIgnoreComment=error
import calendar
import json
import os
import time
from datetime import datetime
import bpy
import ifcopenshell.api.alignment
import ifcopenshell.api.spatial
import ifcopenshell.geom
import ifcopenshell.util.selector
import ifcopenshell.util.sequence
import isodate
from bpy_extras.io_utils import ImportHelper
from dateutil import parser, relativedelta
import bonsai.bim.module.sequence.helper as helper
import bonsai.core.sequence as core
import bonsai.tool as tool
@@ -23,12 +23,16 @@ from bpy.props import (
BoolProperty,
CollectionProperty,
EnumProperty,
FloatProperty,
FloatVectorProperty,
IntProperty,
PointerProperty,
StringProperty,
)
from bpy.types import PropertyGroup
import bonsai.tool as tool
from bonsai.bim.prop import Attribute
from bonsai.bim.prop import Attribute, StrProperty
class BIMAttributeProperties(PropertyGroup):
+2 -11
View File
@@ -16,9 +16,8 @@
# You should have received a copy of the GNU General Public License
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
from typing import TYPE_CHECKING
import bpy
import bpy.props
import bpy.types
class AuginProperties(bpy.types.PropertyGroup):
@@ -28,11 +27,3 @@ class AuginProperties(bpy.types.PropertyGroup):
project_name: bpy.props.StringProperty(name="Project Name")
project_filename: bpy.props.StringProperty(name="IFC Filename")
is_success: bpy.props.BoolProperty(name="Is Successful Upload", default=False)
if TYPE_CHECKING:
username: str
password: str
token: str
project_name: str
project_filename: str
is_success: bool
@@ -19,7 +19,9 @@
import os
from typing import Union
import bcf
import bcf.bcfxml
import bcf.v2.bcfxml
import bpy
import bonsai.tool as tool
+9 -4
View File
@@ -16,18 +16,22 @@
# You should have received a copy of the GNU General Public License
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
import os
import tempfile
import uuid
import webbrowser
from math import atan, degrees, radians, tan
from math import atan, cos, degrees, radians, sin, tan
from pathlib import Path
import bcf
import bcf.agnostic.topic
import bcf.agnostic.visinfo
import bcf.bcfxml
import bcf.v2.bcfxml
import bcf.v2.model
import bcf.v2.topic
import bcf.v2.visinfo
import bcf.v3
import bcf.v3.bcfxml
import bcf.v3.document
import bcf.v3.model
@@ -39,10 +43,11 @@ import ifcopenshell.util.geolocation
import ifcopenshell.util.unit
import numpy as np
from bpy_extras.io_utils import ExportHelper, ImportHelper
from mathutils import Matrix, Vector
from mathutils import Euler, Matrix, Vector, geometry
from xsdata.models.datatype import XmlDateTime
import bonsai.bim.module.bcf.bcfstore as bcfstore
import bonsai.bim.module.bcf.prop as bcf_prop
import bonsai.tool as tool
@@ -1253,8 +1258,8 @@ class ActivateBcfViewpoint(bpy.types.Operator):
else:
obj.data.show_background_images = False
assert (space := tool.Blender.get_view3d_space())
space.region_3d.view_perspective = "CAMERA"
area = next(area for area in context.screen.areas if area.type == "VIEW_3D")
area.spaces[0].region_3d.view_perspective = "CAMERA"
if self.file:
self.set_viewpoint_components(viewpoint, context)
+3 -1
View File
@@ -24,6 +24,8 @@ from bpy.props import (
BoolProperty,
CollectionProperty,
EnumProperty,
FloatProperty,
FloatVectorProperty,
IntProperty,
PointerProperty,
StringProperty,
@@ -230,7 +232,7 @@ class BcfTopic(PropertyGroup):
def get_related_topics(self: "BCFProperties", context: bpy.types.Context) -> list[tuple[str, str, str]]:
global RELATED_TOPICS_ENUM_ITEMS # ty: ignore[unresolved-global]
global RELATED_TOPICS_ENUM_ITEMS
props = self
active_topic = props.active_topic
active_related_topics = active_topic.related_topics.keys()
+1
View File
@@ -18,6 +18,7 @@
from __future__ import annotations
import os
from typing import TYPE_CHECKING
import bpy
@@ -20,6 +20,7 @@ import bmesh
import gpu
from bpy.types import SpaceView3D
from gpu_extras.batch import batch_for_shader
from mathutils import Vector
import bonsai.tool as tool
@@ -27,18 +27,20 @@ import ifcopenshell.api
import ifcopenshell.api.boundary
import ifcopenshell.api.root
import ifcopenshell.geom
import ifcopenshell.ifcopenshell_wrapper as W
import ifcopenshell.util.element
import ifcopenshell.util.placement
import ifcopenshell.util.representation
import ifcopenshell.util.shape
import ifcopenshell.util.unit
import mathutils
import numpy as np
import shapely
import shapely.ops
from ifcopenshell.util.shape_builder import ShapeBuilder
from mathutils import Matrix, Vector
import bonsai.bim.import_ifc as import_ifc
import bonsai.core
import bonsai.core.geometry
import bonsai.tool as tool
from bonsai.bim.ifc import IfcStore
@@ -377,8 +379,6 @@ class EnableEditingBoundary(bpy.types.Operator):
obj = tool.Ifc.get_object(entity)
if entity and obj:
setattr(bprops, blender_property, obj)
bprops.physical_or_virtual = boundary.PhysicalOrVirtualBoundary or "NOTDEFINED"
bprops.internal_or_external = boundary.InternalOrExternalBoundary or "NOTDEFINED"
return {"FINISHED"}
@@ -394,8 +394,6 @@ class DisableEditingBoundary(bpy.types.Operator):
bprops.is_editing = False
for ifc_attribute, blender_property in EDITABLE_ATTRIBUTES.items():
setattr(bprops, blender_property, None)
bprops.physical_or_virtual = "NOTDEFINED"
bprops.internal_or_external = "NOTDEFINED"
return {"FINISHED"}
@@ -415,8 +413,6 @@ class EditBoundaryAttributes(bpy.types.Operator, tool.Ifc.Operator):
obj = getattr(bprops, blender_property, None)
entity = tool.Ifc.get_entity(obj)
attributes[blender_property] = entity
attributes["physical_or_virtual"] = bprops.physical_or_virtual
attributes["internal_or_external"] = bprops.internal_or_external
ifcopenshell.api.boundary.edit_attributes(tool.Ifc.get(), entity=boundary, **attributes)
bpy.ops.bim.disable_editing_boundary()
return {"FINISHED"}
@@ -708,7 +704,6 @@ class AddBoundary(bpy.types.Operator, tool.Ifc.Operator):
while True:
tree.add_element(iterator.get_native())
shape = iterator.get()
assert isinstance(shape, W.TriangulationElement)
shapes[shape.id] = {
"verts": ifcopenshell.util.shape.get_vertices(shape.geometry),
"faces": ifcopenshell.util.shape.get_faces(shape.geometry),
+5 -33
View File
@@ -21,8 +21,13 @@ from typing import TYPE_CHECKING, Union
import bpy
from bpy.props import (
BoolProperty,
CollectionProperty,
EnumProperty,
FloatProperty,
FloatVectorProperty,
IntProperty,
PointerProperty,
StringProperty,
)
from bpy.types import PropertyGroup
@@ -51,43 +56,12 @@ def element_filter(self: "BIMObjectBoundaryProperties", object: bpy.types.Object
return False
def get_internal_or_external_items(
self: "BIMObjectBoundaryProperties", context: bpy.types.Context | None
) -> list[tuple[str, str, str]]:
items = [
("INTERNAL", "Internal", ""),
("EXTERNAL", "External", ""),
]
ifc = tool.Ifc.get()
if not ifc or ifc.schema != "IFC2X3":
items += [
("EXTERNAL_EARTH", "External Earth", ""),
("EXTERNAL_WATER", "External Water", ""),
("EXTERNAL_FIRE", "External Fire", ""),
]
items.append(("NOTDEFINED", "Not Defined", ""))
return items
class BIMObjectBoundaryProperties(PropertyGroup):
is_editing: BoolProperty(name="Is Editing")
relating_space: PointerProperty(name="RelatingSpace", type=bpy.types.Object, poll=space_filter)
related_building_element: PointerProperty(name="RelatedBuildingElement", type=bpy.types.Object, poll=element_filter)
parent_boundary: PointerProperty(name="ParentBoundary", type=bpy.types.Object, poll=boundary_filter)
corresponding_boundary: PointerProperty(name="CorrespondingBoundary", type=bpy.types.Object, poll=boundary_filter)
physical_or_virtual: EnumProperty(
name="PhysicalOrVirtualBoundary",
items=[
("PHYSICAL", "Physical", ""),
("VIRTUAL", "Virtual", ""),
("NOTDEFINED", "Not Defined", ""),
],
default="NOTDEFINED",
)
internal_or_external: EnumProperty(
name="InternalOrExternalBoundary",
items=get_internal_or_external_items,
)
if TYPE_CHECKING:
is_editing: bool
@@ -95,8 +69,6 @@ class BIMObjectBoundaryProperties(PropertyGroup):
related_building_element: Union[bpy.types.Object, None]
parent_boundary: Union[bpy.types.Object, None]
corresponding_boundary: Union[bpy.types.Object, None]
physical_or_virtual: str
internal_or_external: str # values depend on schema: IFC2X3 omits EXTERNAL_EARTH/WATER/FIRE
class BIMBoundaryProperties(PropertyGroup):
+2 -12
View File
@@ -16,7 +16,8 @@
# You should have received a copy of the GNU General Public License
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
from bpy.types import Panel
import bpy
from bpy.types import Panel, UIList
import bonsai.tool as tool
from bonsai.bim.module.boundary.data import SpaceBoundariesData
@@ -77,10 +78,6 @@ class BIM_PT_Boundary(Panel):
self.draw_relation_editor(boundary, "RelatedBuildingElement", "related_building_element")
self.draw_relation_editor(boundary, "ParentBoundary", "parent_boundary")
self.draw_relation_editor(boundary, "CorrespondingBoundary", "corresponding_boundary")
row = self.layout.row()
row.prop(self.bprops, "physical_or_virtual")
row = self.layout.row()
row.prop(self.bprops, "internal_or_external")
else:
row = self.layout.row()
row.operator("bim.enable_editing_boundary", icon="GREASEPENCIL", text="Edit")
@@ -88,8 +85,6 @@ class BIM_PT_Boundary(Panel):
self.draw_relation_data(boundary, "RelatedBuildingElement")
self.draw_relation_data(boundary, "ParentBoundary")
self.draw_relation_data(boundary, "CorrespondingBoundary")
self.draw_enum_data(boundary, "PhysicalOrVirtualBoundary")
self.draw_enum_data(boundary, "InternalOrExternalBoundary")
if hasattr(boundary, "InnerBoundaries"):
for i, inner_boundary in enumerate(getattr(boundary, "InnerBoundaries", ())):
row = self.layout.row(align=True)
@@ -116,11 +111,6 @@ class BIM_PT_Boundary(Panel):
else:
row.label(text="")
def draw_enum_data(self, boundary, ifc_attribute: str):
row = self.layout.row(align=True)
row.label(text=ifc_attribute)
row.label(text=getattr(boundary, ifc_attribute, "") or "")
def draw_relation_editor(self, boundary, ifc_attribute: str, blender_property: str):
if hasattr(boundary, ifc_attribute):
row = self.layout.row(align=True)
+6 -2
View File
@@ -63,7 +63,8 @@ class BrickschemaData:
if namespace == "https://brickschema.org/schema/Brick":
return []
results = []
query = BrickStore.graph.query("""
query = BrickStore.graph.query(
"""
PREFIX brick: <https://brickschema.org/schema/Brick#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
@@ -80,7 +81,10 @@ class BrickschemaData:
}
}
GROUP BY ?object
""".replace("{uri}", uri))
""".replace(
"{uri}", uri
)
)
for row in query:
predicate_uri = row.get("predicate")
predicate_name = predicate_uri.toPython().split("#")[-1]
@@ -19,6 +19,7 @@
import os
import bpy
import ifcopenshell.api
from bpy_extras.io_utils import ExportHelper, ImportHelper
import bonsai.bim.handler
+8 -5
View File
@@ -23,7 +23,10 @@ from bpy.props import (
BoolProperty,
CollectionProperty,
EnumProperty,
FloatProperty,
FloatVectorProperty,
IntProperty,
PointerProperty,
StringProperty,
)
from bpy.types import PropertyGroup
@@ -31,7 +34,7 @@ from bpy.types import PropertyGroup
import bonsai.core.brick as core
import bonsai.tool.brick as tool
from bonsai.bim.module.brick.data import BrickschemaData, BrickschemaReferencesData
from bonsai.bim.prop import StrProperty
from bonsai.bim.prop import Attribute, StrProperty
from bonsai.tool.brick import BrickStore
@@ -46,26 +49,26 @@ def get_libraries(self, context):
def get_namespaces(self, context):
global NAMESPACES_ENUM_ITEMS # ty: ignore[unresolved-global]
global NAMESPACES_ENUM_ITEMS
NAMESPACES_ENUM_ITEMS = [(uri, f"{alias}: {uri}", "") for alias, uri in BrickStore.namespaces]
return NAMESPACES_ENUM_ITEMS
def get_brick_entity_classes(self, context):
global ENTITY_CLASSES_ENUM_ITEMS # ty: ignore[unresolved-global]
global ENTITY_CLASSES_ENUM_ITEMS
entity = self.brick_entity_create_type
ENTITY_CLASSES_ENUM_ITEMS = [(uri, uri.split("#")[-1], "") for uri in BrickStore.entity_classes[entity]]
return ENTITY_CLASSES_ENUM_ITEMS
def get_brick_roots(self, context):
global BRICK_ROOTS_ENUM_ITEMS # ty: ignore[unresolved-global]
global BRICK_ROOTS_ENUM_ITEMS
BRICK_ROOTS_ENUM_ITEMS = [(root, root, "") for root in BrickStore.root_classes]
return BRICK_ROOTS_ENUM_ITEMS
def get_brick_relations(self, context):
global BRICK_RELATIONS_ENUM_ITEMS # ty: ignore[unresolved-global]
global BRICK_RELATIONS_ENUM_ITEMS
BRICK_RELATIONS_ENUM_ITEMS = [(uri, uri.split("#")[-1], "") for uri in BrickStore.relationships]
for relation in BrickschemaData.data["active_relations"]:
if relation["predicate_name"] == "label":
+1 -12
View File
@@ -16,18 +16,9 @@
# You should have received a copy of the GNU General Public License
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
from __future__ import annotations
from typing import TYPE_CHECKING
import bpy
from bpy.types import Panel, UIList
import bonsai.tool as tool
if TYPE_CHECKING:
from bonsai.bim.module.brick.prop import Brick
from bonsai.bim.helper import prop_with_search
from bonsai.bim.module.brick.data import BrickschemaData, BrickschemaReferencesData
from bonsai.tool.brick import BrickStore
@@ -283,9 +274,7 @@ class BIM_PT_brickschema_viewport(Panel):
class BIM_UL_bricks(UIList):
split_screen = False
def draw_item(
self, context, layout: bpy.types.UILayout, data, item: Brick, icon, active_data, active_propname
) -> None:
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
if item:
split = layout.split(factor=0.85, align=True)
row = split.row()
@@ -16,8 +16,13 @@
# You should have received a copy of the GNU General Public License
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
import bpy
import ifcopenshell
import ifcopenshell.util.classification
import ifcopenshell.util.date
import bonsai.tool as tool
from bonsai.bim.ifc import IfcStore
def refresh():
@@ -19,6 +19,7 @@ import textwrap
from typing import Any
import bpy
import bsdd
import ifcopenshell.api.pset
import ifcopenshell.util.element
+4 -1
View File
@@ -23,7 +23,10 @@ from bpy.props import (
BoolProperty,
CollectionProperty,
EnumProperty,
FloatProperty,
FloatVectorProperty,
IntProperty,
PointerProperty,
StringProperty,
)
from bpy.types import PropertyGroup
@@ -31,7 +34,7 @@ from bpy.types import PropertyGroup
import bonsai.tool as tool
from bonsai.bim.module.bsdd.data import BSDDData
from bonsai.bim.module.classification.data import ClassificationsData
from bonsai.bim.prop import Attribute
from bonsai.bim.prop import Attribute, StrProperty
def get_active_dictionary(self: "BIMBSDDProperties", context: object) -> tool.Blender.BLENDER_ENUM_ITEMS:
+3 -3
View File
@@ -17,11 +17,13 @@
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
import math
from math import pi, sqrt
from math import cos, pi, radians, sin, sqrt
from typing import Union
import bmesh
import bpy
import bpy_extras
import ifcopenshell.util.unit
import mathutils
from mathutils import Matrix, Vector
@@ -37,7 +39,6 @@ messages = {
class CadTrimExtend(bpy.types.Operator):
bl_idname = "bim.cad_trim_extend"
bl_label = "CAD Trim / Extend"
bl_description = "Extends/reduces element to 3D cursor"
@classmethod
def poll(cls, context):
@@ -83,7 +84,6 @@ class CadTrimExtend(bpy.types.Operator):
class CadMitre(bpy.types.Operator):
bl_idname = "bim.cad_mitre"
bl_label = "CAD Mitre"
bl_description = "Joins two non-parallel paths at their intersection"
@classmethod
def poll(cls, context):
+2
View File
@@ -22,6 +22,8 @@ from typing import TYPE_CHECKING
import bpy
from bpy.types import PropertyGroup
from bonsai.bim.module.model.data import AuthoringData
class BIMCadProperties(PropertyGroup):
resolution: bpy.props.IntProperty(name="Arc Resolution", min=1, default=1)
+21 -57
View File
@@ -20,10 +20,12 @@ import os
from functools import partial
import bpy
import ifcopenshell.util.unit
from bpy.types import WorkSpaceTool
import bonsai.bim.module.type.prop as type_prop
import bonsai.tool as tool
from bonsai.bim.module.model.data import RailingData, RoofData
from bonsai.bim.module.model.data import AuthoringData, RailingData, RoofData
def load_custom_icons():
@@ -106,37 +108,23 @@ class CadTool(WorkSpaceTool):
)
row = layout.row(align=True)
add_layout_hotkey_operator(
row, "Extend", "S_E", bpy.ops.bim.cad_trim_extend.__doc__.split("\n", 1)[1].strip(), ui_context
)
add_layout_hotkey_operator(row, "Extend", "S_E", "Extends/reduces element to 3D cursor", ui_context)
row = row if ui_context == "TOOL_HEADER" else layout.row(align=True)
add_layout_hotkey_operator(
row, "Join", "S_T", bpy.ops.bim.cad_mitre.__doc__.split("\n", 1)[1].strip(), ui_context
row, "Join", "S_T", "Joins two non-parallel paths at their intersection", ui_context
)
row = row if ui_context == "TOOL_HEADER" else layout.row(align=True)
add_layout_hotkey_operator(
row, "Fillet", "S_F", bpy.ops.bim.add_ifcarcindex_fillet.__doc__.split("\n", 1)[1].strip(), ui_context
)
add_layout_hotkey_operator(row, "Fillet", "S_F", bpy.ops.bim.add_ifcarcindex_fillet.__doc__, ui_context)
row = row if ui_context == "TOOL_HEADER" else layout.row(align=True)
add_layout_hotkey_operator(
row, "Offset", "S_O", bpy.ops.bim.cad_offset.__doc__.split("\n", 1)[1].strip(), ui_context
)
add_layout_hotkey_operator(row, "Offset", "S_O", bpy.ops.bim.cad_offset.__doc__, ui_context)
row = row if ui_context == "TOOL_HEADER" else layout.row(align=True)
add_layout_hotkey_operator(
row, "Rectangle", "S_R", bpy.ops.bim.add_rectangle.__doc__.split("\n", 1)[1].strip(), ui_context
)
add_layout_hotkey_operator(row, "Rectangle", "S_R", bpy.ops.bim.add_rectangle.__doc__, ui_context)
row = row if ui_context == "TOOL_HEADER" else layout.row(align=True)
add_layout_hotkey_operator(
row, "Circle", "S_C", bpy.ops.bim.add_ifccircle.__doc__.split("\n", 1)[1].strip(), ui_context
)
add_layout_hotkey_operator(row, "Circle", "S_C", bpy.ops.bim.add_ifccircle.__doc__, ui_context)
row = row if ui_context == "TOOL_HEADER" else layout.row(align=True)
add_layout_hotkey_operator(
row, "3-Point Arc", "S_V", bpy.ops.bim.set_arc_index.__doc__.split("\n", 1)[1].strip(), ui_context
)
add_layout_hotkey_operator(row, "3-Point Arc", "S_V", bpy.ops.bim.set_arc_index.__doc__, ui_context)
row = row if ui_context == "TOOL_HEADER" else layout.row(align=True)
add_layout_hotkey_operator(
row, "Reset Vertex", "S_X", bpy.ops.bim.reset_vertex.__doc__.split("\n", 1)[1].strip(), ui_context
)
add_layout_hotkey_operator(row, "Reset Vertex", "S_X", bpy.ops.bim.reset_vertex.__doc__, ui_context)
elif (
isinstance(data, tool.Geometry.TYPES_WITH_MESH_PROPERTIES)
@@ -146,21 +134,15 @@ class CadTool(WorkSpaceTool):
layout, "Edit Axis", "bim.edit_extrusion_axis", "bim.disable_editing_extrusion_axis", ui_context
)
row = layout.row(align=True)
add_layout_hotkey_operator(
row, "Extend", "S_E", bpy.ops.bim.cad_trim_extend.__doc__.split("\n", 1)[1].strip(), ui_context
)
add_layout_hotkey_operator(row, "Extend", "S_E", "Extends/reduces element to 3D cursor", ui_context)
row = row if ui_context == "TOOL_HEADER" else layout.row(align=True)
add_layout_hotkey_operator(
row, "Join", "S_T", bpy.ops.bim.cad_mitre.__doc__.split("\n", 1)[1].strip(), ui_context
row, "Join", "S_T", "Joins two non-parallel paths at their intersection", ui_context
)
row = row if ui_context == "TOOL_HEADER" else layout.row(align=True)
add_layout_hotkey_operator(
row, "Fillet", "S_F", bpy.ops.bim.cad_fillet.__doc__.split("\n", 1)[1].strip(), ui_context
)
add_layout_hotkey_operator(row, "Fillet", "S_F", bpy.ops.bim.cad_fillet.__doc__, ui_context)
row = row if ui_context == "TOOL_HEADER" else layout.row(align=True)
add_layout_hotkey_operator(
row, "Offset", "S_O", bpy.ops.bim.cad_offset.__doc__.split("\n", 1)[1].strip(), ui_context
)
add_layout_hotkey_operator(row, "Offset", "S_O", bpy.ops.bim.cad_offset.__doc__, ui_context)
else:
if (
@@ -188,37 +170,19 @@ class CadTool(WorkSpaceTool):
add_layout_hotkey_operator(row, "Set Gable Roof Angle", "S_R", "Set Gable Roof Angle", ui_context)
row = layout.row(align=True)
add_layout_hotkey_operator(
row, "Extend", "S_E", bpy.ops.bim.cad_trim_extend.__doc__.split("\n", 1)[1].strip(), ui_context
)
add_layout_hotkey_operator(row, "Extend", "S_E", "Extends/reduces element to 3D cursor", ui_context)
row = row if ui_context == "TOOL_HEADER" else layout.row(align=True)
add_layout_hotkey_operator(
row, "Join", "S_T", bpy.ops.bim.cad_mitre.__doc__.split("\n", 1)[1].strip(), ui_context
row, "Join", "S_T", "Joins two non-parallel paths at their intersection", ui_context
)
row = row if ui_context == "TOOL_HEADER" else layout.row(align=True)
add_layout_hotkey_operator(
row, "Fillet", "S_F", bpy.ops.bim.add_ifcarcindex_fillet.__doc__.split("\n", 1)[1].strip(), ui_context
)
add_layout_hotkey_operator(row, "Fillet", "S_F", bpy.ops.bim.add_ifcarcindex_fillet.__doc__, ui_context)
row = row if ui_context == "TOOL_HEADER" else layout.row(align=True)
add_layout_hotkey_operator(
row, "Offset", "S_O", bpy.ops.bim.cad_offset.__doc__.split("\n", 1)[1].strip(), ui_context
)
add_layout_hotkey_operator(row, "Offset", "S_O", bpy.ops.bim.cad_offset.__doc__, ui_context)
row = row if ui_context == "TOOL_HEADER" else layout.row(align=True)
add_layout_hotkey_operator(
row,
"2-Point Arc",
"S_C",
bpy.ops.bim.cad_arc_from_2_points.__doc__.split("\n", 1)[1].strip(),
ui_context,
)
add_layout_hotkey_operator(row, "2-Point Arc", "S_C", bpy.ops.bim.cad_arc_from_2_points.__doc__, ui_context)
row = row if ui_context == "TOOL_HEADER" else layout.row(align=True)
add_layout_hotkey_operator(
row,
"3-Point Arc",
"S_V",
bpy.ops.bim.cad_arc_from_3_points.__doc__.split("\n", 1)[1].strip(),
ui_context,
)
add_layout_hotkey_operator(row, "3-Point Arc", "S_V", bpy.ops.bim.cad_arc_from_3_points.__doc__, ui_context)
class CadHotkey(bpy.types.Operator):
@@ -18,6 +18,9 @@
import json
import bpy
import ifcopenshell.util.element
import bonsai.tool as tool
@@ -17,6 +17,7 @@
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
import blf
import bmesh
import gpu
from bpy.types import SpaceView3D
from bpy_extras.view3d_utils import location_3d_to_region_2d
@@ -18,13 +18,16 @@
import json
import logging
import os
import tempfile
from math import radians
from pathlib import Path
from typing import TYPE_CHECKING
import bmesh
import bpy
import ifcopenshell
import numpy as np
from bpy_extras.io_utils import ExportHelper, ImportHelper
from mathutils import Matrix, Vector
@@ -457,7 +460,9 @@ class HideClash(bpy.types.Operator):
def execute(self, context):
ClashDecorator.uninstall()
tool.Blender.update_all_viewports(context)
for area in context.screen.areas:
if area.type == "VIEW_3D":
area.tag_redraw()
return {"FINISHED"}
+2 -1
View File
@@ -26,6 +26,7 @@ from bpy.props import (
FloatProperty,
FloatVectorProperty,
IntProperty,
PointerProperty,
StringProperty,
)
from bpy.types import PropertyGroup
@@ -33,7 +34,7 @@ from ifcopenshell.geom.main import CLASH_TYPE_ITEMS, ClashType
from mathutils import Vector
import bonsai.tool as tool
from bonsai.bim.prop import BIMFilterGroup, StrProperty
from bonsai.bim.prop import Attribute, BIMFilterGroup, StrProperty
class ClashSource(PropertyGroup):
@@ -18,6 +18,7 @@
import bpy
import ifcopenshell
import ifcopenshell.api
import ifcopenshell.api.classification
import ifcopenshell.api.pset
import ifcopenshell.util.classification
@@ -23,7 +23,10 @@ from bpy.props import (
BoolProperty,
CollectionProperty,
EnumProperty,
FloatProperty,
FloatVectorProperty,
IntProperty,
PointerProperty,
StringProperty,
)
from bpy.types import PropertyGroup
@@ -25,6 +25,7 @@ import ifcopenshell.util.classification
from bpy.types import Panel, UIList
import bonsai.bim.helper
import bonsai.bim.module.classification.prop as classification_prop
import bonsai.tool as tool
from bonsai.bim.module.classification.data import (
ClassificationsData,
@@ -19,6 +19,7 @@
from typing import TYPE_CHECKING
import bpy
import ifcopenshell.api
import ifcopenshell.api.constraint
import bonsai.bim.helper
@@ -20,13 +20,19 @@ from typing import TYPE_CHECKING, Literal
import bpy
from bpy.props import (
BoolProperty,
CollectionProperty,
EnumProperty,
FloatProperty,
FloatVectorProperty,
IntProperty,
PointerProperty,
StringProperty,
)
from bpy.types import PropertyGroup
from ifcopenshell.util.doc import get_entity_doc
import bonsai.tool as tool
from bonsai.bim.module.constraint.data import ConstraintsData
from bonsai.bim.prop import Attribute
@@ -18,6 +18,7 @@
from typing import Any
import bpy
import ifcopenshell
import bonsai.tool as tool
+7 -1
View File
@@ -20,13 +20,19 @@ from typing import TYPE_CHECKING
import bpy
from bpy.props import (
BoolProperty,
CollectionProperty,
EnumProperty,
FloatProperty,
FloatVectorProperty,
IntProperty,
PointerProperty,
StringProperty,
)
from bpy.types import PropertyGroup
from bonsai.bim.prop import Attribute
from bonsai.bim.module.context.data import ContextData
from bonsai.bim.prop import Attribute, StrProperty
class BIMContextProperties(PropertyGroup):
@@ -18,11 +18,13 @@
from typing import Any, Union
import bpy
import ifcopenshell
import ifcopenshell.util.cost
import ifcopenshell.util.date
import ifcopenshell.util.element
import ifcopenshell.util.unit
from ifcopenshell.util.doc import get_entity_doc, get_predefined_type_doc
import bonsai.tool as tool
@@ -987,10 +987,10 @@ class ExportCostSchedulesToPDF(bpy.types.Operator, ExportHelper):
@classmethod
def poll(cls, context):
try:
import typst # noqa: F401
import typst
return True
except ModuleNotFoundError:
except:
cls.poll_message_set(
"Typst not available.\nIt can be installed from Quality and\nControl -> Debug and using 'typst' with Pip Install.\n(Run Blender as Administrator)"
)
@@ -19,13 +19,16 @@
from typing import TYPE_CHECKING, Literal, Union
import bpy
import ifcopenshell.api
import ifcopenshell.api.cost
from bpy.props import (
BoolProperty,
CollectionProperty,
EnumProperty,
FloatProperty,
FloatVectorProperty,
IntProperty,
PointerProperty,
StringProperty,
)
from bpy.types import PropertyGroup
+15 -44
View File
@@ -26,11 +26,10 @@ from bpy.types import Panel, UIList
import bonsai.bim.helper
import bonsai.bim.module.cost.prop as CostProp
import bonsai.tool as tool
from bonsai.bim.module.cost.data import CostItem, CostSchedulesData
from bonsai.bim.module.cost.data import CostSchedulesData
if TYPE_CHECKING:
from bonsai.bim.module.cost.prop import BIMCostProperties, CostItemQuantity
from bonsai.bim.prop import StrProperty
class BIM_PT_cost_schedules(Panel):
@@ -399,8 +398,8 @@ class BIM_PT_cost_item_types(Panel):
op = row2.operator("bim.calculate_cost_item_resource_value", text="", icon="DISC")
op.cost_item = cost_item.ifc_definition_id
rtprops = context.scene.BIMResourceTreeProperties
rprops = tool.Resource.get_resource_props()
rtprops = rprops.tree
if rtprops.resources and rprops.active_resource_index < len(rtprops.resources):
if has_quantity_names:
op = row2.operator("bim.assign_cost_item_quantity", text="", icon="PROPERTIES")
@@ -662,18 +661,9 @@ class BIM_UL_cost_items_trait:
split2.alignment = "LEFT"
split2.label(text="Rate")
def draw_item(
self,
context,
layout: bpy.types.UILayout,
data: BIMCostProperties,
item: CostProp.CostItem,
icon,
active_data,
active_propname,
) -> None:
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
if item:
self.props = data
self.props = tool.Cost.get_cost_props()
cost_item = CostSchedulesData.data["cost_items"][item.ifc_definition_id]
row = layout.row(align=True)
@@ -704,7 +694,7 @@ class BIM_UL_cost_items_trait:
# TODO: reimplement "bim.copy_cost_item_values" somewhere with better UX
def draw_parent_operator(self, row: bpy.types.UILayout, cost_item_id: int) -> None:
def draw_parent_operator(self, row, cost_item_id):
if self.props.active_cost_item_id:
if self.props.active_cost_item_id != cost_item_id:
op = row.operator("bim.change_parent_cost_item", text="", icon="LINKED", emboss=False).new_parent = (
@@ -713,7 +703,7 @@ class BIM_UL_cost_items_trait:
else:
row.label(text="", icon="BLANK1")
def draw_hierarchy(self, row: bpy.types.UILayout, item: CostProp.CostItem) -> None:
def draw_hierarchy(self, row, item):
for i in range(0, item.level_index):
row.label(text="", icon="BLANK1")
if item.has_children:
@@ -759,7 +749,7 @@ class BIM_UL_cost_items_trait:
else:
row.label(text="-")
def draw_value_column(self, layout: bpy.types.UILayout, cost_item: CostItem) -> None:
def draw_value_column(self, layout, cost_item):
if cost_item["TotalAppliedValue"]:
text = "{0:,.2f}".format(cost_item["TotalAppliedValue"]).replace(",", " ")
if cost_item["UnitBasisValueComponent"] not in [None, 1]:
@@ -770,13 +760,13 @@ class BIM_UL_cost_items_trait:
else:
layout.label(text="-")
def draw_total_cost_column(self, layout: bpy.types.UILayout, cost_item: CostItem) -> None:
def draw_total_cost_column(self, layout, cost_item):
format_numbers = "{0:,.2f}".format(cost_item["TotalCost"]).replace(",", " ")
currency = CostSchedulesData.data["currency"]
text = "{} {}".format(format_numbers, currency["name"]) if currency else format_numbers
layout.label(text=text)
def draw_order_operator(self, row: bpy.types.UILayout, ifc_definition_id: int, cost_item: CostItem) -> None:
def draw_order_operator(self, row, ifc_definition_id, cost_item):
if cost_item["NestingIndex"] is not None:
if cost_item["NestingIndex"] == 0:
op = row.operator("bim.reorder_cost_item_nesting", icon="TRIA_DOWN", text="")
@@ -803,14 +793,12 @@ class BIM_UL_cost_item_rates(BIM_UL_cost_items_trait, UIList):
def draw_quantity_column(self, layout, cost_item):
self.draw_uom_column(layout, cost_item)
def draw_total_cost_column(self, layout: bpy.types.UILayout, cost_item: CostItem) -> None:
def draw_total_cost_column(self, layout, cost_item):
pass # No such thing as a total cost in a schedule of rates
class BIM_UL_cost_columns(UIList):
def draw_item(
self, context, layout: bpy.types.UILayout, data, item: StrProperty, icon, active_data, active_propname
) -> None:
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
if item:
row = layout.row(align=True)
row.prop(item, "name", emboss=False, text="")
@@ -818,17 +806,9 @@ class BIM_UL_cost_columns(UIList):
class BIM_UL_cost_item_types(UIList):
def draw_item(
self,
context,
layout: bpy.types.UILayout,
data: BIMCostProperties,
item: CostProp.CostItemType,
icon,
active_data,
active_propname,
) -> None:
cost_item = data.cost_items[data.active_cost_item_index]
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
props = tool.Cost.get_cost_props()
cost_item = props.cost_items[props.active_cost_item_index]
if item:
row = layout.row(align=True)
@@ -864,16 +844,7 @@ class BIM_UL_cost_item_quantities(UIList):
class BIM_UL_product_cost_items(UIList):
def draw_item(
self,
context,
layout: bpy.types.UILayout,
data,
item: CostItemQuantity,
icon,
active_data,
active_propname,
) -> None:
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
if item:
row = layout.row(align=True)
op = row.operator("bim.highlight_product_cost_item", text="", icon="STYLUS_PRESSURE")
@@ -21,6 +21,7 @@ import os
from functools import partial
import bpy
import ifcopenshell
from bpy.types import WorkSpaceTool
import bonsai.tool as tool
@@ -20,6 +20,7 @@ import json
from math import atan2, degrees
import bpy
import ifcopenshell
import ifcopenshell.util.element
import bonsai.tool as tool
@@ -19,10 +19,14 @@
from __future__ import annotations
import json
import logging
import os
import tempfile
from collections import Counter
from typing import TYPE_CHECKING
import bpy
import ifccsv
import ifcopenshell
import ifcopenshell.util.selector
from bpy_extras.io_utils import ExportHelper, ImportHelper
+5 -1
View File
@@ -23,11 +23,15 @@ from bpy.props import (
BoolProperty,
CollectionProperty,
EnumProperty,
FloatProperty,
FloatVectorProperty,
IntProperty,
PointerProperty,
StringProperty,
)
from bpy.types import PropertyGroup
from bonsai.bim.prop import BIMFilterGroup
from bonsai.bim.prop import BIMFilterGroup, StrProperty
class CsvAttribute(PropertyGroup):
@@ -29,6 +29,7 @@ from typing import TYPE_CHECKING, Any, Literal, Union, assert_never, get_args
import bpy
import ifcopenshell
import ifcopenshell.api
import ifcopenshell.api.pset
import ifcopenshell.geom
import ifcopenshell.ifcopenshell_wrapper as W
@@ -20,9 +20,13 @@ from typing import TYPE_CHECKING, Literal, get_args
import bpy
from bpy.props import (
BoolProperty,
CollectionProperty,
EnumProperty,
FloatProperty,
FloatVectorProperty,
IntProperty,
PointerProperty,
StringProperty,
)
from bpy.types import PropertyGroup
@@ -32,10 +32,18 @@
from typing import TYPE_CHECKING
import bpy
# Properties have many different data types. We won't use all of them in this
# demo module, but this is a list for your reference.
from bpy.props import (
BoolProperty,
CollectionProperty,
EnumProperty,
FloatProperty,
FloatVectorProperty,
IntProperty,
PointerProperty,
StringProperty,
)
from bpy.types import PropertyGroup
@@ -98,8 +98,8 @@ class VisualiseDiff(bpy.types.Operator):
obj.color = (0.0, 1.0, 0.0, 1.0)
elif global_id in diff["changed"]:
obj.color = (0.0, 0.0, 1.0, 1.0)
assert (space := tool.Blender.get_view3d_space())
space.shading.color_type = "OBJECT"
area = next(area for area in context.screen.areas if area.type == "VIEW_3D")
area.spaces[0].shading.color_type = "OBJECT"
return {"FINISHED"}
+5 -1
View File
@@ -23,12 +23,16 @@ from bpy.props import (
BoolProperty,
CollectionProperty,
EnumProperty,
FloatProperty,
FloatVectorProperty,
IntProperty,
PointerProperty,
StringProperty,
)
from bpy.types import PropertyGroup
from bonsai.bim.module.diff.data import DiffData
from bonsai.bim.prop import BIMFilterGroup
from bonsai.bim.prop import BIMFilterGroup, StrProperty
def update_diff_json_file(self: "DiffProperties", context: bpy.types.Context) -> None:
@@ -19,6 +19,8 @@
import os
import bpy
import ifcopenshell
import ifcopenshell.util.schema
from natsort import natsorted
import bonsai.tool as tool
@@ -19,11 +19,11 @@
import json
import bpy
import ifcopenshell.util.element
import bonsai.bim.handler
import bonsai.core.document as core
import bonsai.tool as tool
from bonsai.bim.module.document.data import ObjectDocumentData
from bonsai.bim.module.document.data import DocumentData, ObjectDocumentData
class LoadProjectDocuments(bpy.types.Operator):
@@ -1,18 +1,21 @@
from typing import TYPE_CHECKING, Literal, Union
from typing import TYPE_CHECKING, Union
import bpy
from bpy.props import (
BoolProperty,
CollectionProperty,
EnumProperty,
FloatProperty,
FloatVectorProperty,
IntProperty,
PointerProperty,
StringProperty,
)
from bpy.types import PropertyGroup
import bonsai.tool as tool
from bonsai.bim.module.document.data import DocumentData, refresh
from bonsai.bim.prop import Attribute
from bonsai.bim.prop import Attribute, StrProperty
def update_document_name(self: "Document", context: bpy.types.Context) -> None:
@@ -66,7 +69,7 @@ class Document(PropertyGroup):
tree_depth: int
has_children: bool
is_expanded: bool
document_type: Literal["PROJECT", "INFORMATION", "REFERENCE"]
document_type: str
class DocumentObject(PropertyGroup):
+6 -33
View File
@@ -16,22 +16,10 @@
# You should have received a copy of the GNU General Public License
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
from __future__ import annotations
from typing import TYPE_CHECKING
import bpy
from bpy.types import Panel, UIList
import bonsai.tool as tool
if TYPE_CHECKING:
from bonsai.bim.module.document.prop import (
BIMDocumentProperties,
Document,
DocumentObject,
)
from bonsai.bim.helper import draw_attributes
from bonsai.bim.module.document.data import DocumentData, ObjectDocumentData
@@ -220,16 +208,7 @@ class BIM_PT_object_documents(Panel):
class BIM_UL_documents(UIList):
def draw_item(
self,
context,
layout: bpy.types.UILayout,
data: BIMDocumentProperties,
item: Document,
icon,
active_data,
active_propname,
) -> None:
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
if item:
row = layout.row(align=True)
indent_depth = 0
@@ -274,22 +253,16 @@ class BIM_UL_documents(UIList):
class BIM_UL_document_objects(UIList):
def draw_item(
self,
context,
layout: bpy.types.UILayout,
data: BIMDocumentProperties,
item: DocumentObject,
icon,
active_data,
active_propname,
) -> None:
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
if item:
row = layout.row(align=True)
row.prop(item, "name", text="", emboss=False, icon="OBJECT_DATA")
row.operator("bim.select_object", text="", icon="RESTRICT_SELECT_OFF").obj_name = item.name
if document := data.active_document:
props = tool.Document.get_document_props()
if props.active_document:
document = props.active_document
op = row.operator("bim.unassign_document", text="", icon="X")
op.document = document.ifc_definition_id
op.obj = item.name
@@ -18,12 +18,15 @@
from __future__ import annotations
import math
import os
from pathlib import Path
from typing import Optional
import bmesh
import bpy
import ifcopenshell.util.element
from mathutils import Vector
from mathutils import Matrix, Vector
import bonsai.tool as tool
+2 -2
View File
@@ -22,9 +22,9 @@ from pathlib import Path
from typing import Any, Union
import bpy
import ifcopenshell.util.classification
import ifcopenshell.util.element
import ifcopenshell.util.placement
import ifcopenshell.util.representation
import ifcopenshell.util.selector
import ifcopenshell.util.unit
from natsort import natsorted
@@ -23,7 +23,7 @@ from functools import cache
from math import acos, atan, cos, degrees, pi, radians, sin
from pathlib import Path
from timeit import default_timer as timer
from typing import Optional
from typing import Optional, Union
import blf
import bmesh
@@ -34,6 +34,7 @@ import ifcopenshell.util.element
import ifcopenshell.util.representation
import ifcopenshell.util.unit
import numpy as np
import shapely
from bpy.types import SpaceView3D
from bpy_extras.view3d_utils import location_3d_to_region_2d
from gpu_extras.batch import batch_for_shader
@@ -70,9 +71,9 @@ class profile_consequential:
cls.start_time = None
lines = "\n".join(cls.lines)
print(lines)
import pyperclip
assert (wm := bpy.context.window_manager)
wm.clipboard = lines
pyperclip.copy(lines)
cls.lines = []
@@ -1787,7 +1788,7 @@ class CutDecorator:
# Handle both old float64 and new float32 checksums for version compatibility
rot_checksum_bytes: bytes = eval(DecoratorData.camera_rotation_checksum)
rot_check = tool.Blender.np_frombuffer_legacy(rot_checksum_bytes, 9).reshape(3, 3)
rot_check = tool.Blender.np_frombuffer_legacy(rot_checksum_bytes, 9)
rot_real = tool.Blender.np_array_legacy(obj.matrix_world.to_3x3())
rot_dot = np.dot(rot_check, rot_real.T)
angle_rad = np.arccos(np.clip((np.trace(rot_dot) - 1) / 2, -1, 1))
@@ -151,7 +151,7 @@ from bpy_extras.view3d_utils import (
)
from gpu_extras.batch import batch_for_shader
from ifcopenshell.util.unit import si_conversions
from mathutils import Matrix, Vector, geometry
from mathutils import Matrix, Vector
from mathutils.geometry import intersect_line_line
from mathutils.kdtree import KDTree
@@ -1243,7 +1243,9 @@ class SnapManager:
@staticmethod
def _redraw_viewport() -> None:
"""Force 3D viewport redraw."""
tool.Blender.update_all_viewports()
for area in bpy.context.screen.areas:
if area.type == "VIEW_3D":
area.tag_redraw()
def build_snap_cache(
self, context: bpy.types.Context, active_obj: bpy.types.Object, include_active: bool = False
@@ -1285,7 +1287,7 @@ class SnapManager:
continue
coords = np.empty(vertex_count * 3, dtype=np.float32)
mesh.vertices.foreach_get("co", coords)
mesh.vertices.foreach_get("co", coords) # type: ignore[arg-type]
coords = coords.reshape(-1, 3)
matrix = np.array(obj_eval.matrix_world, dtype=np.float32)
@@ -17,8 +17,10 @@
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
import math
from typing import Union
import bpy
import ifcopenshell
import ifcopenshell.util.element
import ifcopenshell.util.unit
import mathutils.geometry
@@ -456,8 +458,7 @@ def format_distance(
tx_dist = fmt % d_cm
else:
assert f"Unexpected unit_system - '{unit_system}'."
# tx_dist = fmt % value
tx_dist = fmt % value
return tx_dist
+227 -137
View File
@@ -18,9 +18,9 @@
import hashlib
import json
import logging
import multiprocessing
import os
import re
import shutil
import subprocess
import time
@@ -41,8 +41,8 @@ from typing import (
import bmesh
import bpy
import ifcopenshell
import ifcopenshell.api
import ifcopenshell.api.document
import ifcopenshell.api.geometry
import ifcopenshell.api.pset
import ifcopenshell.api.style
import ifcopenshell.geom
@@ -50,18 +50,19 @@ import ifcopenshell.ifcopenshell_wrapper
import ifcopenshell.util.element
import ifcopenshell.util.representation
import ifcopenshell.util.selector
import ifcopenshell.util.shape_builder
import ifcopenshell.util.unit
import numpy as np
import shapely
import shapely.ops
from bpy_extras.image_utils import load_image
from bpy_extras.io_utils import ImportHelper
from lxml import etree
from mathutils import Color, Vector
from mathutils import Color, Matrix, Vector
import bonsai.bim.export_ifc
import bonsai.bim.handler
import bonsai.bim.import_ifc
import bonsai.bim.helper
import bonsai.bim.module.drawing.annotation as annotation
import bonsai.bim.module.drawing.sheeter as sheeter
import bonsai.bim.module.drawing.svgwriter as svgwriter
import bonsai.core.drawing as core
@@ -137,7 +138,7 @@ class AddAnnotationType(bpy.types.Operator, tool.Ifc.Operator):
element.ApplicableOccurrence = f"IfcAnnotation/{object_type}"
if props.create_representation_for_type and object_type == "IMAGE":
bpy.ops.bim.add_reference_image("INVOKE_DEFAULT", existing_object_by_name=obj.name)
bpy.ops.bim.add_reference_image("INVOKE_DEFAULT", use_existing_object_by_name=obj.name)
class EnableAddAnnotationType(bpy.types.Operator):
@@ -927,7 +928,7 @@ class CreateDrawing(bpy.types.Operator):
props = tool.Project.get_project_props()
for link in props.get_loaded_links_for_drawings():
files[link.filepath] = self.get_linked_file(link)
files[link.name] = self.get_linked_file(link)
target_view = ifcopenshell.util.element.get_psets(self.camera_element)["EPset_Drawing"]["TargetView"]
self.setup_serialiser(target_view)
@@ -1373,7 +1374,7 @@ class CreateDrawing(bpy.types.Operator):
return True
def get_linked_file(self, link: "Link") -> ifcopenshell.file:
link_path = link.filepath
link_path = link.name
ifc_file = IfcStore.session_files.get(link_path, None)
if ifc_file is not None:
return ifc_file
@@ -1426,7 +1427,6 @@ class CreateDrawing(bpy.types.Operator):
"/Pset_.*Common/.Status",
"EPset_Status.Status",
"EPset_Status.UserDefinedStatus",
"Material.Name",
]
group = root.find("{http://www.w3.org/2000/svg}g")
@@ -1759,7 +1759,7 @@ class AddAnnotation(bpy.types.Operator, tool.Ifc.Operator):
enable_editing=True,
)
if props.object_type == "IMAGE":
bpy.ops.bim.add_reference_image("INVOKE_DEFAULT", existing_object_by_name=obj.name)
bpy.ops.bim.add_reference_image("INVOKE_DEFAULT", use_existing_object_by_name=obj.name)
class AddSheet(bpy.types.Operator, tool.Ifc.Operator):
@@ -2896,16 +2896,12 @@ class AddSchedule(bpy.types.Operator, tool.Ifc.Operator, ImportHelper):
bl_options = {"REGISTER", "UNDO"}
bl_description = "Add an .ods, .xls or .xlsx file as a schedule"
files: bpy.props.CollectionProperty(name="Files", type=bpy.types.OperatorFileListElement)
directory: bpy.props.StringProperty(subtype="DIR_PATH")
filter_glob: bpy.props.StringProperty(default="*.ods;*.xls;*.xlsx", options={"HIDDEN"})
use_relative_path: bpy.props.BoolProperty(name="Use Relative Path", default=True)
def _execute(self, context):
for filepath in tool.Blender.get_selected_files(
self.directory, self.files, use_relative_path=self.use_relative_path
):
core.add_document(tool.Ifc, tool.Drawing, "SCHEDULE", uri=filepath)
filepath = tool.Ifc.get_uri(self.filepath, use_relative_path=self.use_relative_path)
core.add_document(tool.Ifc, tool.Drawing, "SCHEDULE", uri=filepath)
class RemoveSchedule(bpy.types.Operator, tool.Ifc.Operator):
@@ -3094,16 +3090,23 @@ class AddReference(bpy.types.Operator, tool.Ifc.Operator, ImportHelper):
bl_description = "Import a .svg file to the project as a reference"
bl_options = {"REGISTER", "UNDO"}
files: bpy.props.CollectionProperty(name="Files", type=bpy.types.OperatorFileListElement)
directory: bpy.props.StringProperty(subtype="DIR_PATH")
filter_glob: bpy.props.StringProperty(default="*.svg", options={"HIDDEN"})
use_relative_path: bpy.props.BoolProperty(name="Use Relative Path", default=True)
filename_ext = ".svg"
files: bpy.props.CollectionProperty(type=bpy.types.OperatorFileListElement)
directory: bpy.props.StringProperty(subtype="DIR_PATH")
def _execute(self, context):
for filepath in tool.Blender.get_selected_files(
self.directory, self.files, use_relative_path=self.use_relative_path
):
# Handle both single and multiple file selection
if self.files:
for file_elem in self.files:
filepath = os.path.join(self.directory, file_elem.name)
uri = tool.Ifc.get_uri(filepath, use_relative_path=self.use_relative_path)
core.add_document(tool.Ifc, tool.Drawing, "REFERENCE", uri=uri)
else:
# Fallback for single file (backward compatibility)
filepath = tool.Ifc.get_uri(self.filepath, use_relative_path=self.use_relative_path)
core.add_document(tool.Ifc, tool.Drawing, "REFERENCE", uri=filepath)
@@ -3162,7 +3165,6 @@ class EditTextPopup(bpy.types.Operator):
bpy.ops.bim.disable_editing_text()
def execute(self, context):
# TODO: check for possible subtle undo bug here
# can't use invoke() because this operator
# will be run indirectly by hotkey
# so we use execute() and track whether it's the first run of the operator
@@ -3306,8 +3308,9 @@ class AddTextLiteral(bpy.types.Operator):
attr.data_type = "string"
attr.string_value = literal_attr_values[attr_name]
literal_props.align_vertical = "bottom"
literal_props.align_horizontal = "left"
box_alignment_mask = [False] * 9
box_alignment_mask[6] = True # bottom_left box_alignment
literal_props.box_alignment = box_alignment_mask
return {"FINISHED"}
@@ -3365,55 +3368,57 @@ class OrderTextLiteralDown(bpy.types.Operator):
return {"FINISHED"}
class AssignSelectedObjectAsProduct(bpy.types.Operator, tool.Ifc.Operator):
# Ifc Operator is unnecessary, because suboperator is handling IFC changes.
class AssignSelectedObjectAsProduct(bpy.types.Operator):
bl_idname = "bim.assign_selected_as_product"
bl_label = "Assign Selected Object As Product"
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
if len(context.selected_objects) < 2:
cls.poll_message_set("At least 2 objects need to be selected")
if len(context.selected_objects) != 2:
cls.poll_message_set("2 objects need to be selected")
return False
return True
def _execute(self, context):
def execute(self, context):
assert bpy.context.view_layer
objs = context.selected_objects[:]
ifc_objs = [(o, tool.Ifc.get_entity(o)) for o in objs if tool.Ifc.get_entity(o)]
obj1, obj2 = objs
element1 = tool.Ifc.get_entity(obj1)
element2 = tool.Ifc.get_entity(obj2)
assert element1 and element2
annotations = [(o, e) for o, e in ifc_objs if e.is_a("IfcAnnotation")]
non_annotations = [(o, e) for o, e in ifc_objs if not e.is_a("IfcAnnotation")]
# Check if at least one object is an IfcAnnotation
is_annotation1 = element1.is_a("IfcAnnotation")
is_annotation2 = element2.is_a("IfcAnnotation")
if not annotations:
self.report({"ERROR"}, "At least one selected object must be an IfcAnnotation.")
if not (is_annotation1 or is_annotation2):
self.report({"ERROR"}, "At least one of the selected objects must be IfcAnnotation.")
return {"CANCELLED"}
if len(non_annotations) == 1:
# One product, one or more annotations — assign all annotations to the product.
product = non_annotations[0][1]
elif len(non_annotations) == 0 and len(annotations) == 2:
# Both objects are annotations — use the non-active one as the relating product.
# If both are annotations, use the currently active object as relating product
if is_annotation1 and is_annotation2:
active_obj = context.active_object
if annotations[0][0] == active_obj:
annotation_obj, annotation = annotations[0]
product = annotations[1][1]
if active_obj == obj1:
other_selected_object = obj1
bpy.context.view_layer.objects.active = obj2
else:
annotation_obj, annotation = annotations[1]
product = annotations[0][1]
core.edit_assigned_product(tool.Ifc, tool.Drawing, obj=annotation_obj, product=product)
tool.Blender.update_viewport()
return
other_selected_object = obj2
bpy.context.view_layer.objects.active = obj1
# If only one is an annotation, make it the active object
elif is_annotation1:
other_selected_object = obj2
bpy.context.view_layer.objects.active = obj1
else:
self.report(
{"ERROR"},
"Select exactly one product object and one or more IfcAnnotation objects.",
)
return {"CANCELLED"}
other_selected_object = obj1
bpy.context.view_layer.objects.active = obj2
for annotation_obj, _ in annotations:
core.edit_assigned_product(tool.Ifc, tool.Drawing, obj=annotation_obj, product=product)
tool.Blender.update_viewport()
assert (active_obj := context.active_object)
props = tool.Drawing.get_object_assigned_product_props(active_obj)
props.relating_product = other_selected_object
bpy.ops.bim.edit_assigned_product()
return {"FINISHED"}
class EditAssignedProduct(bpy.types.Operator, tool.Ifc.Operator):
@@ -3799,51 +3804,83 @@ class AddReferenceImage(bpy.types.Operator, tool.Ifc.Operator, ImportHelper):
use_relative_path: bpy.props.BoolProperty(name="Use Relative Path", default=True)
filter_image: bpy.props.BoolProperty(default=True, options={"HIDDEN", "SKIP_SAVE"})
filter_folder: bpy.props.BoolProperty(default=True, options={"HIDDEN", "SKIP_SAVE"})
override_existing_image: bpy.props.BoolProperty(
name="Override Existing Image",
default=True,
description=(
"Override image if it was previously loaded to Blender. If disabled, will always create a new image"
),
)
use_existing_object_by_name: bpy.props.StringProperty(
name="Use Existing Object By Name",
description="Existing object name to add a style with reference image to. If not provided will create a new object.",
options={"SKIP_SAVE"},
)
x_length: bpy.props.FloatProperty(
name="X Length",
description="Width of the reference image",
description="Width of the reference image in project units",
default=1.0,
min=0.001,
soft_min=0.01,
precision=3,
unit="LENGTH",
)
y_length: bpy.props.FloatProperty(
name="Y Length",
description="Height of the reference image",
description="Height of the reference image in project units",
default=1.0,
min=0.001,
soft_min=0.01,
precision=3,
unit="LENGTH",
)
show_texture_solid_mode: bpy.props.BoolProperty(
name="Show Texture in Solid mode (slow)",
description="Show Texture in Solid mode (slow)",
default=False,
)
@classmethod
def poll(cls, context):
if not tool.Ifc.get():
cls.poll_message_set("No IFC project is loaded.")
return False
return True
show_dimensions_dialog: bpy.props.BoolProperty(default=False, options={"HIDDEN", "SKIP_SAVE"})
def draw(self, context):
layout = self.layout
if getattr(self, "show_dimensions_dialog", False):
if tool.Ifc.get():
length_unit = ifcopenshell.util.unit.get_project_unit(tool.Ifc.get(), "LENGTHUNIT")
if length_unit:
unit_name = ifcopenshell.util.unit.get_full_unit_name(length_unit).lower()
else:
unit_name = "project units"
layout.label(text=f"Set Reference Image Dimensions (in {unit_name}):")
else:
layout.label(text="Set Reference Image Dimensions (in project units):")
layout.separator()
layout.prop(self, "x_length")
layout.prop(self, "y_length")
else:
if Path(tool.Ifc.get_path()).is_file():
layout.prop(self, "use_relative_path")
else:
self.use_relative_path = False
layout.label(text="Save the .ifc file first ")
layout.label(text="to use relative paths.")
layout.prop(self, "override_existing_image")
layout.prop(self, "use_existing_object_by_name")
def invoke(self, context, event):
self._last_filepath = ""
return super().invoke(context, event)
def check(self, context):
if not hasattr(self, "_last_filepath"):
self._last_filepath = ""
if self.filepath and self.filepath != self._last_filepath:
self._last_filepath = self.filepath
if not getattr(self, "show_dimensions_dialog", False):
context.window_manager.fileselect_add(self)
return {"RUNNING_MODAL"}
else:
return context.window_manager.invoke_props_dialog(self)
def execute(self, context):
if not getattr(self, "show_dimensions_dialog", False):
abs_path = Path(self.filepath).absolute().resolve()
if abs_path.exists() and abs_path.is_file():
image = load_image(abs_path.name, str(abs_path.parent), check_existing=False)
if self.override_existing_image:
params = {"check_existing": True, "force_reload": True}
else:
params = {"check_existing": False}
try:
image = load_image(abs_path.name, str(abs_path.parent), **params)
image_width_px = image.size[0]
image_height_px = image.size[1]
aspect_ratio = image_width_px / image_height_px
@@ -3856,23 +3893,17 @@ class AddReferenceImage(bpy.types.Operator, tool.Ifc.Operator, ImportHelper):
self.y_length = 1.0
bpy.data.images.remove(image)
return True
return False
except Exception as e:
self.report({"ERROR"}, f"Failed to load image: {str(e)}")
return {"CANCELLED"}
def draw(self, context):
layout = self.layout
if Path(tool.Ifc.get_path()).is_file():
layout.prop(self, "use_relative_path")
else:
self.use_relative_path = False
layout.prop(self, "show_texture_solid_mode")
layout.prop(self, "x_length")
layout.prop(self, "y_length")
self.show_dimensions_dialog = True
return context.window_manager.invoke_props_dialog(self)
return self._execute(context)
def _execute(self, context):
project_props = tool.Project.get_project_props()
project_props.load_indexed_maps = self.show_texture_solid_mode
space = tool.Blender.get_view3d_space()
if space.shading.color_type != "TEXTURE":
space.shading.color_type = "TEXTURE"
@@ -3885,65 +3916,121 @@ class AddReferenceImage(bpy.types.Operator, tool.Ifc.Operator, ImportHelper):
image_filepath = Path(tool.Ifc.get_uri(self.filepath, use_relative_path=self.use_relative_path))
ifc_file = tool.Ifc.get()
image = load_image(abs_path.name, str(abs_path.parent), check_existing=False)
if self.override_existing_image:
params = {"check_existing": True, "force_reload": True}
else:
params = {"check_existing": False}
image = load_image(abs_path.name, str(abs_path.parent), **params)
mesh = bpy.data.meshes.new(image_filepath.stem)
obj = bpy.data.objects.new(image_filepath.stem, mesh)
element = tool.Drawing.run_root_assign_class(
obj=obj, ifc_class="IfcAnnotation", predefined_type="IMAGE", should_add_representation=False
)
def bm_add_image_plane(mesh):
bm = tool.Blender.get_bmesh_for_mesh(mesh, clean=True)
builder = ifcopenshell.util.shape_builder.ShapeBuilder(ifc_file)
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(ifc_file)
hx = self.x_length * 0.5 / unit_scale
hy = self.y_length * 0.5 / unit_scale
verts = [(-hx, -hy, 0.0), (hx, -hy, 0.0), (hx, hy, 0.0), (-hx, hy, 0.0)]
item = builder.mesh(verts, [[0, 1, 2, 3]])
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(ifc_file)
plane_scale = Vector((self.x_length * unit_scale / 2.0, self.y_length * unit_scale / 2.0, 1.0))
matrix = Matrix.LocRotScale(None, None, plane_scale)
bmesh.ops.create_grid(bm, x_segments=1, y_segments=1, size=1, matrix=matrix, calc_uvs=False)
ifc_context = ifcopenshell.util.representation.get_context(ifc_file, "Model", "Body", "MODEL_VIEW")
representation = builder.get_representation(ifc_context, [item])
ifcopenshell.api.geometry.assign_representation(ifc_file, element, representation)
if not bm.loops.layers.uv:
uv_layer = bm.loops.layers.uv.new()
else:
uv_layer = bm.loops.layers.uv.active
style = ifcopenshell.api.style.add_style(tool.Ifc.get(), name=image_filepath.stem)
ifcopenshell.api.style.assign_representation_styles(
ifc_file, shape_representation=representation, styles=[style]
)
min_x = min(v.co.x for v in bm.verts)
max_x = max(v.co.x for v in bm.verts)
min_y = min(v.co.y for v in bm.verts)
max_y = max(v.co.y for v in bm.verts)
width = max_x - min_x
height = max_y - min_y
for face in bm.faces:
for loop in face.loops:
vert = loop.vert
u = (vert.co.x - min_x) / width if width > 0 else 0.5
v = (vert.co.y - min_y) / height if height > 0 else 0.5
u = max(0.0, min(1.0, u))
v = max(0.0, min(1.0, v))
loop[uv_layer].uv = (u, v)
tool.Blender.apply_bmesh(mesh, bm)
if self.use_existing_object_by_name:
obj = bpy.data.objects[self.use_existing_object_by_name]
bm_add_image_plane(obj.data)
bpy.ops.bim.update_representation(obj=obj.name, ifc_representation_class="")
else:
temp_mesh = bpy.data.meshes.new("temp_mesh")
bm_add_image_plane(temp_mesh)
obj = bpy.data.objects.new(image_filepath.stem, temp_mesh)
tool.Drawing.run_root_assign_class(
obj=obj,
ifc_class="IfcAnnotation",
predefined_type="IMAGE",
should_add_representation=True,
context=ifcopenshell.util.representation.get_context(ifc_file, "Model", "Body", "MODEL_VIEW"),
ifc_representation_class=None,
)
tool.Blender.remove_data_block(temp_mesh)
element = tool.Ifc.get_entity(obj)
if element and isinstance(obj.data, bpy.types.Mesh):
representation = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
if representation and representation.Items:
item_id = representation.Items[0].id()
num_faces = len(obj.data.polygons)
obj.data["ios_item_ids"] = [item_id] * num_faces
tool.Blender.Attribute.fill_attribute(obj.data, "ios_item_ids", "FACE", "INT", [item_id] * num_faces)
for item in representation.Items:
if item.is_a("IfcPolygonalFaceSet") and item.Coordinates:
new_coords = []
for vertex in obj.data.vertices:
co = obj.matrix_world @ vertex.co
new_coords.append([co.x, co.y, co.z])
item.Coordinates.CoordList = new_coords
tool.Blender.set_active_object(obj)
material = bpy.data.materials.new(name=image_filepath.stem)
obj.data.materials.append(None) # new slot
obj.material_slots[0].material = material
bpy.ops.bim.add_style()
style = tool.Ifc.get_entity(material)
assert style
tool.Style.assign_style_to_object(style, obj)
# TODO: IfcSurfaceStyleRendering is unnecessary here, added it only because
# we don't support IfcSurfaceStyleWithTextures without Rendering yet
shading_attributes = {
"SurfaceColour": {"Red": 1.0, "Green": 1.0, "Blue": 1.0},
"SurfaceColour": {
"Red": 1.0,
"Green": 1.0,
"Blue": 1.0,
},
"Transparency": 0.0,
"ReflectanceMethod": "NOTDEFINED",
}
ifcopenshell.api.style.add_surface_style(
tool.Ifc.get(), style=style, ifc_class="IfcSurfaceStyleRendering", attributes=shading_attributes
tool.Ifc.get(),
style=style,
ifc_class="IfcSurfaceStyleRendering",
attributes=shading_attributes,
)
if tool.Ifc.get_schema() == "IFC2X3":
texture = ifc_file.create_entity(
"IfcImageTexture",
RepeatS=True,
RepeatT=True,
TextureType="TEXTURE",
UrlReference=image_filepath.as_posix(),
)
else:
texture = ifc_file.create_entity("IfcImageTexture", Mode="DIFFUSE", URLReference=image_filepath.as_posix())
ifc_file.create_entity("IfcTextureCoordinateGenerator", Maps=[texture], Mode="COORD")
texture = ifc_file.create_entity("IfcImageTexture", Mode="DIFFUSE", URLReference=image_filepath.as_posix())
textures = [texture]
ifc_file.create_entity("IfcTextureCoordinateGenerator", Maps=textures, Mode="COORD") # UV map
ifcopenshell.api.style.add_surface_style(
ifc_file, style=style, ifc_class="IfcSurfaceStyleWithTextures", attributes={"Textures": textures}
ifc_file,
style=style,
ifc_class="IfcSurfaceStyleWithTextures",
attributes={"Textures": textures},
)
tool.Style.reload_material_from_ifc(material)
tool.Geometry.record_object_materials(obj)
logger = logging.getLogger("ImportIFC")
ifc_import_settings = bonsai.bim.import_ifc.IfcImportSettings.factory(bpy.context, None, logger)
ifc_importer = bonsai.bim.import_ifc.IfcImporter(ifc_import_settings)
ifc_importer.file = tool.Ifc.get()
ifc_importer.create_style(style)
bonsai.core.geometry.switch_representation(tool.Ifc, tool.Geometry, obj=obj, representation=representation)
return {"FINISHED"}
class ConvertSVGToDXF(bpy.types.Operator):
@@ -4175,7 +4262,10 @@ class SelectSimilarTextLiteralValue(bpy.types.Operator):
should_select = True
break
elif self.attribute_type == "box_alignment":
if literal.get_box_alignment() == self.literal_value:
box_alignment_attr = next(
(attr for attr in literal.attributes if attr.name == "BoxAlignment"), None
)
if box_alignment_attr and box_alignment_attr.string_value == self.literal_value:
should_select = True
break

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