Compare commits

...

235 Commits

Author SHA1 Message Date
Gorgious 481676e51b fix - recalculate normals for wall-mounted handrail railings
Fixes inverted normals on SweptDiskSolid end caps by recalculating
face normals after IFC geometry is loaded into Blender.

Fixes #6483
2025-11-22 00:04:57 +01:00
Gorgious56 fe502577c9 Merge pull request #7400 from IfcOpenShell/0.8.0
fix - apply object scale before using it to void an element #6378
2025-11-21 23:56:10 +01:00
Gorgious 65e8ca17d2 fix - apply object scale before using it to void an element #6378 2025-11-21 23:54:20 +01:00
Gorgious56 6bd7f02208 Merge pull request #7399 from IfcOpenShell/main
fix - handle float64 checksums from files saved in previous Blender v…
2025-11-21 23:28:28 +01:00
Gorgious 452d0f6023 fix - handle float64 checksums from files saved in previous Blender versions
Files saved in previous Blender versions stored position checksums as float64
(24 bytes for location, 72 bytes for rotation), but the current code only
expected float32 format. This caused a shape mismatch error (6,) vs (3,) when
comparing checksums during save operations.

Added byte size detection in is_moved() methods to automatically handle both
old float64 and new float32 checksum formats, ensuring backward compatibility
with files from previous versions.

