Compare commits

...

643 Commits

Author SHA1 Message Date
Ryan Schultz 0844550d06 feat(selector): add variable substitution and arithmetic to format()
- Add {{variable}} syntax for element property references
- Add arithmetic operators: +, -, *, / with proper precedence
- Extend format() with optional element parameter
- Variables work with all format functions (imperial_length, round, etc.)
- Maintains full backward compatibility

Example: format("imperial_length({{z}} / 2, 4)", element)
2025-11-24 11:25:33 -06:00
Andrej730 1d0b4675c0 Ruff - fix usage of deprecated Tuple, List 2025-11-24 11:01:26 +05:00
Andrej730 497ebc55e7 black . 2025-11-24 11:01:26 +05:00
Ryan Schultz 97a501036b Have dimensions like 4' - 1/2" read like 4' - 0 1/2", as is more the convention. 2025-11-23 18:22:40 -06:00
Gorgious 592c51b526 Add interactive gizmo system for editing parmeterized door
Implement visual parameter manipulation in 3D viewport with reusable
gizmo components. Features snap-to-mesh (Ctrl), precision mode (Shift),
and real-time feedback. Includes cone gizmos for dimensions,
arc gizmo for swing direction, and icon gizmos for edit controls. Gizmos are globally are individually toggleable in the addon preferences.

May be expanded upon later to add gizmos to other paramaterized elements (window, railing, roof, stairs, array, ...).

Unfortunately gizmos can't tap into the builtin snap system or bonsai snap system which is built for modal operators so gizmos implement yet another custom rather naive snap system.
2025-11-23 20:16:27 +01:00
Gorgious 3dd7290ee1 Display default parameters for parametric elements under "Model" in the preferences 2025-11-23 15:43:59 +01:00
Ryan Schultz d7ac5f2441 Fix #7397 - don't delete aggregate if relatingobject is a IfcSpatialElement 2025-11-23 08:09:13 -06:00
Thomas Krijnen 461404cd45 Don't terminate parsing on invalid entity names 2025-11-23 11:16:47 +01:00
Ryan Schultz bfd5b1bede add to https://github.com/IfcOpenShell/IfcOpenShell/commit/57bc709b6fef6381bbb2810da15e568290c4a77a: Fix elevation display by converting from project units to meters on import
When importing spatial decomposition, get_storey_elevation() returns values in the project's unit system (e.g., feet), but format_distance() expects values in meters. Added unit_scale conversion (elevation * unit_scale) before formatting to ensure elevations display correctly regardless of project units.
2025-11-22 16:53:28 -06:00
Ryan Schultz 0d0b6cc6b2 Additional fixes to https://github.com/IfcOpenShell/IfcOpenShell/commit/57bc709b6fef6381bbb2810da15e568290c4a77a 2025-11-22 15:40:47 -06:00
Ryan Schultz 57bc709b6f Fix imperial unit conversion for plan and section level decorators
Level annotations were incorrectly converting feet to meters and back,
causing values like 3ft to display as 9'10". Updated format_value() to
accept in_unit_length parameter and pass it to format_distance(), since
Blender's Z coordinates are already in project units.

- Add in_unit_length parameter to BaseDecorator.format_value()
- Set in_unit_length=True in PlanLevelDecorator.draw_labels()
- Set in_unit_length=True in SectionLevelDecorator.draw_labels()
2025-11-22 14:39:13 -06:00
Ryan Schultz b7b6e4aadf Consolidate format_distance functions and fix imperial unit formatting
Replaced duplicate format_distance in unit.py with comprehensive MeasureIt-ARCH version from helper.py. Fixed critical floating point errors by changing inPerFoot from 11.9999 to 12.0, eliminating spurious fractions on whole numbers. Implemented auto-precision detection to simplify fractions (64/256 → 1/4) while preserving precise values like 3/256 when needed. Enhanced imperial parsing to support flexible input formats (optional symbols, mixed fractions, leading decimals) and fixed suppress_zero_inches functionality in dimension annotations.
2025-11-22 13:49:30 -06:00
Gorgious 9c39ac0a48 You can now edit default parameters when generating a new parametric geometry for Door, Window, Railing, Roof and Stair types.
UI is in the addon preferences.
2025-11-22 18:12:50 +01:00
Gorgious a77f1b9fd1 Fix bug where one could setup negative lengths in the parametric door properties that whould throw an error when validating the geometry 2025-11-22 15:28:51 +01:00
Gorgious 63921e3fd8 Improve formatting for imperial units for the elevation of spatial elements elevation in the spatial decomposition panel (Re #7373) 2025-11-22 13:32:19 +01:00
Massimo Fabbro 27d0b04db5 black 2025-11-22 09:02:53 +01:00
Massimo Fabbro 87283874a1 Add simple examples for ifc5d import from csv 2025-11-22 09:02:53 +01:00
Gorgious 584d37de50 Fix #6048 (and #7373 ?) Adding a distance with a unit for an IfcSpatialElement in the Spatial Decomposition list doesn't save value
Support imperial units
Spatial element empty is moved in the 3d viewport accordingly
2025-11-22 01:16:39 +01:00
Gorgious ca2058d56b Fix #4974 - remap parent relationships when duplicating objects 2025-11-22 00:30:01 +01:00
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
Ryan Schultz 3f2cad0490 Add suppress_zero_inches parameter to imperial_length formatting function 2025-10-28 22:24:40 -05:00
Andrej730 5ec974ff94 Exclude build-all.py from main ruff check 2025-10-28 18:20:43 +05:00
Andrej730 2c585b649a Fix RUFF037 (redundant quotes) 2025-10-28 18:20:14 +05:00
Andrej730 36c28f3953 Mention in docs wasm-wheels as the main wheels source for pyodide builds 2025-10-28 18:20:13 +05:00
Andrej730 d1763237d2 ifcopenshell docs make - add server target 2025-10-28 18:20:13 +05:00
Andrej730 e8544d78ee ci.yml - note on rtti in rocksdb 2025-10-28 17:17:20 +05:00
Andrej730 e715a9467d ci.yml - exclude PYTHON_LIBRARY as redundant 2025-10-28 17:17:20 +05:00
Andrej730 1f8c12a4c8 build_pyodide - test wasm wheel working 2025-10-28 17:17:20 +05:00
Andrej730 89a6eac535 build-all.py - access pyodide for necessary variables, simplify debug build env 2025-10-28 17:17:20 +05:00
Andrej730 d1ffb329c8 pyodide/setup.py - fix missing ifcopenshell subpackages 2025-10-28 17:17:20 +05:00
Andrej730 4396126a43 build-all.py - fix for upcoming pyodide changes (cmake INIT flags) 2025-10-28 17:17:20 +05:00
Andrej730 7448feed44 build-all.py - reinitialize wasm cxx flags from cflags to accomodate issue upstream 2025-10-28 17:17:19 +05:00
Andrej730 6bc4046b17 ifcwrap cmake - fix issue with debug build after migration to FindPython
In FindPython variable was renamed to Python_LIBRARY_DEBUG
2025-10-28 17:17:19 +05:00
Andrej730 1a83e52935 ifcwrap cmake - avoid using deprecated swig_link_libraries 2025-10-28 17:17:19 +05:00
Andrej730 f16d42ba14 ifcwrap cmake - use usual name for wrapper target 2025-10-28 17:17:19 +05:00
Andrej730 75135fe45d cache_dependencies.py - fix tarfile deprecation warnings in 3.12-3.13 2025-10-28 17:17:19 +05:00
Andrej730 de89ab31cf ifcwrap cmake - move DEBUG_POSTFIX logic to one place 2025-10-28 17:17:19 +05:00
Andrej730 c8a4f1bbd2 cmake - build without Python libraries on Unix 2025-10-28 17:17:19 +05:00
Andrej730 5be1858767 ifcwrap cmake - accomodate bug in cmake makefiles generator 2025-10-28 17:17:19 +05:00
Andrej730 ace1db739f build-all - use shutil.which instead of custom function
To make it more idiomatic and custom function also was misinterpreting folders with the same name as existing executables.
2025-10-28 17:17:19 +05:00
Andrej730 80504310b6 build_pyodide - use ccache 2025-10-28 17:17:19 +05:00
Andrej730 a4dae9650f build_rocky - switch to main branch ccache 2025-10-28 17:17:18 +05:00
Andrej730 c27a995480 build_pyodide - use prebuilt pyodide build env 2025-10-28 17:17:18 +05:00
Andrej730 5b4c54abd2 build-all.py - switch to gmp ftpmirror
previous url is inaccessible
2025-10-28 17:17:18 +05:00
Andrej730 57d0beedee build_pyodide - display logs in github action output 2025-10-28 17:17:18 +05:00
Andrej730 d0ba675b1b build_pyodide - upload build logs for debugging 2025-10-28 17:17:18 +05:00
Andrej730 f4f2fa9535 build_pyodide - pack cached dependencies 2025-10-28 17:17:18 +05:00
Andrej730 67707914b9 build_osx - remove setup-python
as it's already preinstalled on the image
2025-10-28 17:17:18 +05:00
Andrej730 12766d61d4 build-all.py - print BUILD_DIR 2025-10-28 17:17:18 +05:00
Andrej730 34374cba41 build_pyodide - no need to adjust version
since it's now inferred automatically
2025-10-28 17:17:18 +05:00
Andrej730 f8bb04eae3 build-all.py - accomodate pyodide build system automatically
as it's the main way to build the wasm package
2025-10-28 17:17:18 +05:00
Andrej730 aee8cbe479 build_pyodide.yaml - move build_pyodide to a separate script 2025-10-28 17:17:18 +05:00
Andrej730 cb36e10279 Add notes for pyodide building process 2025-10-28 17:17:18 +05:00
Andrej730 7ca44b5677 build-all.py wasm - move SIDE_MODULE=2 to cmake
Next to `EXPORTED_FUNCTIONS` and avoid possible issues with pyodide in the future (pyodide is using `SIDE_MODULE=1`, but currently not providing it libraries of type `MODULE`, only for shared ones).
2025-10-28 17:17:17 +05:00
Andrej730 a6921b83a1 ifcwrap cmake - provide correct suffix for wasm python extension 2025-10-28 17:17:17 +05:00
Andrej730 c3c94b9c59 pyodide setup.py - add dependencies
so micropip would be able to gather them automatically
2025-10-28 17:17:17 +05:00
Andrej730 576ef336fc pyodide setup.py - add a note on not switching to pyproject.toml 2025-10-28 17:17:17 +05:00
Andrej730 b1fb541912 pyodide setup.py - ensure we have correct pyodide_2025_0_wasm32 suffix
Adding extension, but avoiding adding sources as we're not actually building.
As a result build will have a correct suffix `pyodide_2025_0_wasm32.whl` that's pyodide is currently using and this is pyodide is plan to use when it's going to release to PyPI, see https://peps.python.org/pep-0783/
2025-10-28 17:17:17 +05:00
Andrej730 6345b052da build-all.py - add a note about alternative gmp mirror 2025-10-28 17:17:17 +05:00
Andrej730 061554d921 cmake - deduplicate ccache set_property code 2025-10-28 17:17:17 +05:00
Andrej730 bcaa412f4b build-all.py - don't pass side module flags as usual flags
As they're parsed by emcmake automatically https://github.com/pyodide/pyodide-build/blob/bdd0b1422064de6d92264a08c599e85001a28567/pyodide_build/tools/cmake/Modules/Platform/Emscripten.cmake#L105-L107
2025-10-28 17:17:17 +05:00
Andrej730 9f6536b263 build-all.py - option to build wasm outside pyodide for debugging 2025-10-28 17:17:17 +05:00
Andrej730 226581f76e build-all.py - provide exported functions to target explicitly
Otherwise it broke compilation in some cases when cmake would try to test that linker is working.
2025-10-28 17:17:17 +05:00
Andrej730 e833d0cd13 build-all - wasm to use packages instead of providing includes/libs paths explicitly
More complicated than I anticipated😅

Details
https://discourse.cmake.org/t/provide-absolute-paths-to-dependencies-with-cmake-find-root-path-search-during-cross-compilation-wasm/15241
2025-10-28 17:17:16 +05:00
Andrej730 ed3c95d9d7 build-all.py - move unsupported dependencies to script 2025-10-28 17:17:16 +05:00
Andrej730 7a4823a1bc build-deps - use nuget to download ccache 2025-10-28 17:17:16 +05:00
Andrej730 80c0932174 build-all.py - print IFCOS_SCHEMAS 2025-10-28 17:17:16 +05:00
Andrej730 73a238e7b0 build-all.py - document required wasm variables 2025-10-28 17:17:16 +05:00
Andrej730 b95900f154 build-all.py - fix issue building wasm on apple device 2025-10-28 17:17:16 +05:00
Andrej730 da66e3c908 build-all.py - remove --host none from wasm args
As it's now obsolete and producing warnings `configure: WARNING: the "none" host is obsolete, use --disable-assembly`.
2025-10-28 17:17:16 +05:00
Andrej730 e04cc47022 build-all.py - bump freetype to avoid issues on cmake 4+ 2025-10-28 17:17:16 +05:00
Andrej730 ce427698d9 build-all.py - add note on --without-xxx option 2025-10-28 17:17:16 +05:00
Andrej730 d3beae1374 Add Python 3.14 builds 2025-10-28 17:17:16 +05:00
Andrej730 389d481364 build-deps - set NUGET_EXE from PATH if it's available 2025-10-28 17:17:15 +05:00
Andrej730 3efd88b0d6 build-all - deduce python components from python path 2025-10-28 17:17:15 +05:00
Andrej730 0a5388f386 ifcwrap cmake - migrate to FindPython
Also simplify run-cmake to just prefix the path, instead of providing 3 paths explicitly.
2025-10-28 17:17:15 +05:00
Andrej730 71fa002707 build-deps - support overriding PYTHON_VERSION 2025-10-28 17:17:15 +05:00
Andrej730 dd80115aa2 build-deps - download Python using nuget 2025-10-28 17:17:15 +05:00
Andrej730 bc66710fec build-deps - download nuget
to later download python using it
2025-10-28 17:17:15 +05:00
Andrej730 921241fbdd build-deps - include ccache for convenience 2025-10-28 17:17:15 +05:00
Andrej730 44b9dd64f6 build_win - extend cache size 2025-10-28 17:17:15 +05:00
Andrej730 bfa77255a5 build_win - use fork to resolve cache misses and duplicated cache 2025-10-28 17:17:14 +05:00
Andrej730 b125c651a8 build_win - test ccache 2025-10-28 17:17:14 +05:00
Thomas Krijnen eb1c81514b Check for attribute nil #7276 #7291 2025-10-28 10:51:31 +01:00
Thomas Krijnen 6df5632f91 black 2025-10-26 13:33:10 +01:00
Thomas Krijnen 391a412e65 test cases #7291 2025-10-26 13:31:31 +01:00
Thomas Krijnen b10415b1b3 Rework iterator constructor #7291 2025-10-26 13:31:31 +01:00
Thomas Krijnen b1c44d42e3 Reset logger product to none 2025-10-26 13:31:31 +01:00
Dion Moult 5f65e21878 Fix bug where you couldn't get elements using a predefined property set. 2025-10-26 23:05:05 +11:00
Dion Moult 0d3be9d101 Fix #5108. Selector now supports both GlobalId and Name as filter values. 2025-10-26 22:52:31 +11:00
Dion Moult eea585a3cd Fix #6258. Fix #5727 properly. Only assume layerset axis on "standard case" elements, nothing else. 2025-10-26 22:33:39 +11:00
Thomas Krijnen 1789406299 Ruff 2025-10-26 12:04:22 +01:00
Dion Moult b503061dc0 Fix #3868. Automatic annotation references are now auto deleted if their reference is also gone. 2025-10-26 20:58:15 +11:00
Dion Moult 90ccaf4aab Let users manually create automatic references too. 2025-10-26 20:46:32 +11:00
Dion Moult 05ff47fcff Remap deprecated entities in Brick 1.4 to new RealEstateCore URIs. 2025-10-26 20:38:58 +11:00
Louis Casteleyn c40ecdd550 deleted deprecated entities for v1.4 (#6511) 2025-10-26 20:32:28 +11:00
daniel-augusto 3a0622e9df Update introduction with hyperlinks for tools (#7290) 2025-10-26 20:29:30 +11:00
Dion Moult 0bb03750dc Fix #4914. Write docs for syntax and schema validation and schema querying 2025-10-26 20:27:07 +11:00
Thomas Krijnen 89defa9b6b build-all.py ruff: noqa: UP035 2025-10-26 10:20:13 +01:00
Thomas Krijnen 459aad54aa Black 2025-10-26 10:17:49 +01:00
Thomas Krijnen 6f3065f6ce ci.yml USE_MMAP=On 2025-10-26 10:15:52 +01:00
Thomas Krijnen 69863ce4a9 Try and fix stub test 2025-10-26 10:03:43 +01:00
Dion Moult e5ae19fd34 See #7291. Temporary workaround whilst segfaults are sorted. 2025-10-26 19:36:41 +11:00
Dion Moult afe617fb93 Revert "Bump IOS - necessary as last bunch of commits are incompatible with previous binaries"
This reverts commit d922f0b9ad.
2025-10-26 19:32:35 +11:00
Dion Moult d922f0b9ad Bump IOS - necessary as last bunch of commits are incompatible with previous binaries 2025-10-26 19:12:22 +11:00
Dion Moult f9d2d494b1 Fix #4275. Using psets_only kwarg in get_pset utility now includes predefined psets. 2025-10-26 18:21:06 +11:00
Thomas Krijnen 806aa14bd0 Compatibility for older builds 2025-10-25 21:55:06 +02:00
Thomas Krijnen 80066ba9e7 Different hack for allowing f.header.file_description.file.header.file_description.file 2025-10-25 21:54:42 +02:00
Thomas Krijnen 7cb1f07a0b hacky way to retain attribute too many messages on header entities in validate.py 2025-10-25 21:12:12 +02:00
Thomas Krijnen bdb52dcffd Pass header entity types during pass to resolve empty list type 2025-10-25 20:29:41 +02:00
Thomas Krijnen 933ad6c801 Fix header and schema validation errors in pass-rigid-op-IfcPlaneAngleMeasure-IfcPlaneAngleMeasure-ifc4x3_add2.ifc 2025-10-25 20:29:19 +02:00
Thomas Krijnen 1594b9505c Don't emit Parsing terminated on benign files by not reading past stream in skipComment() 2025-10-25 20:14:02 +02:00
Thomas Krijnen 325745a822 Test IfcConvert only on single file 2025-10-25 15:54:15 +02:00
Thomas Krijnen 74f8b047fb osx compat 2025-10-25 15:40:08 +02:00
Thomas Krijnen 0f8c6ffe9e Run the bulk of pytest in parallel 2025-10-25 15:38:25 +02:00
Thomas Krijnen fa46fe0cf0 Typos 2025-10-25 15:30:36 +02:00
Thomas Krijnen f0a1efa1f2 ruff --fix 2025-10-25 15:27:46 +02:00
Thomas Krijnen 71c1058e9e .gitignore for .exp files 2025-10-25 15:27:38 +02:00
Thomas Krijnen 7575bd4dfb Commit some files because I do not want them cleaned 2025-10-25 15:25:56 +02:00
Thomas Krijnen d697ed3384 Commit some files because I do not want them cleaned 2025-10-25 15:25:10 +02:00
Thomas Krijnen aed70c8a02 Run black 2025-10-25 15:20:36 +02:00
Thomas Krijnen e82ca0e932 Less back and forths by returning semi count in page 2025-10-25 15:08:21 +02:00
Thomas Krijnen 5910b39766 geom.stats module for preemptive scanning through file 2025-10-25 13:58:32 +02:00
Thomas Krijnen ea9e0c9ab4 readInstancePy() option to obtain type decls instead of strings 2025-10-25 13:55:55 +02:00
Thomas Krijnen 2e951eb952 Whitespace handling now that token strings are built on the fly 2025-10-25 13:55:15 +02:00
Thomas Krijnen 2497611abd Merge branch 'v0.8.0' of https://github.com/IfcOpenShell/IfcOpenShell into v0.8.0 2025-10-25 13:54:52 +02:00
Bruno Postle 90bf56d9ba Fix ifc5d documentation
Documentation claimed that ODS/XLSX output was to a given filename, but
the code actually writes files with generated names in a folder.

(also create the folder if necessary for convenience)
2025-10-24 22:19:01 +01:00
Bruno Postle e1b73ef0ef Fix build broken by a6d20c0 - link iostreams
FileReader.cpp now uses boost::iostreams::mapped_file regardless of
USE_MMAP setting
2025-10-24 15:35:19 +01:00
Thomas Krijnen bcb5fd7810 Missing return type 2025-10-24 13:32:18 +02:00
Thomas Krijnen 40c5145a13 Address some omissions in the FileReader rewrite regarding unicode paths on windows 2025-10-24 13:26:27 +02:00
Thomas Krijnen e304c3ad31 Merge branch 'v0.8.0' of https://github.com/IfcOpenShell/IfcOpenShell into v0.8.0 2025-10-24 13:25:05 +02:00
Thomas Krijnen 0494bd9677 Option to bypass storing types when opening model 2025-10-24 12:06:59 +02:00
Dion Moult fc0cdfabb9 Fix #7255. Bug where you couldn't edit header data. 2025-10-24 19:11:29 +11:00
nameloCmaS 2dac137801 Limit clipping plane euler rotation angles to <= 360 degrees (#6877)
The "Flip Clipping Plane" operator adds 180 degrees to the angle's value, meaning the angle will keep increasing with each press.
2025-10-24 14:10:38 +11:00
Dion Moult 8ffa6fcf47 Fix #7267. Bug where duplicating a drawing would cause annotation references to be associated with both the original and duplicate. 2025-10-24 14:09:00 +11:00
Dion Moult 8824e6e2c2 Fix #7268. Automatic annotation references (sections, elevations, etc) are now intelligently regenerated 2025-10-24 12:47:58 +11:00
Ryan Schultz 5dbfe539ac Need a little more freedom to rotate a RCP 2025-10-23 08:31:31 -05:00
Dion Moult d1453e1139 Fix bug where panels for psets/qtos/classifications/types were showing up on irrelevant objects (e.g. grid axes) 2025-10-23 08:59:15 +11:00
Dion Moult 5b5dcb3c6d Fix #7257. Always quote psets and props in our colour by property dropdown to account for spaces and characters. 2025-10-23 08:58:23 +11:00
Dion Moult 4f467d199e See #7253. See #7268. Rewrite grid generation to use IFC data, not rely on Blender object.
This also deprecates one more instance of the add_representation API
call. As this is slowly cleaned up we can move towards "regenerating"
annotations, not just generating from scratch.
2025-10-23 08:39:25 +11:00
Thomas Krijnen a6d20c0cc4 Follow up after 4d688170e0 2025-10-22 21:42:37 +02:00
Ryan Schultz d5d942bb2e Adds a TargetView CSS class to the drawings. 2025-10-22 10:06:43 -05:00
Thomas Krijnen 9e2c7e653f Commit suggestion in #7276 2025-10-22 14:37:10 +02:00
Thomas Krijnen 200c0bf773 Commit suggestion in #7276 2025-10-22 13:23:34 +02:00
Dion Moult 381b0d379a Optimise operators when a drawing is active by not needlessly regenerating the cut after each operation. 2025-10-22 18:03:40 +11:00
Dion Moult f48b5b9d92 Fix #7273. See #7253. Auto annotation references now cannot be deleted, but can be excluded. 2025-10-22 18:03:40 +11:00
Massimo Fabbro 1f47aadc94 Fix #7227. Sync cost item assigned resources also for SOR 2025-10-22 07:14:01 +02:00
falken10vdl bb3cd7e49c Annotation rotation when doing bulk tags (#7049)
* first attempt to do annotation rotation when dong bulk tags

* Enhance annotation rotation handling based on camera orientation

* handle properly S_G

* black .

* reorder property in UI

* cleanup imports
2025-10-22 15:07:52 +11:00
daniel-augusto edd01d44de Include direct links to tools (#7128) 2025-10-22 15:06:56 +11:00
Michael Godsven 52b899d070 Add icons (#7256) 2025-10-22 11:30:54 +11:00
Dion Moult 008292df0f Revert "Always show edit UI even if you don't have a BIM Tool activated."
This reverts commit 87bfd182d7.
2025-10-22 11:08:55 +11:00
Dion Moult 87bfd182d7 Always show edit UI even if you don't have a BIM Tool activated. 2025-10-22 11:07:18 +11:00
Dion Moult 8fcc622b8a Optimisation to only recalculate all drawing cuts if the camera is moved, not just selected. 2025-10-22 10:12:24 +11:00
Thomas Krijnen 9415878f57 Remove warning about combination of RefDirection as Axis #7251 2025-10-21 13:33:32 +02:00
Thomas Krijnen 919ac41a74 Consider case of only RefDirection in sectioned shapes #7251 2025-10-21 13:32:51 +02:00
Thomas Krijnen 80093f3777 Loft all wires not only the outer one #7251 2025-10-21 13:09:10 +02:00
Thomas Krijnen 28f5d6e91c Copy internal status on interpolated loops #7251 2025-10-21 13:08:29 +02:00
Thomas Krijnen 550779deee Merge branch 'v0.8.0' of https://github.com/IfcOpenShell/IfcOpenShell into v0.8.0 2025-10-21 13:06:05 +02:00
Thomas Krijnen e2cf60f0c1 Update to gmp 6.3.0 #7200 2025-10-21 12:18:48 +02:00
Dion Moult 546a042d40 Explicitly disable any voids, openings, and booleans in IFC4 Reference View MVD
It turns out vendors are still exporting them and creating a mess.
2025-10-21 10:49:17 +11:00
Thomas Krijnen 86b89f2153 Comment behaviour #7264 2025-10-20 10:37:35 +02:00
Thomas Krijnen 34f4aa430a Keep file_ as nullptr to not bypass addEntity() #7264 2025-10-20 10:33:09 +02:00
Thomas Krijnen 67678cb7f1 Merge branch 'v0.8.0' of https://github.com/IfcOpenShell/IfcOpenShell into v0.8.0 2025-10-20 10:27:50 +02:00
Thomas Krijnen f05b0d946d if -> ifdef #7174 2025-10-19 13:20:58 +02:00
Ryan Schultz 486651e0b7 additional tweak to the preceding commit. 2025-10-18 13:43:34 -05:00
Ryan Schultz 5885924bb7 Fix #5709: The RCP will not be denied! :) 2025-10-18 12:47:35 -05:00
Ryan Schultz ead849625d Automatically select annotations after creation to make it easier to move them in bulk. 2025-10-16 21:10:43 -05:00
Ryan Schultz f52aafdd6a closes #7249: for stairs, change the first/last tread lengths to less than the typical tread run. And even go to zero, whereby removing the tread altogether. 2025-10-14 21:53:22 -05:00
Ryan Schultz b0ae0a2c82 extend https://github.com/IfcOpenShell/IfcOpenShell/commit/322a2179e51a9ec4802fe9e35039c7e3e29f9678: Merges names with number suffix, as well (ex: foo, foo.001, foo.002) 2025-10-14 11:29:44 -05:00
Thomas Krijnen 4d688170e0 Storage rework WIP 2025-10-13 20:47:43 +02:00
Ryan Schultz 322a2179e5 Merge entities, if match on name or identification attribute only. 2025-10-13 11:25:36 -05:00
Dion Moult 8014980b88 Fix #7076. Lock representation items scale to prevent user confusion. 2025-10-13 08:52:59 +11:00
Dion Moult 65237cab65 Fix #7080. Potential bug with merging styles that didn't replace elements of the same class. 2025-10-13 08:44:47 +11:00
Dion Moult 155827528a Visibility during collector assignment now depends on the visibility mode set in the UI. 2025-10-13 08:08:35 +11:00
Dion Moult fb15435296 See #7229. Simpler solution to ensure representation isn't affected by visibility until assign class can be cleaned up more. 2025-10-13 08:08:10 +11:00
Dion Moult 6bba3ebeeb See #7159. Make polls consistent in add foo to sheet operators. 2025-10-13 08:02:06 +11:00
Dion Moult 3343f3fb35 Revert "Closes #7229: Unhide object before mesh tessellation export to prevent "Cannot edit hidden object" error when collector.assign() hides the object."
This reverts commit 96d05300bb.
2025-10-13 07:49:07 +11:00
Ryan Schultz 1b451d45cf Closes #7159: Fix AddReferenceToSheet operator poll checking wrong data_dir property (bim_props instead of prefs) 2025-10-12 13:51:14 -05:00
Ryan Schultz f5f676cf51 black . 2025-10-12 12:46:49 -05:00
Ryan Schultz 1dd477d382 Delete aggregate if all its parts are deleted. Prevents having orphaned aggregates, if you do a bulk delete with empties turned off. 2025-10-12 12:17:57 -05:00
Ryan Schultz 96d05300bb Closes #7229: Unhide object before mesh tessellation export to prevent "Cannot edit hidden object" error when collector.assign() hides the object. 2025-10-12 09:57:27 -05:00
Dion Moult 0073a4c0a2 Fix #7205. Regression when switching from drawing mode to model mode didn't hide drawings. 2025-10-12 21:21:27 +11:00
Dion Moult d3a5071d9d Fix #7211. Explain to user why they can't remove the last grid axis. 2025-10-12 21:07:13 +11:00
Dion Moult 471bd34fc6 Fix #7206. For convenience, deleted annotation references are marked as excluded and not regenerated. 2025-10-12 21:00:55 +11:00
Ryan Schultz 44cc12763b Allows the ability to activate an attached drawing_style regardless of whether a drawing has a underlay, or not. 2025-10-11 17:47:37 -05:00
Ryan Schultz b68aca04ae added EPset_Status.UserDefinedStatus to default join_criteria 2025-10-11 17:41:41 -05:00
Bruno Postle dd22264877 Fix sheet regeneration regression from d4f3280
Remove validate_sheet_files() check in regenerate_sheet() that prevented
regenerating missing LAYOUT files. The validation was blocking the
exact scenario that regeneration was designed to handle.

This allows users to open a bare IFC file with configured drawings and
sheets and have the SVG layouts automatically recreated with drawings
placed at default positions.
2025-10-11 20:25:13 +01:00
Ryan Schultz cc6ef0268f Introduces a toggle to turn on/off material layers in printed svg. 2025-10-11 09:52:55 -05:00
Dion Moult feecb89c8e Fix #7212. IfcTester now supports IFC2X3 type element mapping. 2025-10-11 21:30:49 +11:00
Dion Moult cb5c488cfd Fix #7213. IfcTester now supports checking for USERDEFINED predefined types
Can I just take this moment to complain about the overengineered
complexity of how predefined types work in IFC.
2025-10-11 21:09:41 +11:00
Dion Moult b68e8dedd2 Fix #5675. Bug where separate islands of polygons still merged CSS classes incorrectly. 2025-10-11 20:39:48 +11:00
Dion Moult 0975eb455e All default drawing patterns now have a white background, not transparent. 2025-10-11 20:37:28 +11:00
Dion Moult df0e8bba8c Fix #7225. Handle freestyle linework regardless of generated SVG filename. 2025-10-11 19:15:50 +11:00
Dion Moult 71d5239823 Revert "Closes ##7225 - Improve generate_freestyle_linework to robustly handle SVG output by falling back to the first generated linework file if the expected file is missing."
This reverts commit 89c039252b.
2025-10-11 19:02:04 +11:00
Dion Moult 92ae79f64e See #7133. Sync viewport visibility with render visibility when generating underlays. 2025-10-11 18:48:46 +11:00
Dion Moult 9c1f636cd4 Revert "Reintroduce https://github.com/IfcOpenShell/IfcOpenShell/issues/7133 : Add hide_render synchronization to isolate_objects so hidden viewport objects are also hidden from render"
This reverts commit 23c60b38fd.
2025-10-11 18:09:52 +11:00
Ryan Schultz 23c60b38fd Reintroduce https://github.com/IfcOpenShell/IfcOpenShell/issues/7133 : Add hide_render synchronization to isolate_objects so hidden viewport objects are also hidden from render 2025-10-10 22:24:57 -05:00
Ryan Schultz 89c039252b Closes ##7225 - Improve generate_freestyle_linework to robustly handle SVG output by falling back to the first generated linework file if the expected file is missing. 2025-10-10 17:28:15 -05:00
Thomas Krijnen 60d85b4259 Skip ifcxml writing tests 2025-10-09 14:03:35 +02:00
Thomas Krijnen e8a382b02a Only use cyclic dependencies when amongst static libs #7180 2025-10-09 13:24:31 +02:00
Thomas Krijnen 2b3ec66017 mark failed_on_purpose on IfcBoundingBox without --keep-bounding-boxes #7215 2025-10-09 08:45:25 +02:00
Thomas Krijnen 97dd37b345 Fix circular definition issue in cmake #7180 2025-10-08 15:25:38 +02:00
Thomas Krijnen e4877e8246 Ignore steal_instances() in python 2025-10-08 14:55:28 +02:00
Thomas Krijnen e216d345c1 Reimplement cecho.cmd to not depend on starting powershell.exe for speed boost 2025-10-08 14:18:52 +02:00
Thomas Krijnen 14c1b70768 Address some compiler warnings 2025-10-08 14:18:17 +02:00
Thomas Krijnen e15f222a19 Address segfault #7215 #7185 2025-10-08 14:17:47 +02:00
Ryan Schultz 4ab2664529 closes #7134 - Wall layer cuts exported out to a SVG during Freestyle linework mode. 2025-10-07 14:43:11 -05:00
Dion Moult bd0cfa5366 Fix bug where grid decorator only considered hide states, not hide viewport state or local isolation, etc 2025-10-07 14:44:57 +11:00
Dion Moult 2e4b0e83a2 Add feature to have work schedule columns for total inputs, outputs, and elements (inputs + outputs) 2025-10-07 13:34:07 +11:00
Dion Moult cf61111386 Fix bug where nested elements didn't show elements of the active task (only children) 2025-10-07 13:06:26 +11:00
Dion Moult 3aa5ddb070 Implement auto element selection for tasks in work schedule. Also add manual selection (instead of going into ICOM panel for speed) 2025-10-07 12:27:38 +11:00
Dion Moult 25dacc8fed Fix #7208. Merge duplicates patch recipe can now control merging by empty attributes. 2025-10-06 23:18:32 +11:00
Dion Moult 7cba63af9c Add IfcTester webapp workflow (#7209) 2025-10-06 23:15:16 +11:00
Dion Moult 3c1d6abc7e Update ci-ifctester-org.yml attempt 2 2025-10-06 17:51:50 +11:00
Dion Moult 8da4b9f23f Create ci-ifctester-org.yml
This definitely won't work the first time
2025-10-06 17:41:21 +11:00
Dion Moult 99fa77edee Fix #6873. Bug where geometry created through sync_references didn't have item editing metadata. 2025-10-06 17:23:29 +11:00
Andrej730 839c5ad932 black . 2025-10-06 11:02:33 +05:00
Dion Moult b68ce08811 Deprecate should_reload and is_global in switch representation as they don't do anything now.
Superseded by item editing mode, so always reload, and always global.
2025-10-06 16:57:54 +11:00
Dion Moult e662308665 Deprecate should_sync_changes_first for switching representation, since it is no longer relevant in item editing. 2025-10-06 16:19:39 +11:00
Andrea Ghensi a2ccff67ca fix(bcf): Create directory entries in the zip file's central directory (#7175)
This is done to comply with the Implementation Agreements
https://github.com/buildingSMART/BCF-XML/tree/release_3_0/Documentation#create-directory-folder-entries-in-the-zip-files-central-directory
2025-10-06 15:50:03 +11:00
Christoph Mellüh 8e045d772c Add "hide empty Specifications" to Bonsai IFCTester (#7190)
* add filter

* apply filter property

* allow name filter

* black .

* add hide-skipped to html

* rename prop

* only hide optional specifications
2025-10-06 15:49:35 +11:00
Christoph Mellüh 16fd614886 Add instructions to IFCTester in Blender (#7195)
* add instructions to blender ui

* add requirement instructions to html

* black .
2025-10-06 15:48:45 +11:00
Dion Moult f31df307c6 Fix #6674. Bug where newly created annotation types didn't have the necessary metadata for item editing. 2025-10-06 15:46:16 +11:00
Dion Moult 11873a7c51 Fix #7203. Help indicate that author should be an email. Add error styling. 2025-10-06 14:48:22 +11:00
Dion Moult ccccbe23f4 Fix #6709. Support same spatial element type even if not explicit in concept template. 2025-10-06 14:32:53 +11:00
Dion Moult 2173a562be Fix #7157. Don't fail if the bin dir isn't where we expect it (maybe it's packaged differently)
Honestly someone with motivation and coffee needs to take a long hard
look at our packaging and see if we can do a better job.
2025-10-06 14:14:49 +11:00
Dion Moult a11f81f2ce Fix #5701. Spaces are now invisible by default and use viewport hiding. 2025-10-06 14:07:20 +11:00
Dion Moult d3b553c4fb Blender tools for hiding and showing objects 2025-10-06 14:05:57 +11:00
Dion Moult 5eb11c3516 Remove "make spatial elements unselectable" setting because it's superseded by spatial locking. 2025-10-06 14:05:31 +11:00
Dion Moult c195799801 See #7202. Revert to safer way to control visibility.
Pity can't control hide_viewport in bulk, but this is perhaps the next
best thing.
2025-10-06 12:19:03 +11:00
Massimo Fabbro ac5a44a962 See #6570. Now it's possible to specify Description in csv import
Now it is possible to specify the column Description in the csv file in order to specify also the cost item description
2025-10-05 17:49:23 +02:00
Dion Moult 86880e9e62 See #4481. Optimise object visibility even further by using foreach_set.
Note: now we are using hide_viewport not hide_set anymore. There might
be pending issues from this. Also, now because we use foreach_set, we
can quickly also apply hide_render, which saves time during underlay
generation.
2025-10-05 23:20:32 +11:00
Dion Moult f8ccdf1170 See #4481. Optimise first load of drawing by lazy loading annotations. 2025-10-05 23:17:42 +11:00
Dion Moult e3d8473d47 Fix #7166. Don't create glazing constituent for doors without transoms. Clarify framing name. 2025-10-05 20:24:13 +11:00
Dion Moult bd5d4cecbf See #7166. Only show total thickness for layer sets. 2025-10-05 20:13:40 +11:00
Dion Moult 2ede8a2206 Fix #7158. UI didn't expect to find a material constituent set with no constituents.
It's technically valid but makes no sense.
2025-10-05 19:41:52 +11:00
Dion Moult cb77f9bd26 Fix #7179. Allow drawing polylines with segments as short as 1mm (previously 100mm which was too high) 2025-10-05 19:30:48 +11:00
Dion Moult 0bb21fedae Fix #7169. Bug where unit scale wasn't set for geometric operations on new projects. 2025-10-05 19:09:47 +11:00
Dion Moult 478f90c2d4 Update IfcTester webapp to use IfcTester logo 2025-10-05 18:46:05 +11:00
Dion Moult a016f6e5c8 Fix #7186. Support upgrading IFC2X3 IfcExtendedMaterialProperties to IFC4 2025-10-05 18:44:14 +11:00
Dion Moult 1b2fa4f994 Fix #7113. IfcTester webapp should use uppercase entity names 2025-10-05 15:32:55 +11:00
Christoph Mellüh 2d8383efc6 Fix MASSUNIT bug (#7173)
* add exception

* black
2025-10-05 15:31:06 +11:00
Dion Moult add5132dec Fix launching of IfcTester webapp with Bonsai 2025-10-05 15:11:18 +11:00
Andrej730 fe333051df build-deps - note on cloning opencollada 2025-10-04 21:40:07 +05:00
Andrej730 8d1b533f91 run-cmake.bat - echo SWIG_INSTALL_DIR
as `SWIG_DIR` is no longer used
2025-10-04 21:40:07 +05:00
Andrej730 fb304e141a build-all.py - note on doubled compilation times 2025-10-04 21:40:07 +05:00
Andrej730 bd094f930e build_rocky - support ccache 2025-10-04 21:40:07 +05:00
Andrej730 b61000d2fe ci.yml - bump ccache action version 2025-10-04 21:40:07 +05:00
Andrej730 c72cadb234 build_rocky_arm - use ccache 2025-10-04 21:40:06 +05:00
Andrej730 10642177a9 build_osx - bump ccache workflow version 2025-10-04 21:40:06 +05:00
Andrej730 0b8ea28463 aud - use main repo
As PR with a fix was merged
2025-10-04 21:40:06 +05:00
Andrej730 9b24f54f0d cmake - override CGAL_Boost_USE_STATIC_LIBS
The issue was with ci.yml - it was using system boost shared libraries (since Boost_USE_STATIC_LIBS by default is OFF), but CGALConfig was overriding leading to compilation issues.
2025-10-04 21:40:06 +05:00
Andrej730 289645dacc ci.yml - let cmake figure out system dependencies automatically 2025-10-04 21:40:06 +05:00
Andrej730 c365acb517 ci.yml - don't provide ccache explicitly
as it's covered automatically by cmake
2025-10-04 21:40:06 +05:00
Andrej730 785cf56621 cmake - specify cxx component for hdf5 package
by default it's targeting `C` and this was part of the issue why ci.yml was failing
2025-10-04 21:40:06 +05:00
Andrej730 60617eecce ci.yml - add ccache for rocksdb
Revert "ci.yml - add ccache for rocksdb"

This reverts commit 71178d4bbc023c793b0800f5987a979fdddc1c30.

Update ci.yml
2025-10-04 21:40:06 +05:00
Andrej730 26c68c00b7 cmake - proceed to package search if HDF5_LIBRARY_DIR not provided
Fixes issue with ci.yml
2025-10-04 21:40:05 +05:00
Esteban Dugueperoux ab0a602f9f black: Format incorrectly formated files (#7194) 2025-10-04 18:59:42 +05:00
Osyotr 066326ab3b Fix warning C4275
It's not enough to disable this warning by adding /wd4257 at build time because it's triggered when the library is consumed.
2025-10-03 18:12:56 +02:00
Thomas Krijnen 02713dbd39 nix build script add occt 7.9.1 patch 2025-10-03 18:08:37 +02:00
Thomas Krijnen d58dae5d2f nix build script add occt 7.9.1 patch 2025-10-03 18:05:39 +02:00
Thomas Krijnen ca3685900f Update clear_schemas() which is now handled by unique_ptr 2025-10-03 14:18:50 +02:00
Thomas Krijnen 5e10e61aca Clean-up parsed simple types #7185 2025-10-03 14:18:07 +02:00
Osyotr 92e25ce3cb Fix warning C4702: unreachable code 2025-10-03 13:56:10 +02:00
Thomas Krijnen 804a670454 Revert revert 2025-10-03 09:04:43 +02:00
Thomas Krijnen efa8382fc5 ifcopenshell.draw --arange-zones see http://blog.ifcopenshell.org/2025/10/bim-legal-2d-notorial-drawings-directly.html 2025-10-02 21:44:52 +02:00
Thomas Krijnen 92f9d5f506 Writing .ifcxml is not supported #7181 2025-10-02 20:28:17 +02:00
Ryan Schultz 8800c14105 extended previous commit to add functionality to printed svg, as well. 2025-10-01 23:21:14 -05:00
Ryan Schultz ec2d0bfa4a Add support for reversing and customizing separators in text annotation lists
Text annotations can now reverse the order of list values (e.g., materials) and use custom separators instead of the default comma-space. Two new properties have been added to EPset_Annotation: Reverse_List (boolean) and List_Separator (string). When text literals contain IFC list/tuple values, they are now detected during variable replacement and can be reversed and joined with the specified separator. The list separator supports escape sequences like \n for newlines, enabling multi-line list displays. These properties are exposed in the text editing UI and properly persist to the IFC file through the new update_text_annotation_properties method, which consolidates all annotation property updates into a single efficient operation.
2025-10-01 22:44:34 -05:00
Thomas Krijnen d684d47dc8 Make sure compound structure is retained as SvgSerializer depends on it 2025-10-01 15:01:50 +02:00
Andrej730 babf95785c Move IFCOPENSHELL_VERSION definition from IfcParse.h
To prevent recompiling all files that have IfcParse.h include and improve caching.
2025-09-30 19:17:46 +05:00
Andrej730 f6ebf80fab ifcopenshell-python/scripts/dev_environment.py 2025-09-30 17:47:30 +05:00
Andrej730 ea2cff3311 typing 2025-09-30 17:47:30 +05:00
Andrej730 bf4aa8cbb1 cmake - link mappings against IfcGeom
Not sure why it occurred only now, maybe after recent changes with shared libraries this problem just got more apparent and previously it was masked by some other library after mappings also using IfcGeom.
But the issue was that mapping were not linked against IfcGeom, though it's symbols were used in them. E.g. IfcSectionedSurface.cpp was using `has_loft` from `infra_sweep_helper`.
Issue occurred only on Linux - msvc and clang are less strict and probably do multiple passes to fetch symbols.

Example errors:
```
libgeometry_mapping_ifc4x3.a(IfcSectionedSolidHorizontal.cpp.o): In function `ifcopenshell::geometry::mapping_Ifc4x3::map_impl(Ifc4x3::IfcSectionedSolidHorizontal const*)':
IfcSectionedSolidHorizontal.cpp:(.text._ZN12ifcopenshell8geometry14mapping_Ifc4x38map_implEPKN6Ifc4x327IfcSectionedSolidHorizontalE+0x19a4): undefined reference to `ifcopenshell::geometry::make_loft(ifcopenshell::geometry::Settings const&, IfcUtil::IfcBaseClass const*, std::shared_ptr<ifcopenshell::geometry::taxonomy::function_item> const&, std::vector<ifcopenshell::geometry::cross_section, std::allocator<ifcopenshell::geometry::cross_section> >&)'
libgeometry_mapping_ifc4x3.a(IfcSectionedSurface.cpp.o): In function `ifcopenshell::geometry::mapping_Ifc4x3::map_impl(Ifc4x3::IfcSectionedSurface const*)':
IfcSectionedSurface.cpp:(.text._ZN12ifcopenshell8geometry14mapping_Ifc4x38map_implEPKN6Ifc4x319IfcSectionedSurfaceE+0x19c4): undefined reference to `ifcopenshell::geometry::make_loft(ifcopenshell::geometry::Settings const&, IfcUtil::IfcBaseClass const*, std::shared_ptr<ifcopenshell::geometry::taxonomy::function_item> const&, std::vector<ifcopenshell::geometry::cross_section, std::allocator<ifcopenshell::geometry::cross_section> >&)'
libgeometry_mapping_ifc4x3_tc1.a(IfcSectionedSolidHorizontal.cpp.o): In function `ifcopenshell::geometry::mapping_Ifc4x3_tc1::map_impl(Ifc4x3_tc1::IfcSectionedSolidHorizontal const*)':
IfcSectionedSolidHorizontal.cpp:(.text._ZN12ifcopenshell8geometry18mapping_Ifc4x3_tc18map_implEPKN10Ifc4x3_tc127IfcSectionedSolidHorizontalE+0x19a4): undefined reference to `ifcopenshell::geometry::make_loft(ifcopenshell::geometry::Settings const&, IfcUtil::IfcBaseClass const*, std::shared_ptr<ifcopenshell::geometry::taxonomy::function_item> const&, std::vector<ifcopenshell::geometry::cross_section, std::allocator<ifcopenshell::geometry::cross_section> >&)'
libgeometry_mapping_ifc4x3_tc1.a(IfcSectionedSurface.cpp.o): In function `ifcopenshell::geometry::mapping_Ifc4x3_tc1::map_impl(Ifc4x3_tc1::IfcSectionedSurface const*)':
IfcSectionedSurface.cpp:(.text._ZN12ifcopenshell8geometry18mapping_Ifc4x3_tc18map_implEPKN10Ifc4x3_tc119IfcSectionedSurfaceE+0x19c4): undefined reference to `ifcopenshell::geometry::make_loft(ifcopenshell::geometry::Settings const&, IfcUtil::IfcBaseClass const*, std::shared_ptr<ifcopenshell::geometry::taxonomy::function_item> const&, std::vector<ifcopenshell::geometry::cross_section, std::allocator<ifcopenshell::geometry::cross_section> >&)'
libgeometry_mapping_ifc4x3_add1.a(IfcSectionedSolidHorizontal.cpp.o): In function `ifcopenshell::geometry::mapping_Ifc4x3_add1::map_impl(Ifc4x3_add1::IfcSectionedSolidHorizontal const*)':
IfcSectionedSolidHorizontal.cpp:(.text._ZN12ifcopenshell8geometry19mapping_Ifc4x3_add18map_implEPKN11Ifc4x3_add127IfcSectionedSolidHorizontalE+0x19a4): undefined reference to `ifcopenshell::geometry::make_loft(ifcopenshell::geometry::Settings const&, IfcUtil::IfcBaseClass const*, std::shared_ptr<ifcopenshell::geometry::taxonomy::function_item> const&, std::vector<ifcopenshell::geometry::cross_section, std::allocator<ifcopenshell::geometry::cross_section> >&)'
libgeometry_mapping_ifc4x3_add1.a(IfcSectionedSurface.cpp.o):IfcSectionedSurface.cpp:(.text._ZN12ifcopenshell8geometry19mapping_Ifc4x3_add18map_implEPKN11Ifc4x3_add119IfcSectionedSurfaceE+0x19c4): more undefined references to `ifcopenshell::geometry::make_loft(ifcopenshell::geometry::Settings const&, IfcUtil::IfcBaseClass const*, std::shared_ptr<ifcopenshell::geometry::taxonomy::function_item> const&, std::vector<ifcopenshell::geometry::cross_section, std::allocator<ifcopenshell::geometry::cross_section> >&)' follow
collect2: error: ld returned 1 exit status
```
2025-09-30 13:10:59 +05:00
Andrej730 8b751577f4 Fix Ruff UP037 (unnecessary quotes in annotations) 2025-09-30 13:04:48 +05:00
Andrej730 6fbe982623 Fix Ruff UP006 (List -> list) 2025-09-30 13:04:48 +05:00
Andrej730 0b93694097 black . 2025-09-30 13:04:48 +05:00
Dion Moult 2027c71d99 Fix #7164. Property facets with a restriction in the baseName would incorrectly test null values.
Our tests missed this detail. The previous test also did
should_purge=True by default which doesn't property test for nullness.
2025-09-30 00:07:14 +10:00
Dion Moult 6da51c2a6e Update IfcTester to support changed upstream spec where prohibited specs cannot have requirements. 2025-09-30 00:05:57 +10:00
Fernando Gussão Bellon 53def55dc9 Added the file extension to the viewpoint guid comparison 2025-09-29 23:41:37 +10:00
Robin Quint 8722350c0b Plan view fixes (#7022) 2025-09-29 23:40:35 +10:00
Andrea Ghensi 34c92d4d9c fix(bcf): viewport creation functions result in malformed XML
fixes #6244
2025-09-29 23:33:15 +10:00
daniel-augusto 725cb40ef2 Update highlight from block code 2025-09-29 23:30:39 +10:00
daniel-augusto 0c81b41ac7 Update highlight of block code 2025-09-29 23:30:39 +10:00
Yan Peng a8a82450a5 Update documentation of bsdd based on the updated info of uri and bsdd functions. 2025-09-29 23:29:57 +10:00
Andrea Ghensi 1b5b9fdcf6 fix: use numpy 2d indexing, avoid np.matrix
fixes #7141
2025-09-29 23:29:08 +10:00
c-mellueh af1e542064 black 2025-09-29 23:28:20 +10:00
c-mellueh 78154d17bf add button 2025-09-29 23:28:20 +10:00
c-mellueh 9bc8012aa4 remove old function 2025-09-29 23:27:04 +10:00
c-mellueh 407962ec93 black 2025-09-29 23:27:04 +10:00
c-mellueh 21fd09b424 add bsdd_json 2025-09-29 23:27:04 +10:00
Andrej730 2264e69a1e cmake - remove redundant msvc check for ccache (e02d6c3f) 2025-09-29 17:39:43 +05:00
Andrej730 e02d6c3f4b cmake - support ccache for msvc (required cmake 3.25+)
As described in https://github.com/ccache/ccache/wiki/MS-Visual-Studio#usage-with-cmake
2025-09-29 16:43:29 +05:00
Andrej730 ec0abb7b37 cmake - fix missing installation for ifcgeom/serialization headers
Was trying to reproduce IfcOpenHouse example outside of IfcOpenShell project and noticed that it's referring to a header not present in installation.
2025-09-29 16:43:29 +05:00
Andrej730 477becab0b ruff - use preview behaviour
No particular preview rules we need, just to possibly early adapt new behavior.
2025-09-29 16:43:28 +05:00
Andrej730 60bf424461 ruff - remove from config a rule removed from ruff 2025-09-29 16:43:28 +05:00
Andrej730 923e8423f9 black . 2025-09-29 16:43:28 +05:00
Thomas Krijnen e228095391 Fix geomserver compile 2025-09-29 13:19:46 +02:00
myoualid 1492a660c8 adding paths to include schema files for wasm build (#7168) 2025-09-29 11:55:42 +01:00
Thomas Krijnen 7a931ea6f2 Submodule 2025-09-29 12:07:49 +02:00
Thomas Krijnen 2d59bb9cb8 Remove dependency on stdc++fs lib in cmake #7131 2025-09-29 11:30:40 +02:00
Thomas Krijnen 06f5ded038 Don't crash on missing schema when loading rdb 2025-09-29 11:29:52 +02:00
Thomas Krijnen 91f371a46e Be sure to init header_section_schema when obtaining schemas in python #7131 2025-09-29 11:29:35 +02:00
Thomas Krijnen 573e53ebfd Ugly workarounds to not depend on std::filesystem #7131 2025-09-29 11:29:07 +02:00
myoualid a492be5971 include schema files for Pyodide build via MANIFEST.in 2025-09-29 09:41:09 +01:00
Dion Moult ceac85ca02 Temporarily revert to old build behaviour since new builds are failing. 2025-09-29 11:00:59 +10:00
Dion Moult bfa981709a Fix regression. Schema should not be a Python package. 2025-09-29 10:43:14 +10:00
myoualid c64b477e53 last attempt to fix WASM build: libxml2 without threads and use explicit libxml2 paths 2025-09-29 00:49:12 +01:00
myoualid 4f7501e1e8 set Eigen3 path for WASM build to avoid find_package issues following commit 235eb3c83c 2025-09-28 21:56:27 +01:00
myoualid a1f20ec7bb add explicit OCCT paths for WASM builds 2025-09-28 20:20:43 +01:00
myoualid e2ee6c6e56 Fix WASM build by reverting CGAL handling to explicit paths from commit 3ec7b71565 2025-09-28 16:43:58 +01:00
myoualid 6940a05f2b remove yacc dependency for wasm build 2025-09-28 13:51:38 +01:00
myoualid ff8f4ecd3c Revert "add bison dependency for pyodide wasm build"
This reverts commit cfa21b3cbf.
2025-09-28 13:37:30 +01:00
Dion Moult de2bd3ff1a Fix #6914. Bug where HTML IDS report did not visually indicate prohibited results correctly. 2025-09-28 22:26:39 +10:00
myoualid cfa21b3cbf add bison dependency for pyodide wasm build 2025-09-28 12:21:50 +01:00
Dion Moult db7c3e3b01 Fix #6960. String reporting of cardinality did not match upstream docs.
The meaning of the upstream docs changed subtly a year ago and I never
caught up. There is still work to do.
2025-09-28 20:35:55 +10:00
myoualid 0532ceb029 Fix missing files in WASM build (#7168) 2025-09-28 11:30:23 +01:00
Dion Moult 0fe869c169 See #7131. Rollback to prevent instant segfaults on Linux. 2025-09-28 18:15:13 +10:00
myoualid bcde68d851 reproduce WASM missing schema files bug (#7168) 2025-09-27 20:54:23 +01:00
Bruno Perdigão 960a90e36c See commit 17d6b8a. Add a patch to update linked aggregates psets.
https://github.com/IfcOpenShell/IfcOpenShell/commit/17d6b8af61c8dd7cd82f6e72b2fb3831d851a33d#commitcomment-163151609
2025-09-26 22:57:18 -03:00
Bruno Perdigão aea0395fd6 See #7046. Remove snapping points that are not visible with clipping
planes.
2025-09-26 21:59:18 -03:00
Bruno Perdigão 416d51f7c7 Fix small issue with None value in polyline increment snap 2025-09-26 21:59:18 -03:00
Bruno Perdigão a9b13fd056 See #7046. Adapt snapping system to focal length value. 2025-09-26 21:59:17 -03:00
Andrej730 080056806e build_osx - separate arm64 ccache to avoid override 2025-09-26 21:53:33 +05:00
Andrej730 7a33a9eb02 build_osx - fix issue building arm64 2025-09-26 21:53:33 +05:00
Andrej730 07ba483a13 build-all - bump Python version to fix issue cross compiling
Not sure which patch version exactly fixed it, but 3.13.6 works.
2025-09-26 21:53:33 +05:00
Andrej730 4925741060 build_osx - add deps for cross compilation 2025-09-26 21:53:33 +05:00
Andrej730 7c4505b2fd build_osx - exclude arm64 gmp dependency to avoid cross compilation issues 2025-09-26 21:53:33 +05:00
Andrej730 f9ee838cd2 build-all - cross compile dependencies 2025-09-26 21:53:32 +05:00
Andrej730 cce0c39ab2 build-all.py - provide with-openssl for python on Mac 2025-09-26 21:53:32 +05:00
Andrej730 cd57fd4b1d build_osx - note on preinstalled dependencies 2025-09-26 21:53:32 +05:00
Andrej730 6b705f0448 build-all.py - document ADD_COMMIT_SHA env var 2025-09-26 21:53:32 +05:00
Andrej730 9cff6c87f0 cmake - don't fail silently with ADD_COMMIT_SHA and missing git 2025-09-26 21:53:32 +05:00
Andrej730 cac41064e0 build_osx - attempt to cross-compile intel binaries
ccache step broke (https://github.com/hendrikmuhs/ccache-action/issues/374) and macos-13 is now deprecated in general -
https://github.blog/changelog/2025-09-19-github-actions-macos-13-runner-image-is-closing-down/
2025-09-26 21:53:32 +05:00
Andrej730 350cf0406d build-all.py - override minimum cmake version for OpenCOLLADA 2025-09-26 21:53:32 +05:00
Andrej730 47fd52b7c1 build-all.py - bump eigen package to avoid issues on cmake 4 2025-09-26 21:53:32 +05:00
Andrej730 8a33e2fea1 workflows - unify ccache names 2025-09-26 21:53:31 +05:00
Andrej730 d4c02a312a build-all.py - bump json package to avoid issues on cmake 4 2025-09-26 21:53:31 +05:00
Andrej730 c20294b993 build_osx - upload logs artifacts to help debugging 2025-09-26 21:53:31 +05:00
Andrej730 7500631b81 build-rocky - upload logs artifacts to help debugging 2025-09-26 21:53:31 +05:00
Andrej730 7cc1a849a6 build-all.py - opencollada patch to support config package on Unix 2025-09-26 21:53:31 +05:00
Andrej730 4df93af1fd build-all.py - add OpenCOLLADA to IfcGeom dependencies
Otherwise it can be overlooked if you build IfcOpenShell-Python or any other target specifically, it was only built if you run build-all.py without any target arguments.
2025-09-26 21:53:31 +05:00
Andrej730 fc1c8f2071 build-all.py - CGAL to use v5.6.3
5.6.x-branch was removed, see https://github.com/CGAL/cgal/issues/9079
2025-09-26 21:53:31 +05:00
Andrej730 deedffa84e build-all.py - alter rocksdb's deps search only on Windows (da4508e) 2025-09-26 21:53:31 +05:00
Andrej730 e8d924426f build-all.py - add a note about xz requirement 2025-09-26 21:53:30 +05:00
Andrej730 5a3f809d03 build-all.py - fix Python 3.6 support for rocky (566c9af) 2025-09-26 21:53:30 +05:00
Andrej730 58ffd8bf62 ruff ci - check python 3.7 compatibility for build-all.py 2025-09-26 21:53:30 +05:00
Andrej730 b22452aaac build-all.py - print all missing commands simultaneoously 2025-09-26 21:53:30 +05:00
Andrej730 6c05e2ea8c Fix missing yacc in linux builds 2025-09-26 21:53:30 +05:00
Andrej730 a1773b6f46 black . 2025-09-26 21:53:30 +05:00
Andrej730 b3583d4191 typing 2025-09-26 21:52:35 +05:00
Thomas Krijnen 50e8d9f67e submodule 2025-09-26 14:32:34 +02:00
Thomas Krijnen 0d4819d6e7 dllimport/export #6926 2025-09-26 14:30:52 +02:00
Thomas Krijnen ce91d296b6 dllimport/export #6926 2025-09-26 14:24:49 +02:00
Ryan Schultz 21137b9268 closes:#6821 - This modification checks if the layer's material has a Category attribute and if it's not empty, then adds it as a CSS class with the format layer-material-category-{category_name}.
This will give you additional styling granularity when working with material layers, allowing you to target elements not just by their material name but also by their material category in your SVG styling.
2025-09-25 14:57:41 -05:00
Thomas Krijnen 0dc319e510 Fix compilation on older gcc versions required pod type for __thread 2025-09-25 13:17:12 +02:00
Bruno Perdigão 798beb0030 See #7046. Improve snapping inaccuracies in orthographic view 2025-09-24 17:46:55 -03:00
falken10vdl a717b348e0 Fix bounding box best origin default value
Initialize best_origin to 0 for the case no better origin is found.
Cheers!
2025-09-24 14:42:31 -03:00
falken10vdl 14ed4d1d08 feat: add comparison property to ifc_filter in draw_filter and update search query logic 2025-09-24 12:28:20 -03:00
Bruno Perdigão bd3250aff6 See #6883. Keeps each linked aggregate in their original container after refreshing. 2025-09-24 09:46:02 -03:00
Bruno Perdigão 1ee5bb73fa Fix RefreshLinkedAggregates renaming of aggregates 2025-09-24 09:46:02 -03:00
Thomas Krijnen 9a6dc54f6c Thread local current_product_ #7155 2025-09-24 13:56:35 +02:00
Thomas Krijnen ab59173135 Add a stream2_from_string() (mostly for wasm) 2025-09-23 11:32:52 +02:00
Thomas Krijnen b6abb24858 Support single circ/elip compcurves #7149 2025-09-23 11:11:42 +02:00
Thomas Krijnen 2b24d5ebe5 Initialize header schema in constructor and not statically #7131 2025-09-23 10:26:01 +02:00
Ryan Schultz 10a99fff05 Closes #7133: when activating a drawing, Bonsai will turn on/off 'Display in Render' when it turns on/off 'Hide in Viewport'. 2025-09-22 20:12:24 -05:00
Ryan Schultz 64aa1d07fb extend #6777 to have elevation print out as well. 2025-09-21 16:26:36 -05:00
Bruno Perdigão 730eafa74f IfcMaterialLayerSetUsage - Disable custom offset properties when the checkbox is unchecked 2025-09-19 17:41:03 -03:00
Andrej730 c077727374 Fix header data attr to match Blender prop names (559636e) 2025-09-19 19:07:44 +05:00
Andrej730 2e4241e435 typing 2025-09-19 19:04:53 +05:00
Andrej730 8a03a1379e cmake - match ifc4x3 for examples more precisely 2025-09-19 18:56:15 +05:00
Andrej730 8bb7ef5e25 cmake - only build examples if 2x3 is present in SCHEMA_VERSIONS
As examples currently require ifc2x3
2025-09-19 18:56:15 +05:00
Andrej730 da4508ef9a cmake - fix issue rocksdb locating zstd on Windows
See https://github.com/facebook/rocksdb/pull/13975 for the details.
2025-09-19 18:44:18 +05:00
Thomas Krijnen f3dedba320 Map opt<bool> in taxonony to none,false,true 2025-09-19 13:24:02 +02:00
Thomas Krijnen f659b9bb7b Fix exception in file.from_string() 2025-09-19 13:24:02 +02:00
Andrej730 488dee82d7 bim.create_all_shapes - add description and more readable output 2025-09-19 13:16:11 +05:00
Andrej730 3c4020be8d create_all_shapes - expose geom library selection to UI
Example - https://files.catbox.moe/oyv9bi.png
2025-09-19 13:16:11 +05:00
Thomas Krijnen ede4e49c0c submodule 2025-09-19 09:54:01 +02:00
Andrej730 3d2f361585 cmake - fix error with occt package (1857d0624) 2025-09-18 18:36:48 +05:00
Andrej730 4505ed1c98 cmake - remove trailing spaces 2025-09-18 18:36:48 +05:00
Andrej730 4cbad8804f cmake - use OpenCASCADE_VERSION as a version source if available 2025-09-18 18:36:48 +05:00
Andrej730 3ec7b71565 build-all.py - use find_package for CGAL 2025-09-18 17:53:35 +05:00
Andrej730 c0ec2e7e2b cmake - use find_package if explicit paths are not provided 2025-09-18 17:53:35 +05:00
Andrej730 048a94ddc5 build-all.py - bump json version and use find_package 2025-09-18 17:53:34 +05:00
Andrej730 ec43415822 cmake - support find_package for json 2025-09-18 17:53:34 +05:00
Andrej730 235eb3c83c build-all.py - use find_package for eigen3 2025-09-18 17:53:34 +05:00
Andrej730 4d0737c49b build-all, run-cmake - pass BOOST_ROOT as cmake prefix path 2025-09-18 17:53:34 +05:00
Andrej730 b499686c7c build-all.py - use find_package for occt 2025-09-18 17:53:34 +05:00
Andrej730 1857d06245 cmake - use find_package for occ if variables are not provided
cmake configs seems to be provided with the default installation, so `find_package` should do the job that `find_path` and `find_library` did previously.
2025-09-18 17:53:33 +05:00
Andrej730 663d233e3c build-all.py - document more env variables 2025-09-18 17:53:33 +05:00
Andrej730 fa1936c10a build-all.py - avoid building 3d viewer for OCCT 2025-09-18 17:53:33 +05:00
Andrej730 8c89ba497b build-all.py - use find_package for opencollada 2025-09-18 17:20:57 +05:00
Andrej730 2284ddd68c build-all.py - use find_package for libxml2 2025-09-18 17:20:55 +05:00
Andrej730 8daf6b0b04 build-all.py - use find_package for swig 2025-09-18 17:20:52 +05:00
Andrej730 e7a6f86299 build-all.py - don't break in case if some optional dependencies are missing 2025-09-18 14:07:58 +05:00
Andrej730 cae711b393 build-all.py - mention ssl requirement for Python (4ee6863) 2025-09-18 14:07:58 +05:00
Andrej730 566c9afa77 build-all.py - print live logs to the log file 2025-09-18 14:07:58 +05:00
Andrej730 867852688e cmake - use find_package for hdf5 as a general fallback
Previously we were setting `HDF5_LIBRARIES` explicitly, but I'm not sure if really worked, since we never set `HDF5_INCLUDE_DIR` and user would have to provide it manually either way.
`find_package(HDF5)` will search for default paths and will set `HDF5_LIBRARIES` and `HDF5_INCLUDE_DIR` automatically.
2025-09-18 14:07:58 +05:00
Andrej730 94851016d7 build-all.py - use find_package for hdf5 2025-09-18 14:07:58 +05:00
Andrej730 1dd3c298df build-all.py - explicitly disable xlib
I assume it's not needed, but Desktop Ubuntu seems to trying to build it anyway and fails since `libx11-dev` is missing.
2025-09-18 14:07:58 +05:00
Andrej730 c384ac26cd build-all.py - use pcre2 for swig
As it was needed for more recent swig version. Bumped swig in ae771cb without knowing this, so essentially building swig with build-all.py was broken for a bit.
2025-09-18 14:07:58 +05:00
Andrej730 a57080da74 build-all.py - ensure yacc is installed 2025-09-18 14:07:58 +05:00
Andrej730 71e59fe4c2 build-all.py - remove 'rel-' from swig tags
As they were used by the older versions and no longer provided after 4.1.0, see https://github.com/swig/swig/tags
2025-09-18 14:07:58 +05:00
Andrej730 aedae0b3c1 build-all.py - hdf5 to use cmake instead of ctest
After version 1.13.0 it seems hdf5 stopped publishing builds on the ftp and they also stopped publishing `CMake-hdf5-*` builds.
Switching to cmake, it will also make it consistent with build-deps.cmd
2025-09-18 14:07:58 +05:00
Andrej730 658f9b87a5 build-deps.cmd - bump hdf5 to match build-all
Has to skip hdf5 1.13.0 because it had a bug in it's cmake config setup, so `find_package` couldn't recognize, and the issue was fixed in 1.13.1.
2025-09-18 14:07:58 +05:00
Andrej730 fa9105e174 black . 2025-09-18 14:07:58 +05:00
Andrej730 d0d5f972df cmake - use find_package for rocksdb and zstd 2025-09-18 14:07:58 +05:00
Bruno Perdigão f5220fba56 Add custom offset to walls IfcMaterialLayerSetUsage 2025-09-15 17:08:25 -03:00
Bruno Perdigão 365f4f114b Add custom offset to slab IfcMaterialLayerSetUsage 2025-09-15 17:08:25 -03:00
427 changed files with 16590 additions and 5484 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"
+36 -12
View File
@@ -11,7 +11,7 @@ jobs:
matrix:
include:
- os: macos
runner: macos-13
runner: macos-14
arch: x64
oldarch:
- os: macos
@@ -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
@@ -34,14 +34,10 @@ jobs:
lfs: true
token: ${{ secrets.BUILD_REPO_TOKEN }}
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.x'
- name: Install Dependencies
run: |
brew update
# preinstalled: xz, cmake
brew install git bison autoconf automake libffi findutils
echo "$(brew --prefix findutils)/libexec/gnubin" >> $GITHUB_PATH
@@ -55,16 +51,44 @@ jobs:
[ -n "$install_root" ] && find "$install_root" -type f -name 'cache-*.tar.gz' -maxdepth 1 -exec tar -xzf {} -C "$install_root" \; || true
- name: ccache
uses: hendrikmuhs/ccache-action@v1
uses: hendrikmuhs/ccache-action@v1.2
with:
key: ${GITHUB_WORKFLOW}-${{ matrix.os }}
key: mac-${{ matrix.arch }}
- name: Run Build Script
shell: bash
run: |
if [ "${{ matrix.os }}" == "macos" ]; then
DARWIN_C_SOURCE=-D_DARWIN_C_SOURCE
fi
CXXFLAGS="-O3" CFLAGS="-O3 ${DARWIN_C_SOURCE}" ADD_COMMIT_SHA=1 BUILD_CFG=Release python3 ./nix/build-all.py --diskcleanup
if [ "${{ matrix.arch }}" == "x64" ]; then
arch -x86_64 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
MAC_INTEL=-mac-cross-compile-intel
# We don't use gmpcxx, but it comes preinstalled on macos runner as arm64 bottle
# and CGAL detects it and breaks cross compilation.
brew uninstall --ignore-dependencies gmp
# Otherwise Python will fallback to use arm64 `pkg-config`,
# will pick up arm64 libraries ('zstd' in particular),
# and break the build.
/usr/local/bin/brew install pkg-config
# Required by Python.
/usr/local/bin/brew install gettext openssl
fi
set -o pipefail
CXXFLAGS="-O3" CFLAGS="-O3 ${DARWIN_C_SOURCE}" ADD_COMMIT_SHA=1 BUILD_CFG=Release \
python3 ./nix/build-all.py -v --diskcleanup ${MAC_INTEL} \
| tee build.log
- name: Upload Build Logs
if: always()
uses: actions/upload-artifact@v5
with:
name: build-logs-osx-${{ matrix.arch }}
path: |
build.log
build/*/*/*/logs/*.log
build/*/*/*/build/ifcopenshell/**/CMakeCache.txt
retention-days: 30
- name: Pack Dependencies
run: |
@@ -115,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 }}
+58 -24
View File
@@ -9,41 +9,75 @@ jobs:
steps:
- name: Checkout Repository
uses: actions/checkout@v3
uses: actions/checkout@v5
with:
submodules: recursive
path: IfcOpenShell
- name: Checkout Pyodide
uses: actions/checkout@v3
- name: Checkout Build Repository
uses: actions/checkout@v5
with:
submodules: recursive
repository: pyodide/pyodide
ref: '0.28.0a3'
repository: IfcOpenShell/build-outputs
path: ifcopenshell_build
ref: wasm
lfs: true
token: ${{ secrets.BUILD_REPO_TOKEN }}
path: pyodide
- name: Unpack Dependencies
run: |
cd ifcopenshell_build
python ../IfcOpenShell/pyodide/cache_dependencies.py unpack
- name: ccache
uses: hendrikmuhs/ccache-action@v1.2
with:
key: ubuntu-22.04-${{ runner.arch }}
- name: Build
run: |
VERSION=`cat IfcOpenShell/VERSION`
sed -i s/0.8.0/$VERSION/g IfcOpenShell/pyodide/meta.yaml
sed -i s/0.8.0/$VERSION/g IfcOpenShell/pyodide/setup.py
echo '#!/usr/bin/bash' > script.sh
echo 'cd pyodide' >> script.sh
echo 'make' >> script.sh
echo 'cd ..' >> script.sh
echo 'mkdir -p packages/ifcopenshell' >> script.sh
echo 'cp IfcOpenShell/pyodide/meta.yaml packages/ifcopenshell' >> script.sh
echo 'PYODIDE_ROOT=/src/pyodide \' >> script.sh
echo 'PATH=/src/pyodide/emsdk/emsdk:/src/pyodide/emsdk/emsdk/node/22.16.0_64bit/bin:/src/pyodide/emsdk/emsdk/upstream/emscripten:$PATH \' >> script.sh
echo 'pyodide build-recipes ifcopenshell --install' >> script.sh
chmod +x script.sh
sed -i s/--tty// pyodide/run_docker
pyodide/run_docker ./script.sh
mv dist/ifcopenshell-$VERSION-py3-none-any.whl dist/ifcopenshell-$VERSION+${GITHUB_SHA:0:7}-cp313-cp313-emscripten_4_0_9_wasm32.whl
./IfcOpenShell/pyodide/build_pyodide.sh
FILE=`echo dist/ifcopenshell-*.whl`
NEW_FILE=`echo $FILE | sed "s/-/+${GITHUB_SHA:0:7}-/2"`
mv $FILE $NEW_FILE
- name: Upload Build Logs
if: always()
uses: actions/upload-artifact@v5
with:
name: build-logs-pyodide
path: |
ifcopenshell_build/*/*/logs/*.log
retention-days: 30
- name: Run wheel tests
run: |
cp -r IfcOpenShell/pyodide/test test
# venv set up in build_pyodide.sh.
source .venv/bin/activate
uv pip install pytest-pyodide
PYODIDE_ROOT_DIST=`pyodide config get pyodide_root`/dist
# `pytest-pyodide` requires pyodide in 'pyodide' directory in cwd, when running `pytest`.
cp -r $PYODIDE_ROOT_DIST test/pyodide
cp dist/ifcopenshell-*.whl test/pyodide
cd test
pytest --capture=no
- name: Pack Dependencies
run: |
cd ifcopenshell_build
python ../IfcOpenShell/pyodide/cache_dependencies.py pack
- name: Commit and Push Changes to Build Repository
run: |
cd ifcopenshell_build
git config user.name "IfcOpenBot"
git config user.email "ifcopenbot@ifcopenshell.org"
git add */*/install/cache-*.tar.gz
git commit -m "Update build artifacts [skip ci]" || echo "No changes to commit"
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 }}
+22 -10
View File
@@ -16,7 +16,7 @@ jobs:
bzip2 patch mesa-libGL-devel libffi-devel fontconfig-devel \
sqlite-devel bzip2-devel zlib-devel openssl-devel xz-devel \
readline-devel ncurses-devel libffi-devel libuuid-devel git-lfs \
findutils xz
findutils xz byacc
python3 -m pip install typing_extensions
git config --global --add safe.directory '*'
@@ -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
@@ -47,15 +47,27 @@ jobs:
install_root=$(find ./build -maxdepth 4 -type d -name install 2>/dev/null | head -n 1 || true)
[ -n "$install_root" ] && find "$install_root" -type f -name 'cache-*.tar.gz' -maxdepth 1 -exec tar -xzf {} -C "$install_root" \; || true
# Not supported on docker
# - name: ccache
# uses: hendrikmuhs/ccache-action@v1
# with:
# key: ${GITHUB_WORKFLOW}-rockylinux8-x64
- name: ccache
# TODO: Use tag after 1.2.20 releases.
uses: hendrikmuhs/ccache-action@5ebbd400eff9e74630f759d94ddd7b6c26299639
with:
key: ubuntu-22.04-${{ runner.arch }}-rockylinux8
- name: Run Build Script
shell: bash
run: |
CXXFLAGS="-O3" CFLAGS="-O3 ${DARWIN_C_SOURCE}" ADD_COMMIT_SHA=1 BUILD_CFG=Release python3 ./nix/build-all.py --diskcleanup
set -o pipefail
CXXFLAGS="-O3" CFLAGS="-O3 ${DARWIN_C_SOURCE}" ADD_COMMIT_SHA=1 BUILD_CFG=Release python3 ./nix/build-all.py -v --diskcleanup 2>&1 | tee build.log
- name: Upload Build Logs
if: always()
uses: actions/upload-artifact@v5
with:
name: build-logs-rocky
path: |
build.log
build/*/*/logs/*.log
retention-days: 30
- name: Pack Dependencies
run: |
@@ -106,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 }}
+22 -10
View File
@@ -16,7 +16,7 @@ jobs:
bzip2 patch mesa-libGL-devel libffi-devel fontconfig-devel \
sqlite-devel bzip2-devel zlib-devel openssl-devel xz-devel \
readline-devel ncurses-devel libffi-devel libuuid-devel git-lfs \
findutils xz
findutils xz byacc
python3 -m pip install typing_extensions
git config --global --add safe.directory '*'
@@ -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
@@ -47,15 +47,27 @@ jobs:
install_root=$(find ./build -maxdepth 4 -type d -name install 2>/dev/null | head -n 1 || true)
[ -n "$install_root" ] && find "$install_root" -type f -name 'cache-*.tar.gz' -maxdepth 1 -exec tar -xzf {} -C "$install_root" \; || true
# Not supported on docker
# - name: ccache
# uses: hendrikmuhs/ccache-action@v1
# with:
# key: ${GITHUB_WORKFLOW}-rockylinux8-x64
- name: ccache
# TODO: Use tag after 1.2.20 releases.
uses: hendrikmuhs/ccache-action@5ebbd400eff9e74630f759d94ddd7b6c26299639
with:
key: ubuntu-22.04-${{ runner.arch }}-rockylinux8
- name: Run Build Script
shell: bash
run: |
CXXFLAGS="-O3" CFLAGS="-O3 ${DARWIN_C_SOURCE}" ADD_COMMIT_SHA=1 BUILD_CFG=Release python3 ./nix/build-all.py --diskcleanup
set -o pipefail
CXXFLAGS="-O3" CFLAGS="-O3 ${DARWIN_C_SOURCE}" ADD_COMMIT_SHA=1 BUILD_CFG=Release python3 ./nix/build-all.py -v --diskcleanup 2>&1 | tee build.log
- name: Upload Build Logs
if: always()
uses: actions/upload-artifact@v5
with:
name: build-logs-rocky-arm64
path: |
build.log
build/*/*/logs/*.log
retention-days: 30
- name: Pack Dependencies
run: |
@@ -106,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 }}
+14 -52
View File
@@ -9,16 +9,15 @@ jobs:
strategy:
fail-fast: false
matrix:
python: ['3.9.11', '3.10.3', '3.11.8', '3.12.1', '3.13.0']
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
@@ -30,14 +29,6 @@ jobs:
run: |
choco install -y sed 7zip.install awscli
- name: Install Python
run: |
$installer = "python-${{ matrix.python }}-amd64.exe"
$url = "https://www.python.org/ftp/python/${{ matrix.python }}/$installer"
Invoke-WebRequest -Uri $url -OutFile $installer
Start-Process -Wait -FilePath .\$installer -ArgumentList '/quiet InstallAllUsers=0 PrependPath=0 Include_test=0 TargetDir=C:\Python\${{ matrix.python }}'
Remove-Item .\$installer
- name: Unpack Dependencies
run: |
cd _deps-vs2022-x64-installed
@@ -45,21 +36,21 @@ jobs:
7z x $_.FullName
}
- name: Run Build Script
- name: ccache
# TODO: Use tag after 1.2.20 releases.
uses: hendrikmuhs/ccache-action@5ebbd400eff9e74630f759d94ddd7b6c26299639
with:
key: win-${{ matrix.arch }}
# Windows ccache needs ~1GB
# and with default 500MB some cache gets deleted, leading to misses.
max-size: 5000MB
- name: Run Build Script And Pack .zip Archives
shell: cmd
run: |
setlocal EnableDelayedExpansion
SET PYTHON_VERSION=${{ matrix.python }}
for /f "tokens=1,2,3 delims=." %%a in ("%PYTHON_VERSION%") do (
set PY_VER_MAJOR_MINOR=%%a%%b
)
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat"
SET IFCOS_INSTALL_PYTHON=FALSE
cd win
echo y | call build-deps.cmd vs2022-x64 Release
SET PYTHONHOME=C:\Python\${{ matrix.python }}
call run-cmake.bat vs2022-x64 -DENABLE_BUILD_OPTIMIZATIONS=On -DGLTF_SUPPORT=ON -DADD_COMMIT_SHA=ON -DVERSION_OVERRIDE=ON
call install-ifcopenshell.bat vs2022-x64 Release
python build-all-win.py
- name: Pack Dependencies
run: |
@@ -81,37 +72,8 @@ jobs:
git commit -m "Update build artifacts [skip ci]" || echo "No changes to commit"
git push || echo "Push failed"
- name: Package .zip Archives
run: |
$VERSION = 'v' + ((Get-Content VERSION).Trim())
$SHA = ${env:GITHUB_SHA}.Substring(0, 7)
$OUTPUT_DIR = "$env:USERPROFILE\output"
New-Item -ItemType Directory -Force -Path $OUTPUT_DIR
if ("${{ matrix.python }}" -eq "3.9.11") {
# only for the first python version the executables are assembled for upload
cd _installed-vs2022-x64/bin
Get-ChildItem -Path . | ForEach-Object {
echo $_
$exe = $_.Name
$baseName = $exe.Substring(0, $exe.Length - 4)
$zipName = "${baseName}-$VERSION-$SHA-win64.zip"
7z a $zipName $exe
}
mv *.zip $OUTPUT_DIR
}
$pyVersion = "${{ matrix.python }}"
$pyVersionMajor = ($pyVersion -split '\.')[0..1] -join ''
cd C:\Python\${{ matrix.python }}\Lib\site-packages
Remove-Item -Recurse -Force ifcopenshell\__pycache__ -ErrorAction SilentlyContinue
Get-ChildItem -Path ifcopenshell -Filter "*.pyc" -Recurse | Remove-Item -Force
$zipName = "ifcopenshell-python-$pyVersionMajor-$VERSION-$SHA-win64.zip"
7z a $zipName ifcopenshell
mv $zipName $OUTPUT_DIR
- 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
+9 -5
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"
@@ -26,6 +26,7 @@ jobs:
curl -LsSf https://astral.sh/uv/install.sh | sh
uv tool install ruff
uv tool install black
uv tool install poethepoet
# black doesn't catch all syntax errors, so we check them explicitly.
- name: Check syntax errors
@@ -40,13 +41,16 @@ jobs:
- name: Black formatter
id: black
run: |
uvx black --diff --check .
black --diff --check .
continue-on-error: true
- name: Ruff check
id: ruff
run: |
uvx ruff check
ERROR=0
poe ruff-main || ERROR=1
poe ruff-old || ERROR=1
exit $ERROR
continue-on-error: true
- name: Final check
+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
@@ -24,7 +24,7 @@ jobs:
strategy:
fail-fast: false
matrix:
pyver: [py39, py310, py311, py312, py313]
pyver: [py39, py310, py311, py312, py313, py314]
config:
- {
name: "Windows 64bit",
@@ -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
+3 -3
View File
@@ -19,7 +19,7 @@ jobs:
strategy:
fail-fast: false
matrix:
pyver: [py39, py310, py311, py312, py313]
pyver: [py39, py310, py311, py312, py313, py314]
config:
- {
name: "Windows 64bit",
@@ -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'
+32
View File
@@ -0,0 +1,32 @@
name: ci-ifctester-org
on:
workflow_dispatch:
push:
paths:
- src/ifctester/**
jobs:
publish_website:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v5
- name: Checkout ifctester_org_static_html
uses: actions/checkout@v5
with:
repository: IfcOpenShell/ifctester_org_static_html
token: ${{ secrets.IFCOPENBOT_TOKEN }}
path: ifctester_org_static_html
- name: Build webapp
working-directory: ./src/ifctester
run: |
sudo apt update && sudo apt install -y nodejs
git config --global user.name 'IfcOpenBot'
git config --global user.email 'IfcOpenBot@users.noreply.github.com'
make webapp-build
- name: Commit and push
run: |
cp -r src/ifctester/webapp/dist/* ifctester_org_static_html/
git -C ifctester_org_static_html add .
git -C ifctester_org_static_html commit --allow-empty -m "$(git log --oneline -1)"
git -C ifctester_org_static_html push
+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
+69 -35
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
@@ -49,7 +49,8 @@ jobs:
python -m pip install --upgrade pip
pip install xmlschema xsdata numpy lxml pytest isodate lark networkx tabulate python-dateutil shapely
pip install src/bcf --no-deps
pip install https://github.com/Andrej730/aud/archive/refs/heads/master-reduced-size.zip
pip install git+https://github.com/zdhoward/aud
pip install pytest-xdist==3.8.0
- name: Install C++ dependencies
run: |
@@ -63,22 +64,28 @@ 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
# 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: ${GITHUB_WORKFLOW}
key: ubuntu-22.04-${{ runner.arch }}
# rocksdb on debian distros misses RTTI?
# RTTI is only enabled by default in Debug builds of rocksdb.
# Distros are using Release builds, so we're compiling it ourselves with RTTI forced on.
# https://github.com/facebook/rocksdb/blob/a3aa44a7167b8336f9bc15c8aba063260268ff68/CMakeLists.txt#L433
- name: build rocksdb
run: |
git clone https://github.com/facebook/rocksdb --branch v9.11.2
cd rocksdb
mkdir build && cd build
# rocksdb is using ccache automatically.
cmake -DFAIL_ON_WARNINGS=Off \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX=/usr/local \
@@ -93,15 +100,50 @@ 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
echo ${{ env.pythonLocation }}
mkdir build && cd build
# Ubuntu 22.04's libocct-foundation-dev package doesn't have Config.cmake, so we provide OCC paths directly.
# In later versions of Ubuntu, this can be simplified and the OCC paths can be removed.
cmake \
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
-DCMAKE_CXX_STANDARD=17 \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_PREFIX_PATH=/usr \
@@ -110,39 +152,31 @@ 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 \
-DPYTHON_LIBRARY:FILEPATH=${{ env.pythonLocation }}/lib/libpython3.11.so \
-DCOLLADA_SUPPORT=Off \
-DUSE_MMAP=On \
"-DSCHEMA_VERSIONS=2x3;4;4x3_add2" \
-DGLTF_SUPPORT=On \
-DJSON_INCLUDE_DIR=/usr/include \
-DCGAL_INCLUDE_DIR=/usr/include \
-DGMP_INCLUDE_DIR=/usr/include \
-DMPFR_INCLUDE_DIR=/usr/include \
-DGMP_LIBRARY_DIR=/usr/lib/x86_64-linux-gnu \
-DMPFR_LIBRARY_DIR=/usr/lib/x86_64-linux-gnu \
-DHDF5_INCLUDE_DIR=/usr/include/hdf5/serial \
-DEIGEN_DIR=/usr/include/eigen3 \
-DROCKSDB_INCLUDE_DIR=/usr/local/include \
-DROCKSDB_LIBRARY_DIR=/usr/local/lib \
-DWITH_ROCKSDB=On \
../cmake
sudo make -j $(nproc)
sudo make install
- name: Run IfcConvert on Sample files
run: |
(find test/input src/bonsai/test/files -name '*.ifc' | while read i; do \
echo $i | tee -a log; \
timeout 1m "$(which IfcConvert)" -yv "$i" "$i.obj" --validate >> log 2>&1; \
echo $i $? >> statuses; \
done) || true
echo Failed
grep -v 0$ statuses
grep -v 0$ statuses | wc -l
echo Succeeded
grep 0$ statuses
grep 0$ statuses | wc -l
# - name: Run IfcConvert on Sample files
# run: |
# (find test/input src/bonsai/test/files -name '*.ifc' | while read i; do \
# echo $i | tee -a log; \
# timeout 1m "$(which IfcConvert)" -yv "$i" "$i.obj" --validate >> log 2>&1; \
# echo $i $? >> statuses; \
# done) || true
# echo Failed
# grep -v 0$ statuses
# grep -v 0$ statuses | wc -l
# echo Succeeded
# grep 0$ statuses
# grep 0$ statuses | wc -l
- name: Run IfcConvert on Sample file
run: |
IfcConvert test/input/acad2010_walls.ifc test/input/acad2010_walls.obj
- name: Test ifcopenshell-python
run: |
@@ -152,7 +186,7 @@ jobs:
mv ifcopenshell ifcopenshell-local # Force testing on installed module
pip install -e ../ifcpatch --no-deps # Needed for sql.py tests.
ERROR=0
make test || ERROR=1
make test-parallel || ERROR=1
cd ../bcf && make test || ERROR=1
pip install requests
cd ../bsdd && make test || ERROR=1
@@ -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
+6 -6
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
@@ -109,4 +106,7 @@ src/bonsai/bonsai/bim/schema/Brick.ttl
bonsaiDecoratorForLoads.code-workspace
dev_environment.bat
.pixi/
.pixi/
src/ifcopenshell-python/ifcopenshell/express/*.exp
src/ifcopenshell-python/ifcopenshell/express/*.exp.cache.dat
-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
+18 -18
View File
@@ -37,25 +37,25 @@ Contents
| Name | Description | License | Service |
| ------------------------- | --------------------------------------------------------------------- | ------------------- | ------- |
| bcf | Library to read and write BCF-XML and query OpenCDE BCF-API modules | LGPL-3.0-or-later | [![PyPI](https://img.shields.io/pypi/v/bcf-client?label=PyPI&color=006dad)](https://pypi.org/project/bcf-client/) [![Anaconda-Server Badge](https://anaconda.org/conda-forge/bcf-client/badges/version.svg)](https://anaconda.org/conda-forge/bcf-client) |
| bonsai | Add-on to Blender providing a graphical native IFC authoring platform | GPL-3.0-or-later | [![Official](https://img.shields.io/badge/BonsaiBIM.org-Download-70ba35)](https://bonsaibim.org/download.html) [![GitHub Unstable](https://img.shields.io/github/v/release/ifcopenshell/ifcopenshell?filter=bonsai-*&label=GitHub-Unstable&color=f6f8fa)](https://github.com/IfcOpenShell/IfcOpenShell/releases?q=bonsai&expanded=true) [![Chocolatey](https://img.shields.io/chocolatey/v/blenderbim-nightly?label=Chocolatey&color=5c9fd8)](https://community.chocolatey.org/packages/blenderbim-nightly/) |
| bsdd | Library to query the bSDD API | LGPL-3.0-or-later | [![PyPI](https://img.shields.io/pypi/v/bsdd?label=PyPI&color=006dad)](https://pypi.org/project/bsdd/) |
| ifc2ca | Utility to convert IFC structural analysis models to Code_Aster | LGPL-3.0-or-later |
| ifc4d | Convert to and from IFC and project management software | LGPL-3.0-or-later | [![PyPI](https://img.shields.io/pypi/v/ifc4d?label=PyPI&color=006dad)](https://pypi.org/project/ifc4d/) |
| ifc5d | Report and optimise cost information from IFC | LGPL-3.0-or-later | [![PyPI](https://img.shields.io/pypi/v/ifc5d?label=PyPI&color=006dad)](https://pypi.org/project/ifc5d/) |
| ifcbimtester | Wrapper for Gherkin based unit testing for IFC models | LGPL-3.0-or-later |
| [bcf](https://docs.ifcopenshell.org/bcf.html) | Library to read and write BCF-XML and query OpenCDE BCF-API modules | LGPL-3.0-or-later | [![PyPI](https://img.shields.io/pypi/v/bcf-client?label=PyPI&color=006dad)](https://pypi.org/project/bcf-client/) [![Anaconda-Server Badge](https://anaconda.org/conda-forge/bcf-client/badges/version.svg)](https://anaconda.org/conda-forge/bcf-client) |
| [bonsai](https://docs.ifcopenshell.org/bonsai.html) | Add-on to Blender providing a graphical native IFC authoring platform | GPL-3.0-or-later | [![Official](https://img.shields.io/badge/BonsaiBIM.org-Download-70ba35)](https://bonsaibim.org/download.html) [![GitHub Unstable](https://img.shields.io/github/v/release/ifcopenshell/ifcopenshell?filter=bonsai-*&label=GitHub-Unstable&color=f6f8fa)](https://github.com/IfcOpenShell/IfcOpenShell/releases?q=bonsai&expanded=true) [![Chocolatey](https://img.shields.io/chocolatey/v/blenderbim-nightly?label=Chocolatey&color=5c9fd8)](https://community.chocolatey.org/packages/blenderbim-nightly/) |
| [bsdd](https://docs.ifcopenshell.org/bsdd.html) | Library to query the bSDD API | LGPL-3.0-or-later | [![PyPI](https://img.shields.io/pypi/v/bsdd?label=PyPI&color=006dad)](https://pypi.org/project/bsdd/) |
| [ifc2ca](https://docs.ifcopenshell.org/ifc2ca.html) | Utility to convert IFC structural analysis models to Code_Aster | LGPL-3.0-or-later |
| [ifc4d](https://docs.ifcopenshell.org/ifc4d.html) | Convert to and from IFC and project management software | LGPL-3.0-or-later | [![PyPI](https://img.shields.io/pypi/v/ifc4d?label=PyPI&color=006dad)](https://pypi.org/project/ifc4d/) |
| [ifc5d](https://docs.ifcopenshell.org/ifc5d.html) | Report and optimise cost information from IFC | LGPL-3.0-or-later | [![PyPI](https://img.shields.io/pypi/v/ifc5d?label=PyPI&color=006dad)](https://pypi.org/project/ifc5d/) |
| [ifcbimtester](https://docs.ifcopenshell.org/bimtester.html) | Wrapper for Gherkin based unit testing for IFC models | LGPL-3.0-or-later |
| ifcblender | Historic Blender IFC import add-on | LGPL-3.0-or-later\* |
| ifccityjson | Convert CityJSON to IFC | LGPL-3.0-or-later | [![PyPI](https://img.shields.io/pypi/v/ifccityjson?label=PyPI&color=006dad)](https://pypi.org/project/ifccityjson/) |
| ifcclash | Clash detection library and CLI app | LGPL-3.0-or-later | [![PyPI](https://img.shields.io/pypi/v/ifcclash?label=PyPI&color=006dad)](https://pypi.org/project/ifcclash/) |
| ifcconvert | CLI app to convert IFC to many other formats | LGPL-3.0-or-later\* | [![Official](https://img.shields.io/badge/IfcOpenShell.org-Download-70ba35)](https://docs.ifcopenshell.org/ifcconvert/installation.html) [![GitHub](https://img.shields.io/github/v/release/ifcopenshell/ifcopenshell?filter=ifcconvert-*&label=GitHub&color=f6f8fa)](https://github.com/IfcOpenShell/IfcOpenShell/releases?q=ifcconvert&expanded=true)
| ifccsv | Library and CLI app to export and import schedules from IFC | LGPL-3.0-or-later | [![PyPI](https://img.shields.io/pypi/v/ifccsv?label=PyPI&color=006dad)](https://pypi.org/project/ifccsv/) |
| ifcdiff | Compare changes between IFC models | LGPL-3.0-or-later | [![PyPI](https://img.shields.io/pypi/v/ifcdiff?label=PyPI&color=006dad)](https://pypi.org/project/ifcdiff/) |
| ifcfm | Extract IFC data for FM handover requirements | LGPL-3.0-or-later | [![PyPI](https://img.shields.io/pypi/v/ifcfm?label=PyPI&color=006dad)](https://pypi.org/project/ifcfm/) |
| ifcmax | Historic extension for IFC support in 3DS Max | LGPL-3.0-or-later\* | [![Official](https://img.shields.io/badge/IfcOpenShell.org-Download-70ba35)](https://docs.ifcopenshell.org/ifcmax.html)
| ifcopenshell-python | Python library for IFC manipulation | LGPL-3.0-or-later\* | [![Official](https://img.shields.io/badge/IfcOpenShell.org-Download-70ba35)](https://docs.ifcopenshell.org/ifcopenshell-python/installation.html) [![GitHub](https://img.shields.io/github/v/release/ifcopenshell/ifcopenshell?filter=ifcopenshell-python-*&label=GitHub&color=f6f8fa)](https://github.com/IfcOpenShell/IfcOpenShell/releases?q=ifcopenshell-python&expanded=true) [![PyPI](https://img.shields.io/pypi/v/ifcopenshell?label=PyPI&color=006dad)](https://pypi.org/project/ifcopenshell/) [![Anaconda](https://img.shields.io/conda/vn/conda-forge/ifcopenshell?label=Anaconda&color=43b02a)](https://anaconda.org/conda-forge/ifcopenshell) [![Anaconda](https://img.shields.io/conda/vn/ifcopenshell/ifcopenshell?label=Anaconda-Unstable&color=43b02a)](https://anaconda.org/ifcopenshell/ifcopenshell) [![Docker](https://img.shields.io/docker/pulls/aecgeeks/ifcopenshell?label=Docker&color=1D63ED)](https://hub.docker.com/r/aecgeeks/ifcopenshell) [![AUR](https://img.shields.io/aur/version/ifcopenshell?label=AUR&color=1793d1)](https://aur.archlinux.org/packages/ifcopenshell) [![AUR Unstable](https://img.shields.io/aur/version/ifcopenshell-git?label=AUR-Unstable&color=1793d1)](https://aur.archlinux.org/packages/ifcopenshell-git) |
| ifcpatch | Utility to run pre-packaged scripts to manipulate IFCs | LGPL-3.0-or-later | [![PyPI](https://img.shields.io/pypi/v/ifcpatch?label=PyPI&color=006dad)](https://pypi.org/project/ifcpatch/) |
| ifcsverchok | Blender Add-on for visual node programming with IFC | GPL-3.0-or-later | [![GitHub Unstable](https://img.shields.io/github/v/release/ifcopenshell/ifcopenshell?filter=ifcsverchok-*.*.*.*&label=GitHub-Unstable&color=f6f8fa)](https://github.com/IfcOpenShell/IfcOpenShell/releases?q=ifcsverchok&expanded=true)
| ifctester | Library, CLI and webapp for IDS model auditing | LGPL-3.0-or-later | [![PyPI](https://img.shields.io/pypi/v/ifctester?label=PyPI&color=006dad)](https://pypi.org/project/ifctester/) |
| [ifccityjson](https://docs.ifcopenshell.org/ifccityjson.html) | Convert CityJSON to IFC | LGPL-3.0-or-later | [![PyPI](https://img.shields.io/pypi/v/ifccityjson?label=PyPI&color=006dad)](https://pypi.org/project/ifccityjson/) |
| [ifcclash](https://docs.ifcopenshell.org/ifcclash.html) | Clash detection library and CLI app | LGPL-3.0-or-later | [![PyPI](https://img.shields.io/pypi/v/ifcclash?label=PyPI&color=006dad)](https://pypi.org/project/ifcclash/) |
| [ifcconvert](https://docs.ifcopenshell.org/ifcconvert.html) | CLI app to convert IFC to many other formats | LGPL-3.0-or-later\* | [![Official](https://img.shields.io/badge/IfcOpenShell.org-Download-70ba35)](https://docs.ifcopenshell.org/ifcconvert/installation.html) [![GitHub](https://img.shields.io/github/v/release/ifcopenshell/ifcopenshell?filter=ifcconvert-*&label=GitHub&color=f6f8fa)](https://github.com/IfcOpenShell/IfcOpenShell/releases?q=ifcconvert&expanded=true)
| [ifccsv](https://docs.ifcopenshell.org/ifccsv.html) | Library and CLI app to export and import schedules from IFC | LGPL-3.0-or-later | [![PyPI](https://img.shields.io/pypi/v/ifccsv?label=PyPI&color=006dad)](https://pypi.org/project/ifccsv/) |
| [ifcdiff](https://docs.ifcopenshell.org/ifcdiff.html) | Compare changes between IFC models | LGPL-3.0-or-later | [![PyPI](https://img.shields.io/pypi/v/ifcdiff?label=PyPI&color=006dad)](https://pypi.org/project/ifcdiff/) |
| [ifcfm](https://docs.ifcopenshell.org/ifcfm.html) | Extract IFC data for FM handover requirements | LGPL-3.0-or-later | [![PyPI](https://img.shields.io/pypi/v/ifcfm?label=PyPI&color=006dad)](https://pypi.org/project/ifcfm/) |
| [ifcmax](https://docs.ifcopenshell.org/ifcmax.html) | Historic extension for IFC support in 3DS Max | LGPL-3.0-or-later\* | [![Official](https://img.shields.io/badge/IfcOpenShell.org-Download-70ba35)](https://docs.ifcopenshell.org/ifcmax.html)
| [ifcopenshell-python](https://docs.ifcopenshell.org/ifcopenshell-python.html) | Python library for IFC manipulation | LGPL-3.0-or-later\* | [![Official](https://img.shields.io/badge/IfcOpenShell.org-Download-70ba35)](https://docs.ifcopenshell.org/ifcopenshell-python/installation.html) [![GitHub](https://img.shields.io/github/v/release/ifcopenshell/ifcopenshell?filter=ifcopenshell-python-*&label=GitHub&color=f6f8fa)](https://github.com/IfcOpenShell/IfcOpenShell/releases?q=ifcopenshell-python&expanded=true) [![PyPI](https://img.shields.io/pypi/v/ifcopenshell?label=PyPI&color=006dad)](https://pypi.org/project/ifcopenshell/) [![Anaconda](https://img.shields.io/conda/vn/conda-forge/ifcopenshell?label=Anaconda&color=43b02a)](https://anaconda.org/conda-forge/ifcopenshell) [![Anaconda](https://img.shields.io/conda/vn/ifcopenshell/ifcopenshell?label=Anaconda-Unstable&color=43b02a)](https://anaconda.org/ifcopenshell/ifcopenshell) [![Docker](https://img.shields.io/docker/pulls/aecgeeks/ifcopenshell?label=Docker&color=1D63ED)](https://hub.docker.com/r/aecgeeks/ifcopenshell) [![AUR](https://img.shields.io/aur/version/ifcopenshell?label=AUR&color=1793d1)](https://aur.archlinux.org/packages/ifcopenshell) [![AUR Unstable](https://img.shields.io/aur/version/ifcopenshell-git?label=AUR-Unstable&color=1793d1)](https://aur.archlinux.org/packages/ifcopenshell-git) [Pyodide WASM Wheels](https://github.com/IfcOpenShell/wasm-wheels#pyodide-test-wheels) |
| [ifcpatch](https://docs.ifcopenshell.org/ifcpatch.html) | Utility to run pre-packaged scripts to manipulate IFCs | LGPL-3.0-or-later | [![PyPI](https://img.shields.io/pypi/v/ifcpatch?label=PyPI&color=006dad)](https://pypi.org/project/ifcpatch/) |
| [ifcsverchok](https://docs.ifcopenshell.org/ifcsverchok.html) | Blender Add-on for visual node programming with IFC | GPL-3.0-or-later | [![GitHub Unstable](https://img.shields.io/github/v/release/ifcopenshell/ifcopenshell?filter=ifcsverchok-*.*.*.*&label=GitHub-Unstable&color=f6f8fa)](https://github.com/IfcOpenShell/IfcOpenShell/releases?q=ifcsverchok&expanded=true)
| [ifctester](https://docs.ifcopenshell.org/ifctester.html) | Library, CLI and webapp for IDS model auditing | LGPL-3.0-or-later | [![PyPI](https://img.shields.io/pypi/v/ifctester?label=PyPI&color=006dad)](https://pypi.org/project/ifctester/) |
The IfcOpenShell C++ codebase is split into multiple interal libraries:
+1 -1
View File
@@ -1 +1 @@
0.8.4
0.8.5
+117 -792
View File
File diff suppressed because it is too large Load Diff
-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
+444 -161
View File
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,13 @@
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 34300d41ad..09b2e0d45f 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -721,6 +721,8 @@ else()
OCCT_CHECK_AND_UNSET ("3RDPARTY_DOT_EXECUTABLE")
endif()
+list (REMOVE_ITEM BUILD_TOOLKITS ExpToCasExe)
+
# bison
if (BUILD_YACCLEX)
list (APPEND OCCT_3RDPARTY_CMAKE_LIST "adm/cmake/bison")
@@ -0,0 +1,13 @@
diff --git a/OpenCOLLADAConfig.cmake.in b/OpenCOLLADAConfig.cmake.in
index d765b108..6800c2a7 100644
--- a/OpenCOLLADAConfig.cmake.in
+++ b/OpenCOLLADAConfig.cmake.in
@@ -31,7 +31,7 @@ set(OPENCOLLADA_INCLUDE_DIRS
include("@PACKAGE_OPENCOLLADA_INST_CMAKECONFIG@/OpenCOLLADATargets.cmake")
# Set the library variable
-if(UNIX)
+if(0)
set(OPENCOLLADA_LIBRARIES
ftoa_shared
buffer_shared
+32
View File
@@ -0,0 +1,32 @@
There are two ways to build pyodide ifcopenshell Python wrapper wheel.
1. Using pyodide build system (`build_pyodide.yml` does it):
- install prebuilt pyodide build and emscripten environment (see `build_pyodide.sh`)
- clone IfcOpenShell to `IfcOpenShell` folder
- create `packages/ifcopenshell` folder that will be used by pyodide build system
- from `IfcOpenShell` move building recipe `pyodide/meta.yaml` to `packages/ifcopenshell`
- run `pyodide build-recipes ifcopenshell --install`, it will
- execute `meta.yaml` recipe - it will:
- copy IfcOpenShell source to build folder `packages/ifcopenhell/build/ifcopenshell-0.8.0`
- build ifcopenshell and its dependencies
- note that rerunning `pyodide build-recipes` will remove previous build folder and rebuild all dependencies.
The way to avoid it, if build fails, is to use `pyodide build-recipes-no-deps ifcopenshell --continue` instead.
- run `setup.py` in `IfcOpenShell` root, producing a wheel in `IfcOpenShell/dist`
- copy that wheel to `packages/ifcopenshell/dist`
- `--install` it to current build envrionment
- copy the wheel next to `dist` folder (in root directory, next to `packages`)
- add wheel to `dist/pyodide-lock.json`
2. Build it outside of pyodide build system.
Building inside pyodide build system should be preferred, option to build it outside is useful for debugging purposes,
since it's pure cmake without any additional moving parts.
- setup pyodide environment, see above
- clone IfcOpenShell repo next to it to `IfcOpenShell` folder
- run `python nix/build-all.py -wasm -py-313` in `IfcOpenShell`
- it will produce Python package in `IfcOpenShell/ifcopenshell`
- run `pyodide build`
- it will produce a wheel in `IfcOpenShell/dist`
+36
View File
@@ -0,0 +1,36 @@
#!/usr/bin/bash
set -ex
# Install uv.
curl -LsSf https://astral.sh/uv/install.sh | sh
uv venv --python 3.13
source .venv/bin/activate
# Install pyodide cross build environment.
# Instructions: https://pyodide.org/en/stable/development/building-packages.html
uv pip install pyodide-build
# `uv run` is required, so xbuildenv would skip using `pip`.
uv run pyodide xbuildenv install
# Emscripten doesn't come with xbuildenv.
git clone https://github.com/emscripten-core/emsdk
pushd emsdk
PYODIDE_EMSCRIPTEN_VERSION=$(pyodide config get emscripten_version)
./emsdk install ${PYODIDE_EMSCRIPTEN_VERSION}
./emsdk activate ${PYODIDE_EMSCRIPTEN_VERSION}
source emsdk_env.sh
which emcc
popd
mkdir -p packages/ifcopenshell
VERSION=`cat IfcOpenShell/VERSION`
cp IfcOpenShell/pyodide/meta.yaml packages/ifcopenshell
sed -i s/0.8.0/$VERSION/g packages/ifcopenshell/meta.yaml
# Use custom build ifcopenshell directory in build-all to make caching simpler
# Otherwise pyodide build path typically includes package version, so cached cmake configs might break.
export BUILD_DIR=`readlink -f ifcopenshell_build`
# Use build-recipes-no-deps first, so logs would be printed to stdout.
pyodide build-recipes-no-deps ifcopenshell
pyodide build-recipes ifcopenshell --install
+64
View File
@@ -0,0 +1,64 @@
"""
Cache built dependencies for builds.
This script is finding common install directory and either
packs each folder into a tar.gz archive, if it wasn't packed before,
or unpacks existing archives.
Usage: python cache_dependencies.py [pack|unpack]
"""
import tarfile
import sys
from pathlib import Path
from typing import Literal
CACHE_PREFIX = "cache-"
def get_install_dir() -> Path:
for data in Path.cwd().glob("*/*/install"):
return data
raise Exception("No install dir found")
def pack_dependencies(install_dir: Path) -> None:
# Process each install_dir
for dependency_path in install_dir.iterdir():
if not dependency_path.is_dir():
continue
dependency_name = dependency_path.name
tar_path = install_dir / f"{CACHE_PREFIX}{dependency_name}.tar.gz"
if tar_path.exists():
print(f"Skipping existing cache: '{tar_path}'")
else:
with tarfile.open(tar_path, "w:gz") as tar:
tar.add(dependency_path, arcname=dependency_path.name)
print(f"Created cache: '{tar_path}'")
def unpack_dependencies(install_dir: Path) -> None:
# `filter` argument was fully introduced in 3.12
# and results in deprecation warnings in 3.12-3.13, if not provided.
tar_filter: dict[Literal["filter"], Literal["data"]] = (
{"filter": "data"} if bool(sys.version_info >= (3, 12)) else {}
)
for tar_path in install_dir.glob(f"{CACHE_PREFIX}*.tar.gz"):
with tarfile.open(tar_path, "r:gz") as tar:
tar.extractall(path=install_dir, **tar_filter)
print(f"Extracted cache: '{tar_path.name}'.")
if __name__ == "__main__":
if len(sys.argv) != 2 or (action := sys.argv[1].lower()) not in ("pack", "unpack"):
print(__doc__)
sys.exit(1)
install_dir = get_install_dir()
print(f"Found install dir: '{install_dir}'")
if action == "pack":
pack_dependencies(install_dir)
else:
unpack_dependencies(install_dir)
+2 -3
View File
@@ -3,13 +3,12 @@ package:
version: 0.8.0
source:
# meta.yaml is placed as `packages/ifcopenshell/meta.yaml`.
path: ../../IfcOpenShell
build:
script: |
BUILD_CFG=Release python nix/build-all.py --without-rocksdb --without-hdf5 --without-opencollada --without-swig --without-pcre -v --wasm --py313 IfcOpenShell-Python
mv package/ifcopenshell .
cp pyodide/setup.py .
BUILD_CFG=Release python nix/build-all.py -v --wasm --py313
about:
home: http://ifcopenshell.org
+45 -9
View File
@@ -1,11 +1,47 @@
from setuptools import setup, find_packages
# setup.py is getting deprecated, but we still use it,
# because `tool.setuptools.ext-modules` is still experimental in pyproject.toml
# and we need it to get the wheel suffix right.
import os
from pathlib import Path
setup(name='ifcopenshell',
version='0.8.0',
description='IfcOpenShell is an open source (LGPL) software library for working with the Industry Foundation Classes (IFC) file format.',
author='Thomas Krijnen',
author_email='thomas@aecgeeks.com',
url='http://ifcopenshell.org',
packages=find_packages(),
package_data={'': ['*.so', '*.json']},
import tomllib
from setuptools import Extension, find_packages, setup
REPO_FOLDER = Path(__file__).parent
def get_version() -> str:
if "PKG_VERSION" in os.environ:
# Inside pyodide build environment.
return os.environ["PKG_VERSION"]
return (REPO_FOLDER / "VERSION").read_text().strip()
# Read dependencies from pyproject.toml
def get_dependencies() -> list[str]:
pyproject_toml = REPO_FOLDER / "src" / "ifcopenshell-python" / "pyproject.toml"
pyproject_data = tomllib.loads(pyproject_toml.read_text())
dependencies = pyproject_data["project"]["dependencies"]
return dependencies
setup(
name="ifcopenshell",
version=get_version(),
description=(
"IfcOpenShell is an open source (LGPL) software library "
"for working with the Industry Foundation Classes (IFC) file format."
),
author="Thomas Krijnen",
author_email="thomas@aecgeeks.com",
url="https://ifcopenshell.org",
install_requires=get_dependencies(),
packages=find_packages(include=["ifcopenshell", "ifcopenshell.*"]),
package_data={
# "*.so" is needed to include prebuilt binary extension. Otherwise it would try to build it and fail.
"ifcopenshell": ["util/schema/*.json", "util/schema/*.ifc", "*.so"],
"": ["*.json", "*.ifc"],
},
# Has to provide extension to get the correct wheel suffix.
ext_modules=[Extension("ifcopenshell._ifcopenshell_wrapper", sources=[])],
)
+27
View File
@@ -0,0 +1,27 @@
from pathlib import Path
WHEEL_FILENAME = next(
p.name for p in (Path.cwd() / "pyodide").iterdir() if p.name.startswith("ifcopenshell-") and p.suffix == ".whl"
)
def test_ifcopenshell_import(selenium):
selenium.load_package("micropip")
# Important to test it with `micropip.install`
# without any dependencies loaded to ensure micropip will load them automatically.
selenium.run_async(
f"""
import micropip
await micropip.install(f"./{WHEEL_FILENAME}")
import ifcopenshell
ifc_file = ifcopenshell.file()
wall = ifc_file.create_entity("IfcWall")
wall1 = ifc_file.by_type("IfcWall")[0]
print(wall, wall1)
assert wall == wall1, "Wall entity doesn't match"
wall.Name = "Test"
assert wall.Name == "Test", f"Entity name wasn't changed: {{wall}}"
print(wall)
"""
)
+13 -7
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",
@@ -38,6 +35,7 @@ exclude = [
"src/ifc2ca/templates",
]
[tool.ruff.lint]
preview = true
select = [
# Default Ruff rules.
# "E4", # imports
@@ -60,7 +58,15 @@ ignore = [
"UP030", # implicit references for positional format fields
"UP031", # Replace % with .format
"UP032", # Replace .format with f-string
# Deprecated Ruff rules.
"UP038", # Use X | Y in isinstance
]
[tool.poe.tasks]
ruff-main = "ruff check --extend-exclude nix/build-all.py"
# It's actually Python 3.6, but ruff only supports 3.7+, but it should do.
ruff-old = "ruff check nix/build-all.py --target-version py37"
ruff.sequence = ["ruff-main", "ruff-old"]
black = "black ."
format.sequence = ["black", "ruff-main", "ruff-old"]
+3 -12
View File
@@ -6,7 +6,7 @@ from numpy.typing import NDArray
def camera_vectors_from_element_placement(
elem_placement: NDArray[np.float64],
) -> tuple[NDArray[np.float64], NDArray[np.float64], NDArray[np.float64]]:
) -> tuple[list[float], list[float], list[float]]:
"""
Calculate the vectors of a camera pointing to an element.
@@ -22,7 +22,7 @@ def camera_vectors_from_element_placement(
def camera_vectors_from_target_position(
target_position: NDArray[np.float64], offset: Optional[NDArray[np.float64]] = None
) -> tuple[NDArray[np.float64], NDArray[np.float64], NDArray[np.float64]]:
) -> tuple[list[float], list[float], list[float]]:
"""
Calculate the vectors of a camera pointing to a target point.
@@ -38,16 +38,7 @@ def camera_vectors_from_target_position(
camera_direction = unit_vector(-camera_offset) # pylint: disable=invalid-unary-operand-type
camera_right = unit_vector(np.cross(np.array([0.0, 0.0, 1.0]), camera_direction))
camera_up = unit_vector(np.cross(camera_direction, camera_right))
return camera_position, camera_direction, camera_up
# rotation_transform = np.eye(4)
# rotation_transform[0, :3] = camera_right
# rotation_transform[1, :3] = camera_up
# rotation_transform[2, :3] = camera_direction
# translation_transform = np.eye(4)
# translation_transform[:3, -1] = -camera_position
# look_at_transform = np.matmul(rotation_transform, translation_transform)
# mat = np.linalg.inv(look_at_transform)
# return camera_position, -mat[:3, 2], mat[:3, 1]
return camera_position.tolist(), camera_direction.tolist(), camera_up.tolist()
def unit_vector(v: NDArray[np.float64]) -> NDArray[np.float64]:
+2
View File
@@ -192,6 +192,8 @@ class TopicHandler:
bcf_zip: The BCF zip file to save to.
"""
topic_dir = self.guid
# simulating directory creation (ZipFile in python < 3.11 doesn't have mkdir)
destination_zip.writestr(f"{topic_dir}/", "")
self._save_xml(destination_zip, self._markup, "markup.bcf")
self._save_viewpoints(destination_zip, topic_dir)
self._save_bim_snippet(destination_zip)
+2 -4
View File
@@ -319,9 +319,7 @@ def build_viewpoint(element: entity_instance) -> mdl.VisualizationInfo:
ifc_file = element.wrapped_data.file
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(ifc_file)
elem_placement = ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement)
elem_placement[0][3] *= unit_scale
elem_placement[1][3] *= unit_scale
elem_placement[2][3] *= unit_scale
elem_placement[:3, 3] *= unit_scale
return mdl.VisualizationInfo(
guid=str(uuid.uuid4()),
@@ -381,7 +379,7 @@ def build_camera(elem_placement: NDArray[np.float64]) -> mdl.PerspectiveCamera:
def build_camera_from_vectors(
camera_position: NDArray[np.float64], camera_dir: NDArray[np.float64], camera_up: NDArray[np.float64]
camera_position: list[float], camera_dir: list[float], camera_up: list[float]
) -> mdl.PerspectiveCamera:
"""
Return a BCF camera for an IFC element placement matrix.
+2
View File
@@ -179,6 +179,8 @@ class TopicHandler:
bcf_zip: The BCF zip file to save to.
"""
topic_dir = self.guid
# simulating directory creation (ZipFile in python < 3.11 doesn't have mkdir)
destination_zip.writestr(f"{topic_dir}/", "")
self._save_xml(destination_zip, self._markup, "markup.bcf")
self._save_viewpoints(destination_zip, topic_dir)
self._save_bim_snippet(destination_zip)
+2 -4
View File
@@ -319,9 +319,7 @@ def build_viewpoint(element: entity_instance) -> mdl.VisualizationInfo:
ifc_file = element.wrapped_data.file
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(ifc_file)
elem_placement = ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement)
elem_placement[0][3] *= unit_scale
elem_placement[1][3] *= unit_scale
elem_placement[2][3] *= unit_scale
elem_placement[:3, 3] *= unit_scale
return mdl.VisualizationInfo(
guid=str(uuid.uuid4()),
@@ -381,7 +379,7 @@ def build_camera(elem_placement: NDArray[np.float64]) -> mdl.PerspectiveCamera:
def build_camera_from_vectors(
camera_position: NDArray[np.float64], camera_dir: NDArray[np.float64], camera_up: NDArray[np.float64]
camera_position: list[float], camera_dir: list[float], camera_up: list[float]
) -> mdl.PerspectiveCamera:
"""
Return a BCF camera for an IFC element placement matrix.
+5 -2
View File
@@ -87,7 +87,7 @@ BLENDER_PLATFORM:=windows-x64
endif
# Current build commit hash.
OLD:=e795cec
OLD:=e8eb5e4
.PHONY: bump
bump:
ifndef NEW
@@ -127,7 +127,7 @@ endif
cd build && . env/$(VENV_ACTIVATE) && $(PIP) download GitPython --dest=./wheels
# Provides audio playback for costing
# This is a REALLY IMPORTANT feature
cd build && . env/$(VENV_ACTIVATE) && $(PIP) wheel git+https://github.com/Andrej730/aud.git --wheel-dir=./wheels
cd build && . env/$(VENV_ACTIVATE) && $(PIP) wheel git+https://github.com/zdhoward/aud --wheel-dir=./wheels
# IfcOpenShell dependency - support for new typing features
cd build && . env/$(VENV_ACTIVATE) && $(PIP) download typing_extensions --dest=./wheels
# Required by IfcCSV
@@ -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
+40 -9
View File
@@ -21,7 +21,7 @@ import bpy
import bpy.utils.previews
import importlib
from bpy_extras.io_utils import ImportHelper, ExportHelper
from . import handler, ui, prop, operator
from . import handler, ui, prop, operator, gizmo
from typing import Union
from collections.abc import Callable
@@ -148,7 +148,9 @@ classes = [
ui.BIM_UL_clipping_plane,
ui.BIM_UL_generic,
ui.DocPreferences,
ui.BIM_ADDON_preferences,
ui.GizmoPreferencesDoor, # Register before GizmoPreferences
ui.GizmoPreferences,
# ui.DefaultParameters and ui.BIM_ADDON_preferences are registered separately after modules (see late_classes below)
# Tabs panel
ui.BIM_PT_tabs,
# Project overview
@@ -202,6 +204,14 @@ classes = [
ui.BIM_PT_section_with_cappings,
ui.BIM_PT_decorators_overlay,
ui.BIM_PT_snappping,
# Gizmos
gizmo.GizmoArrow,
gizmo.GizmoCone,
gizmo.GizmoLock,
gizmo.GizmoArc,
gizmo.GizmoPen,
gizmo.GizmoValidate,
gizmo.GizmoCancel,
]
for mod in modules.values():
@@ -225,14 +235,33 @@ def on_register(scene):
is_registering = False
def register():
for cls in classes:
# Classes that need to be registered after modules (due to cross-module dependencies)
late_classes = (
ui.DefaultParameters, # Register before BIM_ADDON_preferences
ui.BIM_ADDON_preferences,
)
def register_classes(classes_to_register):
for cls in classes_to_register:
# Prevent crashes in Blender 4.4.0, see #6420.
if issubclass(cls, (ImportHelper, ExportHelper)):
assert getattr(cls, "bl_description", "") or cls.__doc__, cls
bpy.utils.register_class(cls)
def unregister_classes(classes_to_unregister):
for cls in reversed(classes_to_unregister):
if getattr(cls, "is_registered", None) is None:
bpy.utils.unregister_class(cls)
elif cls.is_registered:
bpy.utils.unregister_class(cls)
def register():
register_classes(classes)
bpy.app.handlers.depsgraph_update_post.append(on_register)
bpy.app.handlers.undo_post.append(handler.undo_post)
bpy.app.handlers.redo_post.append(handler.redo_post)
@@ -256,6 +285,9 @@ def register():
for mod in modules.values():
mod.register()
# Delay registering classes that depend on module classes
register_classes(late_classes)
wm = bpy.context.window_manager
if wm.keyconfigs.addon:
km = wm.keyconfigs.addon.keymaps.new(name="Window", space_type="EMPTY")
@@ -287,11 +319,7 @@ def unregister():
bpy.utils.previews.remove(icons)
for cls in reversed(classes):
if getattr(cls, "is_registered", None) is None:
bpy.utils.unregister_class(cls)
elif cls.is_registered:
bpy.utils.unregister_class(cls)
unregister_classes(classes)
bpy.app.handlers.load_post.remove(handler.load_post)
bpy.app.handlers.load_post.remove(handler.loadIfcStore)
@@ -306,6 +334,9 @@ def unregister():
bpy.types.UI_MT_button_context_menu.remove(ui.draw_custom_context_menu)
bpy.types.STATUSBAR_HT_header.remove(ui.draw_statusbar)
# Unregister late classes before modules
unregister_classes(late_classes)
for mod in reversed(list(modules.values())):
mod.unregister()
File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 70 KiB

@@ -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,
Binary file not shown.

After

Width:  |  Height:  |  Size: 215 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 217 B

@@ -13,10 +13,10 @@ DATA;
#6=IFCSIMPLEPROPERTYTEMPLATE('0AK5C2UpL4$eaac2LszAx$',$,'HasUnderlay','',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
#7=IFCSIMPLEPROPERTYTEMPLATE('2j2ZEZR8X5tONm7kli5hM6',$,'HasLinework','',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
#8=IFCSIMPLEPROPERTYTEMPLATE('1ttChRysH9UuEX2FeMj5Hu',$,'HasAnnotation','',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
#9=IFCSIMPLEPROPERTYTEMPLATE('2NPPxuABv1huDTVh32TFgw',$,'GlobalReferencing','',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
#9=IFCSIMPLEPROPERTYTEMPLATE('2NPPxuABv1huDTVh32TFgw',$,'GlobalReferencing','Whether or not this drawing can be referenced in other drawings.',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
#10=IFCSIMPLEPROPERTYTEMPLATE('10hT_1zrzEbRRKMXYAWvtD',$,'Metadata','Comma separated list of selector expressions to evaluate for each drawing elementand add results to their ''class'' attribute.\X2\000A\X0\E.g. ''Name, id'' would add to ''class'' value similar to ''Name-Wall id-1220''.\X2\000A\X0\Then it can be used to applied css styles based on the resulting class.\X2\000A\X0\If attribute is not present on the element, then it won''t be added to it''s ''class''.',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
#11=IFCSIMPLEPROPERTYTEMPLATE('3Z0BXPSG5CWgtI33ioV7aj',$,'Include','Selector expression to include ifc elements in the drawing',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
#12=IFCSIMPLEPROPERTYTEMPLATE('1RVts_g3PAw98PJA2yL3bO',$,'Exclude','Selector expression to exclude ifc elements in the drawing',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
#11=IFCSIMPLEPROPERTYTEMPLATE('3Z0BXPSG5CWgtI33ioV7aj',$,'Include','Selector expression to include elements in the drawing',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
#12=IFCSIMPLEPROPERTYTEMPLATE('1RVts_g3PAw98PJA2yL3bO',$,'Exclude','Selector expression to exclude elements in the drawing',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
#13=IFCSIMPLEPROPERTYTEMPLATE('0c1$8NpYDEaBiJrj16jHIo',$,'Stylesheet','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
#14=IFCSIMPLEPROPERTYTEMPLATE('3mRF52q81FQB$h4oTh7M45',$,'Markers','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
#15=IFCSIMPLEPROPERTYTEMPLATE('1rhr_0N3LDtuORcEJP0KXM',$,'Symbols','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
@@ -5,10 +5,10 @@ FILE_NAME('Psets_BBIM_Annotation.ifc','2020-01-01T00:00:00',$,$,'Psets_BBIM_Anno
FILE_SCHEMA(('IFC4'));
ENDSEC;
DATA;
#1=IFCPROPERTYSETTEMPLATE('3VuPUwdCD2Qx3XDDRs0R1N',$,'EPset_Annotation','',.PSET_TYPEDRIVENOVERRIDE.,'IfcAnnotation,IfcTypeProduct',(#2,#3,#4,#29));
#1=IFCPROPERTYSETTEMPLATE('3VuPUwdCD2Qx3XDDRs0R1N',$,'EPset_Annotation','',.PSET_TYPEDRIVENOVERRIDE.,'IfcAnnotation,IfcTypeProduct',(#4,#33,#29,#32,#3,#2));
#2=IFCSIMPLEPROPERTYTEMPLATE('2P7JN79n96Q9pElZ83LKe4',$,'ZIndex','',.P_SINGLEVALUE.,'IfcInteger',$,$,$,$,$,.READWRITE.);
#3=IFCSIMPLEPROPERTYTEMPLATE('1Wpx_r2xj1_9w5JpI0QRJy',$,'Symbol','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
#4=IFCSIMPLEPROPERTYTEMPLATE('3q0oxMUKP47vZ4jnyG$dDb',$,'Classes','Classes separarated by spaces that end up in classes for this element in svg. Can be used to specify the text font size: small - 1.8mm; regular - 2.5mm; large - 3.5mm; header - 5mm; title - 7mm. By default regular size is used.',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
#4=IFCSIMPLEPROPERTYTEMPLATE('3q0oxMUKP47vZ4jnyG$dDb',$,'Classes','Classes separated by spaces that end up in classes for this element in svg. Can be used to specify the text font size: small - 1.8mm; regular - 2.5mm; large - 3.5mm; header - 5mm; title - 7mm. By default regular size is used.',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
#5=IFCPROPERTYSETTEMPLATE('0iKwujnQL9IevVQato8f7Z',$,'BBIM_Batting','',.PSET_TYPEDRIVENOVERRIDE.,'IfcAnnotation/BATTING,IfcTypeProduct',(#6,#7));
#6=IFCSIMPLEPROPERTYTEMPLATE('0t2LEesGT1QRQtrIZUAR8L',$,'Thickness','Batting thickness',.P_SINGLEVALUE.,'IfcPositiveLengthMeasure',$,$,$,$,$,.READWRITE.);
#7=IFCSIMPLEPROPERTYTEMPLATE('082PndS6v2kBOiJoSboMnh',$,'Reverse pattern direction','Reverse batting pattern (swap starting and ending points)',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
@@ -36,5 +36,7 @@ DATA;
#29=IFCSIMPLEPROPERTYTEMPLATE('2pJmUDpB50VBdCOib1zcJJ',$,'Newline_At','',.P_SINGLEVALUE.,'IfcInteger',$,$,$,$,$,.READWRITE.);
#30=IFCSIMPLEPROPERTYTEMPLATE('2TJn72t_v2cvBUG916Dpev',$,'CustomUnit','Dimension''s custom unit',.P_ENUMERATEDVALUE.,'IfcText',$,#31,$,$,$,.READWRITE.);
#31=IFCPROPERTYENUMERATION('CustomUnit',(IFCTEXT('Feet and Inches - Fractional'),IFCTEXT('Feet - Decimal'),IFCTEXT('Inches - Fractional'),IFCTEXT('Inches - Decimal'),IFCTEXT('Meters'),IFCTEXT('Decimeters'),IFCTEXT('Centimeters'),IFCTEXT('Millimeters')),$);
#32=IFCSIMPLEPROPERTYTEMPLATE('0gjJzDYBX8P85qn1xcAOOo',$,'Reverse_List','',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
#33=IFCSIMPLEPROPERTYTEMPLATE('22TrcxF8jFNB4buSmzjGEF',$,'List_Separator','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
ENDSEC;
END-ISO-10303-21;
+12 -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
@@ -36,6 +37,7 @@ from bonsai.bim.ifc import IfcStore
from mathutils import Vector
from typing import Union
from logging import Logger
from math import radians
class IfcExporter:
@@ -104,7 +106,16 @@ class IfcExporter:
def sync_object_placement(self, obj: bpy.types.Object) -> Union[ifcopenshell.entity_instance, None]:
element = self.file.by_id(tool.Blender.get_object_bim_props(obj).ifc_definition_id)
if tool.Geometry.is_scaled(obj):
# Handle camera scales specially
if obj.type == "CAMERA":
# Check if this is a reflected ceiling plan camera
camera = tool.Ifc.get_entity(obj)
if ifcopenshell.util.element.get_pset(camera, "EPset_Drawing", "TargetView") == "REFLECTED_PLAN_VIEW":
# Ensure reflected ceiling cameras have the correct scale
if obj.scale != (-1, -1, -1):
obj.scale = (-1, -1, -1)
# Skip all other scale handling for cameras
elif tool.Geometry.is_scaled(obj):
bpy.ops.bim.update_representation(obj=obj.name)
# update_representation might not apply scale if the object has openings
# reset it, so let user know that the scale wasn't saved.
+805
View File
@@ -0,0 +1,805 @@
# Bonsai - OpenBIM Blender Add-on
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
#
# This file is part of Bonsai.
#
# Bonsai is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Bonsai is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
"""
Shared gizmo components for reuse across modules.
"""
import bpy
import math
from typing import Optional
from mathutils import Vector
from mathutils.bvhtree import BVHTree
from mathutils.geometry import intersect_line_line
from bpy_extras.view3d_utils import region_2d_to_vector_3d, region_2d_to_origin_3d
import gpu
from gpu_extras.batch import batch_for_shader
SNAP_CIRCLE_SEGMENTS = 16
SNAP_CIRCLE_RADIUS = 0.1
SNAP_CIRCLE_COLOR = (1.0, 0.5, 0.0, 1.0)
SNAP_LINE_WIDTH = 3.0
SNAP_MAX_RADIUS = 10.0
ARROW_SHAFT_LENGTH = 0.8
ARROW_HEAD_LENGTH = 0.2
ARROW_WIDTH = 0.015
ARROW_HEAD_WIDTH_MULTIPLIER = 10
ARROW_CIRCLE_SEGMENTS = 8
CONE_LENGTH = 1.0
CONE_RADIUS = 0.35
CONE_SEGMENTS = 16
ARC_SEGMENTS = 24
ARC_LINE_WIDTH = 0.015
PRECISION_MODE_MULTIPLIER = 0.1
class SnapManager:
"""Manages snap point visualization and mesh snapping."""
def __init__(self):
self._snap_point: Optional[tuple[float, float, float]] = None
self._draw_handler = None
self._shader = None
def set_snap_point(self, point: Optional[tuple[float, float, float]]) -> None:
"""Set snap point and register draw handler if needed."""
self._snap_point = point
if self._draw_handler is None and point is not None:
self._draw_handler = bpy.types.SpaceView3D.draw_handler_add(self._draw, (), "WINDOW", "POST_VIEW")
self._redraw_viewport()
def clear(self) -> None:
"""Clear snap point and unregister handler."""
self._snap_point = None
if self._draw_handler is not None:
bpy.types.SpaceView3D.draw_handler_remove(self._draw_handler, "WINDOW")
self._draw_handler = None
self._redraw_viewport()
def _draw(self) -> None:
"""Draw snap point circles."""
if self._snap_point is None:
return
if self._shader is None:
self._shader = gpu.shader.from_builtin("UNIFORM_COLOR")
self._shader.bind()
self._shader.uniform_float("color", SNAP_CIRCLE_COLOR)
gpu.state.line_width_set(SNAP_LINE_WIDTH)
for plane in ("XY", "XZ", "YZ"):
vertices = generate_circle_vertices(self._snap_point, SNAP_CIRCLE_RADIUS, SNAP_CIRCLE_SEGMENTS, plane)
batch = batch_for_shader(self._shader, "LINE_STRIP", {"pos": vertices})
batch.draw(self._shader)
gpu.state.line_width_set(1.0)
@staticmethod
def _redraw_viewport() -> None:
"""Force 3D viewport redraw."""
for area in bpy.context.screen.areas:
if area.type == "VIEW_3D":
area.tag_redraw()
@staticmethod
def snap_to_mesh(
location: Vector, context: bpy.types.Context, axis_vector: Vector, active_obj: bpy.types.Object
) -> Vector:
"""Snap a location to the nearest mesh element if snapping is enabled.
Args:
location: The world space location to snap
context: The Blender context
axis_vector: The axis direction of the gizmo
active_obj: The active object to exclude from snapping
Returns:
The snapped location or original location if snapping is disabled
"""
tool_settings = context.scene.tool_settings
if not tool_settings.use_snap:
return location
snap_elements = tool_settings.snap_elements_base
mesh_objects = [
obj for obj in context.visible_objects if obj.type == "MESH" and obj != active_obj and obj.visible_get()
]
if not mesh_objects:
return location
nearby_objects = []
for obj in mesh_objects:
bbox_corners = [obj.matrix_world @ Vector(corner) for corner in obj.bound_box]
for corner in bbox_corners:
if (corner - location).length <= SNAP_MAX_RADIUS:
nearby_objects.append(obj)
break
if not nearby_objects:
return location
closest_point = None
closest_distance = float("inf")
for obj in nearby_objects:
if not obj.data.vertices:
continue
depsgraph = context.evaluated_depsgraph_get()
obj_eval = obj.evaluated_get(depsgraph)
mesh = obj_eval.to_mesh()
if not mesh.vertices:
obj_eval.to_mesh_clear()
continue
world_vertices = [obj.matrix_world @ v.co for v in mesh.vertices]
if "VERTEX" in snap_elements:
for v_co in world_vertices:
dist = (v_co - location).length
if dist < closest_distance:
closest_distance = dist
closest_point = v_co
if "EDGE" in snap_elements:
for edge in mesh.edges:
v1 = world_vertices[edge.vertices[0]]
v2 = world_vertices[edge.vertices[1]]
edge_vec = v2 - v1
edge_len_sq = edge_vec.length_squared
if edge_len_sq > 0:
t = max(0, min(1, (location - v1).dot(edge_vec) / edge_len_sq))
closest_on_edge = v1 + t * edge_vec
dist = (closest_on_edge - location).length
if dist < closest_distance:
closest_distance = dist
closest_point = closest_on_edge
if "FACE" in snap_elements:
bvh = BVHTree.FromPolygons(world_vertices, [p.vertices for p in mesh.polygons])
nearest_loc, normal, index, dist = bvh.find_nearest(location)
if nearest_loc and dist < closest_distance:
closest_distance = dist
closest_point = nearest_loc
obj_eval.to_mesh_clear()
if closest_point and closest_distance < SNAP_MAX_RADIUS:
return closest_point
return location
_snap_manager = SnapManager()
def set_snap_point(point: Optional[tuple[float, float, float]]) -> None:
_snap_manager.set_snap_point(point)
def clear_snap_point() -> None:
_snap_manager.clear()
def snap_to_mesh(
location: Vector, context: bpy.types.Context, axis_vector: Vector, active_obj: bpy.types.Object
) -> Vector:
return _snap_manager.snap_to_mesh(location, context, axis_vector, active_obj)
def generate_circle_vertices(
center: tuple[float, float, float], radius: float, segments: int, plane: str = "XY"
) -> list[tuple[float, float, float]]:
"""Generate circle vertices in specified plane.
Args:
center: (x, y, z) tuple for circle center
radius: Circle radius
segments: Number of segments
plane: 'XY', 'XZ', or 'YZ'
Returns:
List of (x, y, z) vertices
"""
vertices = []
for i in range(segments + 1):
angle = (2 * math.pi * i) / segments
cos_a = radius * math.cos(angle)
sin_a = radius * math.sin(angle)
if plane == "XY":
vertices.append((center[0] + cos_a, center[1] + sin_a, center[2]))
elif plane == "XZ":
vertices.append((center[0] + cos_a, center[1], center[2] + sin_a))
else:
vertices.append((center[0], center[1] + cos_a, center[2] + sin_a))
return vertices
def create_quarter_circle_arc(
radius: float = 1.0, segments: int = ARC_SEGMENTS, direction: str = "LEFT", line_width: float = ARC_LINE_WIDTH
) -> tuple[tuple[float, float, float], ...]:
"""Create a quarter circle arc with cross-section thickness for visibility from all angles.
Args:
radius: Radius of the arc
segments: Number of segments for smoothness
direction: 'LEFT' for counterclockwise (0 to 90°), 'RIGHT' for clockwise (0 to -90°)
line_width: Width of the arc line in both perpendicular directions
Returns:
Tuple of arc triangles for drawing geometry visible from all angles
"""
half_width = line_width / 2
arc_points = []
if direction == "LEFT":
for i in range(segments + 1):
angle = (math.pi / 2) * (i / segments)
x = radius * math.cos(angle)
y = radius * math.sin(angle)
arc_points.append((x, y))
else:
for i in range(segments + 1):
angle = (math.pi / 2) * (i / segments)
x = -radius * math.cos(angle)
y = radius * math.sin(angle)
arc_points.append((x, y))
arc_triangles = []
for i in range(len(arc_points) - 1):
x1, y1 = arc_points[i]
x2, y2 = arc_points[i + 1]
dx, dy = x2 - x1, y2 - y1
length = (dx**2 + dy**2) ** 0.5
if length > 0:
px, py = -dy / length * half_width, dx / length * half_width
arc_triangles.extend(
[
(x1 + px, y1 + py, 0.0),
(x1 - px, y1 - py, 0.0),
(x2 + px, y2 + py, 0.0),
]
)
arc_triangles.extend(
[
(x2 + px, y2 + py, 0.0),
(x1 - px, y1 - py, 0.0),
(x2 - px, y2 - py, 0.0),
]
)
arc_triangles.extend(
[
(x1, y1, -half_width),
(x2, y2, -half_width),
(x1, y1, +half_width),
]
)
arc_triangles.extend(
[
(x1, y1, +half_width),
(x2, y2, -half_width),
(x2, y2, +half_width),
]
)
return tuple(arc_triangles)
class GizmoMovable(bpy.types.Gizmo):
"""Base class for movable gizmos with snapping and precision controls.
This class provides common functionality for gizmos that can be dragged
to modify values, including:
- Snap to mesh support (toggle with Ctrl)
- Precision mode (hold Shift for 10x slower movement)
- Header text with value and modifier hints
- Consistent invoke/exit/modal behavior
"""
__slots__ = (
"custom_shape",
"init_value",
"move_get_cb",
"move_set_cb",
"axis",
"local_axis",
"start_location",
"active_obj",
"initial_snap_state",
)
def invoke(self, context: bpy.types.Context, event: bpy.types.Event) -> set:
self.init_value = self.move_get_cb() if self.move_get_cb else 0.0
self.start_location = self.matrix_basis.translation.copy()
self.active_obj = context.active_object
self.initial_snap_state = context.scene.tool_settings.use_snap
return {"RUNNING_MODAL"}
def exit(self, context: bpy.types.Context, cancel: bool) -> None:
context.area.header_text_set(None)
if cancel and self.move_set_cb:
self.move_set_cb(self.init_value)
if hasattr(self, "initial_snap_state"):
context.scene.tool_settings.use_snap = self.initial_snap_state
clear_snap_point()
def get_axis_direction(self) -> Vector:
return self.axis
def modal(self, context: bpy.types.Context, event: bpy.types.Event, tweak) -> set:
region = context.region
rv3d = context.region_data
tool_settings = context.scene.tool_settings
tool_settings.use_snap = not self.initial_snap_state if event.ctrl else self.initial_snap_state
current_coord = (event.mouse_region_x, event.mouse_region_y)
view_origin = region_2d_to_origin_3d(region, rv3d, current_coord)
view_direction = region_2d_to_vector_3d(region, rv3d, current_coord)
axis_direction = self.get_axis_direction()
result = intersect_line_line(
view_origin,
view_origin + view_direction * 1000,
self.start_location,
self.start_location + axis_direction * 1000,
)
current_3d = result[1] if result else self.start_location
delta = (current_3d - self.start_location).dot(axis_direction)
if tool_settings.use_snap and self.active_obj:
snapped_pos = snap_to_mesh(current_3d, context, axis_direction, self.active_obj)
if snapped_pos != current_3d:
delta = (snapped_pos - self.start_location).dot(axis_direction)
set_snap_point(snapped_pos)
else:
clear_snap_point()
else:
clear_snap_point()
if event.shift:
delta *= PRECISION_MODE_MULTIPLIER
if self.move_set_cb:
self.move_set_cb(self.init_value + delta)
self._update_header(context, self.init_value + delta, tool_settings.use_snap, event.shift)
return {"RUNNING_MODAL"}
def _update_header(self, context: bpy.types.Context, value: float, is_snapping: bool, is_precision: bool) -> None:
header_text = f"Value: {value:.3f}m"
hints = []
if is_snapping:
hints.append("Snapping: ON")
if is_precision:
hints.append("Precision Mode (0.1x)")
hints.extend(["Ctrl: Toggle Snap", "Shift: Precision"])
if hints:
header_text += " | " + " | ".join(hints)
context.area.header_text_set(header_text)
class GizmoLock(bpy.types.Gizmo):
"""Reusable lock icon gizmo that switches between closed and open states."""
bl_idname = "VIEW3D_GT_lock"
__slots__ = (
"custom_shape_closed",
"custom_shape_open",
"prop_path",
)
tris_closed = (
(0.16803650558, 0.18791499734, 0.0),
(-0.07805634290, 0.18791499734, 0.0),
(0.16803650558, 0.44701099396, 0.0),
(0.16803650558, 0.44701099396, 0.0),
(-0.07805634290, 0.18791499734, 0.0),
(-0.07805634290, 0.44701099396, 0.0),
(0.20165449381, 0.13603900373, 0.0),
(-0.11167433113, 0.13603900373, 0.0),
(0.20165449381, -0.44701099396, 0.0),
(0.20165449381, -0.44701099396, 0.0),
(-0.11167433113, 0.13603900373, 0.0),
(-0.11167433113, -0.44701099396, 0.0),
(-0.07805634290, 0.18791499734, 0.0),
(-0.39353451133, 0.18791499734, 0.0),
(-0.07805634290, 0.44701099396, 0.0),
(-0.07805634290, 0.44701099396, 0.0),
(-0.39353451133, 0.18791499734, 0.0),
(-0.39353451133, 0.30746498704, 0.0),
(-0.44701099396, 0.18791499734, 0.0),
(-0.44701099396, -0.04477182776, 0.0),
(-0.39353451133, 0.18791499734, 0.0),
(-0.39353451133, 0.18791499734, 0.0),
(-0.44701099396, -0.04477182776, 0.0),
(-0.39353451133, -0.04477182776, 0.0),
)
tris_open = (
(0.16803650558, 0.18791499734, 0.0),
(-0.07805634290, 0.18791499734, 0.0),
(0.16803650558, 0.44701099396, 0.0),
(0.16803650558, 0.44701099396, 0.0),
(-0.07805634290, 0.18791499734, 0.0),
(-0.07805634290, 0.44701099396, 0.0),
(0.20165449381, 0.13603900373, 0.0),
(-0.11167433113, 0.13603900373, 0.0),
(0.20165449381, -0.44701099396, 0.0),
(0.20165449381, -0.44701099396, 0.0),
(-0.11167433113, 0.13603900373, 0.0),
(-0.11167433113, -0.44701099396, 0.0),
(0.16803650558, 0.44701099396, 0.0),
(-0.07805634290, 0.44701099396, 0.0),
(0.16803650558, 0.70610702038, 0.0),
(0.16803650558, 0.70610702038, 0.0),
(-0.07805634290, 0.44701099396, 0.0),
(-0.07805634290, 0.58656096458, 0.0),
(-0.11167433113, 0.44701099396, 0.0),
(-0.11167433113, 0.73432201147, 0.0),
(-0.07805634290, 0.44701099396, 0.0),
(-0.07805634290, 0.44701099396, 0.0),
(-0.11167433113, 0.73432201147, 0.0),
(-0.07805634290, 0.73432201147, 0.0),
)
def get_custom_shape(self, context: bpy.types.Context):
obj = context.active_object
if not obj:
return self.custom_shape_closed
try:
is_open = obj.path_resolve(self.prop_path)
return self.custom_shape_open if is_open else self.custom_shape_closed
except:
return self.custom_shape_closed
def setup(self) -> None:
self.custom_shape_closed = self.new_custom_shape("TRIS", self.tris_closed)
self.custom_shape_open = self.new_custom_shape("TRIS", self.tris_open)
def draw(self, context: bpy.types.Context) -> None:
self.draw_custom_shape(self.get_custom_shape(context))
def draw_select(self, context: bpy.types.Context, select_id: int) -> None:
self.draw_custom_shape(self.get_custom_shape(context), select_id=select_id)
class GizmoArc(bpy.types.Gizmo):
"""Reusable quarter circle arc gizmo."""
bl_idname = "VIEW3D_GT_arc"
__slots__ = (
"custom_shape_left",
"custom_shape_right",
"prop_path",
)
def setup(self) -> None:
"""Create quarter circle shapes for both LEFT and RIGHT directions."""
arc_left = create_quarter_circle_arc(radius=1.0, direction="LEFT")
arc_right = create_quarter_circle_arc(radius=1.0, direction="RIGHT")
self.custom_shape_left = self.new_custom_shape(type="TRIS", verts=arc_left)
self.custom_shape_right = self.new_custom_shape(type="TRIS", verts=arc_right)
def _get_shape_for_direction(self, context: bpy.types.Context):
obj = context.active_object
if not obj:
return self.custom_shape_left
try:
direction_value = obj.path_resolve(self.prop_path)
if "RIGHT" in str(direction_value):
return self.custom_shape_right
except:
pass
return self.custom_shape_left
def draw(self, context: bpy.types.Context) -> None:
self.draw_custom_shape(self._get_shape_for_direction(context))
def draw_select(self, context: bpy.types.Context, select_id: int) -> None:
self.draw_custom_shape(self._get_shape_for_direction(context), select_id=select_id)
class GizmoPen(bpy.types.Gizmo):
"""Reusable pen/edit icon gizmo for entering edit mode."""
bl_idname = "VIEW3D_GT_pen"
__slots__ = ("custom_shape",)
tris = (
(-0.07595771551132202, -0.2948460578918457, 0.0),
(0.16886109113693237, 0.23203276097774506, 0.0),
(0.062240585684776306, 0.28157487511634827, 0.0),
(0.07201281189918518, 0.30260589718818665, 0.0),
(0.21042980253696442, 0.321493536233902, 0.0),
(0.17863331735134125, 0.25306373834609985, 0.0),
(0.062240585684776306, 0.28157487511634827, 0.0),
(-0.1825782209634781, -0.2453039139509201, 0.0),
(-0.07595771551132202, -0.2948460578918457, 0.0),
(-0.1825782209634781, -0.2453039139509201, 0.0),
(-0.19114767014980316, -0.4032624065876007, 0.0),
(-0.07595771551132202, -0.2948460578918457, 0.0),
(0.07201281189918518, 0.30260589718818665, 0.0),
(0.10380929708480835, 0.371035635471344, 0.0),
(0.21042980253696442, 0.321493536233902, 0.0),
)
def setup(self) -> None:
self.custom_shape = self.new_custom_shape("TRIS", self.tris)
def draw(self, context: bpy.types.Context) -> None:
self.draw_custom_shape(self.custom_shape)
def draw_select(self, context: bpy.types.Context, select_id: int) -> None:
self.draw_custom_shape(self.custom_shape, select_id=select_id)
class GizmoValidate(bpy.types.Gizmo):
"""Reusable validate/checkmark icon gizmo for confirming edits."""
bl_idname = "VIEW3D_GT_validate"
__slots__ = ("custom_shape",)
tris = (
(0.36775994300842285, 0.205583393573761, 0.0),
(0.030080009251832962, -0.1881658434867859, 0.0),
(0.030080009251832962, -0.3380376696586609, 0.0),
(-0.22017201781272888, -0.16090886294841766, 0.0),
(0.030080009251832962, -0.1881658434867859, 0.0),
(-0.22017201781272888, -0.011037036776542664, 0.0),
(0.36775994300842285, 0.205583393573761, 0.0),
(0.36775994300842285, 0.355455219745636, 0.0),
(0.030080009251832962, -0.1881658434867859, 0.0),
(-0.22017201781272888, -0.16090886294841766, 0.0),
(0.030080009251832962, -0.3380376696586609, 0.0),
(0.030080009251832962, -0.1881658434867859, 0.0),
)
def setup(self) -> None:
self.custom_shape = self.new_custom_shape("TRIS", self.tris)
def draw(self, context: bpy.types.Context) -> None:
self.draw_custom_shape(self.custom_shape)
def draw_select(self, context: bpy.types.Context, select_id: int) -> None:
self.draw_custom_shape(self.custom_shape, select_id=select_id)
class GizmoCancel(bpy.types.Gizmo):
"""Reusable cancel/X icon gizmo for canceling edits."""
bl_idname = "VIEW3D_GT_cancel"
__slots__ = ("custom_shape",)
tris = (
(0.0, 0.048707593232393265, 0.0),
(0.0, -0.048707593232393265, 0.0),
(0.048707593232393265, 0.0, 0.0),
(0.0, 0.048707593232393265, 0.0),
(-0.21918421983718872, 0.2678918242454529, 0.0),
(-0.048707593232393265, 0.0, 0.0),
(-0.21918421983718872, 0.2678918242454529, 0.0),
(-0.2678918242454529, 0.21918421983718872, 0.0),
(-0.048707593232393265, 0.0, 0.0),
(-0.048707593232393265, 0.0, 0.0),
(-0.2678918242454529, -0.21918421983718872, 0.0),
(-0.21918421983718872, -0.2678918242454529, 0.0),
(0.2678918242454529, 0.21918421983718872, 0.0),
(0.21918421983718872, 0.2678918242454529, 0.0),
(0.0, 0.048707593232393265, 0.0),
(0.21918421983718872, -0.2678918242454529, 0.0),
(0.2678918242454529, -0.21918421983718872, 0.0),
(0.048707593232393265, 0.0, 0.0),
(0.048707593232393265, 0.0, 0.0),
(0.2678918242454529, 0.21918421983718872, 0.0),
(0.0, 0.048707593232393265, 0.0),
(0.0, 0.048707593232393265, 0.0),
(-0.048707593232393265, 0.0, 0.0),
(0.0, -0.048707593232393265, 0.0),
(-0.048707593232393265, 0.0, 0.0),
(-0.21918421983718872, -0.2678918242454529, 0.0),
(0.0, -0.048707593232393265, 0.0),
(0.0, -0.048707593232393265, 0.0),
(0.21918421983718872, -0.2678918242454529, 0.0),
(0.048707593232393265, 0.0, 0.0),
)
def setup(self) -> None:
self.custom_shape = self.new_custom_shape("TRIS", self.tris)
def draw(self, context: bpy.types.Context) -> None:
self.draw_custom_shape(self.custom_shape)
def draw_select(self, context: bpy.types.Context, select_id: int) -> None:
self.draw_custom_shape(self.custom_shape, select_id=select_id)
class GizmoArrow(GizmoMovable):
"""Arrow gizmo for directional value editing."""
bl_idname = "BIM_GT_gizmo_arrow"
bl_target_properties = ({"id": "offset", "type": "FLOAT", "array_length": 1},)
def _get_arrow_triangles(self) -> tuple[tuple[float, float, float], ...]:
"""Generate arrow geometry along +X axis."""
triangles = []
triangles.extend(
[
(0, -ARROW_WIDTH, 0),
(ARROW_SHAFT_LENGTH, -ARROW_WIDTH, 0),
(0, +ARROW_WIDTH, 0),
]
)
triangles.extend(
[
(0, +ARROW_WIDTH, 0),
(ARROW_SHAFT_LENGTH, -ARROW_WIDTH, 0),
(ARROW_SHAFT_LENGTH, +ARROW_WIDTH, 0),
]
)
triangles.extend(
[
(0, 0, -ARROW_WIDTH),
(ARROW_SHAFT_LENGTH, 0, -ARROW_WIDTH),
(0, 0, +ARROW_WIDTH),
]
)
triangles.extend(
[
(0, 0, +ARROW_WIDTH),
(ARROW_SHAFT_LENGTH, 0, -ARROW_WIDTH),
(ARROW_SHAFT_LENGTH, 0, +ARROW_WIDTH),
]
)
head_width = ARROW_WIDTH * ARROW_HEAD_WIDTH_MULTIPLIER
triangles.extend(
[
(ARROW_SHAFT_LENGTH, -head_width, 0),
(ARROW_SHAFT_LENGTH + ARROW_HEAD_LENGTH, 0, 0),
(ARROW_SHAFT_LENGTH, +head_width, 0),
]
)
triangles.extend(
[
(ARROW_SHAFT_LENGTH, 0, -head_width),
(ARROW_SHAFT_LENGTH + ARROW_HEAD_LENGTH, 0, 0),
(ARROW_SHAFT_LENGTH, 0, +head_width),
]
)
for i in range(ARROW_CIRCLE_SEGMENTS):
angle1 = (2 * math.pi * i) / ARROW_CIRCLE_SEGMENTS
angle2 = (2 * math.pi * (i + 1)) / ARROW_CIRCLE_SEGMENTS
y1 = head_width * math.cos(angle1)
z1 = head_width * math.sin(angle1)
y2 = head_width * math.cos(angle2)
z2 = head_width * math.sin(angle2)
triangles.extend(
[
(ARROW_SHAFT_LENGTH, 0, 0),
(ARROW_SHAFT_LENGTH, y1, z1),
(ARROW_SHAFT_LENGTH, y2, z2),
]
)
return tuple(triangles)
def setup(self) -> None:
self.custom_shape = self.new_custom_shape("TRIS", self._get_arrow_triangles())
def draw(self, context: bpy.types.Context) -> None:
self.draw_custom_shape(self.custom_shape)
def draw_select(self, context: bpy.types.Context, select_id: int) -> None:
self.draw_custom_shape(self.custom_shape, select_id=select_id)
class GizmoCone(GizmoMovable):
"""Cone gizmo for directional value editing with local axis support."""
bl_idname = "BIM_GT_gizmo_cone"
bl_target_properties = ({"id": "offset", "type": "FLOAT", "array_length": 1},)
def get_axis_direction(self) -> Vector:
if hasattr(self, "local_axis") and self.active_obj:
obj_rotation = self.active_obj.matrix_world.to_3x3()
axis_direction = obj_rotation @ self.local_axis
axis_direction.normalize()
return axis_direction
return self.axis
def _get_cone_triangles(self) -> tuple[tuple[float, float, float], ...]:
"""Generate cone geometry along +X axis."""
triangles = []
cone_tip_x = CONE_LENGTH
for i in range(CONE_SEGMENTS):
angle1 = (2 * math.pi * i) / CONE_SEGMENTS
angle2 = (2 * math.pi * (i + 1)) / CONE_SEGMENTS
y1 = CONE_RADIUS * math.cos(angle1)
z1 = CONE_RADIUS * math.sin(angle1)
y2 = CONE_RADIUS * math.cos(angle2)
z2 = CONE_RADIUS * math.sin(angle2)
triangles.extend(
[
(cone_tip_x, 0, 0),
(0, y1, z1),
(0, y2, z2),
]
)
triangles.extend(
[
(0, 0, 0),
(0, y2, z2),
(0, y1, z1),
]
)
return tuple(triangles)
def setup(self) -> None:
self.custom_shape = self.new_custom_shape("TRIS", self._get_cone_triangles())
def draw(self, context: bpy.types.Context) -> None:
self.draw_custom_shape(self.custom_shape)
def draw_select(self, context: bpy.types.Context, select_id: int) -> None:
self.draw_custom_shape(self.custom_shape, select_id=select_id)
-2
View File
@@ -222,8 +222,6 @@ def refresh_ui_data():
if isinstance(ifc_file := tool.Ifc.get(), ifcopenshell.sqlite):
ifc_file.clear_cache()
props = tool.Drawing.get_document_props()
props.should_draw_decorations = props.should_draw_decorations
if tool.Web.get_web_props().is_connected:
tool.Web.send_webui_data()
+1
View File
@@ -556,6 +556,7 @@ def draw_filter(
elif ifc_filter.type == "query":
row = box.row(align=True)
row.prop(ifc_filter, "name", text="", icon="POINTCLOUD_DATA")
row.prop(ifc_filter, "comparison", text="")
row.prop(ifc_filter, "value", text="")
elif ifc_filter.type == "instance":
row = box.row(align=True)
+37 -11
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
@@ -283,6 +284,7 @@ class IfcImporter:
self.place_objects_in_collections()
self.profile_code("Place objects in collections")
self.setup_arrays()
self.update_linked_aggregates()
self.profile_code("Setup arrays")
tool.Project.load_linked_models_from_ifc()
self.profile_code("Load linked models")
@@ -624,18 +626,13 @@ class IfcImporter:
print("Done creating geometry")
def create_spatial_elements(self) -> None:
if tool.Blender.get_addon_preferences().spatial_elements_unselectable:
self.create_generic_elements(self.spatial_elements, unselectable=True)
else:
self.create_generic_elements(self.spatial_elements, unselectable=False)
self.create_generic_elements(self.spatial_elements)
def create_elements(self) -> None:
self.create_generic_elements(self.elements)
self.create_generic_elements(self.gross_elements, is_gross=True)
def create_generic_elements(
self, elements: set[ifcopenshell.entity_instance], unselectable=False, is_gross=False
) -> None:
def create_generic_elements(self, elements: set[ifcopenshell.entity_instance], is_gross=False) -> None:
if isinstance(self.file, ifcopenshell.sqlite):
return self.create_generic_sqlite_elements(elements)
@@ -659,10 +656,6 @@ class IfcImporter:
print("{} / {} elements processed ...".format(i, total))
objects.add(self.create_product(element))
if unselectable:
for obj in objects:
obj.hide_select = True
def create_generic_sqlite_elements(self, elements: set[ifcopenshell.entity_instance]) -> None:
assert isinstance(self.file, ifcopenshell.sql.sqlite)
self.geometry_cache = self.file.get_geometry([e.id() for e in elements])
@@ -960,6 +953,9 @@ class IfcImporter:
if not props.ifc_file:
props.ifc_file = self.ifc_import_settings.input_file
self.file = tool.Ifc.get()
# IFC4 Reference View shall have no booleans https://github.com/BuildingSMART/IFC4-CV/issues/14
if self.file.schema == "IFC4" and "ReferenceView" in str(self.file.header.file_description.description):
self.ifc_import_settings.void_limit = 0
def calculate_unit_scale(self):
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(self.file)
@@ -1017,6 +1013,7 @@ class IfcImporter:
)
obj = self.create_product(self.project["ifc"])
obj.hide_select = True
obj.hide_viewport = True
self.project["blender"].objects.link(obj)
self.project["blender"].BIMCollectionProperties.obj = obj
props = tool.Blender.get_object_bim_props(obj)
@@ -1218,6 +1215,35 @@ class IfcImporter:
tool.Blender.Modifier.Array.set_children_lock_state(element, i, True)
tool.Blender.Modifier.Array.constrain_children_to_parent(element)
def update_linked_aggregates(self):
# TODO Remove this after a while. See commit 17d6b8a
# https://github.com/IfcOpenShell/IfcOpenShell/commit/17d6b8af61c8dd7cd82f6e72b2fb3831d851a33d#commitcomment-163151609
groups = self.file.by_type("IfcGroup")
target_groups = [group for group in groups if group.Name == "BBIM_Linked_Aggregate"]
if not target_groups:
return
for i, group in enumerate(target_groups):
name = "Default"
elements = ifcopenshell.util.element.get_grouped_by(self.file.by_id(group.id()), is_recursive=True)
for j, element in enumerate(elements):
split = element.Name.rsplit("_")
name = split[0]
try:
aggregate_index = int(split[1])
except:
aggregate_index = 0
pset = ifcopenshell.util.element.get_pset(element, "BBIM_Linked_Aggregate", should_inherit=False)
if not pset:
return
if "Aggregate_Index" not in pset.keys():
ifcopenshell.api.pset.edit_pset(
self.file,
pset=self.file.by_id(pset["id"]),
properties={"Aggregate_Index": aggregate_index, "Name": name},
)
class IfcImportSettings:
"""
@@ -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
@@ -94,6 +95,9 @@ class BIM_OT_aggregate_unassign_object(bpy.types.Operator, tool.Ifc.Operator):
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
aggregates_to_check = set()
# First pass: unassign all parts and track their aggregates
for obj in tool.Blender.get_selected_objects():
element = tool.Ifc.get_entity(obj)
if not element:
@@ -101,6 +105,10 @@ class BIM_OT_aggregate_unassign_object(bpy.types.Operator, tool.Ifc.Operator):
aggregate = ifcopenshell.util.element.get_aggregate(element)
if not aggregate:
continue
# Track this aggregate for later checking
aggregates_to_check.add(aggregate)
core.unassign_object(
tool.Ifc,
tool.Aggregate,
@@ -116,6 +124,33 @@ class BIM_OT_aggregate_unassign_object(bpy.types.Operator, tool.Ifc.Operator):
pset = tool.Ifc.get().by_id(pset["id"])
ifcopenshell.api.pset.remove_pset(tool.Ifc.get(), product=element, pset=pset)
# Second pass: delete aggregates that now have no parts
deleted_aggregates = []
for aggregate in aggregates_to_check:
# Skip spatial elements - they should not be deleted even if empty
if aggregate.is_a("IfcSpatialElement"):
continue
related_objects = ifcopenshell.util.element.get_parts(aggregate)
if len(related_objects) == 0:
aggregate_name = aggregate.Name or f"{aggregate.is_a()} #{aggregate.id()}"
deleted_aggregates.append(aggregate_name)
aggregate_obj = tool.Ifc.get_object(aggregate)
if aggregate_obj:
ifcopenshell.api.root.remove_product(tool.Ifc.get(), product=aggregate)
bpy.data.objects.remove(aggregate_obj, do_unlink=True)
# Show info message if aggregates were deleted
if deleted_aggregates:
if len(deleted_aggregates) == 1:
self.report(
{"INFO"}, f"Aggregate '{deleted_aggregates[0]}' was deleted because it had no remaining parts"
)
else:
aggregate_list = ", ".join(f"'{name}'" for name in deleted_aggregates)
self.report({"INFO"}, f"Aggregates {aggregate_list} were deleted because they had no remaining parts")
class BIM_OT_enable_editing_aggregate(bpy.types.Operator):
"""Enable editing aggregation relationship"""
@@ -291,6 +326,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
@@ -1215,7 +1215,7 @@ class ActivateBcfViewpoint(bpy.types.Operator):
assert blender_topic
topic = bcfxml.topics[blender_topic.name]
if self.viewpoint_guid:
viewpoint_guid = self.viewpoint_guid
viewpoint_guid = self.viewpoint_guid + ".bcfv"
if viewpoint_guid not in topic.viewpoints:
self.report({"ERROR"}, f"No such viewpoint in the active topic: '{viewpoint_guid}'.")
return {"CANCELLED"}
@@ -1313,7 +1313,7 @@ class ActivateBcfViewpoint(bpy.types.Operator):
x_axis = y_axis.cross(z_axis).normalized()
rotation = Matrix((x_axis, y_axis, z_axis))
rotation.invert()
matrix = np.matrix(
matrix = np.array(
(
[x_axis[0], y_axis[0], z_axis[0], camera.camera_view_point.x],
[x_axis[1], y_axis[1], z_axis[1], camera.camera_view_point.y],
@@ -282,7 +282,11 @@ class BIM_PT_classification_references(Panel, ReferenceUI):
@classmethod
def poll(cls, context):
return bool((obj := context.active_object) and tool.Ifc.get_entity(obj))
return (
(obj := tool.Blender.get_active_object())
and (element := tool.Ifc.get_entity(obj))
and element.is_a("IfcObjectDefinition")
)
def get_object_name(self, context: bpy.types.Context) -> str:
assert (obj := context.active_object)
+44 -12
View File
@@ -28,6 +28,7 @@ import ifcopenshell
import ifcopenshell.api
import ifcopenshell.api.pset
import ifcopenshell.geom
import ifcopenshell.ifcopenshell_wrapper as W
import ifcopenshell.util.element
import ifcopenshell.util.placement
import ifcopenshell.util.unit
@@ -253,13 +254,17 @@ class ProfileImportIFC(bpy.types.Operator):
class CreateAllShapes(bpy.types.Operator):
bl_idname = "bim.create_all_shapes"
bl_label = "Test All Shapes"
bl_description = "Look for errors in all the shapes contained in the file"
bl_description = (
"Look for errors in all the shapes contained in the file.\n\nSee system console for the detailed results."
)
bl_options = {"REGISTER"}
geometry_library: bpy.props.EnumProperty( # pyright: ignore[reportRedeclaration]
name="Geometry Library",
description="Geometry library to use for testing shape creation.",
items=[(i, i, "") for i in get_args(ifcopenshell.geom.GEOMETRY_LIBRARY)],
default="opencascade",
# By default use the same library as used for importing ifc project.
default="hybrid-cgal-simple-opencascade",
)
custom_geometry_library: bpy.props.StringProperty( # pyright: ignore[reportRedeclaration]
name="Custom Geometry Library",
@@ -272,19 +277,23 @@ class CreateAllShapes(bpy.types.Operator):
@classmethod
def poll(cls, context):
return tool.Ifc.get()
if not tool.Ifc.get():
cls.poll_message_set("No IFC file is loaded.")
return False
return True
def execute(self, context):
self.file = tool.Ifc.get()
geometry_library = self.custom_geometry_library or self.geometry_library
elements = self.file.by_type("IfcElement") + self.file.by_type("IfcSpace")
print(f"Testing geometry library '{geometry_library}'.")
total = len(elements)
settings = ifcopenshell.geom.settings()
settings.set("keep-bounding-boxes", True)
settings_2d = ifcopenshell.geom.settings()
settings_2d.set("dimensionality", ifcopenshell.ifcopenshell_wrapper.CURVES_SURFACES_AND_SOLIDS)
failures = []
failures: list[ifcopenshell.entity_instance] = []
excludes = () # For the developer to debug with
for i, element in enumerate(elements, 1):
if element.GlobalId in excludes:
@@ -303,12 +312,11 @@ class CreateAllShapes(bpy.types.Operator):
failures.append(element)
print("***** FAILURE *****")
if shape:
assert isinstance(shape, W.TriangulationElement)
geom = shape.geometry
print(
"Success",
time.time() - start,
len(shape.geometry.verts),
len(shape.geometry.edges),
len(shape.geometry.faces),
f"Success {time.time() - start:.3f}s "
f"V:{(len(geom.verts)//3)} E:{(len(geom.edges)//2)} F:{(len(geom.faces)//3)}"
)
self.report({"INFO"}, f"Failed shapes: {len(failures)}, check the system console for details.")
for failure in failures:
@@ -810,7 +818,12 @@ class PurgeUnusedObjects(bpy.types.Operator, tool.Ifc.Operator):
class MergeIdenticalObjects(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.merge_identical_objects"
bl_label = "Merge Identical Objects"
bl_description = "For materials currently only IfcMaterials are supported"
bl_description = (
"Merge identical IFC objects (that match all attributes).\n"
"\n"
"SHIFT + CLICK to merge by name/identification attribute only.\n"
"Merges names with number suffix, as well (ex: foo, foo.001, foo.002)\n"
)
bl_options = {"REGISTER", "UNDO"}
object_type: bpy.props.EnumProperty( # pyright: ignore[reportRedeclaration]
@@ -818,18 +831,36 @@ class MergeIdenticalObjects(bpy.types.Operator, tool.Ifc.Operator):
items=((s, s.capitalize(), "") for s in get_args(tool.Debug.PurgeMergeObjectType)),
)
by_name_or_identification_only: bpy.props.BoolProperty(
name="By Name/Identification Only",
description="Merge based only on Name or Identification attribute, ignoring other properties",
default=False,
)
if TYPE_CHECKING:
object_type: tool.Debug.PurgeMergeObjectType
def invoke(self, context, event):
# Check if shift key is pressed
if event.shift:
self.by_name_or_identification_only = True
else:
self.by_name_or_identification_only = False
return self.execute(context)
def _execute(self, context):
object_type: str = self.object_type
if object_type in ("PROFILE", "TYPE"):
self.report({"ERROR"}, f"Unsupported object type {object_type}.")
return {"CANCELLED"}
merged_data = tool.Debug.merge_identical_objects(object_type)
merged_data = tool.Debug.merge_identical_objects(
object_type, by_name_or_identification_only=self.by_name_or_identification_only
)
plural_object_type = f"{object_type.lower().replace('_', ' ')}s"
if merged_data:
merge_mode = " by name/identification" if self.by_name_or_identification_only else ""
for element_type, element_names in merged_data.items():
print(f"- {element_type}:")
for name in element_names:
@@ -838,7 +869,8 @@ class MergeIdenticalObjects(bpy.types.Operator, tool.Ifc.Operator):
merged = sum(len(v) for v in merged_data.values())
msg = " See system console for details." if merged else ""
self.report({"INFO"}, f"{merged} identical {plural_object_type} were merged.{msg}")
merge_mode = " (by name/identification)" if self.by_name_or_identification_only else ""
self.report({"INFO"}, f"{merged} identical {plural_object_type} were merged{merge_mode}.{msg}")
if merged == 0:
return
+4 -1
View File
@@ -72,7 +72,10 @@ class BIM_PT_debug(Panel):
row = layout.row()
row.operator("bim.convert_to_blender")
row = layout.row()
row = layout.row(align=True)
op_props = wm.operator_properties_last(bpy.ops.bim.create_all_shapes.idname())
row.prop(op_props, "geometry_library", text="")
row.prop(op_props, "custom_geometry_library", text="", placeholder="Custom Geometry Library")
row.operator("bim.create_all_shapes")
row = layout.row()
@@ -61,6 +61,7 @@ classes = (
operator.EnableEditingAssignedProduct,
operator.EnableEditingElementFilter,
operator.EnableEditingText,
operator.ExcludeAnnotation,
operator.ExpandSheet,
operator.LoadDrawings,
operator.LoadReferences,
+21 -8
View File
@@ -235,12 +235,18 @@ class DecoratorData:
cut_cache = {}
slice_cache = {}
fill_cache = {}
camera_location_checksum = ""
camera_rotation_checksum = ""
@classmethod
def clear_cache(cls):
cls.cut_cache = {}
cls.layerset_cache = {}
cls.fill_cache = {}
@classmethod
def load(cls, handler):
cls.is_loaded = True
cls.cut_cache = {}
cls.layerset_cache = {}
text = {}
dimension = {}
@@ -346,12 +352,10 @@ class DecoratorData:
(font_size_type for font_size_type in FONT_SIZES if font_size_type in classes_split), "regular"
)
font_size = FONT_SIZES[font_size_type]
# get symbol
symbol = tool.Drawing.get_annotation_symbol(element)
# get newline_at
newline_at = pset_data.get("Newline_At", 0)
reverse_list = pset_data.get("Reverse_List", False)
list_separator = pset_data.get("List_Separator") or ", "
# other attributes
literals = tool.Drawing.get_text_literal(obj, return_list=True)
@@ -363,11 +367,20 @@ class DecoratorData:
literal_data = {
"Literal": literal_value,
"BoxAlignment": literal.BoxAlignment,
"CurrentValue": tool.Drawing.replace_text_literal_variables(literal_value, product),
"CurrentValue": tool.Drawing.replace_text_literal_variables(
literal_value, product, reverse_list, list_separator
),
}
literals_data.append(literal_data)
return {"Literals": literals_data, "FontSize": font_size, "Symbol": symbol, "Newline_At": newline_at}
return {
"Literals": literals_data,
"FontSize": font_size,
"Symbol": symbol,
"Newline_At": newline_at,
"Reverse_List": reverse_list,
"List_Separator": list_separator,
}
@classmethod
def get_dimension_data(cls, obj: bpy.types.Object) -> dict[str, Any]:
@@ -19,9 +19,11 @@
import gpu
import bpy
import blf
import os
import math
import bmesh
import shapely
import numpy as np
import ifcopenshell
import ifcopenshell.util.element
import ifcopenshell.util.representation
@@ -40,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
@@ -171,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
@@ -188,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
@@ -241,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:
@@ -376,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`
@@ -498,7 +489,7 @@ class BaseDecorator:
self.draw_label(context, text=text, line_no=line_number_start, multiline=True, **draw_label_kwargs)
@cache
def format_value(self, context, value, custom_unit=None):
def format_value(self, context, value, suppress_zero_inches=False, custom_unit=None, in_unit_length=False):
drawing_pset_data = DrawingsData.data["active_drawing_pset_data"]
precision = drawing_pset_data.get("MetricPrecision", None)
if not precision:
@@ -509,8 +500,9 @@ class BaseDecorator:
value,
precision=precision,
decimal_places=decimal_places,
suppress_zero_inches=True,
suppress_zero_inches=suppress_zero_inches,
custom_unit=custom_unit,
in_unit_length=in_unit_length,
)
def draw_asterisk(self, context: bpy.types.Context, pos: Vector, rotation: float = 0.0, scale: float = 1.0) -> None:
@@ -752,7 +744,12 @@ class DimensionDecorator(BaseDecorator):
if not show_description_only:
length = (v1 - v0).length
text = self.format_value(context, length, custom_unit=dimension_data["custom_unit"])
text = self.format_value(
context,
length,
suppress_zero_inches=dimension_data["suppress_zero_inches"],
custom_unit=dimension_data["custom_unit"],
)
if isinstance(self, DiameterDecorator):
text = "D" + text
text = text_prefix + text + text_suffix
@@ -1205,19 +1202,13 @@ class PlanLevelDecorator(BaseDecorator):
def get_text():
abs_z = verts[0].z
z = abs_z
z = helper.get_relative_z(obj, element, abs_z)
if element:
spatial_container = ifcopenshell.util.element.get_container(element)
# Z is in meters (Blender world space), convert to project units (feet)
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
z = z / unit_scale # Convert meters to feet
if spatial_container:
container_obj = tool.Ifc.get_object(spatial_container)
if container_obj:
spatial_container_z = container_obj.matrix_world.translation.z
z = abs_z - spatial_container_z
rl = self.format_value(context, z)
rl = self.format_value(context, z, in_unit_length=True)
text = "{}{}".format("" if z < 0 else "+", rl)
return text
@@ -1290,7 +1281,11 @@ class SectionLevelDecorator(BaseDecorator):
def get_text():
z = verts[0].z
rl = self.format_value(context, z)
# Z is in meters (Blender world space), convert to project units (feet)
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
z = z / unit_scale # Convert meters to feet
rl = self.format_value(context, z, in_unit_length=True)
text = "RL {}{}".format("" if z < 0 else "+", rl)
return text
@@ -1635,6 +1630,7 @@ class CutDecorator:
if cls.installed:
cls.uninstall()
handler = cls()
handler.cache_camera_matrix()
cls.installed = SpaceView3D.draw_handler_add(handler, (context,), "WINDOW", "POST_VIEW")
@classmethod
@@ -1649,6 +1645,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)
@@ -1737,6 +1740,47 @@ class CutDecorator:
shader.uniform_float("color", color)
batch.draw(shader)
def cache_camera_matrix(self):
obj = bpy.context.scene.camera
# 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
# 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
# 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
self.cache_camera_matrix()
return True
return False
def decorate(self, context, obj: bpy.types.Object, element: ifcopenshell.entity_instance) -> None:
has_cut_cache = element.id() in DecoratorData.cut_cache
has_fill_cache = element.id() in DecoratorData.fill_cache
@@ -1744,9 +1788,9 @@ class CutDecorator:
# Currently selected objects must be recalculated as they may be being moved / edited.
# If the camera is selected, we also recalculate as the user may be moving the camera.
if not has_cut_cache or obj.select_get() or context.scene.camera.select_get():
if not has_cut_cache or obj.select_get() or self.is_camera_moved():
self.recalculate_cut(context, obj, element)
if not has_fill_cache or obj.select_get() or context.scene.camera.select_get():
if not has_fill_cache or obj.select_get() or self.is_camera_moved():
self.recalculate_fill(context, obj, element)
def recalculate_cut(self, context, obj: bpy.types.Object, element: ifcopenshell.entity_instance) -> None:
@@ -1943,8 +1987,7 @@ class DecorationsHandler:
# NOTE: we USE POST_PIXEL here so that we can use both POLYLINE_UNIFORM_COLOR
# and drawing text in the same handler. BUT this means that we supply coordinates in WINSPACE
cls.installed = SpaceView3D.draw_handler_add(handler, (context,), "WINDOW", "POST_PIXEL")
if not DecoratorData.is_loaded:
DecoratorData.load(handler)
DecoratorData.clear_cache()
@classmethod
def uninstall(cls):
@@ -1959,17 +2002,56 @@ 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()
if not DecoratorData.is_loaded:
DecoratorData.load(self)
for obj, decorator in DecoratorData.data["object_decorators"]:
decorator.decorate(context, obj)
+117 -26
View File
@@ -18,6 +18,7 @@
import bpy
import math
import ifcopenshell.util.element
import mathutils.geometry
import ifcopenshell
import ifcopenshell.util.unit
@@ -121,6 +122,45 @@ class BoundingBox:
return retVal
def find_best_precision(fractional_inches, max_precision=256, base_tolerance=0.0001):
"""
Find the simplest fraction denominator that accurately represents the value.
Uses a sliding tolerance - smaller denominators get more tolerance.
:param fractional_inches: The fractional part of inches (0.0 to 1.0)
:param max_precision: Maximum denominator to try (256 for imperial)
:param base_tolerance: Base tolerance in inches (very strict)
:return: Best denominator (power of 2: 2, 4, 8, 16, 32, 64, 128, 256)
"""
if abs(fractional_inches) < 0.00001:
return 1 # No fraction needed
# Try denominators from coarsest to finest: 2, 4, 8, 16, 32, 64, 128, 256
for exp in range(1, 9): # 2^1 to 2^8
denom = 2**exp
if denom > max_precision:
break
# Find nearest numerator for this denominator
numer = round(fractional_inches * denom)
# Skip if numerator is 0
if numer == 0:
continue
# Check if this fraction is close enough
fraction_value = numer / denom
error = abs(fraction_value - fractional_inches)
# Very strict tolerance - only simplify if it's a near-perfect match
# This keeps 3/256 as 3/256, but allows floating point errors to be cleaned up
if error < base_tolerance:
return denom
# If no simpler fraction works, use max precision
return max_precision
# This function stolen from https://github.com/kevancress/MeasureIt_ARCH/blob/dcf607ce0896aa2284463c6b4ae9cd023fc54cbe/measureit_arch_baseclass.py
# MeasureIt-ARCH is GPL-v3
def format_distance(
@@ -186,14 +226,18 @@ def format_distance(
if unit_length == "INCHES":
toInches = 1
if unit_length == "FEET":
toInches = 11.9999
toInches = 12
else:
toInches = 39.3700787401574887
inPerFoot = 11.9999
inPerFoot = 12.0
if isArea:
toInches = 1550
inPerFoot = 143.999
inPerFoot = 144
decInches = value * toInches
decFeet = decInches / 12
if not precision:
precision = 256
elif precision == "1":
@@ -202,8 +246,6 @@ def format_distance(
precision = int(precision.split("/")[1])
base = int(precision)
decInches = value * toInches
decFeet = decInches / 12
# Separate ft and inches
# Unless Inches are the specified Length Unit or unit_fraction is False
@@ -219,20 +261,33 @@ def format_distance(
# Separate Fractional Inches
decInches = abs(decInches) # ignore the sign for inches
inches = math.floor(decInches) # remove decimal
if inches != 0:
frac = round(base * (decInches - inches))
# Clean up floating point errors
fractional_inches = decInches - inches
tolerance = 0.01 # About 1/100 of an inch
# If the fractional part is very small, treat it as zero
if fractional_inches < tolerance:
frac = 0
# If very close to the next whole inch, round up
elif fractional_inches > (1.0 - tolerance):
frac = 0
inches += 1
else:
frac = round(base * (decInches))
# Calculate fraction normally
if inches != 0:
frac = round(base * fractional_inches)
else:
frac = round(base * fractional_inches)
# Set proper numerator and denominator
if frac != base:
numcycles = int(math.log2(base))
for i in range(numcycles):
if frac % 2 == 0:
frac = int(frac / 2)
base = int(base / 2)
else:
break
# Simplify using GCD
from math import gcd
divisor = gcd(int(frac), int(base))
frac = int(frac / divisor)
base = int(base / divisor)
else:
frac = 0
inches += 1
@@ -251,31 +306,46 @@ def format_distance(
frac = None
if not isArea:
add_inches = bool(inches) or not suppress_zero_inches or (inches == 0 and frac)
tx_dist = ""
if feet:
tx_dist += str(feet) + "'"
if not feet and not add_inches:
tx_dist += str(feet) + "'"
if feet and add_inches:
# Add "0' - " when we have inches but no feet
# But only add " - " separator if we actually have inches to show
if not feet and add_inches:
tx_dist += "0' - "
elif feet and add_inches:
tx_dist += " - "
if not feet and value < 0:
tx_dist += "-"
if add_inches:
if feet == 0 and inches == 0:
pass
if feet == 0 and inches == 0 and not frac:
# Special case: exactly zero, show "0"
tx_dist += "0"
elif feet == 0 and inches == 0:
pass # Has fraction, will be added below
else:
tx_dist += str(inches)
if add_inches and frac:
if feet == 0 and inches == 0:
pass
pass # Has fraction, will be added below
else:
tx_dist += " "
if frac:
tx_dist += str(frac) + "/" + str(base)
if add_inches or frac:
tx_dist += '"'
# Only add inch symbol if we actually added inch content
if inches > 0 or frac > 0 or feet == 0:
tx_dist += '"'
if precision == "12" and unit_system == "IMPERIAL":
tx_dist = str(round(decFeet)) + "'"
if tx_dist == '"':
tx_dist = "0' - 0\""
else:
fmt = "%1.3f"
sq_feet = round(value * toInches / inPerFoot, 4)
@@ -421,8 +491,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 +513,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 +563,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
@@ -539,3 +609,24 @@ def add_newline_between_words(text: str, newline_at: int) -> str:
start = space_index + 1 # Skip the space itself
return "\n".join(result)
def get_relative_z(obj: bpy.types.Object, element, abs_z: float) -> float:
"""Return relative Z of an element, accounting for its spatial container.
: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.
:return: Relative Z value if the element is inside a spatial container,
otherwise the absolute Z.
"""
z = abs_z
if element:
spatial_container = ifcopenshell.util.element.get_container(element)
if spatial_container:
container_obj = tool.Ifc.get_object(spatial_container)
if container_obj:
spatial_container_z = container_obj.matrix_world.translation.z
z = abs_z - spatial_container_z
return z
+196 -83
View File
@@ -106,14 +106,17 @@ class AddAnnotationType(bpy.types.Operator, tool.Ifc.Operator):
obj = bpy.data.objects.new(object_type, None)
obj.name = props.type_name
ifc_context = ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model", "Annotation", "MODEL_VIEW")
element = tool.Drawing.run_root_assign_class(
obj=obj,
ifc_class="IfcTypeProduct",
predefined_type=object_type,
should_add_representation=has_representation,
context=ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model", "Annotation", "MODEL_VIEW"),
context=ifc_context,
ifc_representation_class=tool.Drawing.get_ifc_representation_class(object_type),
)
if representation := tool.Drawing.get_representation(element, ifc_context):
tool.Drawing.reload_representation(obj=obj, representation=representation)
element.ApplicableOccurrence = f"IfcAnnotation/{object_type}"
if props.create_representation_for_type and object_type == "IMAGE":
@@ -207,12 +210,6 @@ class DuplicateDrawing(bpy.types.Operator, tool.Ifc.Operator):
should_duplicate_annotations=self.should_duplicate_annotations,
)
# TODO: Why need to resync active drawing, if it wasn't changed.
drawing = props.get_active_drawing()
if drawing is None:
return
core.sync_references(tool.Ifc, tool.Collector, tool.Drawing, drawing=drawing)
class CreateDrawing(bpy.types.Operator):
"""Creates/refreshes a .svg drawing
@@ -435,21 +432,19 @@ class CreateDrawing(bpy.types.Operator):
if os.path.isfile(svg_path) and self.props.should_use_underlay_cache:
return svg_path
visible_object_names = {obj.name for obj in bpy.context.visible_objects}
for obj in bpy.context.view_layer.objects:
obj.hide_render = obj.name not in visible_object_names
assert context.scene and context.view_layer and context.screen
context.scene.render.filepath = str(Path(svg_path).with_suffix(".png"))
assert (drawing_style := self.cprops.get_active_drawing_style())
tool.Blender.sync_render_visibility()
if drawing_style.render_type == "DEFAULT":
bpy.ops.render.render(write_still=True)
else:
previous_visibility: dict[str, bool] = {}
collection = tool.Blender.get_object_bim_props(self.camera).collection
assert collection
# Hie annotations.
# Hide annotations.
for obj in collection.objects:
if context.view_layer.objects.get(obj.name):
previous_visibility[obj.name] = obj.hide_get()
@@ -626,7 +621,7 @@ class CreateDrawing(bpy.types.Operator):
path.attrib["d"] = d
group.append(g)
def generate_wall_layers(self, context: bpy.types.Context, root) -> None:
def generate_material_layers(self, context: bpy.types.Context, root) -> None:
for el in root.findall(".//{http://www.w3.org/2000/svg}g[@{http://www.ifcopenshell.org/ns}guid]"):
if "projection" in el.get("class", "").split():
continue
@@ -795,8 +790,9 @@ class CreateDrawing(bpy.types.Operator):
edge_bm.to_mesh(edge_mesh)
edge_bm.free()
actual_path = svg_path[0:-4] + "0001.svg"
freestyle_svg_exporter = tool.Blender.get_addon("freestyle_svg_exporter")
context.scene.render.filepath = svg_path[0:-4]
actual_path = freestyle_svg_exporter.create_path(bpy.context.scene)
bpy.ops.render.render(write_still=False)
os.replace(actual_path, svg_path)
@@ -840,6 +836,8 @@ class CreateDrawing(bpy.types.Operator):
if tool.Drawing.is_camera_orthographic():
self.generate_bisect_linework(context, root)
if self.cprops.generate_material_layers:
self.generate_material_layers(context, root)
self.merge_linework_and_add_metadata(root)
self.move_elements_to_top(root)
@@ -934,15 +932,42 @@ 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)
self.generate_wall_layers(context, root)
if self.cprops.generate_material_layers:
self.generate_material_layers(context, root)
self.merge_linework_and_add_metadata(root)
self.move_elements_to_top(root)
elif self.cprops.cut_mode == "OPENCASCADE":
self.move_projection_to_bottom(root)
self.generate_wall_layers(context, root)
if self.cprops.generate_material_layers:
self.generate_material_layers(context, root)
self.merge_linework_and_add_metadata(root)
self.move_elements_to_top(root)
@@ -1252,6 +1277,8 @@ class CreateDrawing(bpy.types.Operator):
def get_svg_classes(self, element, layer=None):
classes = [element.is_a()]
# ─── Material ──────────────────────────────────────────────
material = ifcopenshell.util.element.get_material(element, should_skip_usage=True)
material_name = ""
if material:
@@ -1264,18 +1291,27 @@ class CreateDrawing(bpy.types.Operator):
else:
classes.append("material-null")
# ─── Layer ─────────────────────────────────────────────────
if layer:
classes.append(layer.is_a())
material_name = layer.Material.Name or "null"
material_name = tool.Drawing.canonicalise_class_name(material_name)
classes.append(f"layer-material-{material_name}")
layer_material = layer.Material
layer_material_name = layer_material.Name or "null"
layer_material_name = tool.Drawing.canonicalise_class_name(layer_material_name)
classes.append(f"layer-material-{layer_material_name}")
# Add material category if available
if hasattr(layer_material, "Category") and layer_material.Category:
layer_material_category = tool.Drawing.canonicalise_class_name(layer_material.Category)
classes.append(f"layer-material-category-{layer_material_category}")
# ─── Metadata ──────────────────────────────────────────────
for key in self.metadata:
value = ifcopenshell.util.selector.get_element_value(element, key)
if value:
classes.append(
tool.Drawing.canonicalise_class_name(key) + "-" + tool.Drawing.canonicalise_class_name(str(value))
)
return classes
def is_manifold(self, obj) -> bool:
@@ -1342,7 +1378,13 @@ class CreateDrawing(bpy.types.Operator):
join_criteria = join_criteria.split(",")
else:
# Drawing convention states that same objects classes with the same material are merged when cut.
join_criteria = ["class", "material.Name", "/Pset_.*Common/.Status", "EPset_Status.Status", "Material.Name"]
join_criteria = [
"class",
"material.Name",
"/Pset_.*Common/.Status",
"EPset_Status.Status",
"EPset_Status.UserDefinedStatus",
]
group = root.find("{http://www.w3.org/2000/svg}g")
joined_paths = {}
@@ -1471,11 +1513,10 @@ class CreateDrawing(bpy.types.Operator):
joined_paths.setdefault(hash_keys, []).append(el)
for key, els in joined_paths.items():
polygons = []
classes = set()
queue = []
for el in els:
classes.update(el.attrib["class"].split())
classes = set(el.attrib["class"].split())
classes.add(el.attrib["{http://www.ifcopenshell.org/ns}guid"])
is_closed_polygon = False
for path in el.findall("{http://www.w3.org/2000/svg}path"):
@@ -1490,31 +1531,30 @@ class CreateDrawing(bpy.types.Operator):
coords.append(coords[0])
if len(coords) > 2 and coords[0] == coords[-1]:
is_closed_polygon = True
polygons.append(shapely.Polygon(coords))
queue.append((shapely.Polygon(coords), classes))
if is_closed_polygon:
el.getparent().remove(el)
try:
merged_polygons = shapely.ops.unary_union(polygons)
except:
print("Warning. Portions of the merge failed. Please report a bug!", polygons)
merged_polygons = polygons
while queue:
polygon, polygon_classes = queue.pop()
for polygon2, polygon2_classes in queue[:]:
try:
merged_polygon = shapely.union(polygon, polygon2)
except:
print("Warning. Portions of the merge failed. Please report a bug!", polygon, polygon2)
continue
if type(merged_polygon) == shapely.Polygon:
polygon = merged_polygon
polygon_classes.update(polygon2_classes)
queue.remove((polygon2, polygon2_classes))
if type(merged_polygons) == shapely.MultiPolygon:
merged_polygons = merged_polygons.geoms
elif type(merged_polygons) == shapely.Polygon:
merged_polygons = [merged_polygons]
else:
merged_polygons = []
for polygon in merged_polygons:
g = etree.Element("g")
path = etree.SubElement(g, "path")
d = "M" + " L".join([",".join([str(o) for o in co]) for co in polygon.exterior.coords[0:-1]]) + " Z"
for interior in polygon.interiors:
d += " M" + " L".join([",".join([str(o) for o in co]) for co in interior.coords[0:-1]]) + " Z"
path.attrib["d"] = d
g.set("class", " ".join(list(classes)))
g.set("class", " ".join(list(polygon_classes)))
group.append(g)
def drawing_to_model_co(self, x: float, y: float) -> Vector:
@@ -1856,13 +1896,15 @@ class AddDrawingToSheet(bpy.types.Operator, tool.Ifc.Operator):
@classmethod
def poll(cls, context):
props = tool.Drawing.get_document_props()
# Won't be visible in UI anyway.
prefs = tool.Blender.get_addon_preferences()
if not props.sheets or not prefs.data_dir:
return False
if not tool.Drawing.get_active_drawing_item():
cls.poll_message_set("No drawing selected.")
return False
if not props.sheets:
cls.poll_message_set("No sheets available.")
return False
if not tool.Blender.get_user_data_dir():
cls.poll_message_set("BIM data directory not set.")
return False
return True
def _execute(self, context):
@@ -2165,27 +2207,10 @@ class ActivateModel(bpy.types.Operator):
if model_props.show_cut_decorator:
CutDecorator.uninstall()
# Preserve current visibility statuses for:
# - non-ifc objects
# - type product
# - annotations (so we won't unhide other drawings)
ifc_file = tool.Ifc.get()
visibility_status: dict[bpy.types.Object, bool] = {}
for obj in bpy.data.objects:
element = tool.Ifc.get_entity(obj)
if not element:
hide = obj.hide_get()
elif element.is_a("IfcAnnotation"):
hide = True
elif element.is_a("IfcTypeProduct"):
hide = obj.hide_get()
else:
continue
visibility_status[obj] = hide
if not bpy.app.background:
with context.temp_override(**tool.Blender.get_viewport_context()):
bpy.ops.object.hide_view_clear(select=False)
bpy.ops.bim.activate_status_filters(only_if_enabled=True)
elements = {e for obj in context.visible_objects if (e := tool.Ifc.get_entity(obj))}
@@ -2230,15 +2255,10 @@ class ActivateModel(bpy.types.Operator):
tool.Geometry,
obj=obj,
representation=model,
should_reload=False,
is_global=True,
should_sync_changes_first=True,
)
# restore visibility after hide_view_clear()
for obj, hide_status in visibility_status.items():
obj.hide_set(hide_status)
tool.Blender.reset_object_visibility()
tool.Drawing.hide_all_drawing_collections()
tool.Blender.update_viewport()
bonsai.bim.handler.refresh_ui_data()
@@ -2335,9 +2355,8 @@ class ActivateDrawingBase(tool.Ifc.Operator):
dprops.active_drawing_id = self.drawing
dprops.drawing_styles.clear()
if ifcopenshell.util.element.get_pset(drawing, "EPset_Drawing", "HasUnderlay"):
bpy.ops.bim.reload_drawing_styles()
bpy.ops.bim.activate_drawing_style()
bpy.ops.bim.reload_drawing_styles()
bpy.ops.bim.activate_drawing_style()
if tool.Drawing.is_camera_orthographic():
core.sync_references(tool.Ifc, tool.Collector, tool.Drawing, drawing=tool.Ifc.get().by_id(self.drawing))
@@ -2350,10 +2369,22 @@ class ActivateDrawingBase(tool.Ifc.Operator):
camera = context.scene.camera
assert camera
camera_props = tool.Drawing.get_camera_props(camera)
# Check if this is a reflected ceiling camera and preserve its scale
camera_element = tool.Ifc.get_entity(camera)
is_reflected = False
if camera_element:
is_reflected = (
ifcopenshell.util.element.get_pset(camera_element, "EPset_Drawing", "TargetView")
== "REFLECTED_PLAN_VIEW"
)
if is_reflected and camera.scale != (-1, -1, -1):
camera.scale = (-1, -1, -1)
if camera_props.update_representation(camera.matrix_world):
bpy.ops.bim.update_representation(obj=camera.name, ifc_representation_class="")
# See 6452 and 6478.
# bpy.ops.bim.refresh_clipping_planes("INVOKE_DEFAULT")
# Restore the scale after update if needed
if is_reflected:
camera.scale = (-1, -1, -1)
return {"FINISHED"}
@@ -2690,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}' "
@@ -2738,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
@@ -2829,8 +2907,13 @@ class AddScheduleToSheet(bpy.types.Operator, tool.Ifc.Operator):
if not props.schedules:
cls.poll_message_set("No schedule selected.")
return False
prefs = tool.Blender.get_addon_preferences()
return props.schedules and props.sheets and prefs.data_dir
if not props.sheets:
cls.poll_message_set("No sheets available.")
return False
if not tool.Blender.get_user_data_dir():
cls.poll_message_set("BIM data directory not set.")
return False
return True
def _execute(self, context):
props = tool.Drawing.get_document_props()
@@ -2897,8 +2980,13 @@ class AddReferenceToSheet(bpy.types.Operator, tool.Ifc.Operator):
if not props.references:
cls.poll_message_set("No reference selected.")
return False
bim_props = tool.Blender.get_bim_props()
return props.references and props.sheets and bim_props.data_dir
if not props.sheets:
cls.poll_message_set("No sheets available.")
return False
if not tool.Blender.get_user_data_dir():
cls.poll_message_set("BIM data directory not set.")
return False
return True
def _execute(self, context):
props = tool.Drawing.get_document_props()
@@ -3393,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)
@@ -3747,3 +3844,19 @@ class OpenDocumentationWebUi(bpy.types.Operator):
else:
bpy.ops.bim.open_web_browser(page="documentation")
return {"FINISHED"}
class ExcludeAnnotation(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.exclude_annotation"
bl_label = "Exclude Annotation"
bl_description = "Excludes the automatic annotation reference from the drawing"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
if not (obj := bpy.context.scene.camera) or not (drawing := tool.Ifc.get_entity(obj)):
return
for obj in tool.Blender.get_selected_objects(include_active=False):
if (element := tool.Ifc.get_entity(obj)) and tool.Drawing.is_auto_annotation(element):
if referenced_element := tool.Drawing.get_annotation_element(element):
tool.Drawing.exclude_annotation_from_drawing(referenced_element, drawing)
core.sync_references(tool.Ifc, tool.Collector, tool.Drawing, drawing=drawing)
@@ -465,6 +465,12 @@ class DocProperties(PropertyGroup):
return None
return tool.Ifc.get().by_id(drawing_id)
def get_active_target_view(self) -> Union[str, None]:
active_drawing = self.get_active_drawing()
if not active_drawing:
return None
return tool.Drawing.get_drawing_target_view(active_drawing)
def update_width_height(self: "BIMCameraProperties", context: bpy.types.Context) -> None:
self.update_camera_resolution()
@@ -492,6 +498,9 @@ class BIMCameraProperties(PropertyGroup):
name="Linework Mode",
update=get_update_layer_callback("linework_mode", "LineworkMode"),
)
generate_material_layers: bpy.props.BoolProperty(
name="Generate Material Layers", description="Generate material layer linework in drawings", default=True
)
fill_mode: EnumProperty(
items=[
("NONE", "None", "Disable filling areas seen in projection"),
@@ -738,6 +747,11 @@ class BIMTextProperties(PropertyGroup):
name="Font Size",
)
newline_at: IntProperty(name="Newline At")
reverse_list: BoolProperty(name="Reverse List", description="Reverses the order of any list.", default=False)
list_separator: StringProperty( # pyright: ignore[reportRedeclaration]
name="List Separator",
description="Text used to separate lists. Uses a comma (, ) if empty.",
)
symbol: EnumProperty( # pyright: ignore[reportRedeclaration]
name="Symbol",
description="Symbol from symbols.svg to use for this text.",
@@ -754,6 +768,8 @@ class BIMTextProperties(PropertyGroup):
literals: bpy.types.bpy_prop_collection_idprop[LiteralProps]
font_size: str
newline_at: int
reverse_list: bool
list_separator: str
symbol: Union[str, Literal["NO SYMBOL", "CUSTOM SYMBOL"]]
custom_symbol: str
@@ -790,6 +806,8 @@ class BIMTextProperties(PropertyGroup):
"FontSize": float(self.font_size),
"Newline_At": int(self.newline_at),
"Symbol": self.get_symbol(),
"Reverse_List": self.reverse_list,
"List_Separator": self.list_separator or ", ",
}
return text_data
@@ -844,6 +862,19 @@ class BIMAnnotationProperties(PropertyGroup):
)
is_adding_type: bpy.props.BoolProperty(default=False)
type_name: bpy.props.StringProperty(name="Name", default="TYPEX")
tag_rotation_mode: bpy.props.EnumProperty(
name="Tag Rotation Mode",
description="How to orient the tag relative to the tagged object",
items=[
("NONE", "No Rotation", "Keep tag in default orientation"),
("LOCAL_X", "Local X Axis", "Align tag with object's local X axis"),
("LOCAL_Y", "Local Y Axis", "Align tag with object's local Y axis"),
("LOCAL_Z", "Local Z Axis", "Align tag with object's local Z axis"),
("CAMERA_Horizontal", "Camera Horizontal", "Align tag with camera X axis"),
("CAMERA_Vertical", "Camera Vertical", "Align tag with camera Y axis"),
],
default="NONE",
)
if TYPE_CHECKING:
object_type: str
@@ -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 (
@@ -839,6 +848,12 @@ class SvgWriter:
symbol = tool.Drawing.get_annotation_symbol(element)
newline_at = tool.Drawing.get_newline_at(element)
# Get reverse_list and list_separator from EPset_Annotation
pset_data = ifcopenshell.util.element.get_pset(element, "EPset_Annotation") or {}
reverse_list = pset_data.get("Reverse_List", False)
list_separator = pset_data.get("List_Separator") or ", "
template_text_fields = []
if symbol:
symbol_transform = self.get_symbol_transform(text_position_svg_str, angle, text_obj)
@@ -855,7 +870,7 @@ class SvgWriter:
# NOTE: zip makes sure that we iterate over the shortest list
for field, text_literal in zip(template_text_fields, text_literals):
field.text = tool.Drawing.replace_text_literal_variables(
text_literal.Literal, product or element
text_literal.Literal, product or element, reverse_list, list_separator
)
field.attrib["class"] = classes_str
@@ -875,7 +890,9 @@ class SvgWriter:
line_number = 0
for text_literal in text_literals:
text = tool.Drawing.replace_text_literal_variables(text_literal.Literal, product or element)
text = tool.Drawing.replace_text_literal_variables(
text_literal.Literal, product or element, reverse_list, list_separator
)
text_tags = self.create_text_tag(
text,
text_position_svg,
@@ -1012,7 +1029,9 @@ class SvgWriter:
# TODO: allow metric to be configurable
def get_text():
z = (matrix_world @ points[0].co.xyz).z
abs_z = (matrix_world @ points[0].co.xyz).z
z = helper.get_relative_z(obj, element, abs_z)
rl = helper.format_distance(
z,
precision=self.precision,
+14 -2
View File
@@ -99,12 +99,13 @@ class BIM_PT_camera(Panel):
row = self.layout.row()
row.prop(props, "linework_mode")
row = self.layout.row()
row.prop(props, "generate_material_layers")
if props.linework_mode == "OPENCASCADE":
row = self.layout.row()
row.prop(props, "fill_mode")
row = self.layout.row()
row.prop(props, "cut_mode")
row = self.layout.row()
row.prop(props, "width")
row = self.layout.row()
@@ -115,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()
@@ -193,6 +194,7 @@ class BIM_PT_element_filters(Panel):
text = "Exclude Filter" if ElementFiltersData.data["has_exclude_filter"] else "No Exclude Filter Found"
icon = "GREASEPENCIL" if ElementFiltersData.data["has_exclude_filter"] else "ADD"
row.label(text=text, icon="FILTER")
row.operator("bim.exclude_annotation", icon="REMOVE", text="")
row.operator("bim.enable_editing_element_filter", icon=icon, text="").filter_mode = "EXCLUDE"
@@ -590,6 +592,10 @@ class BIM_PT_text(Panel):
row.prop(props, "font_size")
row = self.layout.row(align=True)
row.prop(props, "newline_at")
row = self.layout.row(align=True)
row.prop(props, "reverse_list")
row = self.layout.row(align=True)
row.prop(props, "list_separator")
row = self.layout.row(align=True)
row.prop(props, "symbol")
@@ -648,6 +654,12 @@ class BIM_PT_text(Panel):
row = self.layout.row(align=True)
row.label(text="Newline_At")
row.label(text=str(text_data["Newline_At"]))
row = self.layout.row(align=True)
row.label(text="Reverse_List")
row.label(text=str(text_data["Reverse_List"]))
row = self.layout.row(align=True)
row.label(text="List_Separator")
row.label(text=str(text_data["List_Separator"]))
for literal_data in text_data["Literals"]:
box = self.layout.box()
@@ -180,9 +180,6 @@ def create_annotation_occurrence(context):
tool.Geometry,
obj=obj,
representation=representation,
should_reload=True,
is_global=True,
should_sync_changes_first=False,
)
if obj.data and not relating_type_rep:
@@ -231,7 +228,6 @@ class AnnotationToolUI:
def draw_type_selection_interface(cls):
# shared by both sidebar and header
object_type = cls.props.object_type
row = cls.layout.row(align=True)
row.label(text="", icon="FILE_VOLUME")
prop_with_search(row, cls.props, "object_type", text="")
@@ -251,6 +247,9 @@ class AnnotationToolUI:
add_layout_hotkey_operator(cls.layout, "Add", "S_A", "Create a new annotation")
if object_type in tool.Drawing.ANNOTATION_TYPES_SUPPORT_SETUP:
row = cls.layout.row(align=True)
row.label(text="", icon="DRIVER_ROTATIONAL_DIFFERENCE")
row.prop(cls.props, "tag_rotation_mode", text="")
add_layout_hotkey_operator(
cls.layout,
"Bulk Tag",
@@ -302,6 +301,8 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator):
return
related_objects = bpy.context.selected_objects
created_objects = []
for related_object in related_objects:
obj = core.add_annotation(
tool.Ifc,
@@ -314,7 +315,15 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator):
),
enable_editing=False,
)
tool.Drawing.setup_annotation_object(obj, object_type, related_object)
tool.Drawing.setup_annotation_object(obj, object_type, related_object, props.tag_rotation_mode)
created_objects.append(obj)
# Select the created annotation objects
bpy.ops.object.select_all(action="DESELECT")
for obj in created_objects:
obj.select_set(True)
if created_objects:
bpy.context.view_layer.objects.active = created_objects[-1]
def hotkey_S_A(self):
if bpy.ops.bim.add_annotation.poll():
@@ -344,4 +353,5 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator):
continue
related_object = tool.Ifc.get_object(related_product)
tool.Drawing.setup_annotation_object(obj, annotation_type, related_object)
rotation_mode = tool.Drawing.get_annotation_props().tag_rotation_mode
tool.Drawing.setup_annotation_object(obj, annotation_type, related_object, rotation_mode)
@@ -30,6 +30,7 @@ classes = (
operator.AddSweptAreaSolidItem,
operator.AssignRepresentationLayer,
operator.CopyRepresentation,
operator.DirectProfileEdit,
operator.DisableEditingRepresentationItemShapeAspect,
operator.DisableEditingRepresentationItemStyle,
operator.DisableEditingRepresentationItems,
@@ -109,8 +110,9 @@ def block_scale(scene: bpy.types.Scene) -> None:
if obj.type == "CAMERA":
camera = tool.Ifc.get_entity(obj)
if ifcopenshell.util.element.get_pset(camera, "EPset_Drawing", "TargetView") == "REFLECTED_PLAN_VIEW":
obj.scale = (-1, -1, -1)
obj.rotation_euler = (0.0, 0.0, math.radians(180))
# Only update if scale isn't already (-1, -1, -1)
if obj.scale != (-1, -1, -1):
obj.scale = (-1, -1, -1)
else:
if obj.scale != (1, 1, 1):
obj.scale = (1, 1, 1)
@@ -159,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))
@@ -177,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))
+476 -88
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
@@ -427,9 +428,7 @@ class SwitchRepresentation(bpy.types.Operator, tool.Ifc.Operator):
bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
ifc_definition_id: bpy.props.IntProperty()
should_reload: bpy.props.BoolProperty()
disable_opening_subtractions: bpy.props.BoolProperty()
should_switch_all_meshes: bpy.props.BoolProperty()
@classmethod
def poll(cls, context):
@@ -465,9 +464,6 @@ class SwitchRepresentation(bpy.types.Operator, tool.Ifc.Operator):
tool.Geometry,
obj=obj,
representation=representation,
should_reload=self.should_reload,
is_global=self.should_switch_all_meshes,
should_sync_changes_first=True,
)
@@ -721,9 +717,6 @@ class UpdateParametricRepresentation(bpy.types.Operator):
tool.Geometry,
obj=obj,
representation=tool.Ifc.get().by_id(props.ifc_definition_id),
should_reload=True,
is_global=True,
should_sync_changes_first=False,
)
if show_representation_parameters:
core.get_representation_ifc_parameters(tool.Geometry, obj=obj)
@@ -817,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)
@@ -831,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:
@@ -866,6 +869,10 @@ class OverrideDelete(bpy.types.Operator):
objects_to_remove = context.selected_objects
self.process_arrays(context)
# Track aggregates before deleting their parts
aggregates_to_check = self.track_aggregates(objects_to_remove)
clear_active_object = True
for i, obj in enumerate(objects_to_remove, 1):
@@ -890,13 +897,28 @@ class OverrideDelete(bpy.types.Operator):
continue
if ifcopenshell.util.element.get_pset(element, "BBIM_Array"):
self.report({"INFO"}, "Elements that are part of an array cannot be deleted.")
return {"FINISHED"}
continue
if element.is_a("IfcGridAxis"):
# Deleting the last W axis is OK
if ((grid := element.PartOfU) and len(grid[0].UAxes) == 1) or (
(grid := element.PartOfV) and len(grid[0].VAxes) == 1
):
self.report(
{"INFO"}, "The last grid axis of a grid cannot be deleted. Delete the grid instead."
)
continue
if tool.Drawing.is_auto_annotation(element):
self.report({"INFO"}, "References cannot be deleted. Exclude the referenced element instead.")
continue
tool.Geometry.delete_ifc_object(obj)
elif tool.Geometry.is_representation_item(obj):
tool.Geometry.delete_ifc_item(obj)
else:
bpy.data.objects.remove(obj)
# Delete empty aggregates after deleting their parts
self.delete_empty_aggregates(aggregates_to_check)
for opening in tool.Model.get_model_props().openings:
if opening.obj is not None and not tool.Ifc.get_entity(opening.obj):
bpy.data.objects.remove(opening.obj)
@@ -929,6 +951,53 @@ class OverrideDelete(bpy.types.Operator):
data["old_file"].redo()
tool.Ifc.set(data["new_file"])
def track_aggregates(self, objects_to_remove):
"""Track aggregates that contain objects being deleted"""
aggregates_to_check = set()
for obj in objects_to_remove:
if not tool.Blender.is_valid_data_block(obj):
continue
element = tool.Ifc.get_entity(obj)
if not element:
continue
aggregate = ifcopenshell.util.element.get_aggregate(element)
if aggregate:
aggregates_to_check.add(aggregate)
return aggregates_to_check
def delete_empty_aggregates(self, aggregates_to_check):
"""Delete aggregates that now have no parts"""
deleted_aggregates = []
for aggregate in aggregates_to_check:
# Check if aggregate still exists (might have been deleted already)
try:
aggregate.id()
except:
continue
# Skip spatial elements - they should not be deleted even if empty
if aggregate.is_a("IfcSpatialElement"):
continue
related_objects = ifcopenshell.util.element.get_parts(aggregate)
if len(related_objects) == 0:
aggregate_name = aggregate.Name or f"{aggregate.is_a()} #{aggregate.id()}"
deleted_aggregates.append(aggregate_name)
aggregate_obj = tool.Ifc.get_object(aggregate)
if aggregate_obj and tool.Blender.is_valid_data_block(aggregate_obj):
tool.Geometry.delete_ifc_object(aggregate_obj)
# Show info message if aggregates were deleted
if deleted_aggregates:
if len(deleted_aggregates) == 1:
self.report(
{"INFO"}, f"Aggregate '{deleted_aggregates[0]}' was deleted because it had no remaining parts"
)
else:
aggregate_list = ", ".join(f"'{name}'" for name in deleted_aggregates)
self.report({"INFO"}, f"Aggregates {aggregate_list} were deleted because they had no remaining parts")
def process_arrays(self, context: bpy.types.Context) -> None:
ifc_file = tool.Ifc.get()
selected_objects = set(context.selected_objects)
@@ -976,9 +1045,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
@@ -997,6 +1075,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:
@@ -1067,10 +1148,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):
@@ -1093,7 +1182,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)
@@ -1151,6 +1240,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"
@@ -1173,9 +1265,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)
@@ -1189,8 +1281,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)
@@ -1203,20 +1293,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
@@ -1229,25 +1314,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
@@ -1261,23 +1342,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"]}
@@ -1297,43 +1383,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):
@@ -1369,7 +1485,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."""
@@ -1415,8 +1531,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")
@@ -1424,15 +1540,22 @@ 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)
original_data[group][index] = {"Name": str(pset["Aggregate_Index"]), "Assignment": annotations}
container = ifcopenshell.util.element.get_container(element)
original_data[group][index] = {
"Name": pset["Name"],
"Aggregate_Index": pset["Aggregate_Index"],
"Assignment": annotations,
"Container": container,
}
parts = ifcopenshell.util.element.get_parts(element)
if parts:
for part in parts:
if part.is_a("IfcElementAssembly"):
# TODO: unused expression.
original_data | get_original_data(part)
else:
try:
@@ -1470,13 +1593,22 @@ class RefreshLinkedAggregate(bpy.types.Operator, tool.Ifc.Operator):
index = pset["Index"]
if index == 0:
obj.name = pset["Name"]
obj.name = pset["Name"] + "_" + str(original_data[group][index]["Aggregate_Index"])
ifc_file = tool.Ifc.get()
ifcopenshell.api.pset.edit_pset(
ifc_file,
ifc_file.by_id(pset["id"]),
properties={"Aggregate_Index": int(original_data[group][index]["Name"])},
properties={"Aggregate_Index": int(original_data[group][index]["Aggregate_Index"])},
)
bonsai.core.spatial.assign_container(
tool.Ifc,
tool.Collector,
tool.Spatial,
container=original_data[group][index]["Container"],
element_obj=obj,
)
for part in ifcopenshell.util.element.get_parts(tool.Ifc.get_entity(obj)):
tool.Collector.assign(tool.Ifc.get_object(part))
assignments = original_data[group][index]["Assignment"]
if assignments:
assign_to_annotations(obj, assignments)
@@ -1889,9 +2021,6 @@ class OverrideJoin(bpy.types.Operator, tool.Ifc.Operator):
tool.Geometry,
obj=self.target,
representation=representation,
should_reload=True,
is_global=True,
should_sync_changes_first=False,
apply_openings=True,
)
@@ -2420,6 +2549,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"
@@ -2969,6 +3356,7 @@ class ImportRepresentationItems(bpy.types.Operator, tool.Ifc.Operator):
item_mesh = bpy.data.meshes.new("tmp")
tool.Ifc.link(item, item_mesh)
item_obj = bpy.data.objects.new("tmp", item_mesh)
tool.Geometry.lock_scale(item_obj)
tool.Geometry.name_item_object(item_obj, item)
item_obj.matrix_world = obj.matrix_world
bpy.context.collection.objects.link(item_obj)
@@ -157,8 +157,6 @@ class BIM_PT_representations(Panel):
icon="FILE_REFRESH" if representation["is_active"] else "OUTLINER_DATA_MESH",
text="",
)
op.should_switch_all_meshes = True
op.should_reload = True
op.ifc_definition_id = representation["id"]
op.disable_opening_subtractions = False
row.operator("bim.remove_representation", icon="X", text="").representation_id = representation["id"]
+1 -1
View File
@@ -41,7 +41,7 @@ class SolarData:
@classmethod
def sun_position(cls):
return tool.Blender.get_sun_position_addon()
return tool.Blender.get_addon("sun_position")
@classmethod
def sites(cls):
@@ -202,7 +202,7 @@ class RadianceRender(bpy.types.Operator):
print(f"Camera position: {camera_position}")
print(f"Camera direction: {camera_direction}")
# sun_position = tool.Blender.get_sun_position_addon()
# sun_position = tool.Blender.get_addon("sun_position")
# azimuth, elevation = sun_position.sun_calc.get_sun_coordinates(
# sun_pos_props.time,
# sun_pos_props.latitude,
+6 -5
View File
@@ -40,7 +40,7 @@ from bpy.types import PropertyGroup
from bonsai.bim.module.light.data import SolarData
from bonsai.bim.module.light.decorator import SolarDecorator
sun_position = tool.Blender.get_sun_position_addon()
sun_position = tool.Blender.get_addon("sun_position")
now = datetime.datetime.now()
with open(os.path.join(os.path.dirname(__file__), "spectraldb.json"), "r") as f:
@@ -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"

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