Fixes ValueError when saving IFC files loaded from older Blender versions.
2025-11-21 23:23:57 +01:00
Andrej730 07005a9d3b build-deps - switch to freetype github archives for stability #7390 2025-11-22 01:02:41 +05:00
Andrej730 450ce689ba build-deps - fix set resetting ERRORLEVEL in commands logged in 52d3bae54 #7390 2025-11-22 01:02:41 +05:00
Andrej730 7c210721e6 build-deps - add pushd/popd for consistency 2025-11-22 01:02:16 +05:00
Andrej730 7e48d712a2 cmake - fix error building with GLTF_SUPPORT=OFF (487a4f3, #7390) 2025-11-21 18:28:06 +05:00
Andrej730 9eedbc6f72 FindOpenCASCADE - fix linking issue with non-configs #7390
Unexpected consequence of #7365 - previously `OPENCASCADE_LIBRARIES` supposed to be a list of libraries filepaths, but after refactor `OpenCASCADE_LIBRARIES` end up being both list of library names and list of filepaths.
And since link directories for the names was not provided, it failed to find the libraries to link.
If using OpenCASCADEconfig it wouldn't fail as cmake would evaluate library names as imported target names.

To fix it and simplify it even further, in case of manually provided paths, it now creates `OpenCASCADE_INTERFACE` target and stores it in `OpenCASCADE_LIBRARIES` - target will automatically have all the includes, library names and library dirs.
2025-11-21 18:28:06 +05:00
Andrej730 68b021b0e6 build-deps - fix hdf5 warnings in logs (similar to 890665b77) 2025-11-21 18:28:06 +05:00
Andrej730 a559b72e18 build-deps - fix boost project cache reset targeting wrong path
Noticed by another "The system cannot find the path specified." in logs 😅
2025-11-21 18:28:06 +05:00
Andrej730 32496c40b3 build-deps - fix missing popd for mpfr dir leading to issues installing hdf5 #7390 2025-11-21 18:28:05 +05:00
Andrej730 1b249b1013 build-deps - fix very very old bug with GitCloneAndCheckoutRevision not always checking out revision
Since this if-block happens in parentheses, then all `%VAR%` are expanded at the start and then `%ERRORLEVEL%` and `%RET%` values have no relation to the actual result of `git clone`. So if user were (un)lucky, then `%RET%` would be not yet set to "0" (by the previous function call), then it would skip checking out revision.

`GitCloneOrPullRepository` had similar issue, but it was never used anywhere.
2025-11-21 18:28:05 +05:00
Andrej730 d3fcf39c76 Windows build scripts - ECHO_ON env variable for debugging 2025-11-21 18:28:05 +05:00
Andrej730 692ef5ecea cmake - fix error finding Eigen::Eigen target #7390
`EIgen::Eigen` target used in #7340 for linking exists only if there's a cmake config, while there are cases when there's just include directory. Added same name interface target to mimic config.
2025-11-20 20:12:48 +05:00
Andrej730 f56a97512a Rename IFC_GEOM_EXPORTS replaced in #7340 2025-11-20 20:12:48 +05:00
Andrej730 4f5ebef5da cmake - use HDF5_INCLUDE_DIR instead HDF5_INCLUDE_DIRS
As this is the variable defined by `FindHDF5`.
2025-11-20 20:12:48 +05:00
Andrej730 57a724e80f FindCGAL.cmake 2025-11-20 20:12:48 +05:00
Andrej730 1b8652a789 FindOpenCOLLADA.cmake 2025-11-20 20:12:48 +05:00
Andrej730 c1aa702896 FindHDF5 - describe used variables, initialize from env 2025-11-20 20:12:47 +05:00
Andrej730 5b43d2b75e HDF5Config -> FindHDF5
See the reasoning in https://github.com/IfcOpenShell/IfcOpenShell/pull/7193#issuecomment-3552830928
2025-11-20 20:12:47 +05:00
Andrej730 3330e79556 FindLibXml2 - initialize from env variables 2025-11-20 20:12:47 +05:00
Andrej730 88d09c83b7 FindLibXml2 - add_debug_variants 2025-11-20 20:12:47 +05:00
Andrej730 5e6b96ee99 cmake - remove wasm_sysroot as superseded by e833d0c 2025-11-20 20:12:47 +05:00
Thomas Krijnen bd07078def Remove spurious cd 2025-11-20 16:12:26 +01:00
Andrej730 1d01d1f184 build-all.py - postpone 4396126 2025-11-20 16:58:04 +05:00
Dion Moult 3cee0811d4 Update VERSION 2025-11-20 22:06:24 +11:00
Dion Moult a46228a57e Hotfix to generate IOS docs until we properly package bsdd 2025-11-20 21:16:57 +11:00
Andrej730 158fe92644 HDF5Config.cmake - use CMAKE_SOURCE_DIR instead PROJECT_SOURCE_DIR
It doesn't really matter in this case - it matters really only for FindLibXml2, because `PROJECT_SOURCE_DIR` point to svgfill, when it's config executed, instead of `CMAKE_SOURCE_DIR` - so `CMAKE_SOURCE_DIR ` is never removed from the path, leading to cyclic calls. Adding for HDF5 too just for consistency.
2025-11-19 19:41:30 +05:00
Andrej730 12166ddbba HDF5Config.cmake - restore patched module path at the end 2025-11-19 19:41:30 +05:00
Andrej730 36a0c22217 FindUSD.cmake (untested) 2025-11-19 19:41:30 +05:00
Andrej730 59cc9d4a23 FindLibXml2.cmake 2025-11-19 19:41:29 +05:00
Andrej730 f3d918f76c FindOpenCASCADE - describe used variables at the top, move env vars 2025-11-19 19:41:29 +05:00
Esteban Dugueperoux 613015e37e cmake: Externalize OpenCascade config (#7365)
Co-authored-by: Andrej <azhilenkov@gmail.com>
2025-11-19 18:02:07 +05:00
Esteban DUGUEPEROUX be0eea1bed cmake: Have a dedicated CMakeLists.txt for IfcConvert & co 2025-11-19 13:28:45 +01:00
dependabot[bot] 37bbc0b693 Bump actions/upload-artifact from 4 to 5
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 5.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-19 13:11:39 +01:00
dependabot[bot] 30093499fa Bump aws-actions/configure-aws-credentials from 4 to 5
Bumps [aws-actions/configure-aws-credentials](https://github.com/aws-actions/configure-aws-credentials) from 4 to 5.
- [Release notes](https://github.com/aws-actions/configure-aws-credentials/releases)
- [Changelog](https://github.com/aws-actions/configure-aws-credentials/blob/main/CHANGELOG.md)
- [Commits](https://github.com/aws-actions/configure-aws-credentials/compare/v4...v5)

---
updated-dependencies:
- dependency-name: aws-actions/configure-aws-credentials
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-19 13:08:39 +01:00
dependabot[bot] 8e95bf73f1 Bump actions/upload-pages-artifact from 3 to 4
Bumps [actions/upload-pages-artifact](https://github.com/actions/upload-pages-artifact) from 3 to 4.
- [Release notes](https://github.com/actions/upload-pages-artifact/releases)
- [Commits](https://github.com/actions/upload-pages-artifact/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/upload-pages-artifact
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-19 13:08:09 +01:00
dependabot[bot] 05f7aac853 Bump docker/setup-buildx-action from 1 to 3
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 1 to 3.
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/v1...v3)

---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
  dependency-version: '3'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-19 13:08:02 +01:00
dependabot[bot] b135dd6994 Bump actions/setup-python from 2 to 6
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 2 to 6.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](https://github.com/actions/setup-python/compare/v2...v6)

---
updated-dependencies:
- dependency-name: actions/setup-python
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-19 13:07:34 +01:00
dependabot[bot] a5e7d7c4cc Bump docker/login-action from 1 to 3
Bumps [docker/login-action](https://github.com/docker/login-action) from 1 to 3.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/v1...v3)

---
updated-dependencies:
- dependency-name: docker/login-action
  dependency-version: '3'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-19 13:07:26 +01:00
dependabot[bot] 9680f6b29c Bump docker/build-push-action from 2 to 6
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 2 to 6.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v2...v6)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-19 13:05:03 +01:00
dependabot[bot] bc8dd3a3ab Bump mamba-org/setup-micromamba from 1 to 2
Bumps [mamba-org/setup-micromamba](https://github.com/mamba-org/setup-micromamba) from 1 to 2.
- [Release notes](https://github.com/mamba-org/setup-micromamba/releases)
- [Commits](https://github.com/mamba-org/setup-micromamba/compare/v1...v2)

---
updated-dependencies:
- dependency-name: mamba-org/setup-micromamba
  dependency-version: '2'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-19 13:04:39 +01:00
dependabot[bot] 9caf47b41f Bump docker/setup-qemu-action from 1 to 3
Bumps [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) from 1 to 3.
- [Release notes](https://github.com/docker/setup-qemu-action/releases)
- [Commits](https://github.com/docker/setup-qemu-action/compare/v1...v3)

---
updated-dependencies:
- dependency-name: docker/setup-qemu-action
  dependency-version: '3'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-19 13:04:14 +01:00
dependabot[bot] 5e1787b146 Bump actions/download-artifact from 4.1.7 to 6.0.0
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 4.1.7 to 6.0.0.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v4.1.7...v6.0.0)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-version: 6.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-19 13:03:52 +01:00
dependabot[bot] 07147f4b7f Bump actions/checkout from 2 to 5
Bumps [actions/checkout](https://github.com/actions/checkout) from 2 to 5.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v2...v5)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-19 13:02:55 +01:00
dependabot[bot] 4dfe15bd88 Merge pull request #7270 from IfcOpenShell/dependabot/npm_and_yarn/src/ifctester/webapp/vite-6.4.1 2025-11-19 17:00:32 +05:00
Andrej730 5896630630 Add dependabot to track github actions versions 2025-11-19 16:58:22 +05:00
Andrej730 48b1493c5c build_win - use official ccache-action as PR got merged 2025-11-19 16:58:22 +05:00
Andrej730 ee75ea1182 black . 2025-11-19 16:58:22 +05:00
Esteban DUGUEPEROUX 1876a788a9 cmake: Externalize cmake config for Serializers 2025-11-19 11:24:15 +01:00
Esteban DUGUEPEROUX 9d28628157 cmake: Have a dedicated CMakeLists.txt for kernels 2025-11-19 11:23:04 +01:00
Esteban DUGUEPEROUX 9b257b0a10 Remove support of cityjson for ifcconvert binary 2025-11-19 11:19:58 +01:00
Thomas Krijnen db7900027e fix stub 2025-11-19 10:43:52 +01:00
Andrej730 2af7470950 run-cmake.bat - use boost installation path
Now there's no need for BOOST_ROOT and BOOST_LIBRARYDIR anymore.
2025-11-18 19:59:34 +05:00
Andrej730 e1c3c44fb3 IfcGeomServer cmake - remove headers from source files 2025-11-18 19:59:34 +05:00
Andrej730 e7c422469a IfcGeomServer cmake - remove not required chrono component #7337
Noticed Linux build failing now
2025-11-18 19:59:34 +05:00
Andrej730 52d3bae543 build-deps - add some logs to make errors easier to narrow donw 2025-11-18 19:59:34 +05:00
Andrej730 b130925c5f build-deps - fix missing cd before trying to extract boost archive
Not sure when it was introduced, but it was still using previous dependency path and leading to confusing error:
```
Boost 1.86.0 already downloaded. Skipping.
Extracting Boost 1.86.0 into L:\Projects\Github\IfcOpenShell\_deps from boost-1.86.0-b2-nodocs.7z
An error occurred
```
2025-11-18 19:59:34 +05:00
Andrej730 890665b77b build-deps - fix reappearing "The system cannot find the file specified." error in logs
because of the failing rename
2025-11-18 19:59:34 +05:00
Andrej730 99c95bc92d black . 2025-11-18 19:59:34 +05:00
Bruno Perdigão 8647f9c357 Polyline tool - avoids rounding angle when distance is too big. 2025-11-17 20:38:34 -03:00
Bruno Perdigão 300855276f See #7046. Add Numpy version of a few Blender fuctions to improve precision. The snapping system was having a poor precision with large numbers. Some functions were rewritten to use Numpy instead of Mathutils. This seems to solve the issue, but further adjustments might be necessary
Note: rv3d.perspective_matrix should be avoided in these context. Use a numpy version of np.array(window_matrix) @ np.array(view_matrix). See https://github.com/IfcOpenShell/IfcOpenShell/issues/7046

Note2: region_2d_to_origin_3d_np clamp should be used for the snap to work in orthographic view.
2025-11-17 20:38:34 -03:00
Bruno Perdigão 1a8831012b Fix snapping for blender curve objects 2025-11-17 20:38:34 -03:00
Thomas Krijnen 1bc4e82b7d reorder 2025-11-17 19:15:20 +01:00
Thomas Krijnen fc197c1d32 anon namespace 2025-11-17 19:09:31 +01:00
Thomas Krijnen 33cfa9e2b1 function3 2025-11-17 18:57:22 +01:00
Thomas Krijnen 9e9d664511 .glb --separate-z-up-node option 2025-11-17 18:26:00 +01:00
Ryan Schultz 1b9acadd81 Disable text decorations when not in camera view 2025-11-17 09:01:19 -06:00
Andrej730 c5f6d47c44 build-deps - include swig version to dependency name 2025-11-17 19:29:07 +05:00
Andrej730 d19b6d72ef build-all.py - include swig-version to dependency name
Otherwise it doesn't update and keeps using older cached version.
2025-11-17 19:28:10 +05:00
Andrej730 919231b096 build-all.py - remove outdated comment 2025-11-17 19:28:10 +05:00
Andrej730 cead5cc75d ci - add swig build to fix failing stub test
Older swig versions have some redundant symbols, using the one we use in other build scripts.
2025-11-17 19:28:09 +05:00
Andrej730 9396cb226f ci - add opencollada to fix failing stub test 2025-11-17 19:28:09 +05:00
Andrej730 56a63cab6e build-deps - sync swig version with build-all.py 2025-11-17 19:28:09 +05:00
Andrej730 449d13bd5f Fix stub missing construct_iterator 2025-11-17 19:26:14 +05:00
Thomas Krijnen 487a4f345d Creoox json serializer 2025-11-17 14:06:39 +01:00
Thomas Krijnen d6daa64c48 Support --element-hierarchy in .glb output 2025-11-17 14:05:59 +01:00
Thomas Krijnen bf0aa247a0 IfcConvert: only conditionally bypass properties 2025-11-17 13:50:50 +01:00
Andrej730 a7c8c908fe Fix left out invalid mesh data blocks and fix failing geom core test (after fcee54c) 2025-11-17 16:30:28 +05:00
Andrej730 64686dde5c Remove redundant :type from doc-strings
Args types already covered by annotations
2025-11-17 16:30:27 +05:00
Andrej730 29ab446f8c use common rst syntax in doc-strings 2025-11-17 16:30:27 +05:00
Andrej730 51e2e7cd82 Fix boundary bim test after 6c31d43 2025-11-17 16:30:27 +05:00
Andrej730 e879dac4bd Remove obsolete tests after e662308 2025-11-17 16:30:26 +05:00
Andrej730 145c629d92 Fix core geometry tests after e662308 c4d54f7 2025-11-17 16:30:26 +05:00
Andrej730 3fe9279bb4 pyproject - fix typo 2025-11-17 16:30:26 +05:00
Andrej730 428d88f01c Fix missing bsdd pydantic dependency in #7145 2025-11-17 16:30:26 +05:00
Andrej730 3986d4e85b Remove unused tool.sequence methods 2025-11-17 16:30:26 +05:00
Andrej730 02d35b6047 Fix missing ifc_file definition in 471bd34 2025-11-17 16:30:25 +05:00
Andrej730 3cc5931eae Fix missing imports and don't use api.run 2025-11-17 16:30:25 +05:00
Andrej730 db5ec77730 black format src/pyodide 2025-11-17 16:30:25 +05:00
Andrej730 bec46562f5 gitignore running_pid.json 2025-11-17 16:30:25 +05:00
Andrej730 1508c4ab05 test_feature - restore EPset_Drawing.ifc 2025-11-17 16:30:24 +05:00
Andrej730 99ae768ddf test_feature - clean up .ifc.cache. files after test was executed 2025-11-17 16:30:24 +05:00
Andrej730 fb902a04f5 test_feature - use common prompts for loading/saving temp projects 2025-11-17 16:30:24 +05:00
Andrej730 e46a99870b typing 2025-11-17 16:30:24 +05:00
Andrej 3fbba405d4 Merge pull request #7362 from sboddy/fix_7348_ci_test_normalize_path 2025-11-17 07:12:11 +01:00
Andrej730 989415f423 setup_pytest - use check_call and less repetition 2025-11-17 11:09:15 +05:00
Andrej730 7a3c7704b5 preferences descriptions - document autoreplaced strings in commands 2025-11-17 11:09:14 +05:00
Andrej730 5d7407e2b9 preferences descriptions - adjust line length 2025-11-17 11:09:08 +05:00
Andrej730 4a5f1b01a1 black . 2025-11-17 11:09:08 +05:00
Ryan Schultz a038fa62ba Use spacebar to toggle on/off direct profile edit mode. Works on IfcExtrudedSolids, LAYER2 objects(slabs), and axis-based geometry. 2025-11-16 15:00:10 -06:00
Ryan Schultz bb1127d815 prompt for Blender version 2025-11-15 19:10:15 -06:00
Ryan Schultz 40cf322fb4 fall back to a Windows system font if the font isn’t found in addon folder 2025-11-15 09:58:37 -06:00
Ryan Schultz f32ccec154 Fix #7364: If you change the width/height of a drawing camera it doesn't scale the text decoration. 2025-11-15 08:25:27 -06:00
Ryan Schultz dc0b73f46f Ensure annotation is in front of a RCP camera. 2025-11-15 08:06:06 -06:00
Ryan Schultz 36cb625c4e Fix #7353: Fix section annotations squashed in section views
Add target view detection to generate_section_reference_points() to create
appropriate geometry for each view type. Plan views get horizontal lines
(clip_segment), section/elevation views get vertical lines (elevate_segment).
2025-11-15 07:52:07 -06:00
Stephen Boddy 89314733ff blackwq 2025-11-14 15:47:50 +00:00
Stephen Boddy d4e945222d Fix #7348: test_normalize_path failure on CI/Linux. 2025-11-14 15:41:13 +00:00
Andrej730 fe80620a5d Add basic parametric objects tests #7355 2025-11-13 19:13:28 +05:00
Andrej730 166ddb4799 Fix parametric stair editing after 3617d11 #7355 2025-11-13 19:13:28 +05:00
Andrej730 8e2a310f92 stair generation - remove debug prints (b46136e) 2025-11-13 19:13:28 +05:00
Andrej730 339a0606c0 Fix failing product assignment test after 5a511ee
Moved code from `core`, added a warning so we could possibly narrow down #4014 in the future.
2025-11-13 19:13:27 +05:00
Andrej730 b6f1119eaa Fix failing product assignment test after 555621c 2025-11-13 19:13:27 +05:00
Andrej730 139a024d5f dev_environment.py - bump Blender version to 5.0 2025-11-13 19:13:27 +05:00
Andrej730 9491096485 black . 2025-11-13 19:13:27 +05:00
Thomas Krijnen a34e42a519 Handle infra sweeps of open loops #7338 2025-11-13 12:43:41 +01:00
Esteban DUGUEPEROUX 6a7ecf7f58 cmake: Have a dedicated CMakeLists.txt for IfcGeomServer 2025-11-13 10:14:59 +01:00
Richard Brice 96da78c670 Updates IfcSectionedSurface so it works with taxonomy::function_item 2025-11-12 11:37:07 -08:00
Andrej730 9adc8e484a Bonsai - bump ifcopenshell build #7330 2025-11-12 22:34:42 +05:00
kaplus e8eb5e4d0a Add continue statement to skip duplicate calculations
Skip general path to avoid duplicate calculations.
2025-11-12 13:14:38 +01:00
Andrej730 080222ffe6 black . 2025-11-12 12:56:35 +05:00
Ryan Schultz abef655482 Fix #6938 - Have parent facet select the parent aggregate as well. 2025-11-11 20:19:38 -06:00
Ryan Schultz 85b3f831dc copy query for aggregates into clipboard, to align with common UI pattern. 2025-11-11 20:17:37 -06:00
falken10vdl d8ece32d62 Add markdown-it-py and mdurl dependencies for drawing module annotations (#7346) 2025-11-11 16:33:19 -06:00
Ryan Schultz 0df7117d37 Sort annotations to ensure FILLAREA types are drawn first (at the bottom) 2025-11-11 14:26:55 -06:00
sboddy 91d67e6371 Improve Merge/Mirror error messages to reduce confusion. (#6911) 2025-11-11 13:57:52 -06:00
Ryan Schultz 87d1b0637a Close #7294: Add target-view and scale classes to SVG parent group from IFC data
Reads from EPset_Drawing.TargetView and EPset_Drawing.Scale to add
CSS classes to the linework parent group for view-specific styling.
2025-11-11 12:47:58 -06:00
Esteban Dugueperoux b8adddc648 fix test_number_formatting tests and add cases (#7332) 2025-11-11 11:25:00 -06:00
Andrej730 25d3edc537 Avoid producing test artifacts in src/bonsai 2025-11-11 19:56:57 +05:00
Andrej730 7f8cb80281 Fix TestDrawingStyles after 44cc127 2025-11-11 19:56:57 +05:00
Andrej730 a2ce2ea23a Fix TestDisableEditingText crash after 679fa6d 2025-11-11 19:56:56 +05:00
Andrej730 7f7aef0e6e Fix duplicate drawing core test (8ffa6fc) 2025-11-11 19:56:56 +05:00
Andrej730 821b5380ea Fix copy to container test (ddbe627) 2025-11-11 19:56:56 +05:00
Andrej730 e8bcbc83a4 Update operator.py 2025-11-11 19:56:56 +05:00
Andrej730 091af3c1c9 Add todo for unused expression (17d6b8a) 2025-11-11 19:56:55 +05:00
Andrej730 4cc50301dd Remove unused variable (17d6b8a) 2025-11-11 19:56:55 +05:00
Andrej730 f643a1615e Fix failing aggregate duplication test after 17d6b8a 2025-11-11 19:56:55 +05:00
Andrej730 71e78c67f8 Add visibility and lock steps to geometry.feature
Added steps to set 'is_visible' to TRUE and 'is_locked' to FALSE in the 'Spatial Decomposition' panel for the 'Override paste buffer - with active IFC data' scenario. This ensures the test covers visibility and lock state handling for IFC objects.
2025-11-11 19:56:55 +05:00
Andrej730 e7c4a19d9e bim.override_object_duplicate_move - sync poll with object.duplicate_move 2025-11-11 19:56:54 +05:00
Andrej730 6c31d43e72 Fix feature tests after a11f81f 2025-11-11 19:56:54 +05:00
Andrej730 83b56d591b Fix error activating drawing style
Example error:
```
TypeError: bpy_struct: item.attr = val: enum "Default" not found in ('city.exr', 'courtyard.exr', 'forest.exr', 'interior.exr', 'night.exr', 'studio.exr', 'sunrise.exr', 'sunset.exr')
```

There was a bug in shading_styles.json where we were using `Default` for `studio_light`, which is only available for `SOLID` shading type. Changed value to `forest.exr`, added code to handle old bugged value since users already copied and possibly modified shading_styles.json
2025-11-11 19:56:54 +05:00
Andrej730 e5b26067ab typing 2025-11-11 19:56:54 +05:00
Andrej730 a4fae94be8 test_feature - add separate non-debug option to save ifc project
Also clean up temp folder before save (met issues when tests were using not updated shading_styles.json)
2025-11-11 19:56:53 +05:00
Andrej730 af4851f03a Fixing bsdd feature tests (a929ab0) 2025-11-10 18:58:02 +05:00
Andrej730 155d4c44ae PanelSpy - fix error handling methods that have a return value
E.g. `ReferenceUI` has `self.get_object_name(context)` - since value wasn't returned, it was returning `None`, breaking the tests.
2025-11-10 18:58:02 +05:00
Andrej730 48119baf97 test_feature - non-background mode for debugging 2025-11-10 18:58:02 +05:00
Andrej730 dd1989aebb test_feature - use pformat to print props for readibility 2025-11-10 18:58:01 +05:00
Andrej730 0806430700 TemplateListSpy - add repr 2025-11-10 18:58:01 +05:00
Andrej730 2d97f6638b test_feature - add debugpy option 2025-11-10 18:58:01 +05:00
Andrej730 b10e5ad4d6 PanelSpy - target not all callables
Because sometimes we have `Data` classes as panel attributes and they're also callable.
2025-11-10 18:58:01 +05:00
Andrej730 24f991ceb1 Fix feature tests after a11f81f 2025-11-10 18:58:01 +05:00
Andrej730 ac11baa0c4 Fix the_object_name_does_not_exist not checking for errors
`assert True` was introduced in 1c1035e and original implementation with `assert False` was removed in b0d8101
2025-11-10 18:58:01 +05:00
Andrej730 1609a77b77 typing for PanelSpy related options 2025-11-10 18:58:00 +05:00
Andrej730 dd2f091a52 object.delete and outliner.delete overrides to reuse original poll methods for consistency 2025-11-10 18:58:00 +05:00
Andrej730 a7bf0e7d5e Fix remove drawing feature test (f11d79f) 2025-11-10 18:58:00 +05:00
Andrej730 19bdd3e622 i_set_the_prop_property_to_value - don't fail silently on incorrect bool values 2025-11-10 18:58:00 +05:00
Andrej730 e1bf891ec6 PanelSpy - also support draw_header for panels 2025-11-10 18:58:00 +05:00
Andrej730 4009834b97 PanelSpy - rename panel attr to blender_panel to avoid conflicts with panel method
Also added support for `panel` method.
2025-11-10 18:57:59 +05:00
Andrej730 77f5e1fbad PanelSpy - add typing 2025-11-10 18:57:59 +05:00
Andrej730 c4eb77d544 Blender 5.0 - update shadint_styles eevee name 2025-11-10 18:57:59 +05:00
Andrej730 8a4ea43239 Blender 5.0 - fix error activating drawing styles
Some props were removed in Blender 5.0
2025-11-10 18:57:59 +05:00
Thomas Krijnen f056e22f60 ruff 2025-11-10 11:59:10 +01:00
Thomas Krijnen 05e81040ce black 2025-11-10 11:53:33 +01:00
Thomas Krijnen 8d8133e780 submodule 2025-11-10 11:45:05 +01:00
Thomas Krijnen dceb35782e Add platform tag for linuxarm64 in Makefile #7325 2025-11-10 11:17:24 +01:00
Thomas Krijnen 2c5f0c6cf2 Add Linux ARM 64bit to CI workflow #7325 2025-11-10 11:16:27 +01:00
Thomas Krijnen abd667684d Supply CGAL_WITH_GMPXX to ifcopenshell configure #7330 2025-11-10 10:48:24 +01:00
Thomas Krijnen 31b5f88497 Rename WITH_GMPXX to CGAL_WITH_GMPXX in build script #7330 2025-11-10 10:33:48 +01:00
Thomas Krijnen 77a7026d33 Disable GMPXX in CGAL build #733 2025-11-10 10:15:14 +01:00
Thomas Krijnen 9c5f3f9754 Disable GMPXX in CGAL build #7330 2025-11-10 09:51:38 +01:00
Esteban DUGUEPEROUX 5d56025014 cmake: Separate ifcparse build in its own CMakeLists.txt file 2025-11-10 09:42:51 +01:00
Bruno Postle ad62c75f4f ifcclash ifccsv ifcdiff ifcpatch: README.md stubs
The pyproject.toml files were referencing these missing files
2025-11-09 20:52:12 +00:00
Ryan Schultz 7a5bd03216 Close #7220: Duplicate multiple selected Linked Aggregates at one time 2025-11-09 13:45:09 -06:00
Ryan Schultz 593e0e28a9 fix #4189: local_view turns off/on text decorations, as well as cut decorator. 2025-11-08 16:35:46 -06:00
Massimo Fabbro ed2b2de79a Add profiler in ifcopenshell util and ifc5d quantify function time
Useful for quantify function optimization
2025-11-08 20:00:35 +01:00
Ryan Schultz 5a511eecc1 Fix #4014: Unassign annotation from all products before reassignment
The edit_assigned_product() function now removes the annotation from all
existing IfcRelAssignsToProduct relationships instead of just one, preventing
annotations from being incorrectly assigned to multiple products simultaneously.

This fixes issues where leader lines wouldn't update correctly because the
annotation retained old product assignments.
2025-11-08 12:46:33 -06:00
Bruno Postle 83e0fa1287 Bump python bindings maximum python version
Python 3.14 is now a thing and the build seems ok
2025-11-08 17:38:56 +00:00
Ryan Schultz a2dbdbf8cc Fix #7274: Add unique CSS class suffix to all selectors in sheet drawings
Previously, when multiple CSS selectors were comma-separated in a rule,
only the last selector received the unique drawing ID suffix. This caused
style conflicts when multiple drawings were placed on the same sheet.

Now all selectors in comma-separated lists receive the unique suffix,
ensuring proper style isolation between drawings.

Example:
Before: .cut.Status-DEMOLISH, .cut.Option-D.d2156 { ... }
After:  .cut.Status-DEMOLISH.d2156, .cut.Option-D.d2156 { ... }
2025-11-08 10:41:43 -06:00
Ryan Schultz 3d68bbc0ca Fix #7287: to correctly position IfcAnnotation on RCP's. 2025-11-08 09:11:38 -06:00
Thomas Krijnen 40f9f261ae submodule 2025-11-07 16:22:51 +01:00
Andrej730 c08cc58904 Blender 5.0 - fix opening Web UI
In Blender 5.0 some theme attributes were removed and moved to other places and code broke.

In the commit I've also switched the code to less dynamic approach, so static type checker could caught potential errors.
2025-11-07 19:13:44 +05:00
Andrej730 ebdb122a22 Fix error removing systems 6469f79 2025-11-07 19:13:44 +05:00
Andrej730 5de2752685 light ui - use calendar for simplicity 2025-11-07 19:13:44 +05:00
Andrej730 d2245a95c7 Blender 5.0 - fix issues related to EEVEE renamed again
Ahh, it was a mistake warning about non-`_NEXT` values previously - didn't realized that this name was temporary for Blender.
2025-11-07 19:13:43 +05:00
Andrej730 47075e95bd Blender 5.0 - fix error enabling Rendered mode in Solar Analysis 2025-11-07 19:13:43 +05:00
Thomas Krijnen 9d4c980e02 black 2025-11-07 11:50:33 +01:00
Thomas Krijnen 53c1a1537d ruff 2025-11-07 11:50:18 +01:00
Thomas Krijnen 7db2f66685 WKT footprint massaging 2025-11-07 10:59:33 +01:00
Thomas Krijnen 6dd34b1fa4 Fix failing test by bumping commit ref #7193 2025-11-07 10:59:10 +01:00
Ryan Schultz 862713cda4 related to #7298 - skip material, as well. 2025-11-06 20:56:56 -06:00
Ryan Schultz 06c1668e42 Closes #7298 - skips count on csv import. 2025-11-06 13:20:55 -06:00
Thomas Krijnen 5f9f0f26eb submodule 2025-11-06 19:06:51 +01:00
Andrej730 8db22cdaf7 test_model - fix incorrect stair profile values in generation tests 2025-11-06 18:46:28 +05:00
Andrej730 3bed9262e5 Fix miscalculated stair lengths in some cases (f52aafd)
`get_active_stair_calculated_params` wasn't updated when zero tread runs support was added and it's still was relying on a convention that `0.0` custom tread run = no custom tread run.
2025-11-06 18:46:06 +05:00
Andrej730 0c171a457b stair generation - fix bug with extended top nib with zero first tread run
`s0 = V_(custom_first_last_tread_run[0] or tread_run, tread_rise) + td_vector` was assuming `0` means "no custom first tread run", not it means actually "zero first tread run", so could should be adjusted

before - https://files.catbox.moe/5ovqnh.mp4
after - https://files.catbox.moe/srorl2.mp4
2025-11-06 18:44:49 +05:00
Andrej730 b5e8dfd863 generate_stair_2d_profile - add typing 2025-11-06 18:44:49 +05:00
Thomas Krijnen c9df87fc25 submodule 2025-11-06 10:48:12 +01:00
Esteban DUGUEPEROUX 30925a4b3c fix format 2025-11-05 19:26:22 +01:00
Esteban DUGUEPEROUX ccf0efffce cmake: Simplify hdf5 linkage 2025-11-05 19:26:22 +01:00
Andrej730 169c4392a9 test_model - simplify stair generation kwargs 2025-11-05 19:30:36 +05:00
Andrej730 e543785959 Fix breaking tests for stair length calculations with custom zero tread run (f52aafd)
In both cases it was providing arguments `(0,0)`, but was assuming only one tread will be zeroed.
2025-11-05 19:30:36 +05:00
Andrej730 3617d119cb Fix breaking old stair generation tests after f52aafd
Before f52aafd default `(0,0)` meant that there are no custom tread runs, but after the change it actually means that runs for first and last treads are 0. So default value should be adjusted.
Added explicit `None` value for clarity.
2025-11-05 19:30:36 +05:00
Andrej730 5d1f28c3cb validate_object_selection - don't invalidate active objects when they're not in view layer
Since it's not illegal in Blender and can be helpful - e.g. when you were assigning class to ifc space, you would lose selection completely and would need to reselect object to continue working on it.
2025-11-05 19:19:49 +05:00
Andrej730 6f04eb12d0 black . 2025-11-05 19:18:11 +05:00
Ryan Schultz b46136e9f8 For stairs, Lock total length now takes first and last tread widths into account as well. 2025-11-05 06:55:03 -06:00
Andrej730 c3fe2d0ee3 build_win - revert schema versions limit
Committed by accident when debugging 🥲
2025-11-05 14:05:00 +05:00
Andrej730 aeed371888 ifcopenshell.file - add todo notes for temp workarounds, make workaround more explicit 2025-11-04 19:24:22 +05:00
Andrej730 a50c8869a5 bonsai - fix ifcsqlite import (546a042) 2025-11-04 19:24:21 +05:00
Andrej730 04d1fe4052 sql, stream - more clear error message when accessing wrapped_data 2025-11-04 19:24:21 +05:00
Andrej730 4e1d0bcf22 Fix failing selecting products tests (3aa5ddb07)
After 3aa5ddb07 `select_products` adds to current selection, instead of resetting it.

So couple tests started to fail since they create new objects and `view_layer` is not yet updated (previously `bpy.ops.object.select_all(action="DESELECT")` was also implicitly updating view layer).
```
FAILED test/tool/test_spatial.py::TestSelectProducts::test_select_products - AssertionError: assert bpy.data.objects['Object'] in []
FAILED test/tool/test_system.py::TestSelectSystemProducts::test_run - AssertionError: assert bpy.data.objects['Object'] in []
```
2025-11-04 19:24:21 +05:00
Andrej730 d2126e5929 Blender 5.0 - avoid using deprecated use_nodes 2025-11-04 19:24:20 +05:00
Andrej730 3bdfbebc02 Blender 5.0 - accomodate new mathutils buffer protocol type
Long story short - since 5.0 `np.array(Vector())` is now producing `np.float32` instead of `np.float64`. So we have to provide `dtype` explicitly to support both <5.0 and >= 5.0.

See https://projects.blender.org/blender/blender/issues/149283
2025-11-03 19:32:59 +05:00
Andrej730 1a9bd1891e blender 5.0 - fix moved bl_ui_utils module (bonsai is now launchable in 5.0) 2025-11-03 18:33:34 +05:00
Andrej730 1599bc14b3 Fix core/test_system.py (5de96c9) 2025-11-03 18:33:34 +05:00
Andrej730 30b74ab09c blender 5.0 - ensure the latest module is used for type checking 2025-11-03 18:33:34 +05:00
Andrej730 24bee01f01 black . 2025-11-03 18:33:34 +05:00
Ryan Schultz 7329c485ea Fix quantify() regression where by_type queries and grouped iteration could apply QTOs to all entities in the model instead of the intended element subset. 2025-10-31 10:03:52 -05:00
Andrej730 9c93b12084 Blender 5.0 - fix bpy_restrict_state error
It's probably will be very popular bug, when someone would try to migrate to Blender 5.0 using old Bonsai.

Example traceback:

```
  File "Blender\5.0\extensions\.local\lib\python3.11\site-packages\bonsai\bim\prop.py", line 34, in <module>
    import bonsai.bim.schema
  File "Blender\5.0\extensions\.local\lib\python3.11\site-packages\bonsai\bim\schema.py", line 23, in <module>
    import bpy_restrict_state
ModuleNotFoundError: No module named 'bpy_restrict_state'
```
2025-10-31 18:50:53 +05:00
Andrej730 b97c7d39ad dev_environment.py - small error message tweak 2025-10-31 18:50:52 +05:00
Andrej730 fa0c1c5d2a dev_environment.py - fix typo 2025-10-31 18:50:52 +05:00
Andrej730 e4c49ce57b Fix missing Serializers target when building just IfcGeomServer
Noticed testing command from #7277. I believe Serializers it not actually needed to build IfcGeomServer.

Example error:
```
/usr/bin/ld: cannot find -lSerializers: No such file or directory
```
2025-10-31 18:50:52 +05:00
Andrej730 3ae77c68e8 build-all.py - add VERBOSE=1 for building executables 2025-10-31 18:50:52 +05:00
Andrej730 8b1cb8e7ae black . 2025-10-31 18:50:52 +05:00
Andrej730 ce0348518d cmake - note future cmake bug fixes 2025-10-31 18:50:52 +05:00
Andrej730 a660027b93 test_package - ensure wasm package is present for active build commit 2025-10-31 16:09:22 +05:00
Thomas Krijnen 44ef409335 Don't check for drawing semantics on qto or predefpset #7304 2025-10-31 09:56:54 +01:00
Ryan Schultz 089edff1db fix for #7296: handle openings without Blender representation in get_gross_top_area (#7297) 2025-10-30 20:40:06 -05:00
Thomas Krijnen bd5a1b5bda Account 2x3/4 differences in Shading/Rendering::Transparency() 2025-10-30 18:35:20 +01:00
Andrej730 ba3517f3aa build-all.py - switch to dynamic_lookup for macos builds
1) `-undefined,suppress` is deprecated, linker is sending warnings about it and it seems to be superseded by `dynamic_lookup`
2) `-flat_namespace` is not compatible with GMP when building for intel, for some reason and results in horrible errors during linking:

Example error:
```
ld: branch8 out of range 366651 in ___gmpn_add_nc
final section layout:
    __TEXT               addr=0x00000000, size=0x02d09000, fileOffset=0x00000000
        __text           addr=0x00001400, size=0x0264a5e4, fileOffset=0x00001400
        __stubs          addr=0x0264b9e4, size=0x0000423c, fileOffset=0x0264b9e4
        __stub_helper    addr=0x0264fc20, size=0x00006c58, fileOffset=0x0264fc20
        __gcc_except_tab addr=0x02656878, size=0x002efc04, fileOffset=0x02656878
        __const          addr=0x02946480, size=0x001cdc18, fileOffset=0x02946480
        __cstring        addr=0x02b140a0, size=0x0014324b, fileOffset=0x02b140a0
        __unwind_info    addr=0x02c572ec, size=0x0008d2d0, fileOffset=0x02c572ec
        __eh_frame       addr=0x02ce45c0, size=0x00024a30, fileOffset=0x02ce45c0
    __DATA_CONST         addr=0x02d09000, size=0x0022b000, fileOffset=0x02d09000
        __got            addr=0x02d09000, size=0x00001dc8, fileOffset=0x02d09000
        __mod_init_func  addr=0x02d0adc8, size=0x00000690, fileOffset=0x02d0adc8
        __const          addr=0x02d0b460, size=0x00227fd0, fileOffset=0x02d0b460
    __DATA               addr=0x02f34000, size=0x000c3000, fileOffset=0x02f34000
        __la_symbol_ptr  addr=0x02f34000, size=0x000056a0, fileOffset=0x02f34000
        __data           addr=0x02f396a0, size=0x0005f7b8, fileOffset=0x02f396a0
        __thread_vars    addr=0x02f98e58, size=0x000006d8, fileOffset=0x02f98e58
        __thread_ptrs    addr=0x02f99530, size=0x00000178, fileOffset=0x02f99530
        __thread_data    addr=0x02f996b0, size=0x000000d8, fileOffset=0x02f996b0
        __thread_bss     addr=0x02f99790, size=0x00018850, fileOffset=0x02f99790
        __bss            addr=0x02fb1fe0, size=0x0003b0c0, fileOffset=0x00000000
        __common         addr=0x02fed0a0, size=0x00009098, fileOffset=0x00000000
    __LINKEDIT           addr=0x02ff7000, size=0x0148e000, fileOffset=0x02fb2000
clang: error: linker command failed with exit code 1 (use -v to see invocation)
```
2025-10-30 19:45:24 +05:00
Andrej730 7f144b7b95 build-all.py - remove unused wasm flags
`-Wl,-undefined,suppress` have no meaning in emscripten and they are just ignored. And emscripten is ignoring undefined symbols by default without any flags.
2025-10-30 19:45:24 +05:00
Andrej730 19ac0b6c38 build-all.py - add VERBOSE=1 to the wrapper make command 2025-10-30 19:45:24 +05:00
Andrej730 3562437293 build-all.py - fix missing mac cross compilation host args in gmp
I guess something has changed in gmp 6.3.0 and this issue become more apparent.

Example error:
```
tmp-cnd_sub_n.s:133:14: error: tmp-cnd_add_n.sbrackets expression not supported on this target:
130 stp x8, x9, [x1,#16]
             ^
:2tmp-cnd_sub_n.s: :error: 134invalid instruction mnemonic 'bic':
6 bic x7, x13, x0:
error:  invalid instruction mnemonic 'cset'^
~Lrt: cset x0, cc~

     ^~~tmp-cnd_add_n.s~:
131:2: error: invalid instruction mnemonic 'adcs'
 adcs x8, x10, x6
 ^~~~
tmp-cnd_add_n.s:132:2: error: invalid instruction mnemonic 'adcs'
 adcs x9, x11, x7
 ^~~~
tmp-cnd_add_n.s:133:14: error: brackets expression not supported on this target
 stp x8, x9, [x1,#16]
             ^
tmp-cnd_add_n.s:134:6: error: invalid instruction mnemonic 'cset'
Lrt: cset x0, cs
     ^~~~
```
2025-10-30 19:45:24 +05:00
Andrej730 e8ece71d22 black . 2025-10-30 19:45:24 +05:00
212 changed files with 4774 additions and 2121 deletions
+8
View File
@@ -0,0 +1,8 @@
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
+4 -4
View File
@@ -21,12 +21,12 @@ jobs:
steps:
- name: Checkout Repository
uses: actions/checkout@v3
uses: actions/checkout@v5
with:
submodules: recursive
- name: Checkout Build Repository
uses: actions/checkout@v3
uses: actions/checkout@v5
with:
repository: IfcOpenShell/build-outputs
path: ./build
@@ -81,7 +81,7 @@ jobs:
- name: Upload Build Logs
if: always()
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: build-logs-osx-${{ matrix.arch }}
path: |
@@ -139,7 +139,7 @@ jobs:
cd ..
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
uses: aws-actions/configure-aws-credentials@v5
with:
aws-access-key-id: ${{ secrets.AWS_UPLOAD_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_UPLOAD_SECRET_ACCESS_KEY }}
+4 -4
View File
@@ -9,13 +9,13 @@ jobs:
steps:
- name: Checkout Repository
uses: actions/checkout@v3
uses: actions/checkout@v5
with:
submodules: recursive
path: IfcOpenShell
- name: Checkout Build Repository
uses: actions/checkout@v3
uses: actions/checkout@v5
with:
repository: IfcOpenShell/build-outputs
path: ifcopenshell_build
@@ -42,7 +42,7 @@ jobs:
- name: Upload Build Logs
if: always()
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: build-logs-pyodide
path: |
@@ -77,7 +77,7 @@ jobs:
git push || echo "Push failed"
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
uses: aws-actions/configure-aws-credentials@v5
with:
aws-access-key-id: ${{ secrets.AWS_UPLOAD_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_UPLOAD_SECRET_ACCESS_KEY }}
+6 -6
View File
@@ -29,12 +29,12 @@ jobs:
aws --version
- name: Checkout Repository
uses: actions/checkout@v3
uses: actions/checkout@v5
with:
submodules: recursive
- name: Checkout Build Repository
uses: actions/checkout@v3
uses: actions/checkout@v5
with:
repository: IfcOpenShell/build-outputs
path: ./build
@@ -48,8 +48,8 @@ jobs:
[ -n "$install_root" ] && find "$install_root" -type f -name 'cache-*.tar.gz' -maxdepth 1 -exec tar -xzf {} -C "$install_root" \; || true
- name: ccache
# Latest release (1.2.19) doesn't support Rocky, so using a specific commit.
uses: hendrikmuhs/ccache-action@eca11c308176d48942455b9e5b1b70ff6950a778
# TODO: Use tag after 1.2.20 releases.
uses: hendrikmuhs/ccache-action@5ebbd400eff9e74630f759d94ddd7b6c26299639
with:
key: ubuntu-22.04-${{ runner.arch }}-rockylinux8
@@ -61,7 +61,7 @@ jobs:
- name: Upload Build Logs
if: always()
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: build-logs-rocky
path: |
@@ -118,7 +118,7 @@ jobs:
cd ..
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
uses: aws-actions/configure-aws-credentials@v5
with:
aws-access-key-id: ${{ secrets.AWS_UPLOAD_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_UPLOAD_SECRET_ACCESS_KEY }}
+6 -6
View File
@@ -29,12 +29,12 @@ jobs:
aws --version
- name: Checkout Repository
uses: actions/checkout@v3
uses: actions/checkout@v5
with:
submodules: recursive
- name: Checkout Build Repository
uses: actions/checkout@v3
uses: actions/checkout@v5
with:
repository: IfcOpenShell/build-outputs
path: ./build
@@ -48,8 +48,8 @@ jobs:
[ -n "$install_root" ] && find "$install_root" -type f -name 'cache-*.tar.gz' -maxdepth 1 -exec tar -xzf {} -C "$install_root" \; || true
- name: ccache
# Latest release (1.2.19) doesn't support Rocky, so using a specific commit.
uses: hendrikmuhs/ccache-action@eca11c308176d48942455b9e5b1b70ff6950a778
# TODO: Use tag after 1.2.20 releases.
uses: hendrikmuhs/ccache-action@5ebbd400eff9e74630f759d94ddd7b6c26299639
with:
key: ubuntu-22.04-${{ runner.arch }}-rockylinux8
@@ -61,7 +61,7 @@ jobs:
- name: Upload Build Logs
if: always()
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: build-logs-rocky-arm64
path: |
@@ -118,7 +118,7 @@ jobs:
cd ..
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
uses: aws-actions/configure-aws-credentials@v5
with:
aws-access-key-id: ${{ secrets.AWS_UPLOAD_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_UPLOAD_SECRET_ACCESS_KEY }}
+5 -5
View File
@@ -12,12 +12,12 @@ jobs:
arch: ['x64']
steps:
- name: Checkout Repository
uses: actions/checkout@v3
uses: actions/checkout@v5
with:
submodules: recursive
- name: Checkout Build Repository
uses: actions/checkout@v3
uses: actions/checkout@v5
with:
repository: IfcOpenShell/build-outputs
path: _deps-vs2022-x64-installed
@@ -37,8 +37,8 @@ jobs:
}
- name: ccache
# Use fork to resolve cache misses / duplicated cache entries on Windows.
uses: Andrej730/ccache-action@main
# TODO: Use tag after 1.2.20 releases.
uses: hendrikmuhs/ccache-action@5ebbd400eff9e74630f759d94ddd7b6c26299639
with:
key: win-${{ matrix.arch }}
# Windows ccache needs ~1GB
@@ -73,7 +73,7 @@ jobs:
git push || echo "Push failed"
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v4
uses: aws-actions/configure-aws-credentials@v5
with:
aws-access-key-id: ${{ secrets.AWS_UPLOAD_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_UPLOAD_SECRET_ACCESS_KEY }}
+2 -2
View File
@@ -19,8 +19,8 @@ jobs:
strategy:
fail-fast: false
steps:
- uses: actions/checkout@v2 # https://github.com/actions/checkout
- uses: actions/setup-python@v2 # https://github.com/actions/setup-python
- uses: actions/checkout@v5 # https://github.com/actions/checkout
- uses: actions/setup-python@v6 # https://github.com/actions/setup-python
with:
python-version: '3.11' # Version range or exact version of a Python version to use, using SemVer's version range syntax
architecture: 'x64' # optional x64 or x86. Defaults to x64 if not specified
+3 -3
View File
@@ -9,15 +9,15 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Action - checkout repository
uses: actions/checkout@v4.2.2
uses: actions/checkout@v5
- name: Action - install python
uses: actions/setup-python@v5.3.0
uses: actions/setup-python@v6
with:
python-version: "3.9"
- name: Action - install python
uses: actions/setup-python@v5.3.0
uses: actions/setup-python@v6
with:
python-version: "3.11"
+1 -1
View File
@@ -34,7 +34,7 @@ jobs:
strategy:
fail-fast: false
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
with:
fetch-tags: true
fetch-depth: 0
+3 -3
View File
@@ -54,8 +54,8 @@ jobs:
short_name: macosm1,
}
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2 # https://github.com/actions/setup-python
- uses: actions/checkout@v5
- uses: actions/setup-python@v6 # https://github.com/actions/setup-python
with:
architecture: 'x64' # optional x64 or x86. Defaults to x64 if not specified
python-version: '3.11'
@@ -93,7 +93,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout bonsai_unstable_repo repository
uses: actions/checkout@v2
uses: actions/checkout@v5
with:
repository: IfcOpenShell/bonsai_unstable_repo
token: ${{ secrets.IFCOPENBOT_TOKEN }}
+2 -2
View File
@@ -43,8 +43,8 @@ jobs:
short_name: macosm1,
}
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2 # https://github.com/actions/setup-python
- uses: actions/checkout@v5
- uses: actions/setup-python@v6 # https://github.com/actions/setup-python
with:
architecture: 'x64' # optional x64 or x86. Defaults to x64 if not specified
python-version: '3.11'
+2 -2
View File
@@ -18,8 +18,8 @@ jobs:
strategy:
fail-fast: false
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2 # https://github.com/actions/setup-python
- uses: actions/checkout@v5
- uses: actions/setup-python@v6 # https://github.com/actions/setup-python
with:
python-version: '3.11' # Version range or exact version of a Python version to use, using SemVer's version range syntax
- name: Compile
+2 -2
View File
@@ -18,8 +18,8 @@ jobs:
strategy:
fail-fast: false
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2 # https://github.com/actions/setup-python
- uses: actions/checkout@v5
- uses: actions/setup-python@v6 # https://github.com/actions/setup-python
with:
python-version: '3.11' # Version range or exact version of a Python version to use, using SemVer's version range syntax
- name: Compile
+2 -2
View File
@@ -18,8 +18,8 @@ jobs:
strategy:
fail-fast: false
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2 # https://github.com/actions/setup-python
- uses: actions/checkout@v5
- uses: actions/setup-python@v6 # https://github.com/actions/setup-python
with:
python-version: '3.11' # Version range or exact version of a Python version to use, using SemVer's version range syntax
- name: Compile
+2 -2
View File
@@ -18,8 +18,8 @@ jobs:
strategy:
fail-fast: false
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2 # https://github.com/actions/setup-python
- uses: actions/checkout@v5
- uses: actions/setup-python@v6 # https://github.com/actions/setup-python
with:
python-version: '3.11' # Version range or exact version of a Python version to use, using SemVer's version range syntax
- name: Compile
+2 -2
View File
@@ -18,8 +18,8 @@ jobs:
strategy:
fail-fast: false
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2 # https://github.com/actions/setup-python
- uses: actions/checkout@v5
- uses: actions/setup-python@v6 # https://github.com/actions/setup-python
with:
python-version: '3.11' # Version range or exact version of a Python version to use, using SemVer's version range syntax
- name: Compile
+2 -2
View File
@@ -37,8 +37,8 @@ jobs:
short_name: macosm164
}
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2 # https://github.com/actions/setup-python
- uses: actions/checkout@v5
- uses: actions/setup-python@v6 # https://github.com/actions/setup-python
with:
python-version: '3.11' # Version range or exact version of a Python version to use, using SemVer's version range syntax
architecture: 'x64' # optional x64 or x86. Defaults to x64 if not specified
+2 -2
View File
@@ -18,8 +18,8 @@ jobs:
strategy:
fail-fast: false
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2 # https://github.com/actions/setup-python
- uses: actions/checkout@v5
- uses: actions/setup-python@v6 # https://github.com/actions/setup-python
with:
python-version: '3.11' # Version range or exact version of a Python version to use, using SemVer's version range syntax
- name: Compile
+2 -2
View File
@@ -18,8 +18,8 @@ jobs:
strategy:
fail-fast: false
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2 # https://github.com/actions/setup-python
- uses: actions/checkout@v5
- uses: actions/setup-python@v6 # https://github.com/actions/setup-python
with:
python-version: '3.11' # Version range or exact version of a Python version to use, using SemVer's version range syntax
- name: Compile
+2 -2
View File
@@ -18,8 +18,8 @@ jobs:
strategy:
fail-fast: false
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2 # https://github.com/actions/setup-python
- uses: actions/checkout@v5
- uses: actions/setup-python@v6 # https://github.com/actions/setup-python
with:
python-version: '3.11' # Version range or exact version of a Python version to use, using SemVer's version range syntax
- name: Compile
@@ -24,7 +24,7 @@ jobs:
if: |
github.repository == 'IfcOpenShell/IfcOpenShell'
steps:
- uses: mamba-org/setup-micromamba@v1 # 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: >-
@@ -21,7 +21,7 @@ jobs:
date: ${{ steps.date.outputs.date }}
verdate: ${{ steps.verdate.outputs.verdate }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Set env
run: echo ok go
@@ -76,7 +76,7 @@ jobs:
echo "ARTIFACTS_DIR=/home/runner/work/artifacts" >> $GITHUB_ENV
fi
- uses: actions/checkout@v4
- uses: actions/checkout@v5
with:
submodules: recursive
+8 -8
View File
@@ -21,7 +21,7 @@ jobs:
runs-on: ubuntu-22.04
needs: activate
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v5
with:
submodules: recursive
@@ -76,7 +76,7 @@ jobs:
make package
working-directory: build
- name: Upload
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
# Artifact name
name: ifcos-artifacts
@@ -89,31 +89,31 @@ jobs:
name: Docker Build, Tag, Push
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v5
with:
lfs: true
- name: Download
uses: actions/download-artifact@v4.1.7
uses: actions/download-artifact@v6.0.0
with:
# Artifact name
name: ifcos-artifacts
path: artifacts/
-
name: Set up QEMU
uses: docker/setup-qemu-action@v1
uses: docker/setup-qemu-action@v3
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
uses: docker/setup-buildx-action@v3
-
name: Login to Dockerhub
uses: docker/login-action@v1
uses: docker/login-action@v3
with:
username: aecgeeks
password: ${{ secrets.DOCKER_HUB_TOKEN }}
-
name: Build container image
uses: docker/build-push-action@v2
uses: docker/build-push-action@v6
with:
context: artifacts
repository: aecgeeks/ifcopenshell
@@ -34,6 +34,10 @@ jobs:
name: "Linux 64bit",
short_name: linux64
}
- {
name: "Linux ARM 64bit",
short_name: linuxarm64
}
- {
name: "MacOS Intel 64bit",
short_name: macos64
@@ -43,10 +47,10 @@ jobs:
short_name: macosm164
}
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v5
with:
submodules: recursive
- uses: actions/setup-python@v2 # https://github.com/actions/setup-python
- uses: actions/setup-python@v6 # https://github.com/actions/setup-python
with:
python-version: '3.11' # Version range or exact version of a Python version to use, using SemVer's version range syntax
architecture: 'x64' # optional x64 or x86. Defaults to x64 if not specified
+2 -2
View File
@@ -38,10 +38,10 @@ jobs:
short_name: macosm164
}
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v5
with:
submodules: recursive
- uses: actions/setup-python@v2 # https://github.com/actions/setup-python
- uses: actions/setup-python@v6 # https://github.com/actions/setup-python
with:
python-version: '3.11' # Version range or exact version of a Python version to use, using SemVer's version range syntax
architecture: 'x64' # optional x64 or x86. Defaults to x64 if not specified
+2 -2
View File
@@ -18,8 +18,8 @@ jobs:
strategy:
fail-fast: false
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2 # https://github.com/actions/setup-python
- uses: actions/checkout@v5
- uses: actions/setup-python@v6 # https://github.com/actions/setup-python
with:
python-version: '3.11' # Version range or exact version of a Python version to use, using SemVer's version range syntax
- name: Compile
+2 -2
View File
@@ -25,8 +25,8 @@ jobs:
strategy:
fail-fast: false
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2 # https://github.com/actions/setup-python
- uses: actions/checkout@v5
- uses: actions/setup-python@v6 # https://github.com/actions/setup-python
with:
architecture: 'x64' # optional x64 or x86. Defaults to x64 if not specified
python-version: '3.11'
+2 -2
View File
@@ -19,8 +19,8 @@ jobs:
strategy:
fail-fast: false
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2 # https://github.com/actions/setup-python
- uses: actions/checkout@v5
- uses: actions/setup-python@v6 # https://github.com/actions/setup-python
with:
architecture: 'x64' # optional x64 or x86. Defaults to x64 if not specified
python-version: '3.11'
+2 -2
View File
@@ -10,9 +10,9 @@ jobs:
publish_website:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v5
- name: Checkout ifctester_org_static_html
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
repository: IfcOpenShell/ifctester_org_static_html
token: ${{ secrets.IFCOPENBOT_TOKEN }}
+2 -2
View File
@@ -18,8 +18,8 @@ jobs:
strategy:
fail-fast: false
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2 # https://github.com/actions/setup-python
- uses: actions/checkout@v5
- uses: actions/setup-python@v6 # https://github.com/actions/setup-python
with:
python-version: '3.11' # Version range or exact version of a Python version to use, using SemVer's version range syntax
- name: Compile
+42 -5
View File
@@ -35,12 +35,12 @@ jobs:
runs-on: ubuntu-22.04
needs: activate
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
with:
submodules: recursive
- name: Set up Python
uses: actions/setup-python@v5
uses: actions/setup-python@v6
with:
python-version: 3.11
@@ -64,13 +64,16 @@ jobs:
libboost-regex-dev \
libboost-system-dev \
libboost-thread-dev \
swig libpcre3-dev libxml2-dev \
libpcre3-dev libxml2-dev \
libtbb-dev nlohmann-json3-dev \
libocct-foundation-dev libocct-modeling-algorithms-dev libocct-modeling-data-dev libocct-ocaf-dev libocct-visualization-dev libocct-data-exchange-dev \
libhdf5-dev libcgal-dev libeigen3-dev
- name: ccache
uses: hendrikmuhs/ccache-action@v1.2
# 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 }}
@@ -97,6 +100,41 @@ jobs:
..
sudo make -j$(nproc) install
# OpenCOLLADA is ancient, but we still have it in the main build.
# So adding it to CI to catch any breakages.
- name: build OpenCOLLADA
run: |
git clone https://github.com/KhronosGroup/OpenCOLLADA
cd OpenCOLLADA
git checkout v1.6.68
patch -p1 --batch --forward -i ../nix/patches/opencollada/pr622_and_disable_subdirs.patch
patch -p1 --batch --forward -i ../nix/patches/opencollada/allow_static_libraries_config_on_unix.patch
mkdir build && cd build
cmake .. \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_POLICY_VERSION_MINIMUM=3.5 \
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
-DCMAKE_CXX_FLAGS_INIT="-fPIC"
sudo make -j$(nproc) install
# Ubuntu has `swig` package, but we build it to match the version we use in the main build.
# To avoid failing tests when checking stub generation.
- name: build swig
run: |
# Remove default swig to avoid conflicts.
sudo apt remove --purge swig swig4.0
sudo apt-get install -y libpcre2-dev bison
git clone https://github.com/swig/swig
cd swig
git checkout v4.1.0
mkdir build && cd build
cmake .. \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache
sudo make -j$(nproc) install
- name: Build ifcopenshell
run: |
echo $Python3_ROOT_DIR
@@ -114,7 +152,6 @@ jobs:
-DOCC_LIBRARY_DIR=/usr/lib/x86_64-linux-gnu \
-DPYTHON_EXECUTABLE:FILEPATH=${{ env.pythonLocation }}/bin/python \
-DPYTHON_INCLUDE_DIR:PATH=${{ env.pythonLocation }}/include/python3.11 \
-DCOLLADA_SUPPORT=Off \
-DUSE_MMAP=On \
"-DSCHEMA_VERSIONS=2x3;4;4x3_add2" \
-DGLTF_SUPPORT=On \
@@ -11,10 +11,10 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v2
uses: actions/checkout@v5
- name: Set up Python
uses: actions/setup-python@v2
uses: actions/setup-python@v6
with:
python-version: '3.x'
+2 -2
View File
@@ -9,10 +9,10 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v2
uses: actions/checkout@v5
- name: Set up Python
uses: actions/setup-python@v2
uses: actions/setup-python@v6
with:
python-version: '3.x'
@@ -26,7 +26,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout (recursive)
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
submodules: recursive
fetch-depth: 0
@@ -34,7 +34,7 @@ jobs:
uses: actions/configure-pages@v5
- name: Upload static files as artifact
id: deployment
uses: actions/upload-pages-artifact@v3
uses: actions/upload-pages-artifact@v4
with:
path: src/pyodide/demo-app/
+1 -1
View File
@@ -17,7 +17,7 @@ jobs:
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v5
with:
submodules: recursive
- name: Install C++ dependencies
+2 -5
View File
@@ -17,6 +17,7 @@
# General Python residue
__pycache__
*.py.bak
venv
# Visual Studio Code files
.vscode
@@ -78,8 +79,6 @@ src/bonsai/bonsai/translations.py
# bonsai test temp files
src/bonsai/test/files/temp
src/bonsai/test/files/basic.ifc.cache.blend
src/bonsai/test/files/basic.ifc.cache.sqlite
# bonsai data
src/bonsai/bonsai/bim/data/build/
@@ -87,9 +86,7 @@ src/bonsai/bonsai/bim/data/gantt/index.html
src/bonsai/bonsai/bim/data/gantt/jsgantt.js
src/bonsai/bonsai/bim/data/gantt/jsgantt.css
src/bonsai/bonsai/bim/data/webui/static/js/jquery.min.js
src/bonsai/drawings
src/bonsai/layouts
src/bonsai/bonsai/bim/data/webui/running_pid.json
# ifcopenshell swig and compiled files
src/ifcopenshell-python/ifcopenshell/_ifcopenshell_wrapper*.so
-3
View File
@@ -11,9 +11,6 @@
[submodule "src/ifcopenshell-python/test/Sample-BIM-Files"]
path = src/ifcopenshell-python/test/Sample-BIM-Files
url = https://github.com/IfcOpenShell/ids-test-files
[submodule "src/ifcconvert/cityjson"]
path = src/ifcconvert/cityjson
url = https://github.com/IfcOpenShell/ifc-to-cityjson
[submodule "docs/cpp-api/assets/doxygen-awesome-css"]
path = docs/cpp-api/assets/doxygen-awesome-css
url = https://github.com/jothepro/doxygen-awesome-css.git
+1 -1
View File
@@ -1 +1 @@
0.8.4
0.8.5
+26 -700
View File
@@ -79,7 +79,6 @@ option(HDF5_SUPPORT "Enable HDF5 support (requires HDF5, zlib)" ON)
option(WITH_PROJ "Enable output of Earth-Centered Earth-Fixed glTF output using the PROJ library" OFF)
option(IFCXML_SUPPORT "Build IfcParse with ifcXML support (requires libxml2)." ON)
option(USD_SUPPORT "Build IfcConvert with USD support (requires pixar's USD library)." OFF)
option(CITYJSON_SUPPORT "Build IfcConvert with CityJSON support (requires CityJSON library)." OFF)
option(WITH_RELATIONSHIP_VALIDATION "Build IfcConvert with option to validate geometrical relationships." OFF)
option(WITH_ROCKSDB "Support a RocksDB key-value store as a file backend in IfcOpenShell" OFF)
option(WITH_ZSTD "Use Zstd compression in RocksDB writes" OFF)
@@ -104,6 +103,9 @@ endif()
project(IfcOpenShell VERSION ${RELEASE_VERSION})
# Make sure CMake modules in this project are found first
list(PREPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR})
if(MINIMAL_BUILD)
message(STATUS "Setting options for minimal build")
set(BUILD_GEOMSERVER OFF)
@@ -196,30 +198,13 @@ if(BUILD_SHARED_LIBS)
set(IFCOPENSHELL_LIBRARY_DIR "${LIBDIR}")
endif()
UNIFY_ENVVARS_AND_CACHE(OCC_INCLUDE_DIR)
UNIFY_ENVVARS_AND_CACHE(OCC_LIBRARY_DIR)
UNIFY_ENVVARS_AND_CACHE(BOOST_ROOT)
UNIFY_ENVVARS_AND_CACHE(BOOST_LIBRARYDIR)
UNIFY_ENVVARS_AND_CACHE(EIGEN_DIR)
if(NOT MINIMAL_BUILD)
UNIFY_ENVVARS_AND_CACHE(OPENCOLLADA_INCLUDE_DIR)
UNIFY_ENVVARS_AND_CACHE(OPENCOLLADA_LIBRARY_DIR)
UNIFY_ENVVARS_AND_CACHE(LIBXML2_INCLUDE_DIR)
UNIFY_ENVVARS_AND_CACHE(LIBXML2_LIBRARIES)
UNIFY_ENVVARS_AND_CACHE(PCRE_LIBRARY_DIR)
UNIFY_ENVVARS_AND_CACHE(PYTHON_INCLUDE_DIR)
UNIFY_ENVVARS_AND_CACHE(PYTHON_LIBRARY)
UNIFY_ENVVARS_AND_CACHE(PYTHON_EXECUTABLE)
UNIFY_ENVVARS_AND_CACHE(HDF5_INCLUDE_DIR)
UNIFY_ENVVARS_AND_CACHE(HDF5_LIBRARY_DIR)
UNIFY_ENVVARS_AND_CACHE(HDF5_LIBRARIES)
UNIFY_ENVVARS_AND_CACHE(CGAL_INCLUDE_DIR)
UNIFY_ENVVARS_AND_CACHE(CGAL_LIBRARY_DIR)
UNIFY_ENVVARS_AND_CACHE(GMP_INCLUDE_DIR)
UNIFY_ENVVARS_AND_CACHE(GMP_LIBRARY_DIR)
UNIFY_ENVVARS_AND_CACHE(MPFR_INCLUDE_DIR)
UNIFY_ENVVARS_AND_CACHE(MPFR_LIBRARY_DIR)
endif()
# Get a list of all OPTION flags from the CMakeLists.txt
@@ -230,53 +215,13 @@ foreach(option_flag IN LISTS option_flags)
convert_env_var_to_bool("${option_flag}")
endforeach()
set(CMAKE_FIND_ROOT_PATH_BACKUP "${CMAKE_FIND_ROOT_PATH}")
macro(clear_wasm_sysroot)
if(WASM_BUILD)
# when using the nix/build-all.py build script we should not
# look into the sysroot for most of the dependencies but rather
# in the designated build/ folder created by the script.
set(CMAKE_FIND_ROOT_PATH "")
endif()
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE NEVER)
endmacro()
macro(restore_wasm_sysroot)
if(WASM_BUILD)
# reset to use sysroot
set(CMAKE_FIND_ROOT_PATH "${CMAKE_FIND_ROOT_PATH_BACKUP}")
endif()
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
endmacro()
if(WITH_CGAL)
if(NOT CGAL_INCLUDE_DIR)
# CGAL is not respecting default Boost_USE_STATIC_LIBS value
# and sometiems it's getting in the way.
if(NOT DEFINED Boost_USE_STATIC_LIBS)
set(CGAL_Boost_USE_STATIC_LIBS OFF)
else()
set(CGAL_Boost_USE_STATIC_LIBS "${Boost_USE_STATIC_LIBS}")
endif()
find_package(CGAL REQUIRED)
if(NOT CGAL_DIR)
message(
FATAL_ERROR
"CGAL_SUPPORT enabled, but CGAL_INCLUDE_DIR wasn't provided and CGAL package couldn't be found."
)
endif()
message(STATUS "CGAL: found config at '${CGAL_DIR}'.")
find_package(CGAL REQUIRED)
if(TARGET CGAL::CGAL)
link_libraries(CGAL::CGAL)
endif()
add_definitions(-DIFOPSH_WITH_CGAL)
set(SWIG_DEFINES ${SWIG_DEFINES} -DIFOPSH_WITH_CGAL)
if(CITYJSON_SUPPORT)
add_definitions(-DIFOPSH_WITH_CITYJSON)
endif()
list(APPEND GEOMETRY_KERNELS cgal)
endif()
@@ -286,7 +231,7 @@ if(WITH_OPENCASCADE)
list(APPEND GEOMETRY_KERNELS opencascade)
endif()
if(GLTF_SUPPORT OR CITYJSON_SUPPORT)
if(GLTF_SUPPORT)
UNIFY_ENVVARS_AND_CACHE(JSON_INCLUDE_DIR)
if(NOT JSON_INCLUDE_DIR)
find_package(nlohmann_json CONFIG)
@@ -298,9 +243,7 @@ if(GLTF_SUPPORT OR CITYJSON_SUPPORT)
endif()
if(NOT nlohmann_json_DIR)
clear_wasm_sysroot()
find_path(json_header_path "nlohmann/json.hpp" HINTS ${JSON_INCLUDE_DIR})
restore_wasm_sysroot()
set(JSON_INCLUDE_DIR ${json_header_path})
if(json_header_path)
@@ -316,49 +259,7 @@ endif()
# Add USD support to serializers
if(USD_SUPPORT)
UNIFY_ENVVARS_AND_CACHE(USD_INCLUDE_DIR)
UNIFY_ENVVARS_AND_CACHE(USD_LIBRARY_DIR)
if("${USD_INCLUDE_DIR}" STREQUAL "")
find_path(USD_INCLUDE_DIR pxr.h
PATHS
/usr/include/pxr
/usr/local/include/pxr
REQUIRED
)
if(USD_INCLUDE_DIR)
message(STATUS "Found USD include files in: ${USD_INCLUDE_DIR}")
else()
message(FATAL_ERROR "Unable to find USD include directory, specify USD_INCLUDE_DIR manually.")
endif()
else()
set(USD_INCLUDE_DIR ${USD_INCLUDE_DIR} CACHE FILEPATH "USD header files")
message(STATUS "Looking for USD include files in: ${USD_INCLUDE_DIR}")
endif()
set(USD_LIBRARIES
usd_usd
usd_usdGeom
usd_usdShade
usd_usdLux
usd_vt
usd_sdf
usd_tf
usd_gf
)
find_library(USD_LIBRARY
NAMES ${USD_LIBRARIES}
PATHS ${USD_LIBRARY_DIR})
if(USD_LIBRARY)
message(STATUS "USD libraries ${USD_LIBRARIES} found in: ${USD_LIBRARY_DIR}")
link_directories(${USD_LIBRARY_DIR})
else()
message(FATAL_ERROR "Unable to find USD libraries in: ${USD_LIBRARY_DIR}")
endif()
add_definitions(-DWITH_USD)
set(SWIG_DEFINES ${SWIG_DEFINES} -DWITH_USD)
find_package(USD REQUIRED)
endif(USD_SUPPORT)
if (WITH_ROCKSDB)
@@ -440,44 +341,15 @@ if(USE_MMAP)
add_definitions(-DUSE_MMAP)
endif()
clear_wasm_sysroot()
find_package(Boost REQUIRED COMPONENTS ${BOOST_COMPONENTS})
restore_wasm_sysroot()
message(STATUS "Boost include files found in ${Boost_INCLUDE_DIRS}")
message(STATUS "Boost libraries found in ${Boost_LIBRARY_DIRS}")
if(NOT MINIMAL_BUILD)
# libxml2 is required for IFCXML (optional) and SVGFILL (mandatory)
clear_wasm_sysroot()
if(IFCXML_SUPPORT)
if((NOT LIBXML2_INCLUDE_DIR AND NOT LIBXML2_LIBRARIES))
# First try config mode (probably works with vcpkg, Conan, macOS brew installs, but not on ubuntu 22.04)
# CONFIG is provided using root path, so no need to clear sysroot here.
restore_wasm_sysroot()
find_package(LibXml2 QUIET CONFIG)
clear_wasm_sysroot()
if(NOT LibXml2_FOUND)
# Fallback to CMake's builtin FindLibXml2 module (works on Ubuntu)
find_package(LibXml2 REQUIRED)
else()
message(STATUS "Found LibXml2 config: ${LibXml2_DIR}")
endif()
if(TARGET LibXml2::LibXml2)
# Config mode already gives us the target
set(LIBXML2_LIBRARIES LibXml2::LibXml2)
get_target_property(LIBXML2_INCLUDE_DIR LibXml2::LibXml2 INTERFACE_INCLUDE_DIRECTORIES)
else()
# Module mode (Ubuntu)
set(LIBXML2_LIBRARIES ${LibXml2_LIBRARIES})
set(LIBXML2_INCLUDE_DIR ${LibXml2_INCLUDE_DIRS})
endif()
else()
find_package(LibXml2 REQUIRED)
endif()
find_package(LibXml2 REQUIRED)
endif()
restore_wasm_sysroot()
endif()
if(IFCXML_SUPPORT)
@@ -486,335 +358,18 @@ if(IFCXML_SUPPORT)
endif()
if(BUILD_IFCGEOM)
if(MSVC AND NOT LibXml2_DIR)
add_debug_variants(LIBXML2_LIBRARIES "${LIBXML2_LIBRARIES}" d)
endif()
# Open CASCADE
if(WITH_OPENCASCADE)
if(OCC_INCLUDE_DIR)
set(OCC_INCLUDE_DIR ${OCC_INCLUDE_DIR} CACHE FILEPATH "Open CASCADE header files")
message(STATUS "Looking for Open CASCADE include files in: ${OCC_INCLUDE_DIR}")
endif()
if(OCC_LIBRARY_DIR)
set(OCC_LIBRARY_DIR ${OCC_LIBRARY_DIR} CACHE FILEPATH "Open CASCADE library files")
message(STATUS "Looking for Open CASCADE library files in: ${OCC_LIBRARY_DIR}")
endif()
# No specific paths specified, try to find package.
if(OCC_INCLUDE_DIR AND OCC_LIBRARY_DIR)
message(
STATUS
"Using provided OCC_INCLUDE_DIR ('${OCC_INCLUDE_DIR}') "
"and OCC_LIBRARY_DIR ('${OCC_LIBRARY_DIR}')."
)
# Parse OCC_VERSION_STRING.
file(STRINGS ${OCC_INCLUDE_DIR}/Standard_Version.hxx OCC_MAJOR
REGEX "#define OCC_VERSION_MAJOR.*"
)
string(REGEX MATCH "[0-9]+" OCC_MAJOR ${OCC_MAJOR})
file(STRINGS ${OCC_INCLUDE_DIR}/Standard_Version.hxx OCC_MINOR
REGEX "#define OCC_VERSION_MINOR.*"
)
string(REGEX MATCH "[0-9]+" OCC_MINOR ${OCC_MINOR})
file(STRINGS ${OCC_INCLUDE_DIR}/Standard_Version.hxx OCC_MAINT
REGEX "#define OCC_VERSION_MAINTENANCE.*"
)
string(REGEX MATCH "[0-9]+" OCC_MAINT ${OCC_MAINT})
set(OCC_VERSION_STRING "${OCC_MAJOR}.${OCC_MINOR}.${OCC_MAINT}")
elseif(NOT OCC_INCLUDE_DIR AND NOT OCC_LIBRARY_DIR)
# OCE is not supported for find_package, because it's using a different name (`oce`)
# and also has an odd directory structure (install/lib/oce-0.18/*.cmake).
find_package(OpenCASCADE CONFIG REQUIRED)
set(OCC_INCLUDE_DIR ${OpenCASCADE_INCLUDE_DIR})
# Do not use OpenCASCADE_LIBRARY_DIR for OCC_LIBRARY_DIR - check target property explicitly.
# On Windows there is a case with OpenCASCADE_LIBRARY_DIR points to `lib` folder,
# while TKernel is actually in `libi`.
get_target_property(TKERNEL_LIB_PATH TKernel LOCATION)
get_filename_component(OCC_LIBRARY_DIR "${TKERNEL_LIB_PATH}" DIRECTORY)
set(OCC_VERSION_STRING ${OpenCASCADE_VERSION})
message(
STATUS
"Found Open CASCADE package at '${OpenCASCADE_DIR}', "
"deducing from it OCC_INCLUDE_DIR: '${OCC_INCLUDE_DIR}' "
"and OCC_LIBRARY_DIR: '${OCC_LIBRARY_DIR}'."
)
else()
message(
FATAL_ERROR
"Couldn't find Open CASCADE installation. "
"Either both OCC_INCLUDE_DIR ('${OCC_INCLUDE_DIR}') and OCC_LIBRARY_DIR ('${OCC_LIBRARY_DIR}') "
"must be specified or OpenCASCADE package should be discoverable. "
"If you're using OCE, then providing a package is not available "
"and you need to provide OCE_INCLUDE_DIR and OCE_LIBRARY_DIR directly."
)
endif()
set(
OPENCASCADE_LIBRARY_NAMES
TKernel TKMath TKBRep TKGeomBase TKGeomAlgo TKG3d TKG2d TKShHealing TKTopAlgo TKMesh TKPrim TKBool TKBO
TKFillet TKXSBase TKOffset TKHLR
# @todo investigate the exact conditions when this is necessary
TKBin
)
if(OCC_VERSION_STRING VERSION_LESS 7.8.0)
list(APPEND OPENCASCADE_LIBRARY_NAMES TKIGES TKSTEPBase TKSTEPAttr TKSTEP209 TKSTEP)
else(OCC_VERSION_STRING VERSION_LESS 7.8.0)
list(APPEND OPENCASCADE_LIBRARY_NAMES TKDESTEP TKDEIGES)
endif(OCC_VERSION_STRING VERSION_LESS 7.8.0)
clear_wasm_sysroot()
find_library(libTKernel NAMES TKernel TKerneld PATHS ${OCC_LIBRARY_DIR} NO_DEFAULT_PATH)
restore_wasm_sysroot()
if(libTKernel)
message(STATUS "Required Open Cascade Library files found")
else()
message(
FATAL_ERROR
"Unable to find Open Cascade library files in OCC_LIBRARY_DIR ('${OCC_LIBRARY_DIR}'), aborting"
)
endif()
# Use the found libTKernel as a template for all other OCC libraries
# TODO Extract this into macro/function
foreach(lib ${OPENCASCADE_LIBRARY_NAMES})
# Make sure we'll handle the Windows/MSVC debug postfix convention too.
string(REPLACE TKerneld "${lib}" lib_path "${libTKernel}")
string(REPLACE TKernel "${lib}" lib_path "${lib_path}")
list(APPEND OPENCASCADE_LIBRARIES "${lib_path}")
endforeach()
if(MSVC)
add_definitions(-DHAVE_NO_DLL)
add_debug_variants(OPENCASCADE_LIBRARIES "${OPENCASCADE_LIBRARIES}" d)
endif()
if(WIN32)
# OCC might require linking to Winsock depending on the version and build configuration
list(APPEND OPENCASCADE_LIBRARIES ws2_32.lib)
endif()
# Make sure cross-referenced symbols between static OCC libraries get
# resolved. Also add thread and rt libraries.
get_filename_component(libTKernelExt ${libTKernel} EXT)
if("${libTKernelExt}" STREQUAL ".a")
set(OCCT_STATIC ON)
endif()
if(OCCT_STATIC)
find_package(Threads)
if(WASM_BUILD)
set(OPENCASCADE_LIBRARIES ${OPENCASCADE_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT})
else()
# OPENCASCADE_LIBRARIES repeated N times below in order to fix cyclic dependencies
# tfk: --start-group ... --end-group didn't work on the apple linker when last tested
if(APPLE)
set(OPENCASCADE_LIBRARIES ${OPENCASCADE_LIBRARIES} ${OPENCASCADE_LIBRARIES} ${OPENCASCADE_LIBRARIES} ${OPENCASCADE_LIBRARIES} ${OPENCASCADE_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT})
else()
set(OPENCASCADE_LIBRARIES -Wl,--start-group ${OPENCASCADE_LIBRARIES} -Wl,--end-group ${CMAKE_THREAD_LIBS_INIT})
endif()
endif()
if(NOT APPLE AND NOT WIN32)
set(OPENCASCADE_LIBRARIES ${OPENCASCADE_LIBRARIES} "rt")
endif()
if(NOT WIN32)
set(OPENCASCADE_LIBRARIES ${OPENCASCADE_LIBRARIES} "dl")
endif()
endif()
find_package(OpenCASCADE REQUIRED)
endif(WITH_OPENCASCADE)
endif(BUILD_IFCGEOM)
if(COLLADA_SUPPORT AND (NOT OPENCOLLADA_INCLUDE_DIR AND NOT OPENCOLLADA_LIBRARY_DIR))
# If package is found, automatically sets
# OPENCOLLADA_INCLUDE_DIRS and OPENCOLLADA_LIBRARIES (list of targets, not paths).
find_package(OpenCOLLADA CONFIG)
if(OpenCOLLADA_DIR)
message(STATUS "Found OpenCOLLADA: '${OpenCOLLADA_DIR}'.")
set(OPENCOLLADA_FOUND TRUE)
else()
message(STATUS "OpenCOLLADA package not found, falling back to manual search.")
endif()
if(COLLADA_SUPPORT)
find_package(OpenCOLLADA REQUIRED)
endif()
if(COLLADA_SUPPORT AND NOT OpenCOLLADA_DIR)
# Find OpenCOLLADA
if("${OPENCOLLADA_INCLUDE_DIR}" STREQUAL "")
message(STATUS "No OpenCOLLADA include directory specified")
set(OPENCOLLADA_INCLUDE_DIR "/usr/include/opencollada" CACHE FILEPATH "OpenCOLLADA header files")
else()
set(OPENCOLLADA_INCLUDE_DIR "${OPENCOLLADA_INCLUDE_DIR}" CACHE FILEPATH "OpenCOLLADA header files")
endif()
if("${OPENCOLLADA_LIBRARY_DIR}" STREQUAL "")
message(STATUS "No OpenCOLLADA library directory specified")
find_library(OPENCOLLADA_FRAMEWORK_LIB NAMES OpenCOLLADAFramework
PATHS /usr/lib64/opencollada /usr/lib/opencollada /usr/lib64 /usr/lib /usr/local/lib64 /usr/local/lib)
get_filename_component(OPENCOLLADA_LIBRARY_DIR ${OPENCOLLADA_FRAMEWORK_LIB} PATH)
endif()
find_library(OpenCOLLADAFramework NAMES OpenCOLLADAFramework OpenCOLLADAFrameworkd PATHS ${OPENCOLLADA_LIBRARY_DIR} NO_DEFAULT_PATH)
if(OpenCOLLADAFramework)
message(STATUS "OpenCOLLADA library files found")
else()
message(FATAL_ERROR "COLLADA_SUPPORT enabled, but unable to find OpenCOLLADA libraries. "
"Disable COLLADA_SUPPORT or fix OpenCOLLADA paths to proceed.")
endif()
set(OPENCOLLADA_LIBRARY_DIR "${OPENCOLLADA_LIBRARY_DIR}" CACHE FILEPATH "OpenCOLLADA library files")
set(OPENCOLLADA_INCLUDE_DIRS "${OPENCOLLADA_INCLUDE_DIR}/COLLADABaseUtils" "${OPENCOLLADA_INCLUDE_DIR}/COLLADAStreamWriter")
find_file(COLLADASWStreamWriter_h "COLLADASWStreamWriter.h" ${OPENCOLLADA_INCLUDE_DIRS})
if(COLLADASWStreamWriter_h)
message(STATUS "OpenCOLLADA header files found")
set(OPENCOLLADA_FOUND TRUE)
set(OPENCOLLADA_LIBRARY_NAMES
GeneratedSaxParser MathMLSolver OpenCOLLADABaseUtils OpenCOLLADAFramework OpenCOLLADASaxFrameworkLoader
OpenCOLLADAStreamWriter UTF buffer ftoa
)
# Use the found OpenCOLLADAFramework as a template for all other OpenCOLLADA libraries
foreach(lib ${OPENCOLLADA_LIBRARY_NAMES})
# Make sure we'll handle the Windows/MSVC debug postfix convention too.
string(REPLACE OpenCOLLADAFrameworkd "${lib}" lib_path "${OpenCOLLADAFramework}")
string(REPLACE OpenCOLLADAFramework "${lib}" lib_path "${lib_path}")
list(APPEND OPENCOLLADA_LIBRARIES "${lib_path}")
endforeach()
if("${PCRE_LIBRARY_DIR}" STREQUAL "")
if(WIN32)
find_library(pcre_library NAMES pcre pcred PATHS ${OPENCOLLADA_LIBRARY_DIR} NO_DEFAULT_PATH)
else()
find_library(pcre_library NAMES pcre PATHS ${OPENCOLLADA_LIBRARY_DIR})
endif()
get_filename_component(PCRE_LIBRARY_DIR ${pcre_library} PATH)
else()
find_library(pcre_library NAMES pcre pcred PATHS ${PCRE_LIBRARY_DIR} NO_DEFAULT_PATH)
endif()
if(pcre_library)
set(OPENCOLLADA_LIBRARY_DIR ${OPENCOLLADA_LIBRARY_DIR} ${PCRE_LIBRARY_DIR})
if(MSVC)
# Add release lib regardless whether release or debug found. Debug version will be appended below.
list(APPEND OPENCOLLADA_LIBRARIES "${PCRE_LIBRARY_DIR}/pcre.lib")
else()
list(APPEND OPENCOLLADA_LIBRARIES "${pcre_library}")
endif()
else()
message(FATAL_ERROR "COLLADA_SUPPORT enabled, but unable to find PCRE. "
"Disable COLLADA_SUPPORT or fix PCRE_LIBRARY_DIR path to proceed.")
endif()
if(MSVC)
add_debug_variants(OPENCOLLADA_LIBRARIES "${OPENCOLLADA_LIBRARIES}" d)
endif()
else()
message(FATAL_ERROR "COLLADA_SUPPORT enabled, but unable to find OpenCOLLADA headers. "
"Disable COLLADA_SUPPORT or fix OpenCOLLADA paths to proceed.")
endif()
endif(COLLADA_SUPPORT AND NOT OpenCOLLADA_DIR)
if(COLLADA_SUPPORT AND OPENCOLLADA_FOUND)
add_definitions(-DWITH_OPENCOLLADA)
set(SWIG_DEFINES ${SWIG_DEFINES} -DWITH_OPENCOLLADA)
endif()
if(HDF5_SUPPORT)
if("${HDF5_INCLUDE_DIR}" STREQUAL "")
message(STATUS "No HDF5 include directory specified")
else()
set(HDF5_INCLUDE_DIR "${HDF5_INCLUDE_DIR}" CACHE FILEPATH "HDF5 header files")
endif()
if("${HDF5_LIBRARY_DIR}" STREQUAL "")
message(STATUS "No HDF5 library directory specified")
else()
set(HDF5_LIBRARY_DIR "${HDF5_LIBRARY_DIR}" CACHE FILEPATH "HDF5 library files")
endif()
if(HDF5_LIBRARY_DIR)
# result of the HDF5 ctest package
# Find zlib using cmake find_library. How should this be implemented?
# FIND_LIBRARY(NAMES z libz libz_debug PATHS ... NO_DEFAULT_PATH)
if("$ENV{CONDA_BUILD}" STREQUAL "")
# result of the HDF5 ctest package
if(WIN32)
set(zlib_post lib)
set(lib_ext lib)
else()
set(lib_ext a)
endif()
if("${CMAKE_BUILD_TYPE}" STREQUAL "Debug")
set(debug_postfix "_debug")
endif()
set(HDF5_LIBRARIES
"${HDF5_LIBRARY_DIR}/libhdf5_cpp${debug_postfix}.${lib_ext}"
"${HDF5_LIBRARY_DIR}/libhdf5${debug_postfix}.${lib_ext}"
"${HDF5_LIBRARY_DIR}/libz${zlib_post}${debug_postfix}.${lib_ext}"
"${HDF5_LIBRARY_DIR}/libsz${debug_postfix}.${lib_ext}"
"${HDF5_LIBRARY_DIR}/libaec${debug_postfix}.${lib_ext}"
)
else()
message(STATUS "Packaging hdf5 and zlib for conda distribution")
if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
# macOS
set(zlib_post libz)
set(lib_ext dylib)
set(HDF5_LIBRARIES
"${HDF5_LIBRARY_DIR}/libhdf5_cpp.${lib_ext}"
"${HDF5_LIBRARY_DIR}/libhdf5.${lib_ext}"
"${HDF5_LIBRARY_DIR}/${zlib_post}.${lib_ext}"
)
else()
# linux and windows
# Find HDF5 package
find_package(HDF5 REQUIRED COMPONENTS C CXX)
# Find ZLIB package
find_package(ZLIB REQUIRED)
# Include directories
include_directories(${HDF5_INCLUDE_DIRS} ${ZLIB_INCLUDE_DIRS})
# Link libraries
set(HDF5_LIBRARIES ${HDF5_LIBRARIES} ${ZLIB_LIBRARIES})
message(STATUS "HDF5 libraries: ${HDF5_LIBRARIES}")
endif()
endif()
endif()
if(NOT HDF5_INCLUDE_DIR OR NOT HDF5_LIBRARY_DIR)
# First try to find it as a config.
find_package(HDF5 CONFIG)
if(HDF5_DIR)
message(STATUS "HDF5: found config at '${HDF5_DIR}'.")
set(HDF5_LIBRARIES hdf5_cpp-static)
else()
# If it failed, still try to find as a module.
# E.g. on Ubuntu `libhdf5-dev` doesn't provie hdf5-config.cmake.
# Will automatically fill HDF5_LIBRARIES and HDF5_INCLUDE_DIR.
find_package(HDF5 COMPONENTS CXX)
if(NOT HDF5_INCLUDE_DIR)
message(
FATAL_ERROR
"HDF5_INCLUDE_DIR is not provided (current value: '${HDF5_INCLUDE_DIR}'). "
"HDF5_LIBRARY_DIR is not provided (current value: '${HDF5_LIBRARY_DIR}'). "
"Also could not find HDF5 package (neither module or config)."
)
endif()
endif()
endif()
find_package(HDF5 REQUIRED COMPONENTS C CXX)
add_definitions(-DWITH_HDF5)
set(SWIG_DEFINES ${SWIG_DEFINES} -DWITH_HDF5)
@@ -931,25 +486,12 @@ else()
endif(MSVC)
# Ensure other dependencies are provided.
if(NOT EXISTS "${EIGEN_DIR}")
find_package(Eigen3 CONFIG)
if(Eigen3_DIR)
message(STATUS "Eigen3: found config at '${Eigen3_DIR}'.")
else()
message(
FATAL_ERROR
"EIGEN_DIR is not provided or provided folder doesn't exist (current value: '${EIGEN_DIR}'). "
"Also couldn't find Eigen3 as a package."
)
endif()
link_libraries(Eigen3::Eigen)
endif()
find_package(Eigen3 REQUIRED)
link_libraries(Eigen3::Eigen)
include_directories(${INCLUDE_DIRECTORIES} ${OCC_INCLUDE_DIR} ${OPENCOLLADA_INCLUDE_DIRS}
include_directories(${INCLUDE_DIRECTORIES} ${OPENCOLLADA_INCLUDE_DIRS}
${Boost_INCLUDE_DIRS} ${LIBXML2_INCLUDE_DIR} ${JSON_INCLUDE_DIR} ${HDF5_INCLUDE_DIR}
${EIGEN_DIR} ${CGAL_INCLUDE_DIR} ${GMP_INCLUDE_DIR} ${MPFR_INCLUDE_DIR} ${USD_INCLUDE_DIR}
${CGAL_INCLUDE_DIR} ${GMP_INCLUDE_DIR} ${MPFR_INCLUDE_DIR} ${USD_INCLUDE_DIR}
${TBB_INCLUDE_DIR}
)
@@ -1038,6 +580,7 @@ if(NOT Boost_VERSION LESS 105800)
add_definitions(-DBOOST_OPTIONAL_USE_OLD_DEFINITION_OF_NONE)
endif()
add_subdirectory(../src/ifcparse ifcparse)
set(IFCOPENSHELL_LIBRARIES IfcParse)
if(BUILD_IFCGEOM)
@@ -1048,18 +591,13 @@ if(BUILD_IFCGEOM)
endif()
if(BUILD_CONVERT OR BUILD_IFCPYTHON)
foreach(schema ${SCHEMA_VERSIONS})
set(SERIALIZER_SCHEMA_LIBRARIES ${SERIALIZER_SCHEMA_LIBRARIES} Serializers_ifc${schema})
endforeach()
set(IFCOPENSHELL_LIBRARIES ${IFCOPENSHELL_LIBRARIES} Serializers ${SERIALIZER_SCHEMA_LIBRARIES})
if(WITH_OPENCASCADE)
if(WITH_OPENCASCADE)
foreach(schema ${SCHEMA_VERSIONS})
set(GEOM_SERIALIZER_SCHEMA_LIBRARIES ${GEOM_SERIALIZER_SCHEMA_LIBRARIES} GeometrySerializers_ifc${schema})
add_library(geometry_serializer_ifc${schema} STATIC ../src/ifcgeom/Serialization/schema/Serialization.cpp)
set_target_properties(geometry_serializer_ifc${schema} PROPERTIES COMPILE_FLAGS "-DIFC_GEOMSERIALIZATION_EXPORTS -DIfcSchema=Ifc${schema}")
target_link_libraries(geometry_serializer_ifc${schema} ${OPENCASCADE_LIBRARIES})
target_link_libraries(geometry_serializer_ifc${schema} ${OpenCASCADE_LIBRARIES})
list(APPEND geometry_serializer_libraries geometry_serializer_ifc${schema})
endforeach()
@@ -1070,62 +608,14 @@ if(BUILD_CONVERT OR BUILD_IFCPYTHON)
endif()
endif()
# IfcParse
file(GLOB IFCPARSE_H_FILES_ALL ../src/ifcparse/*.h)
file(GLOB IFCPARSE_CPP_FILES_ALL ../src/ifcparse/*.cpp)
foreach(file ${IFCPARSE_H_FILES_ALL})
get_filename_component(filename "${file}" NAME)
if(NOT "${filename}" MATCHES "[0-9]")
list(APPEND IFCPARSE_H_FILES "${file}")
endif()
endforeach()
foreach(file ${IFCPARSE_CPP_FILES_ALL})
get_filename_component(filename "${file}" NAME)
if(NOT "${filename}" MATCHES "[0-9]")
list(APPEND IFCPARSE_CPP_FILES "${file}")
endif()
endforeach()
foreach(schema ${SCHEMA_VERSIONS})
list(APPEND IFCPARSE_H_FILES
../src/ifcparse/Ifc${schema}.h
../src/ifcparse/Ifc${schema}-definitions.h
)
list(APPEND IFCPARSE_CPP_FILES
../src/ifcparse/Ifc${schema}.cpp
../src/ifcparse/Ifc${schema}-schema.cpp
)
endforeach()
set(IFCPARSE_FILES ${IFCPARSE_CPP_FILES} ${IFCPARSE_H_FILES})
add_library(IfcParse ${IFCPARSE_FILES})
target_link_libraries(IfcParse ${STDCPPFS})
set_target_properties(IfcParse PROPERTIES COMPILE_FLAGS -DIFC_PARSE_EXPORTS VERSION "${PROJECT_VERSION}" SOVERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}")
if(LibXml2_DIR)
target_compile_definitions(IfcParse PRIVATE ${LIBXML2_DEFINITIONS})
endif()
if(WASM_BUILD)
target_link_libraries(IfcParse ${BCRYPT_LIBRARIES} ${LIBXML2_LIBRARIES})
else()
target_link_libraries(IfcParse ${Boost_LIBRARIES} ${BCRYPT_LIBRARIES} ${LIBXML2_LIBRARIES})
endif()
if(BUILD_IFCGEOM)
# CGAL::CGAL target already has dependencies resolved.
if(WITH_CGAL AND CGAL_DIR)
set(CGAL_LIBRARIES CGAL::CGAL)
message(STATUS "Using found CGAL package at '${CGAL_DIR}'")
elseif(WITH_CGAL AND NOT CGAL_DIR)
clear_wasm_sysroot()
find_library(libGMP NAMES gmp mpir PATHS ${GMP_LIBRARY_DIR} NO_DEFAULT_PATH)
find_library(libMPFR NAMES mpfr PATHS ${MPFR_LIBRARY_DIR} NO_DEFAULT_PATH)
restore_wasm_sysroot()
if(NOT libGMP)
message(FATAL_ERROR "Unable to find GMP library files, aborting")
endif()
@@ -1137,33 +627,8 @@ if(BUILD_IFCGEOM)
list(APPEND CGAL_LIBRARIES "${libGMP}")
endif()
foreach(kernel ${GEOMETRY_KERNELS})
string(TOUPPER ${kernel} KERNEL_UPPER)
file(GLOB IFCGEOM_H_FILES ../src/ifcgeom/kernels/${kernel}/*.h)
file(GLOB IFCGEOM_CPP_FILES ../src/ifcgeom/kernels/${kernel}/*.cpp)
set(IFCGEOM_FILES ${IFCGEOM_CPP_FILES} ${IFCGEOM_H_FILES})
add_library(geometry_kernel_${kernel} ${IFCGEOM_FILES} ../src/ifcgeom/kernels/ifc_geomlibrary_api.h)
set_property(TARGET geometry_kernel_${kernel} APPEND PROPERTY COMPILE_FLAGS "-DIFC_GEOMLIBRARY_EXPORTS")
# needed?
# if(NOT WASM_BUILD)
# endif()
target_link_libraries(geometry_kernel_${kernel} ${${KERNEL_UPPER}_LIBRARIES} IfcGeom IfcParse)
list(APPEND kernel_libraries geometry_kernel_${kernel})
if(${kernel} STREQUAL "cgal")
set_property(TARGET geometry_kernel_${kernel} APPEND_STRING PROPERTY COMPILE_FLAGS " -DCGAL_HAS_THREADS")
add_library(geometry_kernel_${kernel}_simple ${IFCGEOM_FILES})
set_target_properties(geometry_kernel_${kernel}_simple PROPERTIES COMPILE_FLAGS "-DIFC_GEOMLIBRARY_EXPORTS -DIFOPSH_SIMPLE_KERNEL -DCGAL_HAS_THREADS")
# needed?
# if(NOT WASM_BUILD)
# endif()
target_link_libraries(geometry_kernel_${kernel}_simple ${${KERNEL_UPPER}_LIBRARIES} IfcGeom IfcParse)
list(APPEND kernel_libraries geometry_kernel_${kernel}_simple)
endif()
endforeach()
add_subdirectory(../src/ifcgeom ifcgeom)
# IfcGeom
foreach(schema ${SCHEMA_VERSIONS})
file(GLOB IFCGEOM_I_FILES ../src/ifcgeom/mapping/*.i)
@@ -1192,134 +657,22 @@ if(BUILD_IFCGEOM)
find_package(Threads)
endif()
target_link_libraries(IfcGeom IfcParse ${mapping_libraries} ${CMAKE_THREAD_LIBS_INIT})
target_link_libraries(IfcGeom IfcParse ${mapping_libraries} ${CMAKE_THREAD_LIBS_INIT} ${OpenCASCADE_LIBRARIES})
endif(BUILD_IFCGEOM)
if(BUILD_CONVERT OR BUILD_IFCPYTHON)
# Serializers
file(GLOB SERIALIZERS_H_FILES ../src/serializers/*.h)
file(GLOB SERIALIZERS_CPP_FILES ../src/serializers/*.cpp)
set(SERIALIZERS_FILES ${SERIALIZERS_H_FILES} ${SERIALIZERS_CPP_FILES})
file(GLOB SERIALIZERS_S_H_FILES ../src/serializers/schema_dependent/*.h)
file(GLOB SERIALIZERS_S_CPP_FILES ../src/serializers/schema_dependent/*.cpp)
set(SERIALIZERS_S_FILES ${SERIALIZERS_S_H_FILES} ${SERIALIZERS_S_CPP_FILES})
foreach(schema ${SCHEMA_VERSIONS})
add_library(Serializers_ifc${schema} STATIC ${SERIALIZERS_S_FILES})
set_target_properties(Serializers_ifc${schema} PROPERTIES COMPILE_FLAGS "-DSERIALIZERS_EXPORTS -DIfcSchema=Ifc${schema}")
if(WASM_BUILD)
target_link_libraries(Serializers_ifc${schema} ${HDF5_LIBRARIES})
else()
target_link_libraries(Serializers_ifc${schema} IfcGeom ${OPENCASCADE_LIBRARIES} ${HDF5_LIBRARIES})
endif()
endforeach()
add_library(Serializers ${SERIALIZERS_FILES})
set_target_properties(Serializers PROPERTIES COMPILE_FLAGS "-DSERIALIZERS_EXPORTS" VERSION "${PROJECT_VERSION}" SOVERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}")
if(WITH_PROJ)
target_compile_definitions(Serializers PRIVATE "WITH_PROJ")
if (PROJ_STATIC)
target_compile_definitions(Serializers PRIVATE "PROJ_DLL=")
endif()
target_include_directories(Serializers PRIVATE ${PROJ_INCLUDE_DIR} ${SQLITE_INCLUDE_DIR})
target_link_libraries(Serializers ${PROJ_LIBRARIES})
endif()
target_link_libraries(Serializers ${SERIALIZER_SCHEMA_LIBRARIES} ${OPENCOLLADA_LIBRARIES} ${USD_LIBRARIES} IfcGeom ${OPENCASCADE_LIBRARIES} ${kernel_libraries} IfcParse)
add_subdirectory(../src/serializers serializers)
set(IFCOPENSHELL_LIBRARIES ${IFCOPENSHELL_LIBRARIES} ${SERIALIZER_SCHEMA_LIBRARIES})
endif(BUILD_CONVERT OR BUILD_IFCPYTHON)
if(BUILD_CONVERT)
if(WITH_CGAL AND CITYJSON_SUPPORT)
message(STATUS "Building CityJSON support")
set(CITYJSON_CONVERT_FILES
../src/ifcconvert/cityjson/geobim.cpp
../src/ifcconvert/cityjson/global_execution_context.cpp
../src/ifcconvert/cityjson/opening_collector.cpp
../src/ifcconvert/cityjson/processing.cpp
../src/ifcconvert/cityjson/radius_comparison.cpp
../src/ifcconvert/cityjson/radius_execution_context.cpp
../src/ifcconvert/cityjson/settings.cpp
../src/ifcconvert/cityjson/writer.cpp
)
add_library(cityjson_converter ${CITYJSON_CONVERT_FILES})
target_include_directories(cityjson_converter PRIVATE ../src)
set(IFCOPENSHELL_LIBRARIES ${IFCOPENSHELL_LIBRARIES} cityjson_converter)
install(TARGETS cityjson_converter
ARCHIVE DESTINATION ${LIBDIR}
LIBRARY DESTINATION ${LIBDIR}
)
add_executable(cityjson_converter_exe ${CITYJSON_CONVERT_FILES})
set_target_properties(cityjson_converter_exe PROPERTIES COMPILE_FLAGS "-DCITYJSON_EXECUTABLE")
target_include_directories(cityjson_converter_exe PRIVATE ../src)
target_link_libraries(cityjson_converter_exe ${IFCOPENSHELL_LIBRARIES} ${OPENCASCADE_LIBRARIES} ${Boost_LIBRARIES} ${HDF5_LIBRARIES} ${USD_LIBRARIES})
install(TARGETS cityjson_converter_exe
RUNTIME DESTINATION ${BINDIR}
)
endif()
# IfcConvert
if (WITH_RELATIONSHIP_VALIDATION)
file(GLOB IFCCONVERT_CPP_FILES ../src/ifcconvert/*.cpp)
file(GLOB IFCCONVERT_H_FILES ../src/ifcconvert/*.h)
else()
file(GLOB IFCCONVERT_CPP_FILES ../src/ifcconvert/IfcConvert.cpp)
file(GLOB IFCCONVERT_H_FILES)
endif()
set(IFCCONVERT_FILES ${IFCCONVERT_CPP_FILES} ${IFCCONVERT_H_FILES})
add_executable(IfcConvert ${IFCCONVERT_FILES})
target_link_libraries(IfcConvert IfcGeom IfcParse Serializers ${kernel_libraries} ${OPENCASCADE_LIBRARIES} ${Boost_LIBRARIES} ${HDF5_LIBRARIES} ${USD_LIBRARIES})
if (WITH_RELATIONSHIP_VALIDATION)
set_property(TARGET IfcConvert APPEND_STRING PROPERTY COMPILE_FLAGS " -DWITH_RELATIONSHIP_VALIDATION")
endif()
if(WITH_CGAL AND CITYJSON_SUPPORT)
set_property(TARGET IfcConvert APPEND_STRING PROPERTY COMPILE_FLAGS " -DIFOPSH_WITH_CITYJSON")
endif()
if((NOT WIN32) AND BUILD_SHARED_LIBS)
# Only set RPATHs when building shared libraries (i.e. IfcParse and
# IfcGeom are dynamically linked). Not necessarily a perfect solution
# but probably a good indication of whether RPATHs are necessary.
SET_INSTALL_RPATHS(IfcConvert "${IFCOPENSHELL_LIBRARY_DIR};${OCC_LIBRARY_DIR};${Boost_LIBRARY_DIRS};${OPENCOLLADA_LIBRARY_DIR}")
endif()
install(TARGETS IfcConvert
ARCHIVE DESTINATION ${LIBDIR}
LIBRARY DESTINATION ${LIBDIR}
RUNTIME DESTINATION ${BINDIR}
)
add_subdirectory(../src/ifcconvert ifcconvert)
endif(BUILD_CONVERT)
# IfcGeomServer
if(BUILD_GEOMSERVER)
if(NOT WITH_OPENCASCADE)
message(FATAL_ERROR "Open CASCADE is required to build IfcGeomServer.")
endif()
file(GLOB CPP_FILES ../src/ifcgeomserver/*.cpp)
file(GLOB H_FILES ../src/ifcgeomserver/*.h)
set(SOURCE_FILES ${CPP_FILES} ${H_FILES})
add_executable(IfcGeomServer ${SOURCE_FILES})
target_link_libraries(IfcGeomServer IfcGeom IfcParse Serializers ${kernel_libraries} ${OPENCASCADE_LIBRARIES} ${Boost_LIBRARIES})
if((NOT WIN32) AND BUILD_SHARED_LIBS)
SET_INSTALL_RPATHS(IfcGeomServer "${IFCOPENSHELL_LIBRARY_DIR};${OCC_LIBRARY_DIR};${Boost_LIBRARY_DIRS}")
endif()
install(TARGETS IfcGeomServer
ARCHIVE DESTINATION ${LIBDIR}
LIBRARY DESTINATION ${LIBDIR}
RUNTIME DESTINATION ${BINDIR}
)
add_subdirectory(../src/ifcgeomserver ifcgeomserver)
endif(BUILD_GEOMSERVER)
if(ADD_COMMIT_SHA)
@@ -1406,17 +759,6 @@ if(BUILD_QTVIEWER)
add_subdirectory(../src/qtviewer qtviewer)
endif()
# CMake installation targets
install(FILES ${IFCPARSE_H_FILES}
DESTINATION ${INCLUDEDIR}/ifcparse
)
install(TARGETS IfcParse
ARCHIVE DESTINATION ${LIBDIR}
LIBRARY DESTINATION ${LIBDIR}
RUNTIME DESTINATION ${BINDIR}
)
if(BUILD_IFCGEOM)
# install(FILES ${IFCGEOM_H_FILES}
# DESTINATION ${INCLUDEDIR}/ifcgeom
@@ -1445,22 +787,6 @@ if(BUILD_IFCGEOM)
)
endif(BUILD_IFCGEOM)
if(BUILD_CONVERT)
install(TARGETS Serializers ${SERIALIZER_SCHEMA_LIBRARIES}
ARCHIVE DESTINATION ${LIBDIR}
LIBRARY DESTINATION ${LIBDIR}
RUNTIME DESTINATION ${BINDIR}
)
install(FILES ${SERIALIZERS_H_FILES}
DESTINATION ${INCLUDEDIR}/serializers/
)
install(FILES ${SERIALIZERS_S_H_FILES}
DESTINATION ${INCLUDEDIR}/serializers/schema_dependent
)
endif(BUILD_CONVERT)
if(BUILD_CONVERT OR BUILD_IFCPYTHON)
if(WITH_OPENCASCADE)
install(TARGETS geometry_serializer ${geometry_serializer_libraries}
-1
View File
@@ -17,7 +17,6 @@
"IFCXML_SUPPORT": "ON",
"HDF5_SUPPORT": "ON",
"SCHEMA_VERSIONS": "4x3_add2",
"CITYJSON_SUPPORT": "OFF",
"CMAKE_GENERATOR_PLATFORM": "",
"CMAKE_GENERATOR_TOOLSET": ""
}
+42
View File
@@ -0,0 +1,42 @@
#
# Input variables:
# - `CGAL_INCLUDE_DIR`
# - `CGAL_LIBRARY_DIR`
# - `GMP_INCLUDE_DIR`
# - `GMP_LIBRARY_DIR`
# - `MPFR_INCLUDE_DIR`
# - `MPFR_LIBRARY_DIR`
# If input variables are not specified, try to find HDF5 config.
# Input variables could also be provided as environment variables.
#
# Output variables:
# - `CGAL_INCLUDE_DIR`
#
UNIFY_ENVVARS_AND_CACHE(CGAL_INCLUDE_DIR)
UNIFY_ENVVARS_AND_CACHE(CGAL_LIBRARY_DIR)
UNIFY_ENVVARS_AND_CACHE(GMP_INCLUDE_DIR)
UNIFY_ENVVARS_AND_CACHE(GMP_LIBRARY_DIR)
UNIFY_ENVVARS_AND_CACHE(MPFR_INCLUDE_DIR)
UNIFY_ENVVARS_AND_CACHE(MPFR_LIBRARY_DIR)
if(NOT CGAL_INCLUDE_DIR)
# CGAL is not respecting default Boost_USE_STATIC_LIBS value
# and sometiems it's getting in the way.
if(NOT DEFINED Boost_USE_STATIC_LIBS)
set(CGAL_Boost_USE_STATIC_LIBS OFF)
else()
set(CGAL_Boost_USE_STATIC_LIBS "${Boost_USE_STATIC_LIBS}")
endif()
find_package(CGAL CONFIG REQUIRED)
if(NOT CGAL_DIR)
message(
FATAL_ERROR
"CGAL_SUPPORT enabled, but CGAL_INCLUDE_DIR wasn't provided and CGAL package couldn't be found."
)
endif()
message(STATUS "CGAL: found config at '${CGAL_DIR}'.")
endif()
add_definitions(-DIFOPSH_WITH_CGAL)
set(SWIG_DEFINES ${SWIG_DEFINES} -DIFOPSH_WITH_CGAL)
+27
View File
@@ -0,0 +1,27 @@
#
# Input variables:
# - `EIGEN_DIR`
# If input variables are not specified, try to find Eigen3 config.
# Input variables could also be provided as environment variables.
#
# Output targets:
# - `Eigen3::Eigen`
UNIFY_ENVVARS_AND_CACHE(EIGEN_DIR)
if(EXISTS "${EIGEN_DIR}")
# Mimic Eigen3Config.cmake target.
add_library(Eigen3::Eigen INTERFACE IMPORTED)
target_include_directories(Eigen3::Eigen INTERFACE "${EIGEN_DIR}")
else()
find_package(Eigen3 CONFIG)
if(Eigen3_DIR)
message(STATUS "Eigen3: found config at '${Eigen3_DIR}'.")
else()
message(
FATAL_ERROR
"EIGEN_DIR is not provided or provided folder doesn't exist (current value: '${EIGEN_DIR}'). "
"Also couldn't find Eigen3 as a package."
)
endif()
endif()
+109
View File
@@ -0,0 +1,109 @@
#
# Input variables:
# - `HDF5_INCLUDE_DIR`
# - `HDF5_LIBRARY_DIR`
# - `HDF5_LIBRARIES`
# If input variables are not specified, try to find HDF5 config.
# Input variables could also be provided as environment variables.
#
# Output variables:
# - `HDF5_INCLUDE_DIR`
# - `HDF5_LIBRARY_DIR`
# - `HDF5_LIBRARIES`
#
UNIFY_ENVVARS_AND_CACHE(HDF5_INCLUDE_DIR)
UNIFY_ENVVARS_AND_CACHE(HDF5_LIBRARY_DIR)
UNIFY_ENVVARS_AND_CACHE(HDF5_LIBRARIES)
# To avoid cyclic calls to this file
list(REMOVE_ITEM CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR})
if("${HDF5_INCLUDE_DIR}" STREQUAL "")
message(STATUS "No HDF5 include directory specified")
else()
set(HDF5_INCLUDE_DIR "${HDF5_INCLUDE_DIR}" CACHE FILEPATH "HDF5 header files")
endif()
if("${HDF5_LIBRARY_DIR}" STREQUAL "")
message(STATUS "No HDF5 library directory specified")
else()
set(HDF5_LIBRARY_DIR "${HDF5_LIBRARY_DIR}" CACHE FILEPATH "HDF5 library files")
endif()
if(HDF5_LIBRARY_DIR)
# result of the HDF5 ctest package
# Find zlib using cmake find_library. How should this be implemented?
# FIND_LIBRARY(NAMES z libz libz_debug PATHS ... NO_DEFAULT_PATH)
if("$ENV{CONDA_BUILD}" STREQUAL "")
# result of the HDF5 ctest package
if(WIN32)
set(zlib_post lib)
set(lib_ext lib)
else()
set(lib_ext a)
endif()
if("${CMAKE_BUILD_TYPE}" STREQUAL "Debug")
set(debug_postfix "_debug")
endif()
set(HDF5_LIBRARIES
"${HDF5_LIBRARY_DIR}/libhdf5_cpp${debug_postfix}.${lib_ext}"
"${HDF5_LIBRARY_DIR}/libhdf5${debug_postfix}.${lib_ext}"
"${HDF5_LIBRARY_DIR}/libz${zlib_post}${debug_postfix}.${lib_ext}"
"${HDF5_LIBRARY_DIR}/libsz${debug_postfix}.${lib_ext}"
"${HDF5_LIBRARY_DIR}/libaec${debug_postfix}.${lib_ext}"
)
else()
message(STATUS "Packaging hdf5 and zlib for conda distribution")
if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
# macOS
set(zlib_post libz)
set(lib_ext dylib)
set(HDF5_LIBRARIES
"${HDF5_LIBRARY_DIR}/libhdf5_cpp.${lib_ext}"
"${HDF5_LIBRARY_DIR}/libhdf5.${lib_ext}"
"${HDF5_LIBRARY_DIR}/${zlib_post}.${lib_ext}"
)
else()
# linux and windows
# Find HDF5 package
find_package(HDF5 REQUIRED COMPONENTS C CXX)
# Find ZLIB package
find_package(ZLIB REQUIRED)
# Include directories
include_directories(${HDF5_INCLUDE_DIRS} ${ZLIB_INCLUDE_DIRS})
# Link libraries
set(HDF5_LIBRARIES ${HDF5_LIBRARIES} ${ZLIB_LIBRARIES})
message(STATUS "HDF5 libraries: ${HDF5_LIBRARIES}")
endif()
endif()
endif()
if(NOT HDF5_INCLUDE_DIR OR NOT HDF5_LIBRARY_DIR)
# First try to find it as a config.
find_package(HDF5 CONFIG)
if(HDF5_DIR)
message(STATUS "HDF5: found config at '${HDF5_DIR}'.")
set(HDF5_LIBRARIES hdf5_cpp-static)
else()
# If it failed, still try to find as a module.
# E.g. on Ubuntu `libhdf5-dev` doesn't provie hdf5-config.cmake.
# Will automatically fill HDF5_LIBRARIES and HDF5_INCLUDE_DIR.
find_package(HDF5 COMPONENTS CXX)
if(NOT HDF5_INCLUDE_DIR)
message(
FATAL_ERROR
"HDF5_INCLUDE_DIR is not provided (current value: '${HDF5_INCLUDE_DIR}'). "
"HDF5_LIBRARY_DIR is not provided (current value: '${HDF5_LIBRARY_DIR}'). "
"Also could not find HDF5 package (neither module or config)."
)
endif()
endif()
endif()
# Restore module path.
list(PREPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR})
+49
View File
@@ -0,0 +1,49 @@
#
# Input variables:
# - `LIBXML2_INCLUDE_DIR`
# - `LIBXML2_LIBRARIES`
# If input variables are not specified, try to find LibXml2 config.
# Input variables could also be provided as environment variables.
#
# Output variables:
# - `LIBXML2_INCLUDE_DIR`
# - `LIBXML2_LIBRARIES`
#
# To avoid cyclic calls to this file
list(REMOVE_ITEM CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR})
UNIFY_ENVVARS_AND_CACHE(LIBXML2_INCLUDE_DIR)
UNIFY_ENVVARS_AND_CACHE(LIBXML2_LIBRARIES)
if((NOT LIBXML2_INCLUDE_DIR AND NOT LIBXML2_LIBRARIES))
# First try config mode (probably works with vcpkg, Conan, macOS brew installs, but not on ubuntu 22.04)
# CONFIG is provided using root path, so no need to clear sysroot here.
find_package(LibXml2 QUIET CONFIG)
if(NOT LibXml2_FOUND)
# Fallback to CMake's builtin FindLibXml2 module (works on Ubuntu)
find_package(LibXml2 REQUIRED)
else()
message(STATUS "Found LibXml2 config: ${LibXml2_DIR}")
endif()
if(TARGET LibXml2::LibXml2)
# Config mode already gives us the target
set(LIBXML2_LIBRARIES LibXml2::LibXml2)
get_target_property(LIBXML2_INCLUDE_DIR LibXml2::LibXml2 INTERFACE_INCLUDE_DIRECTORIES)
else()
# Module mode (Ubuntu)
set(LIBXML2_LIBRARIES ${LibXml2_LIBRARIES})
set(LIBXML2_INCLUDE_DIR ${LibXml2_INCLUDE_DIRS})
endif()
else()
find_package(LibXml2 REQUIRED)
endif()
if(MSVC AND NOT LibXml2_DIR)
add_debug_variants(LIBXML2_LIBRARIES "${LIBXML2_LIBRARIES}" d)
endif()
# Restore module path.
list(PREPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR})
+138
View File
@@ -0,0 +1,138 @@
#
# Input variables:
# - `OCC_INCLUDE_DIR`
# - `OCC_LIBRARY_DIR`
# If input variables are not specified, try to find OpenCASCADE config.
# Input variables could also be provided as environment variables.
#
# Output variables
# - `OpenCASCADE_LIBRARIES`
#
UNIFY_ENVVARS_AND_CACHE(OCC_INCLUDE_DIR)
UNIFY_ENVVARS_AND_CACHE(OCC_LIBRARY_DIR)
if(OCC_INCLUDE_DIR)
set(OCC_INCLUDE_DIR ${OCC_INCLUDE_DIR} CACHE FILEPATH "Open CASCADE header files")
message(STATUS "Looking for Open CASCADE include files in: ${OCC_INCLUDE_DIR}")
endif()
if(OCC_LIBRARY_DIR)
set(OCC_LIBRARY_DIR ${OCC_LIBRARY_DIR} CACHE FILEPATH "Open CASCADE library files")
message(STATUS "Looking for Open CASCADE library files in: ${OCC_LIBRARY_DIR}")
endif()
if(NOT OCC_INCLUDE_DIR AND NOT OCC_LIBRARY_DIR)
# OCE is not supported for find_package, because it's using a different name (`oce`)
# and also has an odd directory structure (install/lib/oce-0.18/*.cmake).
# find_package creates variables:
# - `OpenCASCADE_INCLUDE_DIR`
# - `OpenCASCADE_LIBRARIES`
find_package(OpenCASCADE CONFIG REQUIRED)
return()
endif()
# No specific paths specified, try to find package.
if(OCC_INCLUDE_DIR AND OCC_LIBRARY_DIR)
message(
STATUS
"Using provided OCC_INCLUDE_DIR ('${OCC_INCLUDE_DIR}') "
"and OCC_LIBRARY_DIR ('${OCC_LIBRARY_DIR}')."
)
# Parse OCC_VERSION_STRING.
file(STRINGS ${OCC_INCLUDE_DIR}/Standard_Version.hxx OCC_MAJOR
REGEX "#define OCC_VERSION_MAJOR.*"
)
string(REGEX MATCH "[0-9]+" OCC_MAJOR ${OCC_MAJOR})
file(STRINGS ${OCC_INCLUDE_DIR}/Standard_Version.hxx OCC_MINOR
REGEX "#define OCC_VERSION_MINOR.*"
)
string(REGEX MATCH "[0-9]+" OCC_MINOR ${OCC_MINOR})
file(STRINGS ${OCC_INCLUDE_DIR}/Standard_Version.hxx OCC_MAINT
REGEX "#define OCC_VERSION_MAINTENANCE.*"
)
string(REGEX MATCH "[0-9]+" OCC_MAINT ${OCC_MAINT})
set(OCC_VERSION_STRING "${OCC_MAJOR}.${OCC_MINOR}.${OCC_MAINT}")
else()
message(
FATAL_ERROR
"Couldn't find Open CASCADE installation. "
"Either both OCC_INCLUDE_DIR ('${OCC_INCLUDE_DIR}') and OCC_LIBRARY_DIR ('${OCC_LIBRARY_DIR}') "
"must be specified or OpenCASCADE package should be discoverable. "
"If you're using OCE, then providing a package is not available "
"and you need to provide OCE_INCLUDE_DIR and OCE_LIBRARY_DIR directly."
)
endif()
set(
OpenCASCADE_LIBRARIES
TKernel TKMath TKBRep TKGeomBase TKGeomAlgo TKG3d TKG2d TKShHealing TKTopAlgo TKMesh TKPrim TKBool TKBO
TKFillet TKXSBase TKOffset TKHLR
# @todo investigate the exact conditions when this is necessary
TKBin
)
if(OCC_VERSION_STRING VERSION_LESS 7.8.0)
list(APPEND OpenCASCADE_LIBRARIES TKIGES TKSTEPBase TKSTEPAttr TKSTEP209 TKSTEP)
else(OCC_VERSION_STRING VERSION_LESS 7.8.0)
list(APPEND OpenCASCADE_LIBRARIES TKDESTEP TKDEIGES)
endif(OCC_VERSION_STRING VERSION_LESS 7.8.0)
find_library(libTKernel NAMES TKernel TKerneld PATHS ${OCC_LIBRARY_DIR} NO_DEFAULT_PATH)
if(libTKernel)
message(STATUS "Required Open Cascade Library files found")
else()
message(
FATAL_ERROR
"Unable to find Open Cascade library files in OCC_LIBRARY_DIR ('${OCC_LIBRARY_DIR}'), aborting"
)
endif()
if(MSVC)
add_definitions(-DHAVE_NO_DLL)
add_debug_variants(OpenCASCADE_LIBRARIES "${OpenCASCADE_LIBRARIES}" d)
endif()
if(WIN32)
# OCC might require linking to Winsock depending on the version and build configuration
list(APPEND OpenCASCADE_LIBRARIES ws2_32.lib)
endif()
# Make sure cross-referenced symbols between static OCC libraries get
# resolved. Also add thread and rt libraries.
get_filename_component(libTKernelExt ${libTKernel} EXT)
if("${libTKernelExt}" STREQUAL ".a")
set(OCCT_STATIC ON)
endif()
if(OCCT_STATIC)
find_package(Threads)
if(WASM_BUILD)
set(OpenCASCADE_LIBRARIES ${OpenCASCADE_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT})
else()
# OpenCASCADE_LIBRARIES repeated N times below in order to fix cyclic dependencies
# tfk: --start-group ... --end-group didn't work on the apple linker when last tested
if(APPLE)
set(OpenCASCADE_LIBRARIES ${OpenCASCADE_LIBRARIES} ${OpenCASCADE_LIBRARIES} ${OpenCASCADE_LIBRARIES} ${OpenCASCADE_LIBRARIES} ${OpenCASCADE_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT})
else()
set(OpenCASCADE_LIBRARIES -Wl,--start-group ${OpenCASCADE_LIBRARIES} -Wl,--end-group ${CMAKE_THREAD_LIBS_INIT})
endif()
endif()
if(NOT APPLE AND NOT WIN32)
set(OpenCASCADE_LIBRARIES ${OpenCASCADE_LIBRARIES} "rt")
endif()
if(NOT WIN32)
set(OpenCASCADE_LIBRARIES ${OpenCASCADE_LIBRARIES} "dl")
endif()
endif()
add_library(OpenCASCADE_INTERFACE INTERFACE IMPORTED)
target_include_directories(OpenCASCADE_INTERFACE INTERFACE "${OCC_INCLUDE_DIR}")
target_link_libraries(OpenCASCADE_INTERFACE INTERFACE ${OpenCASCADE_LIBRARIES})
target_link_directories(OpenCASCADE_INTERFACE INTERFACE "${OCC_LIBRARY_DIR}")
set(OpenCASCADE_LIBRARIES OpenCASCADE_INTERFACE)
+118
View File
@@ -0,0 +1,118 @@
#
# Input variables:
# - `OPENCOLLADA_INCLUDE_DIR`
# - `OPENCOLLADA_LIBRARY_DIR`
# - `PCRE_LIBRARY_DIR`
# If input variables are not specified, try to find OpenCOLLADA config.
# Input variables could also be provided as environment variables.
#
# Output variables:
# - `OPENCOLLADA_INCLUDE_DIR`
# - `OPENCOLLADA_LIBRARY_DIR`
# - `OPENCOLLADA_LIBRARIES`
#
UNIFY_ENVVARS_AND_CACHE(OPENCOLLADA_INCLUDE_DIR)
UNIFY_ENVVARS_AND_CACHE(OPENCOLLADA_LIBRARY_DIR)
UNIFY_ENVVARS_AND_CACHE(PCRE_LIBRARY_DIR)
if(NOT OPENCOLLADA_INCLUDE_DIR AND NOT OPENCOLLADA_LIBRARY_DIR)
# If package is found, automatically sets
# OPENCOLLADA_INCLUDE_DIRS and OPENCOLLADA_LIBRARIES (list of targets, not paths).
find_package(OpenCOLLADA CONFIG)
if(OpenCOLLADA_DIR)
message(STATUS "Found OpenCOLLADA: '${OpenCOLLADA_DIR}'.")
set(OPENCOLLADA_FOUND TRUE)
else()
message(STATUS "OpenCOLLADA package not found, falling back to manual search.")
endif()
endif()
if(NOT OpenCOLLADA_DIR)
# Find OpenCOLLADA
if("${OPENCOLLADA_INCLUDE_DIR}" STREQUAL "")
message(STATUS "No OpenCOLLADA include directory specified")
set(OPENCOLLADA_INCLUDE_DIR "/usr/include/opencollada" CACHE FILEPATH "OpenCOLLADA header files")
else()
set(OPENCOLLADA_INCLUDE_DIR "${OPENCOLLADA_INCLUDE_DIR}" CACHE FILEPATH "OpenCOLLADA header files")
endif()
if("${OPENCOLLADA_LIBRARY_DIR}" STREQUAL "")
message(STATUS "No OpenCOLLADA library directory specified")
find_library(OPENCOLLADA_FRAMEWORK_LIB NAMES OpenCOLLADAFramework
PATHS /usr/lib64/opencollada /usr/lib/opencollada /usr/lib64 /usr/lib /usr/local/lib64 /usr/local/lib)
get_filename_component(OPENCOLLADA_LIBRARY_DIR ${OPENCOLLADA_FRAMEWORK_LIB} PATH)
endif()
find_library(OpenCOLLADAFramework NAMES OpenCOLLADAFramework OpenCOLLADAFrameworkd PATHS ${OPENCOLLADA_LIBRARY_DIR} NO_DEFAULT_PATH)
if(OpenCOLLADAFramework)
message(STATUS "OpenCOLLADA library files found")
else()
message(FATAL_ERROR "COLLADA_SUPPORT enabled, but unable to find OpenCOLLADA libraries. "
"Disable COLLADA_SUPPORT or fix OpenCOLLADA paths to proceed.")
endif()
set(OPENCOLLADA_LIBRARY_DIR "${OPENCOLLADA_LIBRARY_DIR}" CACHE FILEPATH "OpenCOLLADA library files")
set(OPENCOLLADA_INCLUDE_DIRS "${OPENCOLLADA_INCLUDE_DIR}/COLLADABaseUtils" "${OPENCOLLADA_INCLUDE_DIR}/COLLADAStreamWriter")
find_file(COLLADASWStreamWriter_h "COLLADASWStreamWriter.h" ${OPENCOLLADA_INCLUDE_DIRS})
if(COLLADASWStreamWriter_h)
message(STATUS "OpenCOLLADA header files found")
set(OPENCOLLADA_FOUND TRUE)
set(OPENCOLLADA_LIBRARY_NAMES
GeneratedSaxParser MathMLSolver OpenCOLLADABaseUtils OpenCOLLADAFramework OpenCOLLADASaxFrameworkLoader
OpenCOLLADAStreamWriter UTF buffer ftoa
)
# Use the found OpenCOLLADAFramework as a template for all other OpenCOLLADA libraries
foreach(lib ${OPENCOLLADA_LIBRARY_NAMES})
# Make sure we'll handle the Windows/MSVC debug postfix convention too.
string(REPLACE OpenCOLLADAFrameworkd "${lib}" lib_path "${OpenCOLLADAFramework}")
string(REPLACE OpenCOLLADAFramework "${lib}" lib_path "${lib_path}")
list(APPEND OPENCOLLADA_LIBRARIES "${lib_path}")
endforeach()
if("${PCRE_LIBRARY_DIR}" STREQUAL "")
if(WIN32)
find_library(pcre_library NAMES pcre pcred PATHS ${OPENCOLLADA_LIBRARY_DIR} NO_DEFAULT_PATH)
else()
find_library(pcre_library NAMES pcre PATHS ${OPENCOLLADA_LIBRARY_DIR})
endif()
get_filename_component(PCRE_LIBRARY_DIR ${pcre_library} PATH)
else()
find_library(pcre_library NAMES pcre pcred PATHS ${PCRE_LIBRARY_DIR} NO_DEFAULT_PATH)
endif()
if(pcre_library)
set(OPENCOLLADA_LIBRARY_DIR ${OPENCOLLADA_LIBRARY_DIR} ${PCRE_LIBRARY_DIR})
if(MSVC)
# Add release lib regardless whether release or debug found. Debug version will be appended below.
list(APPEND OPENCOLLADA_LIBRARIES "${PCRE_LIBRARY_DIR}/pcre.lib")
else()
list(APPEND OPENCOLLADA_LIBRARIES "${pcre_library}")
endif()
else()
message(FATAL_ERROR "COLLADA_SUPPORT enabled, but unable to find PCRE. "
"Disable COLLADA_SUPPORT or fix PCRE_LIBRARY_DIR path to proceed.")
endif()
if(MSVC)
add_debug_variants(OPENCOLLADA_LIBRARIES "${OPENCOLLADA_LIBRARIES}" d)
endif()
else()
message(FATAL_ERROR "COLLADA_SUPPORT enabled, but unable to find OpenCOLLADA headers. "
"Disable COLLADA_SUPPORT or fix OpenCOLLADA paths to proceed.")
endif()
endif(NOT OpenCOLLADA_DIR)
if(OPENCOLLADA_FOUND)
add_definitions(-DWITH_OPENCOLLADA)
set(SWIG_DEFINES ${SWIG_DEFINES} -DWITH_OPENCOLLADA)
endif()
+54
View File
@@ -0,0 +1,54 @@
#
# Input variables:
# - `USD_INCLUDE_DIR`
# - `USD_LIBRARY_DIR`
# Input variables could also be provided as environment variables.
# TODO: Try to find USD config if varibales are not provided.
# TODO: does usd have a config file?
#
# Output variables:
# - `USD_LIBRARIES`
UNIFY_ENVVARS_AND_CACHE(USD_INCLUDE_DIR)
UNIFY_ENVVARS_AND_CACHE(USD_LIBRARY_DIR)
if("${USD_INCLUDE_DIR}" STREQUAL "")
find_path(USD_INCLUDE_DIR pxr.h
PATHS
/usr/include/pxr
/usr/local/include/pxr
REQUIRED
)
if(USD_INCLUDE_DIR)
message(STATUS "Found USD include files in: ${USD_INCLUDE_DIR}")
else()
message(FATAL_ERROR "Unable to find USD include directory, specify USD_INCLUDE_DIR manually.")
endif()
else()
set(USD_INCLUDE_DIR ${USD_INCLUDE_DIR} CACHE FILEPATH "USD header files")
message(STATUS "Looking for USD include files in: ${USD_INCLUDE_DIR}")
endif()
set(USD_LIBRARIES
usd_usd
usd_usdGeom
usd_usdShade
usd_usdLux
usd_vt
usd_sdf
usd_tf
usd_gf
)
find_library(USD_LIBRARY
NAMES ${USD_LIBRARIES}
PATHS ${USD_LIBRARY_DIR})
if(USD_LIBRARY)
message(STATUS "USD libraries ${USD_LIBRARIES} found in: ${USD_LIBRARY_DIR}")
link_directories(${USD_LIBRARY_DIR})
else()
message(FATAL_ERROR "Unable to find USD libraries in: ${USD_LIBRARY_DIR}")
endif()
add_definitions(-DWITH_USD)
set(SWIG_DEFINES ${SWIG_DEFINES} -DWITH_USD)
-1
View File
@@ -42,7 +42,6 @@ cmake -G "Ninja" ^
-D Boost_LIBRARY_DIR:FILEPATH="%LIBRARY_PREFIX%\lib" ^
-D Boost_INCLUDE_DIR:FILEPATH="%LIBRARY_PREFIX%\include" ^
-D Boost_USE_STATIC_LIBS:BOOL=OFF ^
-D CITYJSON_SUPPORT:BOOL=OFF ^
../cmake
if errorlevel 1 exit 1
-1
View File
@@ -42,7 +42,6 @@ cmake ${CMAKE_ARGS} -G Ninja \
-DBUILD_IFCGEOM:BOOL=ON \
-DBUILD_GEOMSERVER:BOOL=OFF \
-DBOOST_USE_STATIC_LIBS:BOOL=OFF \
-DCITYJSON_SUPPORT:BOOL=OFF \
./cmake
ninja
+16 -14
View File
@@ -243,7 +243,9 @@ if WASM:
# Pyodide still in transition from `FLAGS` to `FLAGS_INIT`.
# `FLAGS_INIT` allow us to provide flags using environment variables
# and providing `FLAGS` directly would break pyodide toolchain.
WASM_CMAKE_IS_USING_INIT_VARS = get_pyodide_build_version() >= (0, 30, 8)
# NOTE: changes in pyodide-build currently got postponed:
# https://github.com/pyodide/pyodide-build/pull/249
WASM_CMAKE_IS_USING_INIT_VARS = get_pyodide_build_version() >= (99, 0, 0)
# pyodide provide empty `CXXFLAGS`, leading to issues using C++ files compiled with `-fexceptions`
# which is used by OCCT.
@@ -382,11 +384,13 @@ if MAC_CROSS_COMPILE_INTEL:
MAC_CROSS_COMPILE_INTEL_BJAM_ARGS = ["architecture=x86"]
MAC_CROSS_COMPILE_INTEL_CXX = "clang++ -arch x86_64"
MAC_CROSS_COMPILE_INTEL_CC = "clang -arch x86_64"
MAC_CROSS_COMPILE_INTEL_AUTOCONF_HOST_ARGS = ["--host=x86_64-apple-darwin"]
else:
MAC_CROSS_COMPILE_INTEL_ARGS = []
MAC_CROSS_COMPILE_INTEL_BJAM_ARGS = []
MAC_CROSS_COMPILE_INTEL_CXX = ""
MAC_CROSS_COMPILE_INTEL_CC = ""
MAC_CROSS_COMPILE_INTEL_AUTOCONF_HOST_ARGS = []
OFF_ON = ["OFF", "ON"]
BUILD_STATIC = "shared" not in flags
@@ -939,11 +943,9 @@ if "pcre2" in targets:
download_name=f"pcre2-{PCRE2_VERSION}.tar.bz2",
)
# An issue exists with swig-1.3 and python >= 3.2
# Therefore, build a recent copy from source
if "swig" in targets:
build_dependency(
name="swig",
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",
@@ -1195,7 +1197,9 @@ if "cgal" in targets:
OLD_CC = None
if MAC_CROSS_COMPILE_INTEL:
OLD_CC = os.environ.get("CC")
# Otherwise it's using arm64 `gcc` and fails to build gmp.
os.environ["CC"] = MAC_CROSS_COMPILE_INTEL_CC
gmp_args.extend(MAC_CROSS_COMPILE_INTEL_AUTOCONF_HOST_ARGS)
build_dependency(
name=f"gmp-{GMP_VERSION}",
@@ -1374,6 +1378,7 @@ if "cgal" in targets:
cmake_args_prefix_path.append(f"{DEPS_DIR}/install/cgal-{CGAL_VERSION}")
cmake_args_prefix_path.append(f"{DEPS_DIR}/install/gmp-{GMP_VERSION}")
cmake_args_prefix_path.append(f"{DEPS_DIR}/install/mpfr-{MPFR_VERSION}")
cmake_args.append(f"-DCGAL_WITH_GMPXX=Off")
if "occ" in targets and USE_OCCT:
cmake_args_prefix_path.append(f"{DEPS_DIR}/install/occt-{OCCT_VERSION}")
@@ -1426,6 +1431,9 @@ if "rocksdb" in targets:
]
)
if "swig" in targets:
cmake_args_prefix_path.append(f"{DEPS_DIR}/install/swig-{SWIG_VERSION}")
if not WASM and (not explicit_targets or {"IfcGeom", "IfcConvert", "IfcGeomServer"} & set(explicit_targets)):
logger.info("\rConfiguring executables...")
@@ -1441,17 +1449,14 @@ if not WASM and (not explicit_targets or {"IfcGeom", "IfcConvert", "IfcGeomServe
logger.info("\rBuilding executables... ")
run([make, f"-j{IFCOS_NUM_BUILD_PROCS}"], cwd=executables_dir)
run([make, f"-j{IFCOS_NUM_BUILD_PROCS}", "VERBOSE=1"], cwd=executables_dir)
run([make, "install/strip" if BUILD_CFG == "Release" else "install"], cwd=executables_dir)
if "IfcOpenShell-Python" in targets:
# On OSX the actual Python library is not linked against.
ADDITIONAL_ARGS = ""
if platform.system() == "Darwin":
ADDITIONAL_ARGS = "-Wl,-flat_namespace,-undefined,suppress"
if "wasm" in flags:
ADDITIONAL_ARGS = "-Wl,-undefined,suppress"
ADDITIONAL_ARGS = "-Wl,-undefined,dynamic_lookup"
# NOTE: We don't use `CXXFLAGS` for wrappers, so wrapper is compiled with different flags
# (e.g. ` -fdata-sections` is missing, which is set by default for executables)
@@ -1481,9 +1486,6 @@ if "IfcOpenShell-Python" in targets:
if os.path.exists(cache_path):
os.remove(cache_path)
prefix_paths: list[str] = []
if "swig" in targets:
prefix_paths.append(f"{DEPS_DIR}/install/swig")
if python_path:
# We couldn't just prefix PATH and have to provide all variables explicitly,
# see ifcwrap/cmake for the details.
@@ -1500,7 +1502,7 @@ if "IfcOpenShell-Python" in targets:
run_cmake(
"",
cmake_args
+ get_cmake_args_prefix_path(prefix_paths)
+ get_cmake_args_prefix_path()
+ [
*([f"-DPYTHON_EXECUTABLE={python_executable}"] if python_executable else []),
# Needed because pyodide is expecting setup.py to be in the root.
@@ -1516,7 +1518,7 @@ if "IfcOpenShell-Python" in targets:
logger.info(f"\rBuilding python {python_version} wrapper... ")
run([make, f"-j{IFCOS_NUM_BUILD_PROCS}", "ifcopenshell_wrapper"], cwd=python_dir)
run([make, f"-j{IFCOS_NUM_BUILD_PROCS}", "ifcopenshell_wrapper", "VERBOSE=1"], cwd=python_dir)
run([make, "install/local"], cwd=os.path.join(python_dir, "ifcwrap"))
if python_executable:
+1 -4
View File
@@ -9,10 +9,8 @@ extend-exclude = '''
|src/ifcopenshell-python/ifcopenshell/mvd/*
|src/ifcopenshell-python/ifcopenshell/simple_spf/*
|src/ifc2ca/templates/*
|src/ifcconvert/cityjson/*
|src/svgfill
|src/exterior-shell-extractor
|src/pyodide
'''
[tool.pyright]
@@ -21,12 +19,11 @@ disableBytesTypePromotions = true
reportUnnecessaryTypeIgnoreComment = true
# Define here general ruff settings,
# then they will be inherited projects .toml files.
# then they will be inherited by projects' .toml files.
# This allows using assuming different Python version for different projects.
[tool.ruff]
exclude = [
# Submodules.
"src/ifcconvert/cityjson",
"src/ifcopenshell-python/ifcopenshell/express",
"src/ifcopenshell-python/ifcopenshell/mvd",
"src/ifcopenshell-python/ifcopenshell/simple_spf",
+4 -1
View File
@@ -87,7 +87,7 @@ BLENDER_PLATFORM:=windows-x64
endif
# Current build commit hash.
OLD:=6924012
OLD:=e8eb5e4
.PHONY: bump
bump:
ifndef NEW
@@ -155,6 +155,9 @@ endif
cd build && . env/$(VENV_ACTIVATE) && $(PIP) download six --dest=./wheels
# Required by drawing module
cd build && . env/$(VENV_ACTIVATE) && $(PIP) download lxml $(PYPI_PLATFORM) --python-version $(PYPI_VERSION) --implementation $(PYPI_IMP) --only-binary=:all: --dest=./wheels
# Required by drawing module for markdown hyperlinks in annotations
cd build && . env/$(VENV_ACTIVATE) && $(PIP) download markdown-it-py --dest=./wheels
cd build && . env/$(VENV_ACTIVATE) && $(PIP) download mdurl --dest=./wheels
# Required by qto and drawing module
cd build && . env/$(VENV_ACTIVATE) && $(PIP) download shapely $(PYPI_PLATFORM) --python-version $(PYPI_VERSION) --implementation $(PYPI_IMP) --only-binary=:all: --dest=./wheels
# Required by the BIM type manager thumbnail generator
@@ -7,12 +7,6 @@
1.0,
1.0
],
"scene.render.bake_bias": 0.0010000000474974513,
"scene.render.bake_margin": 16,
"scene.render.bake_margin_type": "ADJACENT_FACES",
"scene.render.bake_samples": 256,
"scene.render.bake_type": "NORMALS",
"scene.render.bake_user_scale": 0.0,
"scene.render.border_max_x": 1.0,
"scene.render.border_max_y": 1.0,
"scene.render.border_min_x": 0.0,
@@ -54,11 +48,6 @@
"scene.render.stamp_note_text": "",
"scene.render.threads": 20,
"scene.render.threads_mode": "AUTO",
"scene.render.use_bake_clear": true,
"scene.render.use_bake_lores_mesh": false,
"scene.render.use_bake_multires": false,
"scene.render.use_bake_selected_to_active": false,
"scene.render.use_bake_user_scale": false,
"scene.render.use_border": false,
"scene.render.use_compositing": true,
"scene.render.use_crop_to_border": false,
@@ -253,12 +242,6 @@
1.0,
1.0
],
"scene.render.bake_bias": 0.0010000000474974513,
"scene.render.bake_margin": 16,
"scene.render.bake_margin_type": "ADJACENT_FACES",
"scene.render.bake_samples": 256,
"scene.render.bake_type": "NORMALS",
"scene.render.bake_user_scale": 0.0,
"scene.render.border_max_x": 1.0,
"scene.render.border_max_y": 1.0,
"scene.render.border_min_x": 0.0,
@@ -300,11 +283,6 @@
"scene.render.stamp_note_text": "",
"scene.render.threads": 20,
"scene.render.threads_mode": "AUTO",
"scene.render.use_bake_clear": true,
"scene.render.use_bake_lores_mesh": false,
"scene.render.use_bake_multires": false,
"scene.render.use_bake_selected_to_active": false,
"scene.render.use_bake_user_scale": false,
"scene.render.use_border": false,
"scene.render.use_compositing": true,
"scene.render.use_crop_to_border": false,
@@ -377,7 +355,7 @@
1.0,
1.0
],
"scene.display.shading.studio_light": "Default",
"scene.display.shading.studio_light": "forest.exr",
"scene.display.shading.studiolight_background_alpha": 0.0,
"scene.display.shading.studiolight_background_blur": 0.0,
"scene.display.shading.studiolight_intensity": 0.0,
@@ -503,18 +481,12 @@
"scene.eevee.shadow_ray_count": 1,
"scene.eevee.shadow_step_count": 6,
"scene.eevee.shadow_resolution_scale": 1.0,
"scene.render.bake_bias": 0.0010000000474974513,
"scene.render.bake_margin": 16,
"scene.render.bake_margin_type": "ADJACENT_FACES",
"scene.render.bake_samples": 256,
"scene.render.bake_type": "NORMALS",
"scene.render.bake_user_scale": 0.0,
"scene.render.border_max_x": 1.0,
"scene.render.border_max_y": 1.0,
"scene.render.border_min_x": 0.0,
"scene.render.border_min_y": 0.0,
"scene.render.dither_intensity": 1.0,
"scene.render.engine": "BLENDER_EEVEE_NEXT",
"scene.render.engine": "BLENDER_EEVEE",
"scene.render.film_transparent": false,
"scene.render.filter_size": 1.5,
"scene.render.fps": 24,
@@ -550,11 +522,6 @@
"scene.render.stamp_note_text": "",
"scene.render.threads": 20,
"scene.render.threads_mode": "AUTO",
"scene.render.use_bake_clear": true,
"scene.render.use_bake_lores_mesh": false,
"scene.render.use_bake_multires": false,
"scene.render.use_bake_selected_to_active": false,
"scene.render.use_bake_user_scale": false,
"scene.render.use_border": false,
"scene.render.use_compositing": true,
"scene.render.use_crop_to_border": false,
@@ -627,7 +594,7 @@
0.800000011920929,
0.800000011920929
],
"scene.display.shading.studio_light": "Default",
"scene.display.shading.studio_light": "forest.exr",
"scene.display.shading.studiolight_background_alpha": 0.0,
"scene.display.shading.studiolight_background_blur": 0.0,
"scene.display.shading.studiolight_intensity": 0.0,
+1
View File
@@ -25,6 +25,7 @@ import zipfile
import tempfile
import ifcopenshell
import ifcopenshell.api
import ifcopenshell.util.element
import ifcopenshell.util.placement
import ifcopenshell.util.unit
import bonsai.tool as tool
+2 -2
View File
@@ -28,6 +28,7 @@ import numpy as np
import numpy.typing as npt
import multiprocessing
import ifcopenshell
import ifcopenshell.api.pset
import ifcopenshell.geom
import ifcopenshell.ifcopenshell_wrapper as W
import ifcopenshell.util.unit
@@ -1237,8 +1238,7 @@ class IfcImporter:
if not pset:
return
if "Aggregate_Index" not in pset.keys():
ifcopenshell.api.run(
"pset.edit_pset",
ifcopenshell.api.pset.edit_pset(
self.file,
pset=self.file.by_id(pset["id"]),
properties={"Aggregate_Index": aggregate_index, "Name": name},
@@ -21,6 +21,7 @@ import ifcopenshell
import ifcopenshell.api
import ifcopenshell.api.group
import ifcopenshell.api.pset
import ifcopenshell.api.root
import ifcopenshell.util.element
import bonsai.tool as tool
import bonsai.core.aggregate as core
@@ -321,6 +322,21 @@ class BIM_OT_select_aggregate(bpy.types.Operator):
aggregate_obj.select_set(True)
bpy.context.view_layer.objects.active = aggregate_obj
# copy selection query to clipboard
result = ""
for aggregate in all_parts:
aggregate_class = aggregate.is_a()
aggregate_name = aggregate.Name or ""
query = f'parent = "{aggregate_name}"'
if not result:
result = query
else:
result += f" + {query}"
if result:
bpy.context.window_manager.clipboard = result
self.report({"INFO"}, f"({result}) was copied to the clipboard.")
self.one_level_deep = False # <-- forcibly reset
return {"FINISHED"}
+2 -2
View File
@@ -235,8 +235,8 @@ class DecoratorData:
cut_cache = {}
slice_cache = {}
fill_cache = {}
camera_location_checksum = None
camera_rotation_checksum = None
camera_location_checksum = ""
camera_rotation_checksum = ""
@classmethod
def clear_cache(cls):
@@ -19,6 +19,7 @@
import gpu
import bpy
import blf
import os
import math
import bmesh
import shapely
@@ -41,7 +42,7 @@ from bonsai.bim.module.drawing.helper import format_distance
from timeit import default_timer as timer
from functools import cache
from typing import Optional, Union
from collections.abc import Iterator
from collections.abc import Generator, Iterator
UNSPECIAL_ELEMENT_COLOR = (0.2, 0.2, 0.2, 1) # GREY
@@ -172,15 +173,11 @@ class BaseDecorator:
def get_camera_width_mm(self):
# Horrific prototype code to ensure bgl draws at drawing scales
# https://blender.stackexchange.com/questions/16493/is-there-a-way-to-fit-the-viewport-to-the-current-field-of-view
def is_landscape(render):
return render.resolution_x > render.resolution_y
camera = bpy.context.scene.camera
render = bpy.context.scene.render
if is_landscape(render):
camera_width_model = camera.data.ortho_scale
else:
camera_width_model = camera.data.ortho_scale / render.resolution_y * render.resolution_x
# Always use the camera's ortho_scale as the width in model space
camera_width_model = camera.data.ortho_scale
scale = tool.Drawing.get_scale_ratio(tool.Drawing.get_diagram_scale(camera)["Scale"])
camera_width_mm = scale * camera_width_model
@@ -189,29 +186,26 @@ class BaseDecorator:
def camera_zoom_to_factor(self, zoom):
return math.pow(((zoom / 50) + math.sqrt(2)) / 2, 2)
def get_splines(self, obj):
def get_splines(self, obj: bpy.types.Object) -> Generator[list[Vector]]:
"""Iterates through splines
Args:
obj: Blender object with Curve data
Yields:
verts: points of each spline, world coords
:param obj: Blender object with Curve data
:yield: points of each spline, world coords
"""
assert type(obj.data) is bpy.types.Curve
for spline in obj.data.splines:
spline_points = spline.bezier_points if spline.bezier_points else spline.points
if len(spline_points) < 2:
continue
yield [obj.matrix_world @ p.co for p in spline_points]
def get_path_geom(self, obj, topo=True):
def get_path_geom(self, obj: bpy.types.Object, topo: bool = True):
"""Parses path geometry into line segments
Args:
obj: Blender object with data of type Curve
topo: bool; if types of vertices are needed
:param obj: Blender object with data of type Curve
:param topo: if types of vertices are needed
Returns:
vertices: 3-tuples of coords
:return: vertices: 3-tuples of coords
indices: 2-tuples of each segment verices' indices
topology: types of vertices
0: internal
@@ -242,11 +236,9 @@ class BaseDecorator:
def get_mesh_geom(self, obj, check_mode=True):
"""Parses mesh geometry into line segments
Args:
obj: Blender object with data of type Mesh
:param obj: Blender object with data of type Mesh
Returns:
vertices: 3-tuples of coords
:return: vertices: 3-tuples of coords
indices: 2-tuples of each segment verices' indices
"""
if check_mode and obj.data.is_editmode:
@@ -377,11 +369,9 @@ class BaseDecorator:
):
"""Draw text label
Args:
pos: bottom-center
multiline: \n characters will be interpreted as line breaks
aligned and centered at segment middle
:param pos: bottom-center
:param multiline: ``\n`` characters will be interpreted as line breaks
aligned and centered at segment middle
NOTE: `blf.draw` seems to ignore the \n character, so we have to split the text ourselves
and use the `line_no` argument of `draw_label`
@@ -1641,6 +1631,13 @@ class CutDecorator:
if not context.scene.camera:
return
# Check if any viewport is in local view - skip decorations if so
for area in context.screen.areas:
if area.type == "VIEW_3D":
space = area.spaces.active
if isinstance(space, bpy.types.SpaceView3D) and space.local_view:
return # Don't draw decorations in local view
self.addon_prefs = tool.Blender.get_addon_preferences()
selected_elements_color = self.addon_prefs.decorator_color_selected
self.fallback_colour = (0.3, 0.3, 0.3, 1)
@@ -1731,21 +1728,38 @@ class CutDecorator:
def cache_camera_matrix(self):
obj = bpy.context.scene.camera
DecoratorData.camera_location_checksum = repr(np.array(obj.matrix_world.translation).tobytes())
DecoratorData.camera_rotation_checksum = repr(np.array(obj.matrix_world.to_3x3()).tobytes())
# Explicit `dtype` for Blender <5.0 compatibility.
DecoratorData.camera_location_checksum = repr(
np.array(obj.matrix_world.translation, dtype=np.float32).tobytes()
)
DecoratorData.camera_rotation_checksum = repr(np.array(obj.matrix_world.to_3x3(), dtype=np.float32).tobytes())
def is_camera_moved(self):
if not DecoratorData.camera_location_checksum:
self.cache_camera_matrix()
return True # Let's be conservative
obj = bpy.context.scene.camera
loc_check = np.frombuffer(eval(DecoratorData.camera_location_checksum))
loc_real = np.array(obj.matrix_world.translation).flatten()
# Handle both old float64 and new float32 checksums for version compatibility
loc_checksum_bytes = eval(DecoratorData.camera_location_checksum)
if len(loc_checksum_bytes) == 24: # Old format: 3 * 8 bytes (float64)
loc_check = np.frombuffer(loc_checksum_bytes, dtype=np.float64).astype(np.float32)
else: # New format: 3 * 4 bytes (float32)
loc_check = np.frombuffer(loc_checksum_bytes, dtype=np.float32)
loc_real = np.array(obj.matrix_world.translation, dtype=np.float32).flatten()
if not np.allclose(loc_check, loc_real, atol=1e-4): # 0.1 mm
self.cache_camera_matrix()
return True
rot_check = np.frombuffer(eval(DecoratorData.camera_rotation_checksum)).reshape(3, 3)
rot_real = np.array(obj.matrix_world.to_3x3())
# Handle both old float64 and new float32 checksums for version compatibility
rot_checksum_bytes = eval(DecoratorData.camera_rotation_checksum)
if len(rot_checksum_bytes) == 72: # Old format: 9 * 8 bytes (float64)
rot_check = np.frombuffer(rot_checksum_bytes, dtype=np.float64).astype(np.float32).reshape(3, 3)
else: # New format: 9 * 4 bytes (float32)
rot_check = np.frombuffer(rot_checksum_bytes, dtype=np.float32).reshape(3, 3)
rot_real = np.array(obj.matrix_world.to_3x3(), dtype=np.float32)
rot_dot = np.dot(rot_check, rot_real.T)
angle_rad = np.arccos(np.clip((np.trace(rot_dot) - 1) / 2, -1, 1))
if angle_rad > 0.0017453292519943296: # 0.1 degrees
@@ -1974,15 +1988,51 @@ class DecorationsHandler:
for object_type in ("SLOPE_ANGLE", "SLOPE_FRACTION", "SLOPE_PERCENT"):
self.decorators[object_type] = self.decorators["FALL"]
self.decorators["MULTI_SYMBOL"] = self.decorators["SYMBOL"]
prefs = tool.Blender.get_addon_preferences()
if drawing_font := prefs.doc.drawing_font:
# Try Blender addon font folder
drawing_font_path = tool.Blender.get_data_dir_path(Path("fonts") / drawing_font)
if drawing_font_path.is_file():
font_id = blf.load(drawing_font_path.__str__())
for decorator in self.decorators.values():
decorator.font_id = font_id
if not drawing_font_path.is_file():
# 2 — Fallback search: Windows font directories
# TODO - Linux
win_font_dirs = [
Path(os.environ.get("WINDIR", "C:\\Windows")) / "Fonts",
# Add any custom enterprise paths here if needed
]
found_font = None
for font_dir in win_font_dirs:
candidate = font_dir / drawing_font
if candidate.is_file():
found_font = candidate
break
if found_font:
drawing_font_path = found_font
else:
print(f"[BIM] Font '{drawing_font}' not found in addon or Windows fonts.")
return # Bail out without assigning fonts
font_id = blf.load(str(drawing_font_path))
for decorator in self.decorators.values():
decorator.font_id = font_id
def __call__(self, context):
# Check if any viewport is in local view - skip decorations if so
for area in context.screen.areas:
if area.type == "VIEW_3D":
space = area.spaces.active
if isinstance(space, bpy.types.SpaceView3D) and space.local_view:
return # Don't draw decorations in local view
# disable decorations when not in camera view
if context.region_data.view_perspective != "CAMERA":
return
if not DrawingsData.is_loaded:
DrawingsData.load()
+11 -12
View File
@@ -18,6 +18,7 @@
import bpy
import math
import ifcopenshell.util.element
import mathutils.geometry
import ifcopenshell
import ifcopenshell.util.unit
@@ -421,8 +422,8 @@ def ortho_view_frame(
Similar to `bpy.types.Camera.view_frame`
:arg camera: camera of drawing
:arg margin: margins, in scene units
:param camera: camera of drawing
:param margin: margins, in scene units
:return: (xmin, xmax, ymin, ymax, zmin, zmax) in local camera coordinates
"""
props = tool.Drawing.get_camera_props(camera)
@@ -443,8 +444,8 @@ def almost_zero(v):
def clip_segment(bounds, segm):
"""Clipping line segment to bounds
:arg bounds: (xmin, xmax, ymin, ymax)
:arg segm: 2 vertices of the segment
:param bounds: (xmin, xmax, ymin, ymax)
:param segm: 2 vertices of the segment
:return: 2 new vertices of segment or None if segment outside the bounding box
"""
# LiangBarsky algorithm
@@ -493,8 +494,8 @@ def clip_segment(bounds, segm):
def elevate_segment(bounds, segm):
"""Elevate line xy-perpendicular segment vertically
:arg bounds: (xmin, xmax, ymin, ymax)
:arg segm: 2 vertices of the segment
:param bounds: (xmin, xmax, ymin, ymax)
:param segm: 2 vertices of the segment
:return: 2 new vertices of segment or None if segment outside the bounding box
"""
_, _, ymin, ymax, zmin, _ = bounds
@@ -544,13 +545,11 @@ def add_newline_between_words(text: str, newline_at: int) -> str:
def get_relative_z(obj: bpy.types.Object, element, abs_z: float) -> float:
"""Return relative Z of an element, accounting for its spatial container.
Args:
obj: The Blender object representing the element.
element: The IFC entity for the object.
abs_z: The absolute Z value in world coordinates.
:param obj: The Blender object representing the element.
:param element: The IFC entity for the object.
:param abs_z: The absolute Z value in world coordinates.
Returns:
Relative Z value if the element is inside a spatial container,
:return: Relative Z value if the element is inside a spatial container,
otherwise the absolute Z.
"""
z = abs_z
@@ -932,6 +932,31 @@ class CreateDrawing(bpy.types.Operator):
return svg_path
# Add target_view and scale classes to the parent group from IFC data
if group is not None:
existing_classes = group.get("class", "").split()
# Add target_view class
if hasattr(self, "cprops") and getattr(self.cprops, "target_view", None):
target_view_class = tool.Drawing.canonicalise_class_name(str(self.cprops.target_view))
target_view_full_class = f"target-view-{target_view_class}"
if target_view_full_class not in existing_classes:
existing_classes.append(target_view_full_class)
# Add scale class from EPset_Drawing.Scale
drawing_pset = ifcopenshell.util.element.get_pset(self.camera_element, "EPset_Drawing")
if drawing_pset and drawing_pset.get("Scale"):
scale_value = drawing_pset["Scale"]
# Remove "1/" prefix if it exists
if isinstance(scale_value, str) and scale_value.startswith("1/"):
scale_value = scale_value[2:]
scale_class = tool.Drawing.canonicalise_class_name(str(scale_value))
scale_full_class = f"scale-{scale_class}"
if scale_full_class not in existing_classes:
existing_classes.append(scale_full_class)
group.set("class", " ".join(existing_classes))
if self.cprops.cut_mode == "BISECT":
self.remove_cut_linework(root)
self.generate_bisect_linework(context, root)
@@ -1287,11 +1312,6 @@ class CreateDrawing(bpy.types.Operator):
tool.Drawing.canonicalise_class_name(key) + "-" + tool.Drawing.canonicalise_class_name(str(value))
)
# ─── Target View ───────────────────────────────────────────
if getattr(self.cprops, "target_view", None):
target_view_class = tool.Drawing.canonicalise_class_name(str(self.cprops.target_view))
classes.append(f"target-view-{target_view_class}")
return classes
def is_manifold(self, obj) -> bool:
@@ -2701,21 +2721,59 @@ class ActivateDrawingStyle(bpy.types.Operator, tool.Ifc.Operator):
def set_raster_style(self, context: bpy.types.Context) -> None:
scene = context.scene # Do not remove. It is used in exec later
assert (space := tool.Blender.get_view3d_space()) # Do not remove. It is used in exec later
style = json.loads(self.drawing_style.raster_style)
style: dict[str, Any] = json.loads(self.drawing_style.raster_style)
VIEWPORT_SHADING_TYPE = "scene.display.shading.type"
def preprocess(path: str, value: Any) -> tuple[str, Any, bool, bool]:
warning = False
skip = False
# @25.05.12
if path == "scene.render.engine" and value == "BLENDER_EEVEE":
value = "BLENDER_EEVEE_NEXT"
BLENDER_5_REMOVED = (
"scene.render.bake_bias",
"scene.render.bake_margin",
"scene.render.bake_margin_type",
"scene.render.bake_samples",
"scene.render.bake_type",
"scene.render.bake_user_scale",
"scene.render.use_bake_clear",
"scene.render.use_bake_lores_mesh",
"scene.render.use_bake_multires",
"scene.render.use_bake_selected_to_active",
"scene.render.use_bake_user_scale",
)
# 25.11.07, Blender 5+
if path == "scene.render.engine" and value in ("BLENDER_EEVEE", "BLENDER_EEVEE_NEXT"):
if value == "BLENDER_EEVEE_NEXT":
print(
f"Warning: Value 'BLENDER_EEVEE_NEXT' is outdated for property '{path}' "
"since Blender 5.0 and should be replaced with 'BLENDER_EEVEE' in shading_styles.json."
)
warning = True
value = tool.Blender.get_eevee_name()
elif tool.Blender.BLENDER_5 and path in BLENDER_5_REMOVED:
print(
f"Warning: Value 'BLENDER_EEVEE' is outdated for property '{path}' "
"since Blender 4.2 and should be replaced with 'BLENDER_EEVEE_NEXT' in shading_styles.json."
f"Warning: Property '{path}' is removed "
"since Blender 5.0 and should be also removed from shading_styles.json."
)
warning = True
skip = True
# @25.11.11
elif (
path == "scene.display.shading.studio_light"
and value == "Default"
and style[VIEWPORT_SHADING_TYPE] in ("RENDERED", "MATERIAL")
):
value = "forest.exr"
print(
f"Warning: Value 'Default' for property '{path}' and "
f"'{VIEWPORT_SHADING_TYPE}' = '{style[VIEWPORT_SHADING_TYPE]}' is outdated "
"and should be replaced with 'forest.exr' in shading_styles.json."
)
warning = True
# @25.05.12
if path == "scene.display.shading.wireframe_color_type" and value == "MATERIAL":
elif path == "scene.display.shading.wireframe_color_type" and value == "MATERIAL":
value = "THEME"
print(
f"Warning: Value 'MATERIAL' is outdated for property '{path}' "
@@ -2749,7 +2807,16 @@ class ActivateDrawingStyle(bpy.types.Operator, tool.Ifc.Operator):
return path, value, warning, skip
for path, value in style.items():
paths = list(style.keys())
PRIORITY_PATHS = (
# `scene.display.shading.studio_light` values depend on `.type`
# so we got to set it first.
VIEWPORT_SHADING_TYPE,
)
paths.sort(key=lambda p: p not in PRIORITY_PATHS)
for path in paths:
value = style[path]
path, value, warning, skip = preprocess(path, value)
self.has_warnings_during_activation |= warning
@@ -3414,6 +3481,15 @@ class LoadDrawings(bpy.types.Operator, tool.Ifc.Operator):
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
# Operator is not accessible through UI if IFC project is not saved,
# but adding poll check to avoid using Drawings UI in scripts with unsaved project.
if tool.Ifc.get_path():
return True
cls.poll_message_set("IFC project is not saved.")
return False
def _execute(self, context):
core.load_drawings(tool.Drawing)
@@ -544,11 +544,10 @@ class Scheduler:
"""
Adds text to svg.
Args:
p_tags: list of cell's P tags from odt file
box_alignment: alignment of text in box
wrap_text: if True, text will be wrapped to fit in cell
cell_width: width of cell, used for wrapping text
:param p_tags: list of cell's P tags from odt file
:param box_alignment: alignment of text in box
:param wrap_text: if True, text will be wrapped to fit in cell
:param cell_width: width of cell, used for wrapping text
"""
text_lines = [str(p) for p in p_tags]
box_alignment_params = SvgWriter.get_box_alignment_parameters(box_alignment)
@@ -341,17 +341,29 @@ class SheetBuilder:
assert style_data is not None
text = ""
brackets_level = 0
selector_buffer = "" # Buffer to accumulate selectors across lines
for l in style_data:
if l == "{":
if brackets_level == 0:
cur_line = text.splitlines()[-1]
text = text[: -len(cur_line)]
# Get all accumulated selector text (may span multiple lines)
# Find where the last rule ended (after last }) or start of text
last_close = text.rfind("}")
if last_close == -1:
selector_text = text
text = ""
else:
selector_text = text[last_close + 1 :]
text = text[: last_close + 1]
# Process all selectors (split by comma)
css_selectors = []
# making sure cases like "text, tspan" will be
# converted to "text.prefix, tspan.prefix"
for css_selector in cur_line.split(","):
css_selector = f"{css_selector.strip()}.{prefix}"
css_selectors.append(css_selector)
for css_selector in selector_text.split(","):
css_selector = css_selector.strip()
if css_selector: # Only process non-empty selectors
css_selector = f"{css_selector}.{prefix}"
css_selectors.append(css_selector)
text += ", ".join(css_selectors) + " "
brackets_level += 1
elif l == "}":
@@ -202,6 +202,15 @@ class SvgWriter:
) -> Self:
self.precision = precision
self.decimal_places = decimal_places
# Sort annotations to ensure FILLAREA types are drawn first (at the bottom)
def sort_key(element):
predefined_type = ifcopenshell.util.element.get_predefined_type(element)
# Return 0 for FILLAREA to draw them first, 1 for everything else
return 0 if predefined_type == "FILL_AREA" else 1
annotations = sorted(annotations, key=sort_key)
for element in annotations:
obj = tool.Ifc.get_object(element)
if (
+1 -1
View File
@@ -116,7 +116,7 @@ class BIM_PT_camera(Panel):
# See #6686.
if (
props.has_underlay
and str(render.engine) == "BLENDER_EEVEE_NEXT"
and str(render.engine) == tool.Blender.get_eevee_name()
and ((megapixels := (render.resolution_x * render.resolution_y / 10**6)) > MEGAPIXELS_WARNING_THRESHOLD)
):
box = self.layout.box()
@@ -30,6 +30,7 @@ classes = (
operator.AddSweptAreaSolidItem,
operator.AssignRepresentationLayer,
operator.CopyRepresentation,
operator.DirectProfileEdit,
operator.DisableEditingRepresentationItemShapeAspect,
operator.DisableEditingRepresentationItemStyle,
operator.DisableEditingRepresentationItems,
@@ -160,6 +161,8 @@ def register():
addon_keymaps.append((km, kmi))
kmi = km.keymap_items.new("bim.override_mode_set_edit", "TAB", "PRESS")
addon_keymaps.append((km, kmi))
kmi = km.keymap_items.new("bim.direct_profile_edit", "SPACE", "PRESS")
addon_keymaps.append((km, kmi))
# Deletion.
kmi = km.keymap_items.new("bim.override_object_delete", "X", "PRESS")
addon_keymaps.append((km, kmi))
@@ -178,6 +181,8 @@ def register():
kmi = km.keymap_items.new("bim.override_mode_set_object", "TAB", "PRESS")
kmi.properties.should_save = True
addon_keymaps.append((km, kmi))
kmi = km.keymap_items.new("bim.direct_profile_edit", "SPACE", "PRESS")
addon_keymaps.append((km, kmi))
kmi = km.keymap_items.new("wm.call_menu", "P", "PRESS")
kmi.properties.name = ui.BIM_MT_hotkey_separate.bl_idname
addon_keymaps.append((km, kmi))
+390 -73
View File
@@ -42,6 +42,7 @@ import bonsai.core.geometry
import bonsai.core.geometry as core
import bonsai.core.aggregate
import bonsai.core.nest
import bonsai.core.spatial
import bonsai.core.style
import bonsai.core.root
import bonsai.core.drawing
@@ -809,9 +810,17 @@ class OverrideDelete(bpy.types.Operator):
@classmethod
def poll(cls, context):
return len(context.selected_objects) > 0
# Match `object.delete` poll for consistency.
# `object.delete` poll just checks for OBJECT mode.
poll = bpy.ops.object.delete.poll()
if poll:
return True
cls.poll_message_set("Only available in OBJECT mode")
return False
def execute(self, context):
if not context.selected_objects:
return {"FINISHED"}
# Deep magick from the dawn of time
if tool.Ifc.get() is None:
bpy.ops.object.delete(use_global=self.use_global, confirm=self.confirm)
@@ -823,6 +832,8 @@ class OverrideDelete(bpy.types.Operator):
return IfcStore.execute_ifc_operator(self, context)
def invoke(self, context, event):
if not context.selected_objects:
return {"FINISHED"}
assert context.window_manager
ifc_file = tool.Ifc.get()
if ifc_file is None:
@@ -1030,9 +1041,18 @@ class OverrideOutlinerDelete(bpy.types.Operator, tool.Ifc.Operator):
@classmethod
def poll(cls, context) -> bool:
return len(getattr(context, "selected_ids", [])) > 0
# Match `outliner.delete` poll for consistency.
# `outliner.delete` just checks `area.type` == `OUTLINER`.
poll = bpy.ops.outliner.delete.poll()
if poll:
return True
cls.poll_message_set("Only available from Outliner.")
return False
def execute(self, context):
if len(getattr(context, "selected_ids", [])) == 0:
return {"FINISHED"}
# In this override, we don't check self.hierarchy. This effectively
# makes Delete and Delete Hierarchy identical. This is on purpose, since
# non-hierarchical deletion may imply a whole bunch of potentially
@@ -1051,6 +1071,9 @@ class OverrideOutlinerDelete(bpy.types.Operator, tool.Ifc.Operator):
return {"FINISHED"}
def invoke(self, context, event):
if len(getattr(context, "selected_ids", [])) == 0:
return {"FINISHED"}
assert context.window_manager
ifc_file = tool.Ifc.get()
if ifc_file:
@@ -1121,10 +1144,18 @@ class OverrideDuplicateMove(bpy.types.Operator):
is_interactive: bpy.props.BoolProperty(name="Is Interactive", default=True)
@classmethod
def poll(cls, context):
return len(context.selected_objects) > 0
def poll(cls, context) -> bool:
# Match `object.duplicate_move` poll for consistency.
# `object.duplicate_move` poll checks for OBJECT mode.
poll = bpy.ops.object.duplicate_move.poll()
if poll:
return True
cls.poll_message_set("Only available in OBJECT mode")
return False
def execute(self, context):
if not context.selected_objects:
return {"FINISHED"}
return OverrideDuplicateMove.execute_duplicate_operator(self, context, linked=False)
def _execute(self, context):
@@ -1147,7 +1178,7 @@ class OverrideDuplicateMove(bpy.types.Operator):
@staticmethod
def execute_ifc_duplicate_operator(operator: bpy.types.Operator, context: bpy.types.Context, linked: bool = False):
objects_to_remove = set()
objects_to_remove: set[bpy.types.Object] = set()
for obj in context.selected_objects:
element = tool.Ifc.get_entity(obj)
@@ -1205,6 +1236,9 @@ class DuplicateMoveLinkedAggregateMacro(bpy.types.Macro):
bl_options = {"REGISTER", "UNDO"}
OldToNewType = dict[ifcopenshell.entity_instance, list[ifcopenshell.entity_instance]]
class DuplicateMoveLinkedAggregate(bpy.types.Operator):
bl_idname = "bim.object_duplicate_move_linked_aggregate"
bl_label = "IFC Duplicate and Move Linked Aggregate"
@@ -1227,9 +1261,9 @@ class DuplicateMoveLinkedAggregate(bpy.types.Operator):
self.new_active_obj = None
self.group_name = "BBIM_Linked_Aggregate"
self.pset_name = "BBIM_Linked_Aggregate"
old_to_new = {}
all_old_to_new = {} # Track all duplicates created
def select_objects_and_add_data(element):
def select_objects_and_add_data(element: ifcopenshell.entity_instance) -> None:
add_linked_aggregate_group(element)
obj = tool.Ifc.get_object(element)
obj.select_set(True)
@@ -1243,8 +1277,6 @@ class DuplicateMoveLinkedAggregate(bpy.types.Operator):
select_objects_and_add_data(part)
else:
index = add_linked_aggregate_pset(part, index)
# index += 1
obj = tool.Ifc.get_object(part)
obj.select_set(True)
@@ -1257,20 +1289,15 @@ class DuplicateMoveLinkedAggregate(bpy.types.Operator):
if not pset:
pset = ifcopenshell.api.pset.add_pset(ifc_file, product=element, name=self.pset_name)
ifcopenshell.api.pset.edit_pset(
ifc_file,
pset=pset,
properties=properties,
)
index += 1
else:
pass
return index
def add_linked_aggregate_group(element):
def add_linked_aggregate_group(element: ifcopenshell.entity_instance) -> None:
linked_aggregate_group = None
product_groups_name = [
r.RelatingGroup.Name
@@ -1283,25 +1310,21 @@ class DuplicateMoveLinkedAggregate(bpy.types.Operator):
linked_aggregate_group = ifcopenshell.api.group.add_group(ifc_file, name=self.group_name)
ifcopenshell.api.group.assign_group(ifc_file, products=[element], group=linked_aggregate_group)
def custom_incremental_naming_for_element_assembly(old_to_new):
def custom_incremental_naming_for_element_assembly(old_to_new: OldToNewType) -> None:
for new in old_to_new.values():
if new[0].is_a("IfcElementAssembly"):
group_elements: list[ifcopenshell.entity_instance] = next(
r.RelatedObjects
for r in getattr(new[0], "HasAssignments", []) or []
if r.is_a("IfcRelAssignsToGroup")
if "BBIM_Linked_Aggregate" in r.RelatingGroup.Name
)
pset = ifcopenshell.util.element.get_pset(new[0], "BBIM_Linked_Aggregate")
new_obj = tool.Ifc.get_object(new[0])
new_obj.name = pset["Name"] + "_" + str(pset["Aggregate_Index"])
new_obj.name = f"{pset['Name']}_{pset['Aggregate_Index']:02d}"
def get_max_index(parts):
psets = [ifcopenshell.util.element.get_pset(p, "BBIM_Linked_Aggregate") for p in parts]
index = [i["Index"] for i in psets if i]
if len(index) > 0:
index = max(index)
psets = [
ifcopenshell.util.element.get_pset(p, "BBIM_Linked_Aggregate")
for p in parts
if ifcopenshell.util.element.get_pset(p, "BBIM_Linked_Aggregate")
]
if psets:
index = max([i["Index"] for i in psets if i])
return index
else:
return 0
@@ -1315,23 +1338,28 @@ class DuplicateMoveLinkedAggregate(bpy.types.Operator):
if r.is_a("IfcRelAssignsToGroup")
if "BBIM_Linked_Aggregate" in r.RelatingGroup.Name
]
ifcopenshell.api.group.assign_group(ifc_file, group=linked_aggregate_group[0], products=new)
group_elements: list[ifcopenshell.entity_instance] = next(
r.RelatedObjects
for r in getattr(new[0], "HasAssignments", []) or []
if r.is_a("IfcRelAssignsToGroup")
if "BBIM_Linked_Aggregate" in r.RelatingGroup.Name
)
if linked_aggregate_group:
ifcopenshell.api.group.assign_group(ifc_file, group=linked_aggregate_group[0], products=new)
pset = ifcopenshell.util.element.get_pset(old, "BBIM_Linked_Aggregate")
if pset:
new_pset = ifcopenshell.api.pset.add_pset(ifc_file, product=new[0], name=self.pset_name)
if pset["Index"] == 0:
group_elements = []
if new[0].is_a("IfcElementAssembly"):
group_elements = next(
(
r.RelatedObjects
for r in getattr(new[0], "HasAssignments", []) or []
if r.is_a("IfcRelAssignsToGroup")
and "BBIM_Linked_Aggregate" in r.RelatingGroup.Name
),
[],
)
properties = {
"Index": pset["Index"],
"Name": pset["Name"],
"Aggregate_Index": len(group_elements) - 1,
"Aggregate_Index": len(group_elements) - 1 if group_elements else 0,
}
else:
properties = {"Index": pset["Index"]}
@@ -1351,43 +1379,73 @@ class DuplicateMoveLinkedAggregate(bpy.types.Operator):
location_diff = new_obj.location - base_obj_location
new_obj.location = context.scene.cursor.location + location_diff
if len(context.selected_objects) != 1:
# Process multiple selected aggregates
selected_aggregates = []
for obj in context.selected_objects:
selected_element = tool.Ifc.get_entity(obj)
if not selected_element:
continue
# Find the aggregate element
if selected_element.is_a("IfcElementAssembly"):
selected_aggregates.append(selected_element)
elif selected_element.Decomposes:
if selected_element.Decomposes[0].RelatingObject.is_a("IfcElementAssembly"):
selected_aggregates.append(selected_element.Decomposes[0].RelatingObject)
# Remove duplicates
selected_aggregates = list(set(selected_aggregates))
if not selected_aggregates:
self.report({"INFO"}, "No Linked Aggregates selected.")
return {"FINISHED"}
selected_obj = context.selected_objects[0]
selected_element = tool.Ifc.get_entity(selected_obj)
assert selected_element
if selected_element.is_a("IfcElementAssembly"):
pass
elif selected_element.Decomposes:
if selected_element.Decomposes[0].RelatingObject.is_a("IfcElementAssembly"):
selected_element = selected_element.Decomposes[0].RelatingObject
selected_obj = tool.Ifc.get_object(selected_element)
else:
self.report({"INFO"}, "Object is not part of a IfcElementAssembly.")
return {"FINISHED"}
select_objects_and_add_data(selected_element)
old_to_new = OverrideDuplicateMove.execute_ifc_duplicate_operator(self, context, linked=True)
tool.Root.recreate_aggregate(old_to_new)
copy_linked_aggregate_data(old_to_new)
custom_incremental_naming_for_element_assembly(old_to_new)
if location_from_3d_cursor:
get_location_from_3d_cursor(old_to_new, selected_element)
# Deselect all first
bpy.ops.object.select_all(action="DESELECT")
new_aggregate = old_to_new[selected_element][0]
tool.Blender.set_active_object(tool.Ifc.get_object(new_aggregate))
# Process each selected aggregate
for aggregate in selected_aggregates:
aggregate_obj = tool.Ifc.get_object(aggregate)
# Select and prepare the aggregate for duplication
select_objects_and_add_data(aggregate)
# Duplicate the aggregate
old_to_new = OverrideDuplicateMove.execute_ifc_duplicate_operator(self, context, linked=True)
all_old_to_new.update(old_to_new) # Collect all duplicates
# Recreate aggregate structure
tool.Root.recreate_aggregate(old_to_new)
# Copy linked aggregate data
copy_linked_aggregate_data(old_to_new)
# Apply custom naming
custom_incremental_naming_for_element_assembly(old_to_new)
# Apply 3D cursor location if requested
if location_from_3d_cursor:
get_location_from_3d_cursor(old_to_new, aggregate)
# Deselect for next iteration
bpy.ops.object.select_all(action="DESELECT")
# Select all newly created aggregates
for aggregate in selected_aggregates:
if aggregate in all_old_to_new:
new_aggregate = all_old_to_new[aggregate][0]
new_aggregate_obj = tool.Ifc.get_object(new_aggregate)
if new_aggregate_obj:
new_aggregate_obj.select_set(True)
# Set active object to the first new aggregate
if all_old_to_new:
first_new_aggregate = next(iter(all_old_to_new.values()))[0]
context.view_layer.objects.active = tool.Ifc.get_object(first_new_aggregate)
bonsai.bim.handler.refresh_ui_data()
return old_to_new
return all_old_to_new
class DuplicateLinkedAggregateTo3dCursor(bpy.types.Operator):
@@ -1423,7 +1481,7 @@ class RefreshLinkedAggregate(bpy.types.Operator, tool.Ifc.Operator):
self.pset_name = "BBIM_Linked_Aggregate"
refresh_start_time = time()
old_to_new = {}
original_data: dict[int, dict[int, str]] = {}
original_data: dict[int, dict[int, dict[str, Any]]] = {}
def delete_objects(element: ifcopenshell.entity_instance) -> None:
"""Remove IfcElementAssembly and it's parts."""
@@ -1469,8 +1527,8 @@ class RefreshLinkedAggregate(bpy.types.Operator, tool.Ifc.Operator):
)
tool.Blender.update_viewport()
def get_original_data(element: ifcopenshell.entity_instance) -> dict[int, dict[int, str]]:
group = next(
def get_original_data(element: ifcopenshell.entity_instance) -> dict[int, dict[int, dict[str, Any]]]:
group: int = next(
r.RelatingGroup
for r in getattr(element, "HasAssignments", []) or []
if r.is_a("IfcRelAssignsToGroup")
@@ -1478,8 +1536,8 @@ class RefreshLinkedAggregate(bpy.types.Operator, tool.Ifc.Operator):
).id()
original_data[group] = {}
pset = ifcopenshell.util.element.get_pset(element, self.pset_name)
index = pset["Index"]
pset: dict[str, Any] = ifcopenshell.util.element.get_pset(element, self.pset_name)
index: int = pset["Index"]
annotations = get_assignments(element)
container = ifcopenshell.util.element.get_container(element)
original_data[group][index] = {
@@ -1493,6 +1551,7 @@ class RefreshLinkedAggregate(bpy.types.Operator, tool.Ifc.Operator):
if parts:
for part in parts:
if part.is_a("IfcElementAssembly"):
# TODO: unused expression.
original_data | get_original_data(part)
else:
try:
@@ -2486,6 +2545,264 @@ class OverrideModeSetObject(bpy.types.Operator, tool.Ifc.Operator):
props.is_changing_mode = False
class DirectProfileEdit(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.direct_profile_edit"
bl_label = "IFC Direct Profile Edit"
bl_description = "Directly enter profile/axis edit mode, or exit back to object mode"
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
# Only allow in OBJECT or EDIT modes
if context.mode not in ("OBJECT", "EDIT_MESH", "EDIT_CURVE"):
return False
# Must have an active object
if not context.active_object:
return False
# In edit mode, we're good to go (we'll exit)
if context.mode in ("EDIT_MESH", "EDIT_CURVE"):
return True
# In object mode, check if it's an editable object type
obj = context.active_object
# Only mesh and curve objects are editable
if obj.type not in ("MESH", "CURVE"):
return False
return True
def _execute(self, context):
# Ensure we're only working with the active object
# Deselect all other objects if multiple are selected
active_obj = context.active_object
if active_obj and context.mode == "OBJECT":
if len(context.selected_objects) > 1:
self.report(
{"WARNING"},
"Currently Direct Profile Edit only works on one object at a time. Operating on active object only.",
)
bpy.ops.object.select_all(action="DESELECT")
active_obj.select_set(True)
context.view_layer.objects.active = active_obj
# If we're already in edit mode, exit to object mode and SAVE changes
if context.mode in ("EDIT_MESH", "EDIT_CURVE"):
return self.handle_exit_edit_mode(context)
# We're in object mode, try to enter profile/axis edit
return self.handle_enter_edit_mode(context)
def handle_exit_edit_mode(self, context):
"""Handle exiting from edit mode and saving changes."""
obj = context.active_object
if not obj:
return {"CANCELLED"}
# Check if we're editing a representation item
if tool.Geometry.is_representation_item(obj):
return self.exit_item_edit_mode(context, obj)
# Check if we're editing an element-level profile or axis
element = tool.Ifc.get_entity(obj)
if element and tool.Geometry.has_mesh_properties(obj.data):
return self.exit_element_edit_mode(context, obj, element)
# For other edit modes, use standard operator
return bpy.ops.bim.override_mode_set_object()
def exit_item_edit_mode(self, context, obj):
"""Exit from SweptAreaSolid item editing."""
try:
item = tool.Geometry.get_active_representation(obj)
if not item or not item.is_a("IfcSweptAreaSolid"):
return bpy.ops.bim.override_mode_set_object()
# Use the standard save workflow for items
from bonsai.bim.module.model.decorator import ProfileDecorator
ProfileDecorator.uninstall()
tool.Blender.toggle_edit_mode(context)
profile = tool.Model.export_profile(obj)
if not profile:
def msg(self, context):
self.layout.label(text="INVALID PROFILE")
context.window_manager.popup_menu(msg, title="Error", icon="ERROR")
tool.Blender.toggle_edit_mode(context)
ProfileDecorator.install(context)
return {"CANCELLED"}
old_profile = item.SweptArea
profile.ProfileName = old_profile.ProfileName
ifc_file = tool.Ifc.get()
for inverse in ifc_file.get_inverse(old_profile):
ifcopenshell.util.element.replace_attribute(inverse, old_profile, profile)
tool.Profile.replace_profile_in_profiles_ui(old_profile.id(), profile.id())
ifcopenshell.util.element.remove_deep2(ifc_file, old_profile)
props = tool.Geometry.get_geometry_props()
if props.representation_obj:
tool.Geometry.reload_representation(props.representation_obj)
tool.Geometry.disable_item_mode()
return {"FINISHED"}
except Exception as e:
self.report({"ERROR"}, f"Failed to save item changes: {str(e)}")
return {"CANCELLED"}
def exit_element_edit_mode(self, context, obj, element):
"""Exit from element-level profile or axis editing."""
try:
mesh_props = tool.Geometry.get_mesh_props(obj.data)
# Check if we're editing axis
if mesh_props.subshape_type == "AXIS":
return bpy.ops.bim.edit_extrusion_axis()
# Otherwise, we're editing a profile
body = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
if body:
body = ifcopenshell.util.representation.resolve_representation(body)
extrusion = tool.Model.get_extrusion(body)
if extrusion:
return bpy.ops.bim.edit_extrusion_profile()
except Exception as e:
self.report({"ERROR"}, f"Failed to save profile/axis changes: {str(e)}")
return {"CANCELLED"}
# Fallback to standard operator
return bpy.ops.bim.override_mode_set_object()
def handle_enter_edit_mode(self, context):
"""Handle entering edit mode from object mode."""
obj = context.active_object
if not obj:
return {"CANCELLED"}
# Check if object has mesh or curve data
if not hasattr(obj, "data") or not obj.data:
self.report({"INFO"}, "Object has no editable data")
return {"CANCELLED"}
element = tool.Ifc.get_entity(obj)
if not element:
self.report({"INFO"}, "Active object is not an IFC element")
return {"CANCELLED"}
# Check if this is a LAYER2 element (wall, railing, etc.)
try:
material_usage = tool.Model.get_usage_type(element)
except:
material_usage = None
if material_usage == "LAYER2":
self.report({"ERROR"}, "LAYER2 elements (walls, railings, etc.) cannot use profile editing.")
return {"CANCELLED"}
# Try to get representation
try:
representation = tool.Geometry.get_active_representation(obj)
except:
representation = None
if not representation:
self.report({"INFO"}, "Object has no active representation")
return {"CANCELLED"}
# Check for PROFILE usage (beams, columns, members) - should edit axis
if material_usage == "PROFILE":
try:
return bpy.ops.bim.enable_editing_extrusion_axis()
except Exception as e:
self.report({"ERROR"}, f"Failed to enable axis editing: {str(e)}")
return {"CANCELLED"}
# Check if this is an element with an extrusion profile (LAYER3, etc)
try:
body = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
if body:
body = ifcopenshell.util.representation.resolve_representation(body)
extrusion = tool.Model.get_extrusion(body)
if extrusion and not tool.Geometry.is_representation_item(obj):
return bpy.ops.bim.enable_editing_extrusion_profile()
except Exception as e:
self.report({"ERROR"}, f"Failed to enable profile editing: {str(e)}")
return {"CANCELLED"}
# Otherwise, try to enter item mode and edit SweptAreaSolid
return self.enter_item_edit_mode(context, obj)
def enter_item_edit_mode(self, context, obj):
"""Enter item mode for SweptAreaSolid editing."""
try:
props = tool.Geometry.get_geometry_props()
if not props.representation_obj:
bpy.ops.bim.import_representation_items()
props = tool.Geometry.get_geometry_props()
# Find the SweptAreaSolid item
swept_solid_obj = None
for item_obj_data in props.item_objs:
item_obj = item_obj_data.obj
if not item_obj:
continue
try:
item = tool.Geometry.get_active_representation(item_obj)
if item and item.is_a("IfcSweptAreaSolid"):
swept_solid_obj = item_obj
break
except:
continue
if not swept_solid_obj:
self.report({"INFO"}, "No editable SweptAreaSolid item found")
tool.Geometry.disable_item_mode()
return {"CANCELLED"}
# Select and activate the swept solid object
bpy.ops.object.select_all(action="DESELECT")
swept_solid_obj.select_set(True)
context.view_layer.objects.active = swept_solid_obj
# Import the profile to prepare for editing
item = tool.Geometry.get_active_representation(swept_solid_obj)
if item and item.is_a("IfcSweptAreaSolid"):
profile = item.SweptArea
res = tool.Model.import_profile(profile, obj=swept_solid_obj)
if res is None:
self.report({"ERROR"}, "Couldn't import profile for editing")
tool.Geometry.disable_item_mode()
return {"CANCELLED"}
tool.Ifc.link(item, swept_solid_obj.data)
tool.Blender.toggle_edit_mode(context)
from bonsai.bim.module.model.decorator import ProfileDecorator
ProfileDecorator.install(context)
if not bpy.app.background:
tool.Blender.set_viewport_tool("bim.cad_tool")
return {"FINISHED"}
except Exception as e:
self.report({"ERROR"}, f"Failed to enter item edit mode: {str(e)}")
try:
tool.Geometry.disable_item_mode()
except:
pass
return {"CANCELLED"}
return {"CANCELLED"}
class FlipObject(bpy.types.Operator):
bl_idname = "bim.flip_object"
bl_label = "Flip Object"
+5 -4
View File
@@ -96,11 +96,12 @@ def update_shadow_mode(self: "BIMSolarProperties", context: bpy.types.Context) -
sun_props = tool.Blender.get_sun_props()
assert sun_props
if sun_props.sun_object is None:
bpy.ops.object.light_add(type="SUN", radius=1, align="WORLD", location=(0, 0, 0), scale=(1, 1, 1))
bpy.ops.object.move_to_collection(collection_index=0)
sun_props.sun_object = bpy.context.active_object
light = bpy.data.lights.new(name="Sun", type="SUN")
sun = bpy.data.objects.new("Sun", light)
context.scene.collection.objects.link(sun)
sun_props.sun_object = sun
update_sun_path(self)
context.scene.render.engine = "BLENDER_EEVEE_NEXT"
context.scene.render.engine = tool.Blender.get_eevee_name()
assert context.scene.display
assert context.scene.display.shading
context.scene.display.shading.light = "FLAT"
+2 -18
View File
@@ -16,6 +16,7 @@
# You should have received a copy of the GNU General Public License
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
import calendar
import bpy
import bonsai.tool as tool
from typing import TYPE_CHECKING
@@ -192,24 +193,7 @@ class BIM_PT_solar(bpy.types.Panel):
row = self.layout.row()
row.prop(props, "year")
row = self.layout.row(align=True)
row.prop(
props,
"month",
text={
1: "January",
2: "February",
3: "March",
4: "April",
5: "May",
6: "June",
7: "July",
8: "August",
9: "September",
10: "October",
11: "November",
12: "December",
}[props.month],
)
row.prop(props, "month", text=calendar.month_name[props.month])
row.prop(props, "day")
row = self.layout.row(align=True)
+1 -1
View File
@@ -478,7 +478,7 @@ def update_door_modifier_bmesh(context: bpy.types.Context) -> None:
class BIM_OT_add_door(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "mesh.add_door"
bl_label = "Door"
bl_label = "Add Door"
bl_options = {"REGISTER", "UNDO"}
@classmethod
+131 -11
View File
@@ -348,24 +348,136 @@ class BIMArrayProperties(PropertyGroup):
def update_total_length_target(self: "BIMStairProperties", context: bpy.types.Context) -> None:
self["tread_run"] = self.total_length_target / (self.number_of_treads + 1)
"""Update tread_run when total_length_target changes"""
# Calculate available length for default treads
available_length = self.total_length_target
n_default_treads = self.number_of_treads + 1 # number of risers
# Subtract custom first tread if not locked and not zero
if not self.custom_tread_lock and self.custom_first_last_tread_run[0] != 0:
available_length -= self.custom_first_last_tread_run[0]
n_default_treads -= 1
# Subtract custom last tread if not locked and not zero
if not self.custom_tread_lock and self.custom_first_last_tread_run[1] != 0:
available_length -= self.custom_first_last_tread_run[1]
n_default_treads -= 1
# Calculate tread_run for remaining treads
if n_default_treads > 0:
self["tread_run"] = available_length / n_default_treads
else:
# All treads are custom, just use target length
self["tread_run"] = self.total_length_target / (self.number_of_treads + 1)
def update_tread_run(self: "BIMStairProperties", context: bpy.types.Context) -> None:
"""Update either number_of_treads or total_length_target when tread_run changes"""
if self.total_length_lock:
self["number_of_treads"] = int((self.total_length_target / self.tread_run) - 1)
# Calculate how much length custom treads take up
custom_length = 0
n_custom_treads = 0
if not self.custom_tread_lock:
if self.custom_first_last_tread_run[0] != 0:
custom_length += self.custom_first_last_tread_run[0]
n_custom_treads += 1
if self.custom_first_last_tread_run[1] != 0:
custom_length += self.custom_first_last_tread_run[1]
n_custom_treads += 1
# Calculate how many default treads fit in remaining space
available_length = self.total_length_target - custom_length
if self.tread_run > 0:
n_default_treads = available_length / self.tread_run
total_treads = n_default_treads + n_custom_treads
# number_of_treads = number_of_risers - 1
self["number_of_treads"] = int(total_treads - 1)
else:
self["total_length_target"] = (self.number_of_treads + 1) * self.tread_run
# Calculate total length from current settings
n_default_treads = self.number_of_treads + 1
total_length = 0
if not self.custom_tread_lock:
if self.custom_first_last_tread_run[0] != 0:
total_length += self.custom_first_last_tread_run[0]
n_default_treads -= 1
if self.custom_first_last_tread_run[1] != 0:
total_length += self.custom_first_last_tread_run[1]
n_default_treads -= 1
total_length += n_default_treads * self.tread_run
self["total_length_target"] = total_length
def update_number_of_treads(self: "BIMStairProperties", context: bpy.types.Context) -> None:
"""Update either tread_run or total_length_target when number_of_treads changes"""
if self.total_length_lock:
self["tread_run"] = self.total_length_target / (self.number_of_treads + 1)
# Calculate available length for default treads
available_length = self.total_length_target
n_default_treads = self.number_of_treads + 1
if not self.custom_tread_lock:
if self.custom_first_last_tread_run[0] != 0:
available_length -= self.custom_first_last_tread_run[0]
n_default_treads -= 1
if self.custom_first_last_tread_run[1] != 0:
available_length -= self.custom_first_last_tread_run[1]
n_default_treads -= 1
if n_default_treads > 0:
self["tread_run"] = available_length / n_default_treads
else:
self["tread_run"] = self.total_length_target / (self.number_of_treads + 1)
else:
self["total_length_target"] = (self.number_of_treads + 1) * self.tread_run
# Calculate total length from current settings
n_default_treads = self.number_of_treads + 1
total_length = 0
if not self.custom_tread_lock:
if self.custom_first_last_tread_run[0] != 0:
total_length += self.custom_first_last_tread_run[0]
n_default_treads -= 1
if self.custom_first_last_tread_run[1] != 0:
total_length += self.custom_first_last_tread_run[1]
n_default_treads -= 1
total_length += n_default_treads * self.tread_run
self["total_length_target"] = total_length
StairType = Literal["CONCRETE", "WOOD/STEEL", "GENERIC"]
def update_custom_first_last_tread_run(self: "BIMStairProperties", context: bpy.types.Context) -> None:
"""Update tread_run or total_length when custom treads change"""
if self.total_length_lock:
# Recalculate tread_run to maintain total length
available_length = self.total_length_target
n_default_treads = self.number_of_treads + 1
if not self.custom_tread_lock:
if self.custom_first_last_tread_run[0] != 0:
available_length -= self.custom_first_last_tread_run[0]
n_default_treads -= 1
if self.custom_first_last_tread_run[1] != 0:
available_length -= self.custom_first_last_tread_run[1]
n_default_treads -= 1
if n_default_treads > 0:
self["tread_run"] = available_length / n_default_treads
else:
# Recalculate total length
n_default_treads = self.number_of_treads + 1
total_length = 0
if not self.custom_tread_lock:
if self.custom_first_last_tread_run[0] != 0:
total_length += self.custom_first_last_tread_run[0]
n_default_treads -= 1
if self.custom_first_last_tread_run[1] != 0:
total_length += self.custom_first_last_tread_run[1]
n_default_treads -= 1
total_length += n_default_treads * self.tread_run
self["total_length_target"] = total_length
class BIMStairProperties(PropertyGroup):
@@ -408,7 +520,7 @@ class BIMStairProperties(PropertyGroup):
has_top_nib: bpy.props.BoolProperty(name="Has Top Nib", default=True)
stair_type: bpy.props.EnumProperty(
name="Stair Type",
items=[(i, i.replace("/", " / ").title(), "") for i in get_args(StairType)],
items=[(i, i.replace("/", " / ").title(), "") for i in get_args(tool.Model.StairType)],
default="CONCRETE",
update=validate_nosing_value,
)
@@ -425,6 +537,7 @@ class BIMStairProperties(PropertyGroup):
min=0,
unit="LENGTH",
size=2,
update=update_custom_first_last_tread_run, # Added update callback
)
nosing_length: bpy.props.FloatProperty(
name="Nosing Length",
@@ -492,16 +605,18 @@ class BIMStairProperties(PropertyGroup):
}
stair_kwargs.update(generic_props)
non_si_units_props = self.non_si_units_props
# If locked, use tread_run for both first and last treads
if self.custom_tread_lock:
stair_kwargs["custom_first_last_tread_run"] = (self.tread_run, self.tread_run)
non_si_units_props += ("custom_first_last_tread_run",)
stair_kwargs["custom_first_last_tread_run"] = (None, None)
else:
stair_kwargs["custom_first_last_tread_run"] = self.custom_first_last_tread_run
if not convert_to_project_units:
return stair_kwargs
stair_kwargs = tool.Model.convert_data_to_project_units(stair_kwargs, self.non_si_units_props)
stair_kwargs = tool.Model.convert_data_to_project_units(stair_kwargs, non_si_units_props)
return stair_kwargs
def get_props_kwargs_for_ifc_export(self, convert_to_project_units=False, stair_type=None):
@@ -513,14 +628,19 @@ class BIMStairProperties(PropertyGroup):
def set_props_kwargs_from_ifc_data(self, kwargs):
kwargs = tool.Model.convert_data_to_si_units(kwargs, self.non_si_units_props)
tread_run = kwargs.get("tread_run", 0.3)
# Determine lock state based on whether custom treads match tread_run
# If custom_tread_lock wasn't saved (old files), infer it from the data
if "custom_tread_lock" not in kwargs:
custom_treads = kwargs.get("custom_first_last_tread_run", (0.0, 0.0))
tread_run = kwargs.get("tread_run", 0.3)
# Lock is off if either custom tread differs from tread_run and is not 0
kwargs["custom_tread_lock"] = not any(ct != 0.0 and ct != tread_run for ct in custom_treads)
kwargs["custom_tread_lock"] = all(ct not in (0.0, tread_run) for ct in custom_treads)
if "custom_first_last_tread_run" in kwargs:
custom_treads = kwargs["custom_first_last_tread_run"]
custom_treads = [tread_run if v is None else v for v in custom_treads]
kwargs["custom_first_last_tread_run"] = custom_treads
for prop_name in kwargs:
setattr(self, prop_name, kwargs[prop_name])
@@ -106,6 +106,13 @@ def update_railing_modifier_ifc_data(context: bpy.types.Context) -> None:
model_representation = ifcopenshell.api.geometry.add_railing_representation(ifc_file, **representation_data)
tool.Model.replace_object_ifc_representation(body, obj, model_representation)
# recalculate normals to ensure correct shading
mesh = obj.data
if isinstance(mesh, bpy.types.Mesh):
bm = tool.Blender.get_bmesh_for_mesh(mesh, clean=False)
bmesh.ops.recalc_face_normals(bm, faces=bm.faces[:])
tool.Blender.apply_bmesh(mesh, bm)
elif props.railing_type == "FRAMELESS_PANEL":
tool.Model.add_body_representation(obj)
+1 -1
View File
@@ -131,7 +131,7 @@ def update_ifc_stair_props(obj: bpy.types.Object) -> None:
class BIM_OT_add_stair(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "mesh.add_stair"
bl_label = "Stair"
bl_label = "Add Stair"
bl_options = {"REGISTER", "UNDO"}
@classmethod
+7 -3
View File
@@ -17,7 +17,6 @@
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
import bpy
import bl_ui_utils.layout
import bonsai.bim
import bonsai.tool as tool
from bpy.types import Panel, Menu
@@ -36,7 +35,12 @@ from bonsai.bim.module.model.stair import regenerate_stair_mesh
from bonsai.bim.module.model.railing import update_railing_modifier_bmesh
from bonsai.bim.module.model.roof import update_roof_modifier_bmesh
from collections.abc import Iterable
from typing import Any
from typing import Any, TYPE_CHECKING
if TYPE_CHECKING or bpy.app.version >= (5, 0, 0):
import _bl_ui_utils.layout as bl_ui_utils_layout
else:
import bl_ui_utils.layout as bl_ui_utils_layout
class BIM_MT_type_manager_menu(bpy.types.Menu):
@@ -56,7 +60,7 @@ class BIM_MT_type_menu(bpy.types.Menu):
def draw(self, context):
props = tool.Model.get_model_props()
layout = self.layout
with bl_ui_utils.layout.operator_context(layout, "INVOKE_REGION_WIN"):
with bl_ui_utils_layout.operator_context(layout, "INVOKE_REGION_WIN"):
op = layout.operator("bim.rename_type", icon="GREASEPENCIL", text="Rename Type")
op.element = props.menu_relating_type_id
op = layout.operator("bim.select_type", icon="OBJECT_DATA")
+1 -1
View File
@@ -399,7 +399,7 @@ def update_window_modifier_bmesh(context: bpy.types.Context) -> None:
class BIM_OT_add_window(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "mesh.add_window"
bl_label = "Window"
bl_label = "Add Window"
bl_options = {"REGISTER", "UNDO"}
@classmethod
@@ -1322,6 +1322,20 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator):
if not bpy.context.selected_objects:
return
if self.active_material_usage == "LAYER2":
if len(bpy.context.selected_objects) != 2:
self.report(
{"ERROR"},
"Exactly two LAYER2 items (walls, railings, etc) must be selected to perform a merge.",
)
return
for item in bpy.context.selected_objects:
element = tool.Ifc.get_entity(item)
if tool.Model.get_usage_type(element) != "LAYER2":
self.report(
{"ERROR"},
"Both selected items must be LAYER2 (walls, railings, etc) to perform a merge.",
)
return
bpy.ops.bim.merge_wall()
else:
if len(bpy.context.selected_objects) == 1:
+17 -2
View File
@@ -859,8 +859,23 @@ def get_gross_top_area(obj: bpy.types.Object, angle: float = 45) -> float:
entity = ifc.by_guid(opening_id)
open_obj = tool.Ifc.get_object(entity)
assert isinstance(open_obj, bpy.types.Object)
opening_area += get_net_top_area(open_obj, angle=angle)
# Check if the opening has a Blender object representation
if open_obj is None:
# If no Blender object exists, create a temporary mesh from the IFC geometry
try:
mesh = get_gross_element_mesh(entity)
temp_obj = bpy.data.objects.new("TempOpening", mesh)
opening_area += get_net_top_area(temp_obj, angle=angle)
delete_obj(temp_obj)
delete_mesh(mesh)
except Exception as e:
# If we can't create geometry, skip this opening
print(f"Warning: Could not calculate opening area for {opening_id}: {e}")
continue
else:
# Opening has a Blender representation, use it directly
opening_area += get_net_top_area(open_obj, angle=angle)
else:
continue
+5 -2
View File
@@ -22,6 +22,7 @@ import ifcopenshell.api
import bonsai.tool as tool
import bonsai.core.qto as core
from bonsai.bim.module.qto import helper
from ifcopenshell.util.profiler import Profiler
class CalculateCircleRadius(bpy.types.Operator):
@@ -148,7 +149,8 @@ class CalculateSingleQuantity(bpy.types.Operator, tool.Ifc.Operator):
}
ifc_file = tool.Ifc.get()
results = ifc5d.qto.quantify(ifc_file, elements, rules)
with Profiler("Quantify function time:"):
results = ifc5d.qto.quantify(ifc_file, elements, rules)
ifc5d.qto.edit_qtos(ifc_file, results)
not_quantified_elements = elements - set(results.keys())
@@ -190,7 +192,8 @@ class PerformQuantityTakeOff(bpy.types.Operator, tool.Ifc.Operator):
) -> set[ifcopenshell.entity_instance]:
rules = ifc5d.qto.rules[rule]
ifc_file = tool.Ifc.get()
results = ifc5d.qto.quantify(ifc_file, elements, rules)
with Profiler("Quantify function time:"):
results = ifc5d.qto.quantify(ifc_file, elements, rules)
ifc5d.qto.edit_qtos(ifc_file, results)
not_quantified_elements = elements - set(results.keys())
return not_quantified_elements
@@ -21,6 +21,7 @@ import ifcopenshell.util.element
import bonsai.tool as tool
import bonsai.core.spatial as core
import bonsai.bim.handler
from typing import TYPE_CHECKING
class ReferenceStructure(bpy.types.Operator, tool.Ifc.Operator):
@@ -233,7 +234,7 @@ class RemoveContainer(bpy.types.Operator, tool.Ifc.Operator):
class CopyToContainer(bpy.types.Operator, tool.Ifc.Operator):
"""
Copies selected 3D elements in the viewport to the selected spatial containers
Copies selected 3D elements in the viewport to the container selected in Spatial Manager.
Example: bulk copy a wall to multiple storeys
@@ -242,9 +243,13 @@ class CopyToContainer(bpy.types.Operator, tool.Ifc.Operator):
Copying containers to other containers currently is not supported."""
bl_idname = "bim.copy_to_container"
bl_label = "Copy To Container"
bl_label = "Copy to Container"
bl_options = {"REGISTER", "UNDO"}
container: bpy.props.IntProperty()
container: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
if TYPE_CHECKING:
container: int
def _execute(self, context):
objs = tool.Spatial.get_selected_objects_without_containers()
@@ -455,7 +455,7 @@ class ActivateExternalStyle(bpy.types.Operator):
return {"CANCELLED"}
self.copy_material_attributes(db["data_block"], material)
if material.use_nodes:
if tool.Style.get_use_nodes(material):
tool.Blender.copy_node_graph(material, db["data_block"])
bpy.data.materials.remove(db["data_block"])
return {"FINISHED"}
+4 -3
View File
@@ -99,7 +99,8 @@ class BIMSystemProperties(PropertyGroup):
systems: CollectionProperty(name="Systems", type=System)
expanded_groups_json: StringProperty(name="Expanded Systems JSON", default="[]")
"""See `expanded_groups_json`. We name it "groups" for compatibility with Group UI code."""
active_system_index: IntProperty(name="Active System Index")
# Named not as `active_system_index` to match `BIMGroupProperties`.
active_group_index: IntProperty(name="Active System Index")
active_system_id: IntProperty(name="Active System Id")
edited_system_id: IntProperty(name="Edited System Id")
system_class: EnumProperty(items=get_system_class, name="Class")
@@ -113,7 +114,7 @@ class BIMSystemProperties(PropertyGroup):
is_adding: bool
systems: bpy.types.bpy_prop_collection_idprop[System]
expanded_groups_json: str
active_system_index: int
active_group_index: int
active_system_id: int
edited_system_id: int
system_class: str
@@ -121,7 +122,7 @@ class BIMSystemProperties(PropertyGroup):
@property
def active_system_ui_item(self) -> Union[System, None]:
return tool.Blender.get_active_uilist_element(self.systems, self.active_system_index)
return tool.Blender.get_active_uilist_element(self.systems, self.active_group_index)
def update_active_zone_index(self: "BIMZoneProperties", context: object) -> None:
+1 -1
View File
@@ -114,7 +114,7 @@ class BIM_PT_systems(Panel):
self.props,
"systems",
self.props,
"active_system_index",
"active_group_index",
)
if self.props.edited_system_id:
@@ -102,6 +102,19 @@ class AddOpening(bpy.types.Operator, tool.Ifc.Operator):
element_had_openings = tool.Geometry.has_openings(voided_element)
body_context = ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model", "Body")
if not element2:
# Apply scale transformation for vanilla Blender objects before creating IFC representation
# This ensures the opening geometry matches the visual scale of the object
if tool.Geometry.is_scaled(obj2):
if obj2.data:
# Make mesh data single-user if it's shared
if obj2.data.users > 1:
obj2.data = obj2.data.copy()
context_override = {}
context_override["object"] = context_override["active_object"] = obj2
context_override["selected_objects"] = context_override["selected_editable_objects"] = [obj2]
with bpy.context.temp_override(**context_override):
bpy.ops.object.transform_apply(location=False, rotation=False, scale=True)
element2 = bonsai.core.root.assign_class(
tool.Ifc,
tool.Collector,
+2 -2
View File
@@ -786,7 +786,7 @@ class BIM_OT_add_section_plane(bpy.types.Operator):
material = bpy.data.materials.get("Section Override")
if not material:
material = bpy.data.materials.new("Section Override")
material.use_nodes = True
tool.Style.set_use_nodes(material, True)
props = tool.Blender.get_bim_props()
if props.should_section_selected_objects:
@@ -809,7 +809,7 @@ class BIM_OT_add_section_plane(bpy.types.Operator):
def override_materials(self):
override = bpy.data.node_groups.get("Section Override")
for material in bpy.data.materials:
material.use_nodes = True
tool.Style.set_use_nodes(material, True)
if material.node_tree.nodes.get("Section Override"):
continue
# In EEVEE rendering engine, `blend_mode` is deprecated and replaced by `surface_render_method`
+6 -1
View File
@@ -20,7 +20,12 @@ import ifcopenshell
import ifcopenshell.util.pset
import bonsai.tool as tool
import bpy
import bpy_restrict_state
from typing import TYPE_CHECKING
if TYPE_CHECKING or bpy.app.version >= (5, 0, 0):
import _bpy_restrict_state as bpy_restrict_state
else:
import bpy_restrict_state
class IfcSchema:
+48 -10
View File
@@ -324,25 +324,51 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
bl_idname = tool.Blender.get_blender_addon_package_name()
svg2pdf_command: StringProperty(
name="SVG to PDF Command",
description='print sheet to pdf together with svg. Leave blank svg is just created, E.g. [["path to application eg. /Applications/Inkscape.app/Contents/MacOS/inkscape", "svg", "-o", "pdf"]]',
description=(
"print sheet to pdf together with svg. Leave blank svg is just created, "
'E.g. [["path to application eg. /Applications/Inkscape.app/Contents/MacOS/inkscape", "svg", "-o", "pdf"]]'
'\n `"svg"`, `"pdf"` will be replaced automatically.'
),
)
svg2dxf_command: StringProperty(
name="SVG to DXF Command",
description='E.g. [["inkscape", "svg", "-o", "eps"], ["pstoedit", "-dt", "-f", "dxf:-polyaslines -mm", "eps", "dxf", "-psarg", "-dNOSAFER"]]',
description=(
'E.g. `[["inkscape", "svg", "-o", "eps"], '
'["pstoedit", "-dt", "-f", "dxf:-polyaslines -mm", "eps", "dxf", "-psarg", "-dNOSAFER"]]`'
'\n `"svg"`, `"eps"`, `"dxf"` will be replaced automatically.'
),
)
svg_command: StringProperty(
name="SVG Command",
description='Software to open generated drawing and sheets. Leave blank system default for .svg is used E.g. [["path to application eg. /Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge", "path"]]',
description=(
"Software to open generated drawing and sheets. Leave blank system default for .svg is used "
'E.g. [["path to application eg. /Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge", "path"]]'
'\n `"path"` will be replaced with the path to the generated .svg file.'
),
)
layout_svg_command: StringProperty(
name="Layout SVG Command",
description='Software to open layouts, before generated. Leave blank system default for .svg is used E.g. [["path to application eg. /Applications/Inkscape.app/Contents/MacOS/inkscape", "path"]]',
description=(
"Software to open layouts, before generated. Leave blank system default for .svg is used. "
'E.g. `[["path to application eg. /Applications/Inkscape.app/Contents/MacOS/inkscape", "path"]]`'
'\n `"path"` will be replaced with the path to the generated .svg file.'
),
)
pdf_command: StringProperty(
name="PDF Command",
description='Software to open .pdf, leave blank uses system default. E.g. [["path to application eg. /Applications/Inkscape.app/Contents/MacOS/inkscape", "path"]]',
description=(
"Software to open .pdf, leave blank uses system default. "
'E.g. `[["path to application eg. /Applications/Inkscape.app/Contents/MacOS/inkscape", "path"]]`'
'\n `"path"` will be replaced with the path to the generated .pdf file.'
),
)
spreadsheet_command: StringProperty(
name="Spreadsheet Command",
description=(
'E.g. [["libreoffice", "path"]]'
'\n `"path"` will be replaced with the path to the generated spreadsheet file.'
),
)
spreadsheet_command: StringProperty(name="Spreadsheet Command", description='E.g. [["libreoffice", "path"]]')
should_hide_empty_props: BoolProperty(
name="Hide Empty Properties",
default=True,
@@ -351,12 +377,18 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
should_setup_workspace: BoolProperty(
name="Setup Workspace Layout for BIM",
default=True,
description="If enabled, this will add a default workspace dedicated to working with BIM models.\nIt is recommended to keep this `Enabled`",
description=(
"If enabled, this will add a default workspace dedicated to working with BIM models.\n"
"It is recommended to keep this `Enabled`"
),
)
activate_workspace: BoolProperty(
name="Activate BIM Workspace on Startup",
default=True,
description="If enabled, this will automatically activate the BIM workspace when opening a project.\nIt is recommended to keep this `Enabled`",
description=(
"If enabled, this will automatically activate the BIM workspace when opening a project.\n"
"It is recommended to keep this `Enabled`"
),
)
should_setup_toolbar: BoolProperty(
name="Always Show Toolbar In 3D Viewport",
@@ -366,7 +398,9 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
should_use_snap: BoolProperty(
name="Enable Snapping on Startup",
default=True,
description="If enabled, snapping will be enabled on new sessions.\nIt is recommended to keep this `Enabled`",
description=(
"If enabled, snapping will be enabled on new sessions.\n" "It is recommended to keep this `Enabled`"
),
)
should_play_chaching_sound: BoolProperty(name="Play A Cha-Ching Sound When Project Costs Updates", default=False)
tmp_dir: StringProperty(
@@ -428,7 +462,10 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
max=100,
subtype="PERCENTAGE",
name="Non-Openings Opacity",
description="When modifying openings, other elements of the model will display with some transparency.\n0 is fully transparent and 100 is fully opaque",
description=(
"When modifying openings, other elements of the model will display with some transparency.\n"
"0 is fully transparent and 100 is fully opaque"
),
)
bsdd_load_preview_dictionaries: BoolProperty(
@@ -498,6 +535,7 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
should_setup_workspace: bool
activate_workspace: bool
should_setup_toolbar: bool
should_use_snap: bool
should_play_chaching_sound: bool
tmp_dir: str
decorations_colour: tuple[float, float, float, float]
+11 -3
View File
@@ -60,10 +60,18 @@ def edit_assigned_product(
) -> None:
element = ifc.get_entity(obj)
assert element
existing_product = drawing.get_assigned_product(element)
if existing_product != product:
if existing_product:
# Temporarily accomodate existing bug and check for multiple products.
existing_products = drawing.get_assigned_product_workaround(element)
if existing_products != [product]:
if product in existing_products:
assert product is not None
existing_products.remove(product)
for existing_product in existing_products:
ifc.run("drawing.unassign_product", relating_product=existing_product, related_object=element)
if product:
ifc.run("drawing.assign_product", relating_product=product, related_object=element)
+5 -4
View File
@@ -147,7 +147,7 @@ def remove_representation(
assert element
element_type = geometry.get_element_type(element)
data = None
has_switched_from_data = False
data_removed_by_switch_representation = False
if element_type and (geometry.is_mapped_representation(representation) or geometry.is_type_product(element)):
representation = geometry.resolve_mapped_representation(representation)
data = geometry.get_representation_data(representation)
@@ -155,7 +155,7 @@ def remove_representation(
for element in geometry.get_elements_of_type(element_type):
obj = ifc.get_object(element)
if obj:
has_switched_from_data = True
data_removed_by_switch_representation = True
geometry.switch_from_representation(obj, representation)
obj = ifc.get_object(element_type)
if obj:
@@ -164,12 +164,13 @@ def remove_representation(
else:
data = geometry.get_representation_data(representation)
if data and geometry.has_data_users(data):
has_switched_from_data = True
data_removed_by_switch_representation = True
geometry.switch_from_representation(obj, representation)
ifc.run("geometry.unassign_representation", product=element, representation=representation)
ifc.run("geometry.remove_representation", representation=representation)
if data and not has_switched_from_data:
if data and not data_removed_by_switch_representation:
geometry.delete_data(data)
+1 -2
View File
@@ -70,8 +70,7 @@ def assign_class(
ifc_representation_class: Optional[str] = None,
) -> Optional[ifcopenshell.entity_instance]:
"""
Args:
context: is not optional if `should_add_representation` is True
:param context: is not optional if ``should_add_representation`` is True
TODO: Do NOT use should_add_representation. Because it internally calls
geometry.add_representation which is 1,000 lines of Blender -> IFC magic.
+15 -2
View File
@@ -308,6 +308,7 @@ class Document:
class Drawing:
def activate_drawing(cls, camera): pass
def add_literal(cls, **attributes): pass
def clear_annotation_relationships(cls, drawing): pass
def copy_representation(cls, source, dest): pass
def create_annotation_context(cls, target_view, object_type=None): pass
def create_annotation_object(cls, drawing, object_type): pass
@@ -342,6 +343,7 @@ class Drawing:
def get_annotation_context(cls, target_view, object_type=None): pass
def get_annotation_representation(cls, element_type): pass
def get_assigned_product(cls, element): pass
def get_assigned_product_workaround(cls, element): pass
def get_body_context(cls): pass
def get_default_drawing_path(cls, name): pass
def get_default_drawing_resource_path(cls, resource): pass
@@ -485,6 +487,19 @@ class Georeference:
def xyz2enh(cls, coordinates): pass
@interface
class Group:
def get_group_props(cls): pass
def get_groups_data(cls, group_type): pass
def import_groups(cls, group_type): pass
def enable_group_editing_ui(cls) : pass
def disable_group_editing_ui(cls): pass
def disable_editing_group(cls): pass
def set_active_group_to_edit(cls, group): pass
def toggle_group(cls, group, group_type, option): pass
def update_uilist_index(cls, group_type): pass
@interface
class Ifc:
def get(cls): pass
@@ -854,8 +869,6 @@ class Sequence:
def get_animation_product_frames(cls, work_schedule, settings): pass
def get_animation_settings(cls): pass
def get_checked_tasks(cls): pass
def get_direct_nested_tasks(cls, task):pass
def get_direct_task_outputs(cls, task): pass
def get_finish_date(cls): pass
def get_highlighted_task(cls): pass
def get_lag_time_attributes(cls): pass
+19 -9
View File
@@ -100,6 +100,7 @@ class Blender(bonsai.core.tool.Blender):
- (identifier, name, description, icon, number)
"""
BLENDER_ENUM_ITEMS = Iterable[BLENDER_ENUM_ITEM]
BLENDER_5 = bpy.app.version >= (5, 0, 0)
@classmethod
def activate_camera(cls, obj: bpy.types.Object) -> None:
@@ -416,7 +417,7 @@ class Blender(bonsai.core.tool.Blender):
cls, blender_material: bpy.types.Material, node_type: str, kwargs: Optional[dict] = {}
) -> Union[bpy.types.ShaderNode, None]:
"""returns first node from the `blender_material` shader graph with type `node_type`"""
if not blender_material.use_nodes:
if not tool.Style.get_use_nodes(blender_material):
return
nodes = blender_material.node_tree.nodes
for node in nodes:
@@ -709,7 +710,7 @@ class Blender(bonsai.core.tool.Blender):
new_selected_objects = [o for o in selected_objects if cls.is_valid_data_block(o) and o in view_layer_objects]
if active_object and (not cls.is_valid_data_block(active_object) or active_object not in view_layer_objects):
if active_object and not cls.is_valid_data_block(active_object):
active_object = None
return context, active_object, new_selected_objects
@@ -1064,7 +1065,7 @@ class Blender(bonsai.core.tool.Blender):
"""Tries to validate the current BIM modifier parameters for the active object
Goes into path editing mode if the modifier supports it
Returns True if an action was taken, False otherwise
:return: True if an action was taken, False otherwise
"""
if cls.is_roof(element):
if cls.is_editing_roof_parameters(obj):
@@ -1088,7 +1089,7 @@ class Blender(bonsai.core.tool.Blender):
def try_canceling_editing_modifier_parameters_or_path(cls, obj: bpy.types.Object) -> bool:
"""Tries to cancel the current BIM modifier parameters or path edition for the active object
Returns True if an action was taken, False otherwise
:return: True if an action was taken, False otherwise
"""
if cls.is_editing_railing_path(obj):
bpy.ops.bim.cancel_editing_railing_path()
@@ -1671,12 +1672,10 @@ class Blender(bonsai.core.tool.Blender):
- "user_interface.wcol_menu.text" (Menu Text)
- "user_interface.wcol_menu.text_sel" (Menu Text Selected)
Args:
color_path (str, optional): The attribute path relative to bpy.context.preferences.themes[0].
threshold (float, optional): The RGB sum threshold for determining dark mode. Default is 1.671.
:param color_path: The attribute path relative to bpy.context.preferences.themes[0].
:param threshold: The RGB sum threshold for determining dark mode. Default is 1.671.
Returns:
str: 'dm' (dark mode) if the RGB sum is > threshold, otherwise 'lm' (light mode).
:return: 'dm' (dark mode) if the RGB sum is > threshold, otherwise 'lm' (light mode).
"""
full_path = f"bpy.context.preferences.themes[0].{color_path}"
@@ -2083,3 +2082,14 @@ class Blender(bonsai.core.tool.Blender):
assert bpy.context.preferences
if props_updated and bpy.context.preferences.use_preferences_save:
bpy.ops.wm.save_userpref()
@classmethod
def get_eevee_name(cls) -> Literal["BLENDER_EEVEE"] | Literal["BLENDER_EEVEE_NEXT"]:
"""Convenience method to get correct eevee render engine name in multiple Blender versions.
In Blender 4.2 eevee was renamed to "eevee next".
In Blender 5 eevee next is now just "eevee" again.
"""
if cls.BLENDER_5:
return "BLENDER_EEVEE"
return "BLENDER_EEVEE_NEXT"
+208 -20
View File
@@ -33,6 +33,7 @@ import sys
import bpy
import math
import bmesh
import numpy as np
import mathutils.geometry
from mathutils import Vector, Matrix, geometry
import itertools
@@ -188,6 +189,33 @@ class Cad:
"""
return geometry.intersect_line_plane(v1, v2, plane_co, plane_no)
def intersect_edge_plane_v2(v1, v2, plane_co, plane_no, eps=1e-9):
"""
Numpy version of intersect_edge_plane
> takes an edges as two vector, and a plane as origin point and normal
< return the intersection point or None
"""
# References: https://rosettacode.org/wiki/Find_the_intersection_of_a_line_with_a_plane#Python,
# https://stackoverflow.com/a/18543221
p0 = np.array((v1.x, v1.y, v1.z), dtype=np.float64)
p1 = np.array((v2.x, v2.y, v2.z), dtype=np.float64)
pc = np.array((plane_co.x, plane_co.y, plane_co.z), dtype=np.float64)
n = np.array((plane_no.x, plane_no.y, plane_no.z), dtype=np.float64)
u = p1 - p0
dot = np.dot(u, n)
if abs(dot) < eps:
# Line is parallel to plane (no intersection or lies in plane)
return None
w = pc - p0
fac = np.dot(w, n) / dot
p = p0 + fac * u
return Vector(p)
@classmethod
def intersect_edges(
cls, edge1: tuple[Vector, Vector], edge2: tuple[Vector, Vector]
@@ -215,47 +243,50 @@ class Cad:
def intersect_edges_v2(cls, edge1, edge2):
"""
Calculate the closest points on two line segments.
Note: This function doesn't use intersect_line_line
Note: This function doesn't use intersect_line_line and uses Numpy for calculations
> edge1: tuple of two vectors (v1, v2) representing the first segment
> edge2: tuple of two vectors (v3, v4) representing the second segment
< returns: tuple of two vectors (C1, C2) or (None, None) if lines are parallel
"""
# This function seems to work better then intersect_line_line
# This function seems to work better than intersect_line_line
# in orthogonal view
# https://en.wikipedia.org/wiki/Skew_lines#Nearest_points
is_2d = False
# Starting and ending points
P1, P1_end = edge1
P2, P2_end = edge2
if len(P1) == 2:
p1, p1_end = edge1
p2, p2_end = edge2
if len(p1) == 2:
is_2d = True
P1, P1_end = P1.to_3d(), P1_end.to_3d()
P2, P2_end = P2.to_3d(), P2_end.to_3d()
p1, p1_end = p1.to_3d(), p1_end.to_3d()
p2, p2_end = p2.to_3d(), p2_end.to_3d()
p1 = np.array((p1.x, p1.y, p1.z), dtype=np.float64)
p1_end = np.array((p1_end.x, p1_end.y, p1_end.z), dtype=np.float64)
p2 = np.array((p2.x, p2.y, p2.z), dtype=np.float64)
p2_end = np.array((p2_end.x, p2_end.y, p2_end.z), dtype=np.float64)
# Directions
d1 = (P1_end - P1).normalized()
d2 = (P2_end - P2).normalized()
d1 = p1_end - p1
d2 = p2_end - p2
d1 /= np.linalg.norm(d1) or 1 # equivalent of Vector.normalized() or Vector / Vector.length
d2 /= np.linalg.norm(d2) or 1
n = d1.cross(d2)
n = np.cross(d1, d2)
# if n is zero, lines are parallel
if abs(n.length) < 1e-6:
if abs(np.linalg.norm(n)) < 1e-6:
return None, None
n2 = d2.cross(n)
C1 = P1 + ((P2 - P1).dot(n2) / (d1.dot(n2))) * d1
n1 = d1.cross(n)
C2 = P2 + ((P1 - P2).dot(n1) / (d2.dot(n1))) * d2
n2 = np.cross(d2, n)
c1 = p1 + (np.dot((p2 - p1), n2) / (np.dot(d1, n2))) * d1
n1 = np.cross(d1, n)
c2 = p2 + (np.dot((p1 - p2), n1) / (np.dot(d2, n1))) * d2
if is_2d:
return C1.to_2d(), C2.to_2d()
return Vector(c1[:2].copy()), Vector(c2[:2].copy())
else:
return C1, C2
return Vector(c1), Vector(c2)
@classmethod
def get_intersection(cls, edge1, edge2):
@@ -807,3 +838,160 @@ class Cad:
v1 = v2
return new_verts
@classmethod
def region_2d_to_vector_3d_np(cls, region: bpy.types.Region, rv3d: bpy.types.RegionView3d, coord: Vector) -> Vector:
"""
Numpy version of view3d_utils.region_2d_to_vector_3d
Return a direction vector from the viewport at the specific 2d region
coordinate.
> region: region of the 3D viewport, typically bpy.context.region.
> rv3d: 3D region data, typically bpy.context.space_data.region_3d.
> coord: 2d coordinates relative to the region:
(event.mouse_region_x, event.mouse_region_y) for example.
< returns a normalized 3d vector.
"""
view_m = np.array(rv3d.view_matrix)
window_m = np.array(rv3d.window_matrix)
viewinv = np.linalg.inv(view_m)
if rv3d.is_perspective:
# For better precision with large numbers, avoid using rv3d.perspective_matrix. See https://github.com/IfcOpenShell/IfcOpenShell/issues/7046
# Calculate it with view_matrix and window_matrix as numpy arrays.
pers_m = window_m @ view_m
persinv = np.linalg.inv(pers_m)
out = np.array(
[(2.0 * coord[0] / region.width) - 1.0, (2.0 * coord[1] / region.height) - 1.0, -0.5, 1.0],
dtype=np.float64,
)
w = out[:3].dot(persinv[3, :3]) + persinv[3, 3]
viewinv_translation = viewinv[:3, 3]
view_vector = (persinv.dot(out)[:3] / w) - viewinv_translation
else:
view_vector = -viewinv[:3, 2].copy() # -Z column
view_vector /= np.linalg.norm(view_vector) or 1 # equivalent to Vector.normalized()
return Vector(view_vector)
@classmethod
def region_2d_to_location_3d_np(
cls, region: bpy.types.Region, rv3d: bpy.types.RegionView3d, coord: Vector, depth_location: Vector
) -> Vector:
"""
Numpy version of view3d_utils.region_2d_to_location_3d
Return a 3d location from the region relative 2d coords, aligned with
*depth_location*.
> region: region of the 3D viewport, typically bpy.context.region.
> rv3d: 3D region data, typically bpy.context.space_data.region_3d.
> coord: 2d coordinates relative to the region:
(event.mouse_region_x, event.mouse_region_y) for example.
< returns a normalized 3d vector.
"""
coord_vec = cls.region_2d_to_vector_3d_np(region, rv3d, coord)
depth_location = np.array([depth_location[0], depth_location[1], depth_location[2]], dtype=np.float64)
origin_start = cls.region_2d_to_origin_3d_np(region, rv3d, coord)
origin_end = origin_start + coord_vec
if rv3d.is_perspective:
viewinv = np.linalg.inv(rv3d.view_matrix)
view_vec = viewinv[:3, 2].copy()
return cls.intersect_edge_plane_v2(
Vector(origin_start), Vector(origin_end), Vector(depth_location), Vector(view_vec)
)
else:
return cls.point_on_edge(
Vector(depth_location),
(Vector(origin_start), Vector(origin_end)),
)
@classmethod
def region_2d_to_origin_3d_np(
cls, region: bpy.types.Region, rv3d: bpy.types.RegionView3d, coord: Vector, *, clamp: float = None
) -> Vector:
"""
Numpy version of view3d_utils.region_2d_to_origin_3d
Return the 3d view origin from the region relative 2d coords.
.. note::
Orthographic views have a less obvious origin,
the far clip is used to define the viewport near/far extents.
Since far clip can be a very large value,
the result may give with numeric precision issues.
To avoid this problem, you can optionally clamp the far clip to a
smaller value based on the data you're operating on.
> region: region of the 3D viewport, typically bpy.context.region.
> rv3d: 3D region data, typically bpy.context.space_data.region_3d.
> coord: 2d coordinates relative to the region:
(event.mouse_region_x, event.mouse_region_y) for example.
> clamp: clamp: Clamp the maximum far-clip value used.
(negative value will move the offset away from the view_location)
< returns the origin of the viewpoint in 3d space.
"""
view_m = np.array(rv3d.view_matrix)
window_m = np.array(rv3d.window_matrix)
viewinv = np.linalg.inv(view_m)
if rv3d.is_perspective:
origin_start = viewinv[:3, 3].copy()
else:
pers_m = window_m @ view_m # See https://github.com/IfcOpenShell/IfcOpenShell/issues/7046
persinv = np.linalg.inv(pers_m)
dx = (2.0 * coord[0] / region.width) - 1.0
dy = (2.0 * coord[1] / region.height) - 1.0
origin_start = (persinv[:3, 0] * dx) + (persinv[:3, 1] * dy) + persinv[:3, 3]
if clamp != 0.0:
if rv3d.view_perspective != "CAMERA":
origin_offset = persinv[:3, 2].copy() # column 2
if clamp is not None:
c = float(clamp)
if c < 0.0:
origin_offset = -origin_offset
c = -c
length = np.linalg.norm(origin_offset)
if length > c and length > 0.0:
origin_offset = (origin_offset / length) * c
origin_start = origin_start - origin_offset
return Vector(origin_start)
@classmethod
def location_3d_to_region_2d_np(
cls, region: bpy.types.Region, rv3d: bpy.types.RegionView3d, coord: Vector, *, default=None
) -> Vector:
"""
Numpy version of view3d_utils.location_3d_to_region_2d
Return the *region* relative 2d location of a 3d position.
> region: region of the 3D viewport, typically bpy.context.region.
> rv3d: 3D region data, typically bpy.context.space_data.region_3d.
> coord: 2d coordinates relative to the region:
(event.mouse_region_x, event.mouse_region_y) for example.
< returns a 2d location.
"""
pt = np.array((coord[0], coord[1], coord[2], 1.0), dtype=np.float64)
view_m = np.array(rv3d.view_matrix)
window_m = np.array(rv3d.window_matrix)
pers_m = window_m @ view_m
prj = pers_m.dot(pt) # 4-vector
w = prj[3]
if w > 0.0:
width_half = region.width / 2.0
height_half = region.height / 2.0
x = width_half + width_half * (prj[0] / w)
y = height_half + height_half * (prj[1] / w)
return Vector((float(x), float(y)))
return default
+5 -6
View File
@@ -143,12 +143,11 @@ class Debug(bonsai.core.tool.Debug):
Note that Styles UI (or other UI) should be updated manually after using this method.
Args:
object_type: The type of object to merge
by_name_or_identification_only: If True, merge based only on Name attribute (or equivalent identifier).
Strips .XXX suffix patterns (e.g., 'foo.001' matches 'foo', 'foo.002').
For PERSON, uses Identification. For APPLICATION, uses ApplicationFullName.
For PERSON_AND_ORGANIZATION, uses combination of person and organization identifiers.
:param object_type: The type of object to merge
:param by_name_or_identification_only: If True, merge based only on Name attribute (or equivalent identifier).
Strips .XXX suffix patterns (e.g., 'foo.001' matches 'foo', 'foo.002').
For PERSON, uses Identification. For APPLICATION, uses ApplicationFullName.
For PERSON_AND_ORGANIZATION, uses combination of person and organization identifiers.
"""
def normalize_name(name: str) -> str:
+57 -6
View File
@@ -38,12 +38,15 @@ import bonsai.core.geometry
import bonsai.core.type
import bonsai.tool as tool
import ifcopenshell.api
import ifcopenshell.api.geometry
import ifcopenshell.api.context
import ifcopenshell.api.drawing
import ifcopenshell.api.document
import ifcopenshell.api.pset
import ifcopenshell.api.root
import ifcopenshell.geom
import ifcopenshell.util.placement
import ifcopenshell.util.unit
import ifcopenshell.util.representation
import ifcopenshell.util.element
import ifcopenshell.util.selector
@@ -220,6 +223,7 @@ class Drawing(bonsai.core.tool.Drawing):
def exclude_annotation_from_drawing(
cls, element: ifcopenshell.entity_instance, drawing: ifcopenshell.entity_instance
) -> None:
ifc_file = tool.Ifc.get()
pset = tool.Pset.get_element_pset(drawing, "EPset_Drawing")
if not pset:
pset = ifcopenshell.api.pset.add_pset(ifc_file, product=drawing, name="EPset_Drawing")
@@ -893,6 +897,33 @@ class Drawing(bonsai.core.tool.Drawing):
return axis
return product
@classmethod
def get_assigned_product_workaround(
cls, element: ifcopenshell.entity_instance
) -> list[ifcopenshell.entity_instance]:
"""Get all products assigned to the element.
A workaround allowing to unassign accumulated products until we properly resolve #4014.
In theory annotations should have more than one product assigned,
but there's still undefined bug causing that in some cases.
"""
assigned_products: list[ifcopenshell.entity_instance] = []
for rel in element.HasAssignments:
if not rel.is_a("IfcRelAssignsToProduct"):
continue
assigned_products.append(rel.RelatingProduct)
if len(assigned_products) > 1:
print(
f"WARNING. Detected multiple assigned products ({len(assigned_products)}) for annotation '{element}'."
"\nIf you can reproduce this, please report it to Bonsai developers "
"at https://github.com/IfcOpenShell/IfcOpenShell/issues/4014."
"\nAssigned products:\n" + "\n".join([str(p) for p in assigned_products])
)
return assigned_products
@classmethod
def import_annotations_in_group(cls, group: ifcopenshell.entity_instance) -> None:
elements = set(
@@ -1705,11 +1736,22 @@ class Drawing(bonsai.core.tool.Drawing):
im = camera.matrix_world.inverted()
v1, v2 = [im @ Vector((m @ np.append(v, 1.0))[:3]) for v in [v1, v2]]
target_view = cls.get_drawing_target_view(drawing)
bounds = helper.ortho_view_frame(camera.data)
if not (points := helper.clip_segment(bounds, [v1, v2])):
if target_view in ("PLAN_VIEW", "REFLECTED_PLAN_VIEW"):
# For plan views, clip to XY bounds and set Z=0
if not (points := helper.clip_segment(bounds, [v1, v2])):
return
for v in points:
v.z = 0
elif target_view in ("ELEVATION_VIEW", "SECTION_VIEW"):
# For section/elevation views, elevate the segment vertically
if not (points := helper.elevate_segment(bounds, [v1, v2])):
return
else:
return
for v in points:
v.z = 0
return points
@classmethod
@@ -1969,9 +2011,18 @@ class Drawing(bonsai.core.tool.Drawing):
@classmethod
def get_default_annotation_matrix(cls, camera, matrix_world=None):
if matrix_world is None:
matrix_world = camera.matrix_world.copy()
annotation_offset = Vector((0, 0, -camera.data.clip_start - 0.05))
annotation_offset = camera.matrix_world.to_quaternion() @ annotation_offset
# Use normalized camera matrix (without scale) for RCP compatibility
matrix_world = cls.get_camera_matrix(camera)
# Check if this is an RCP (reflected ceiling plan) by checking for negative scale
camera_scale = camera.matrix_world.to_scale()
is_rcp = camera_scale.x < 0 or camera_scale.y < 0 or camera_scale.z < 0
# For RCP, the offset needs to be positive instead of negative
# because the Z axis is flipped 180° in get_camera_matrix
offset_direction = 1 if is_rcp else -1
annotation_offset = Vector((0, 0, offset_direction * (camera.data.clip_start + 0.05)))
annotation_offset = matrix_world.to_quaternion() @ annotation_offset
matrix_world.translation += annotation_offset
return matrix_world
+11 -4
View File
@@ -1146,8 +1146,9 @@ class Geometry(bonsai.core.tool.Geometry):
def record_object_position(cls, obj: bpy.types.Object) -> None:
# These are recorded separately because they have different numerical tolerances
props = tool.Blender.get_object_bim_props(obj)
props.location_checksum = repr(np.array(obj.matrix_world.translation).tobytes())
props.rotation_checksum = repr(np.array(obj.matrix_world.to_3x3()).tobytes())
# Explicit dtype for Blender <5.0 compatibility.
props.location_checksum = repr(np.array(obj.matrix_world.translation, dtype=np.float32).tobytes())
props.rotation_checksum = repr(np.array(obj.matrix_world.to_3x3(), dtype=np.float32).tobytes())
@classmethod
def remove_connection(cls, connection: ifcopenshell.entity_instance) -> None:
@@ -1261,7 +1262,7 @@ class Geometry(bonsai.core.tool.Geometry):
if tool.Ifc.get().schema == "IFC2X3":
return False
for slot in obj.material_slots:
if slot.material and slot.material.use_nodes:
if slot.material and tool.Style.get_use_nodes(slot.material):
for node in slot.material.node_tree.nodes:
if node.type == "TEX_COORD" and node.outputs["UV"].links:
return True
@@ -1359,6 +1360,8 @@ class Geometry(bonsai.core.tool.Geometry):
If no other representation present, will replace object with an empty.
Method assumes that `obj` does have a current representation (it could be not `representation`).
Will clean up old ``obj.data`` if no other users exist.
"""
element = tool.Ifc.get_entity(obj)
assert element
@@ -1377,7 +1380,11 @@ class Geometry(bonsai.core.tool.Geometry):
# `representation` is the only representation for object.
if new_representation is None:
old_data = obj.data
assert old_data is not None
cls.recreate_object_with_data(obj, None)
if not cls.has_data_users(old_data):
cls.delete_data(old_data)
return
bonsai.core.geometry.switch_representation(
@@ -2020,7 +2027,7 @@ class Geometry(bonsai.core.tool.Geometry):
continue
if not (style := tool.Ifc.get_entity(material)):
style = ifcopenshell.api.style.add_style(ifc_file, name=material.name)
if material.use_nodes:
if tool.Style.get_use_nodes(material):
ifc_class = "IfcSurfaceStyleRendering"
attributes = tool.Style.get_surface_rendering_attributes(material)
else:
+18 -4
View File
@@ -110,12 +110,26 @@ class Ifc(bonsai.core.tool.Ifc):
oprops = tool.Blender.get_object_bim_props(obj)
if not oprops.location_checksum:
return True # Let's be conservative
loc_check = np.frombuffer(eval(oprops.location_checksum))
loc_real = np.array(obj.matrix_world.translation).flatten()
# Handle both old float64 and new float32 checksums for version compatibility
loc_checksum_bytes = eval(oprops.location_checksum)
if len(loc_checksum_bytes) == 24: # Old format: 3 * 8 bytes (float64)
loc_check = np.frombuffer(loc_checksum_bytes, dtype=np.float64).astype(np.float32)
else: # New format: 3 * 4 bytes (float32)
loc_check = np.frombuffer(loc_checksum_bytes, dtype=np.float32)
loc_real = np.array(obj.matrix_world.translation, dtype=np.float32).flatten()
if not np.allclose(loc_check, loc_real, atol=1e-4): # 0.1 mm
return True
rot_check = np.frombuffer(eval(oprops.rotation_checksum)).reshape(3, 3)
rot_real = np.array(obj.matrix_world.to_3x3())
# Handle both old float64 and new float32 checksums for version compatibility
rot_checksum_bytes = eval(oprops.rotation_checksum)
if len(rot_checksum_bytes) == 72: # Old format: 9 * 8 bytes (float64)
rot_check = np.frombuffer(rot_checksum_bytes, dtype=np.float64).astype(np.float32).reshape(3, 3)
else: # New format: 9 * 4 bytes (float32)
rot_check = np.frombuffer(rot_checksum_bytes, dtype=np.float32).reshape(3, 3)
rot_real = np.array(obj.matrix_world.to_3x3(), dtype=np.float32)
rot_dot = np.dot(rot_check, rot_real.T)
angle_rad = np.arccos(np.clip((np.trace(rot_dot) - 1) / 2, -1, 1))
if angle_rad > 0.0017453292519943296: # 0.1 degrees
+3 -3
View File
@@ -116,7 +116,7 @@ class Loader(bonsai.core.tool.Loader):
if transparency := surface_style.get("Transparency", None):
alpha = 1 - transparency
blender_material.diffuse_color = surface_style["SurfaceColour"] + (alpha,)
blender_material.use_nodes = False
tool.Style.set_use_nodes(blender_material, False)
@classmethod
def restart_material_node_tree(cls, blender_material: bpy.types.Material) -> None:
@@ -204,7 +204,7 @@ class Loader(bonsai.core.tool.Loader):
# TODO: reset pins to default values if no values passed
if reflectance_method in ["PHYSICAL", "NOTDEFINED"]:
blender_material.use_nodes = True
tool.Style.set_use_nodes(blender_material, True)
cls.restart_material_node_tree(blender_material)
bsdf = tool.Blender.get_material_node(blender_material, "BSDF_PRINCIPLED")
assert bsdf
@@ -231,7 +231,7 @@ class Loader(bonsai.core.tool.Loader):
blender_material.blend_method = "BLEND"
elif reflectance_method == "FLAT":
blender_material.use_nodes = True
tool.Style.set_use_nodes(blender_material, True)
cls.restart_material_node_tree(blender_material)
output = tool.Blender.get_material_node(blender_material, "OUTPUT_MATERIAL")
+93 -48
View File
@@ -44,11 +44,8 @@ import mathutils
from math import atan, cos, degrees, pi, radians
from mathutils import Matrix, Vector
from copy import deepcopy
from functools import partial
from bonsai.bim import import_ifc
from bonsai.bim.module.model.data import AuthoringData, RailingData, RoofData, WindowData, DoorData
from bonsai.bim.module.model.opening import FilledOpeningGenerator
from ifcopenshell.util.shape_builder import ShapeBuilder, np_to_3d
from typing import Optional, Union, TypeVar, Any, Literal, TYPE_CHECKING, TypedDict, assert_never
from collections.abc import Iterable, Sequence
@@ -145,9 +142,10 @@ class Model(bonsai.core.tool.Model):
for prop_name in data:
if prop_name in non_si_props:
continue
prop_value = data[prop_name]
# `None` is used by `custom_first_last_tread_run`.
prop_value: Iterable[float | None] | float = data[prop_name]
if isinstance(prop_value, collections.abc.Iterable):
data[prop_name] = [v * si_conversion for v in prop_value]
data[prop_name] = [v if v is None else v * si_conversion for v in prop_value]
else:
data[prop_name] = prop_value * si_conversion
return data
@@ -1103,6 +1101,7 @@ class Model(bonsai.core.tool.Model):
return
from PIL import Image, ImageDraw
from bonsai.bim.module.model.data import AuthoringData
obj = tool.Ifc.get_object(element)
if not obj:
@@ -1208,6 +1207,8 @@ class Model(bonsai.core.tool.Model):
Allows postponing the thumbnail update until it is actually needed by the user.
"""
from bonsai.bim.module.model.data import AuthoringData
element_id = element.id()
if element_id not in AuthoringData.type_thumbnails:
return
@@ -1311,23 +1312,34 @@ class Model(bonsai.core.tool.Model):
@classmethod
def is_parametric_roof_active(cls) -> bool:
from bonsai.bim.module.model.data import RoofData
return bool((RoofData.is_loaded or not RoofData.load()) and RoofData.data["pset_data"])
@classmethod
def is_parametric_railing_active(cls) -> bool:
from bonsai.bim.module.model.data import RailingData
return bool((RailingData.is_loaded or not RailingData.load()) and RailingData.data["pset_data"])
@classmethod
def is_parametric_window_active(cls) -> bool:
from bonsai.bim.module.model.data import WindowData
return bool((WindowData.is_loaded or not WindowData.load()) and WindowData.data["pset_data"])
@classmethod
def is_parametric_door_active(cls) -> bool:
from bonsai.bim.module.model.data import DoorData
return bool((DoorData.is_loaded or not DoorData.load()) and DoorData.data["pset_data"])
CustomTreadRunType = Union[tuple[float, float], tuple[None, None]]
@classmethod
def get_active_stair_calculated_params(cls, pset_data: Optional[dict[str, Any]] = None) -> dict[str, Any]:
props = bpy.context.active_object.BIMStairProperties
assert (obj := bpy.context.active_object)
props = tool.Model.get_stair_props(obj)
if props.is_editing:
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
@@ -1338,38 +1350,48 @@ class Model(bonsai.core.tool.Model):
last_tread_run = props.custom_first_last_tread_run[1] / si_conversion
nosing_length = props.nosing_length / si_conversion
else:
number_of_treads = pset_data["number_of_treads"]
height = pset_data["height"]
tread_run = pset_data["tread_run"]
assert pset_data
number_of_treads: int = pset_data["number_of_treads"]
height: float = pset_data["height"]
tread_run: float = pset_data["tread_run"]
# use .get to not break the old .ifc models
custom_first_last_tread_run = pset_data.get("custom_first_last_tread_run", (0, 0))
custom_first_last_tread_run: tool.Model.CustomTreadRunType = pset_data.get(
"custom_first_last_tread_run", (0, 0)
)
first_tread_run, last_tread_run = custom_first_last_tread_run
nosing_length = pset_data.get("nosing_length", 0)
calculated_params = {}
calculated_params: dict[str, Any] = {}
number_of_rises = number_of_treads + 1
calculated_params["Number of Risers"] = number_of_rises
calculated_params["Tread Rise"] = round(height / number_of_rises, 5)
# calculate stair length
# Start with all treads using default tread_run
n_default_tread_runs = number_of_rises
length = 0
if first_tread_run != 0:
length = 0.0
# If first tread has custom width (non-None), use it instead of default.
if first_tread_run is not None:
n_default_tread_runs -= 1
length += first_tread_run
if last_tread_run != 0:
# If last tread has custom width (non-None), use it instead of default
if last_tread_run is not None:
n_default_tread_runs -= 1
if n_default_tread_runs >= 0:
length += last_tread_run
length += tread_run * max(n_default_tread_runs, 0)
# nosing overlaps
# are not part of the tread run
# so they don't affect the stair length
# except the first tread's nosing
length += last_tread_run
# Add remaining default tread runs
length += tread_run * n_default_tread_runs
# Handle nosing length effects on total length
# Nosing overlaps don't affect tread run spacing,
# but the first tread's nosing extends the total length
if nosing_length > 0: # nosing overlaps
length += nosing_length
if nosing_length < 0: # tread gaps
if nosing_length < 0: # tread gaps between treads
length += abs(nosing_length) * number_of_treads
calculated_params["Length"] = round(length, 5)
pitch = height / length
pitch_formatted = str(round(pitch * 100, 1)) + " % / " + str(round(degrees(atan(pitch)), 1)) + " deg"
@@ -1377,35 +1399,38 @@ class Model(bonsai.core.tool.Model):
return calculated_params
StairType = Literal["CONCRETE", "WOOD/STEEL", "GENERIC"]
@classmethod
def generate_stair_2d_profile(
cls,
number_of_treads,
height,
width,
tread_run,
stair_type,
number_of_treads: int,
height: float,
width: float,
tread_run: float,
stair_type: StairType,
# WOOD/STEEL CONCRETE STAIR ARGUMENTS
tread_depth=None,
tread_depth: Union[float, None] = None,
# CONCRETE STAIR ARGUMENTS
has_top_nib=None,
top_slab_depth=None,
base_slab_depth=None,
custom_first_last_tread_run=(0, 0),
nosing_length=0,
has_top_nib: Union[bool, None] = None,
top_slab_depth: Union[float, None] = None,
base_slab_depth: Union[float, None] = None,
custom_first_last_tread_run: Union[tuple[float, float], tuple[None, None]] = (None, None),
nosing_length: float = 0.0,
# CONCRETE GENERIC STAIR ARGUMENTS
nosing_depth=0,
):
nosing_depth: float = 0.0,
) -> tuple[list[Vector], list[tuple[int, ...]], list[[list[int]]]]:
"""returns a tuple of stair profile data: (vertices, edges, faces)"""
vertices = []
edges = []
faces = []
vertices: list[Vector] = []
edges: list[tuple[int, ...]] = []
faces: list[[list[int]]] = []
number_of_risers = number_of_treads + 1
tread_rise = height / number_of_risers
nosing_overlap = max(nosing_length, 0)
nosing_tread_gap = -min(nosing_length, 0)
nosing_overlap_offset = -V_(nosing_overlap, 0)
first_tread_run = custom_first_last_tread_run[0] if custom_first_last_tread_run[0] is not None else tread_run
def define_generic_stair_treads():
vertices.append(Vector([0, 0]))
@@ -1421,7 +1446,12 @@ class Model(bonsai.core.tool.Model):
elif nosing_depth == 0:
default_tread_verts = (V_(-nosing_overlap, tread_rise), V_(tread_run, tread_rise))
else: # nosing_overlap > 0 nosing_depth > 0
# kind of L shape
# kind of L shape:
# (2)●───────────────────────────●(3)
# |
# |
# ●──────────────●
# (1) (0)
default_tread_verts = (
V_(0, tread_rise - nosing_depth),
V_(-nosing_overlap, tread_rise - nosing_depth),
@@ -1470,12 +1500,12 @@ class Model(bonsai.core.tool.Model):
current_offset += tread_offset
if stair_type == "WOOD/STEEL":
builder = ShapeBuilder(None)
assert tread_depth is not None
# full tread rectangle
def get_tread_verts(*args, **kwargs):
fn = partial(builder.get_rectangle_coords, position=V_(0, -(tread_depth - tread_rise)))
return [Vector(x) for x in fn(*args, **kwargs)]
def get_tread_verts(size: Vector) -> list[Vector]:
coords = ShapeBuilder.get_rectangle_coords(position=V_(0, -(tread_depth - tread_rise)), size=size)
return [Vector(x) for x in coords]
default_tread_verts = get_tread_verts(size=V_(tread_run + nosing_overlap, tread_depth))
default_tread_offset = V_(tread_run + nosing_tread_gap, tread_rise)
@@ -1504,6 +1534,7 @@ class Model(bonsai.core.tool.Model):
# each tread is a separate shape
cur_offset = V_(0, 0)
tread_index = 0
for i in range(number_of_risers):
tread_offset, tread_verts = get_tread_data(i)
@@ -1538,6 +1569,11 @@ class Model(bonsai.core.tool.Model):
elif stair_type == "CONCRETE":
define_generic_stair_treads()
assert has_top_nib is not None
assert top_slab_depth is not None
assert base_slab_depth is not None
assert tread_depth is not None
# add the nibs
# basically we define stair bottom line as a line at `tread_depth` distance
# from the tread diagonal line
@@ -1551,15 +1587,20 @@ class Model(bonsai.core.tool.Model):
# s0 is just a sampled point from the bottom line
# we stick to the third point as the first point
# is affected by customized tread run
s0 = V_(custom_first_last_tread_run[0] or tread_run, tread_rise) + td_vector
s0 = V_(first_tread_run, tread_rise) + td_vector
# comes from y = stair_tan * x + b
b = s0.y - stair_tan * s0.x
def get_point_on_2d_line(x=None, y=None):
if y is None:
def get_point_on_2d_line(
x: Union[float, None] = None,
y: Union[float, None] = None,
) -> Vector:
if x is not None and y is None:
y = stair_tan * x + b
elif x is None:
elif x is None and y is not None:
x = (y - b) / stair_tan
else:
assert False
return V_(x, y)
# top nib
@@ -1603,11 +1644,13 @@ class Model(bonsai.core.tool.Model):
else:
raise Exception(f"Unsupported stair type: {stair_type}")
vertices = (v.to_3d().xzy for v in vertices)
vertices = [v.to_3d().xzy for v in vertices]
return (vertices, edges, faces)
@classmethod
def update_simple_openings(cls, element: ifcopenshell.entity_instance) -> None:
from bonsai.bim.module.model.opening import FilledOpeningGenerator
ifc_file = tool.Ifc.get()
fillings = {e: tool.Ifc.get_object(e) for e in tool.Ifc.get_all_element_occurrences(element)}
@@ -2170,6 +2213,8 @@ class Model(bonsai.core.tool.Model):
@classmethod
def add_filled_opening(cls, voided_obj: bpy.types.Object, filling_obj: bpy.types.Object) -> None:
from bonsai.bim.module.model.opening import FilledOpeningGenerator
FilledOpeningGenerator().generate(filling_obj, voided_obj)
@classmethod
+4 -3
View File
@@ -172,23 +172,24 @@ class Polyline(bonsai.core.tool.Polyline):
angle = tool.Cad.angle_3_vectors(
second_to_last_point, last_point, mouse_vector, new_angle=None, degrees=True
)
angle_round_threshold = 1000 # Avoids rounding when distance is too big
# Round angle to the nearest 0.05
angle = round(angle / 0.05) * 0.05
angle = round(angle / 0.05) * 0.05 if distance < angle_round_threshold else angle
orientation_angle = tool.Cad.angle_3_vectors(
world_second_to_last_point, last_point, mouse_vector, new_angle=None, degrees=True
)
# Round angle to the nearest 0.05
orientation_angle = round(orientation_angle / 0.05) * 0.05
orientation_angle = round(orientation_angle / 0.05) * 0.05 if distance < angle_round_threshold else angle
if distance == 0:
angle = 0
orientation_angle = 0
if input_ui:
if should_round:
angle = 5 * round(angle / 5)
angle = 5 * round(angle / 5) if distance < angle_round_threshold else angle
factor = tool.Snap.get_increment_snap_value(context)
distance = factor * round(distance / factor)
input_ui.set_value("X", mouse_vector.x)
+11
View File
@@ -18,6 +18,7 @@
from __future__ import annotations
import os
import shutil
import bpy
import ifcopenshell
import ifcopenshell.api.document
@@ -502,3 +503,13 @@ class Project(bonsai.core.tool.Project):
d2 = v3 - v2
normals.append((v1, d1.cross(d2).normalized()))
return normals
TEMP_PROJECT_PATH = Path.cwd() / "test/files/temp/test_project.ifc"
@classmethod
def save_test_project(cls) -> None:
tmp = cls.TEMP_PROJECT_PATH.parent
# Clean up previous test project to avoid conflicts with non-updated assets.
if tmp.exists():
shutil.rmtree(tmp)
bpy.ops.bim.save_project(filepath=cls.TEMP_PROJECT_PATH.__str__(), should_save_as=True)
+13 -8
View File
@@ -93,7 +93,7 @@ class Raycast(bonsai.core.tool.Raycast):
return None
for v in bbox:
coord_2d = view3d_utils.location_3d_to_region_2d(context.region, context.space_data.region_3d, v)
coord_2d = tool.Cad.location_3d_to_region_2d_np(context.region, context.space_data.region_3d, v)
if coord_2d is not None:
transposed_bbox.append(coord_2d)
@@ -180,8 +180,11 @@ class Raycast(bonsai.core.tool.Raycast):
if not mouse_pos:
mouse_pos = event.mouse_region_x, event.mouse_region_y
view_vector = view3d_utils.region_2d_to_vector_3d(region, rv3d, mouse_pos)
ray_origin = view3d_utils.region_2d_to_origin_3d(region, rv3d, mouse_pos)
view_vector = tool.Cad.region_2d_to_vector_3d_np(region, rv3d, mouse_pos)
ray_origin = tool.Cad.region_2d_to_origin_3d_np(
region, rv3d, mouse_pos, clamp=10
) # TODO clamp is hardcoded but might be necessary to adapt
ray_target = ray_origin + view_vector
ray_direction = ray_target - ray_origin
@@ -247,7 +250,7 @@ class Raycast(bonsai.core.tool.Raycast):
snap_threshold = cls.calculate_snap_threshold(rv3d.view_distance)
try:
loc = view3d_utils.region_2d_to_location_3d(region, rv3d, mouse_pos, ray_direction)
loc = tool.Cad.region_2d_to_location_3d_np(region, rv3d, mouse_pos, ray_direction)
except:
loc = Vector((0, 0, 0))
@@ -267,7 +270,9 @@ class Raycast(bonsai.core.tool.Raycast):
points.append(snap_point)
return points
if obj and obj.type == "CURVE":
mw = obj.matrix_world.copy()
obj = bpy.data.objects.new("new_object", obj.to_mesh().copy())
obj.matrix_world = mw @ obj.matrix_world
if not custom_bmesh:
bm = bmesh.new()
@@ -340,7 +345,7 @@ class Raycast(bonsai.core.tool.Raycast):
snap_threshold = cls.calculate_snap_threshold(rv3d.view_distance)
try:
loc = view3d_utils.region_2d_to_location_3d(region, rv3d, mouse_pos, ray_direction)
loc = tool.Cad.region_2d_to_location_3d_np(region, rv3d, mouse_pos, ray_direction)
except:
loc = Vector((0, 0, 0))
@@ -399,8 +404,8 @@ class Raycast(bonsai.core.tool.Raycast):
default_container_elevation = 0.0
intersection = Vector((0, 0, default_container_elevation))
try:
loc = view3d_utils.region_2d_to_location_3d(region, rv3d, mouse_pos, ray_direction)
intersection = mathutils.geometry.intersect_line_plane(ray_target, loc, plane_origin, plane_normal)
loc = tool.Cad.region_2d_to_location_3d_np(region, rv3d, mouse_pos, ray_direction)
intersection = tool.Cad.intersect_edge_plane_v2(ray_target, loc, plane_origin, plane_normal)
except:
intersection = Vector((0, 0, default_container_elevation))
@@ -418,7 +423,7 @@ class Raycast(bonsai.core.tool.Raycast):
snap_threshold = cls.calculate_snap_threshold(rv3d.view_distance)
try:
loc = view3d_utils.region_2d_to_location_3d(region, rv3d, mouse_pos, ray_direction)
loc = tool.Cad.region_2d_to_location_3d_np(region, rv3d, mouse_pos, ray_direction)
except:
loc = Vector((0, 0, 0))

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