Compare commits

...

912 Commits

Author SHA1 Message Date
Andrej730 b4740b6949 append_asset to also match material sets by the name #6762 2025-07-21 18:10:11 +05:00
Andrej730 d66dd1be18 Button that allows changing linked ifc project filepath
Example - https://files.catbox.moe/usc5cu.png
2025-07-21 18:10:11 +05:00
Andrej730 adfe56c777 typing 2025-07-21 18:10:11 +05:00
carlopav dcf8329b04 Pdf ifc cost schedule export improvements (#6884)
* Export_IfcCostSchedule_to_PDF_improvements

* IfcCostSchedule CSV export: Added ItemIsASum column

New column in the ifc export that tracks if IfcCostItem is a sum, also added a new static method to the IfcDataGetter class.

* IfcCostSchedule CSV export: Added cost quantities column

Cost quantities are a serialsed list containing the name of the quantity and the quantity value.

* IfcCostScheduel PDF export: add options to fine tune export

New options include nested_structure_depth, should_print_cover, should_print_description, should_print_rates, should_print_summary, should_print_cost_ids.

Also pass project currency to typst (still not used).

Added footer with "proudly created with IfcOpenShell".

Updated Cover with formatting and IfcCostSchedule Description
2025-07-21 11:34:00 +05:00
Richard Brice d49a3486b9 Commented out debugging code for alignment 2025-07-18 14:47:14 -07:00
Richard Brice 78759a63f2 Updates helmert curve business2geometry mapping (again) 2025-07-18 12:29:28 -07:00
Richard Brice 913076de94 Updates helmert curve business2geometry mapping 2025-07-18 09:47:59 -07:00
Andrej730 e318cbd9e6 dev_environment.py - process broken symlinks 2025-07-18 20:17:04 +05:00
Andrej730 2f1de2f104 Autoload active bsdd from IFC file
Now we store in the IFC file list of active bsdd user was using in Bonsai.
So when you open IFC file it automatically marks dictionaries as active and there's no need to go to bsdd UI, unless there are new dictionaries to add/remove.

Example - https://files.catbox.moe/kzwa2n.mp4
2025-07-18 20:17:04 +05:00
Andrej730 0e74ac8eea dev_environment.py - support Mac 2025-07-18 20:17:04 +05:00
Andrej730 31ec4820fc dev_environment.py - readable errors in case bonsai path wasn't found
Since we added lookup for multiple paths, it was failing with confusing "Could not find Bonsai path in expected locations" even if the problem was with BLENDER_PATH.
2025-07-18 20:17:03 +05:00
Andrej730 917e7f9f15 dev_environment scripts - bump Blender version 2025-07-18 20:17:03 +05:00
Andrej730 2f0014a489 Mac compilation instructions - update Python version 2025-07-18 20:17:03 +05:00
Andrej730 92340f159d Mac compilation instructions - include eigen
Otherwise compilation doesn't work
2025-07-18 20:17:03 +05:00
Richard Brice a392a1fbfb Adds alignment as offset curve 2025-07-17 10:48:42 -07:00
Andrej730 728f9e2c04 util.shape - replace ShapeType with W.Triangulation for accuracy 2025-07-17 19:58:48 +05:00
Andrej730 2edfe1e5d8 profile.add_parameterized_profile - add profile_type arg
To create more valid profiles by default. Noticed then Bonsai creates new profiles, it doesn't set ProfileType, leading to validation errors.
2025-07-17 19:58:48 +05:00
Andrej730 0b48120a66 bim.create_all_shapes - allow selecting library to use 2025-07-17 19:58:48 +05:00
Andrej730 577d46eb70 typing 2025-07-17 19:58:47 +05:00
Andrej730 83a3813f53 debug operators - remove unnecessary undo step 2025-07-17 19:58:47 +05:00
Bruno Perdigão dd14f33c0b Allow "Refresh Linked Aggregate" to be called when uppermost aggregate is selected. 2025-07-16 17:52:48 -03:00
Bruno Perdigão b305e2354b Fix issue with DuplicateMoveLinkedAggregate object selection.
When dealing with multiple aggregates, the operator was not selecting
the right aggregate to move.
2025-07-16 17:52:48 -03:00
Andrej730 afb2742c2f standalone_drawer - print example args 2025-07-16 18:55:38 +05:00
Andrej730 55d533309e Change Python log level from UI
Location - https://files.catbox.moe/4umtyc.png

E.g. can be useful to narrow down some issues during ifc import
2025-07-16 18:55:37 +05:00
Andrej730 00f7871386 BIM_UL_topics - move to bcf module 2025-07-16 18:55:37 +05:00
Andrej730 f15a23483e typing 2025-07-16 18:55:37 +05:00
Andrej730 f14b4c467d Revert "Bump IOS" #6909
This reverts commit a4dd1c3463.
2025-07-16 16:36:16 +05:00
Dion Moult 4a9cf433d1 Fix bug in IfcTester thanks to Sayan where parsing XML text using ET would expand namespaces and therefore break namespaces during decode 2025-07-16 18:14:45 +10:00
Andrej730 866362c6ed More readable error creating a drawing with underlay but no active style #4870 2025-07-16 12:31:10 +05:00
Andrej730 0dc17a2141 Revert optional pyradiance 41e6478 #6633
Since issue is resolved upstream and there's no need to keep import optional.
2025-07-16 11:22:45 +05:00
Andrej730 d55e7faf2a Fix ifc4x3 breaking import of csvs that have enum values after c9847ca #6708 2025-07-15 18:01:57 +05:00
Andrej730 52762f828c Save all properties from Preferences UI as actual preferences
Previously some of them were saved only for current .blend file, moving them to preferences will make it more consistent with usual Blender UX. If needed we'll be able to add some way to fine-grain them later.

There's also a temporary patch that's going to migrate old .blend-props to new preferences-props to make process less disruptive.
2025-07-15 17:47:38 +05:00
Andrej730 1d0868eb18 bim.attribute_search_values - enable by default if possible
Since release just happened, it's more safe to try out propagating attribute search feature everywhere.
2025-07-15 17:47:37 +05:00
Andrej730 9461c6369e ci-bonsai-daily - use Blender 4.5 build 2025-07-15 17:47:37 +05:00
Andrej730 0f3b570800 Fix get_segment_length bug with non-parametrized profiles (f9c4b6a)
`settings` wasn't defined, so `create_shape` was always resulting in an error. Also, `create_shape` is returning `Triangulation` in this kind of cases, so there's no `.geometry`.

Also removed IfcCircleProfileDef if-check since it's covered by IfcParameterizedProfileDef.
2025-07-15 17:47:37 +05:00
Andrej730 30cbfad871 Fix code typo in 40f92d1 2025-07-15 17:47:37 +05:00
Andrej730 262739f37a owner.remove_role - fix error handling resource level rel (fe1e307)
It's probably was just an overlook in the original commit - it was referring to `settings["organisation"]`, though `"organization"` was never passed, leading to an error. And probably by accident remove_role tests were using the same tests as remove_organization, so the issue went under the radar for a while.
2025-07-15 17:47:37 +05:00
Andrej730 03fa850ce2 Fix old bug in FixArchiCADToRevitDoorSwings (1d74306)
It was using variable `f` that was never defined
2025-07-15 17:47:36 +05:00
Andrej730 8d2c0d0785 Fix old error setting up bcf camera in some cases (0d6cacd)
`cam_height` and `cam_width` were used in `setup_camera()` but never defined.
2025-07-15 17:47:36 +05:00
Andrej730 742a9df311 Fix error starting import_ifc profiling (in 515bfd1) 2025-07-15 17:47:36 +05:00
Andrej730 c95a0e8ed5 Fix error in generating error message in 8e9d4be
`profile` variable was never defined in this context
2025-07-15 17:47:36 +05:00
Andrej730 a28478c193 Fix bim.align_product operator (after 2db6b86)
It was using `core` in it's code though it was never defined.
2025-07-15 17:47:36 +05:00
Andrej730 b521901c90 Fix error message when filtering more than 50 selected objects (after c4ffc06)
name variable was undefined leading to an error
2025-07-15 17:47:35 +05:00
Andrej730 20f8737fb3 generate_furniture_library - remove unused methods
We have them in demo library either way
2025-07-15 17:47:35 +05:00
Andrej730 f18c93c9ee typing 2025-07-15 17:47:35 +05:00
Richard Brice a461281b1e Fixes problem moving alignment representation from parent to child alignment. Forgot to assign the ObjectPlacement 2025-07-14 11:09:02 -07:00
Andrej730 2d3d747d49 Move props from 'Other' preferences tab to preferences #6733
To prevent users confusion since it's a general Blender UX to expect here general preferences, not per-blend file settings.
2025-07-14 18:53:11 +05:00
Andrej730 d165cd2692 Update material classification UI on changing selected materials #6902 2025-07-14 18:53:11 +05:00
Andrej730 3f0e30245b Fix TestSelectSimilarContainer after 553003e 2025-07-14 18:53:11 +05:00
Andrej730 c111aafd3a Fix TestAddContext after 2698ba6 2025-07-14 18:53:11 +05:00
Andrej730 465bd23d6f import_attributes2 -> import_attributes
import_attributes were removed in 76f0b57ff
2025-07-14 18:53:10 +05:00
Andrej730 c7d3e00dca typing 2025-07-14 18:53:10 +05:00
Andrej730 72af88a4b3 black . 2025-07-14 18:53:10 +05:00
Richard Brice 73b76ba666 Adds IfcPolyline representation for alignment. Also some general cleanup 2025-07-13 16:34:52 -07:00
Dion Moult e93a66be49 Update VERSION 2025-07-13 20:25:14 +10:00
Dion Moult a4dd1c3463 Bump IOS 2025-07-13 19:55:21 +10:00
Andrej730 6d8e513f97 Temporary hack for path tags without 'd' attr #6871 #6876 2025-07-11 18:24:00 +05:00
Andrej730 baeaa073df dimensionality - add description 2025-07-11 18:24:00 +05:00
Andrej730 f07391181d context-ids, context-types, context-identifiers - add descriptions
Hope I don't miss anything, but couldn't find any references to ContextTypes, ContextIdentifiers and they seem to have no effect.
2025-07-11 18:23:59 +05:00
Andrej730 722f71b3b3 ifcgeom/ConversionSettings.h - fix duplicated ContextIds derived type
I guess it only affected how `defineOption` worked. Noticed the issue when was looking through `ifcconvert --help` and `context-ids` were duplicated 3 times.
2025-07-11 18:23:59 +05:00
Andrej730 76e4ec0069 bim.select_uri_attribute - simplify data_path eval 2025-07-11 18:23:59 +05:00
Andrej730 df18124bec vs-cfg.cmd - update error message
To make it a bit more easier to understand what to do in cases like below (the solution is to provide generator explicitly: `run-cmake.bat vs2022-x64 -DADD_COMMIT_SHA=ON -DVERSION_OVERRIDE=ON`).

```
run-cmake.bat -DADD_COMMIT_SHA=ON -DVERSION_OVERRIDE=ON

vs-cfg.cmd: The used CMake version does not support generator `-DADD_COMMIT_SHA` - cannot proceed.
```
2025-07-11 18:23:59 +05:00
Andrej730 370488cd27 docs conf.py - update year 2025-07-11 18:23:59 +05:00
Andrej730 4bbc463c8f ifcopenshell-python\docs\requirements.txt 2025-07-11 18:23:59 +05:00
Andrej730 4a11a117ad Fix bim.activate_model hiding all elements #6893
It was an overlook with using by accident bim.disable_status_filters instead of bim.enable_status_filters in bim.activate_status_filters, but it was also kind of unexpectedly introduced in 7858cb1.

But thinking about it, bim.activate_model shouldn't activate Statuses UI either way, since it might bring their filters they had before but explicitly disabled.
2025-07-11 18:23:58 +05:00
Andrej730 19be8107b3 Fix Ruff UP037 (quoted-annotation) 2025-07-11 18:23:58 +05:00
Andrej730 e1976d5bfd Fix Ruff UP013 (convert-typed-dict-functional-to-class)
Apparently `type` is okay and doesn't break things in even in Python 3.12
2025-07-11 18:23:58 +05:00
Andrej730 0fcc47508a Structural UI - refactor common code to tool, fix bugs
Also revert previous change in UI - `draw_attributes` doesn't cover it and there indeed should be special handling.
2025-07-10 19:42:31 +05:00
Andrej730 76f0b57ff1 Migrate to import_attributes2 2025-07-10 19:42:30 +05:00
Andrej730 1d8ee406af Fix alignment.__all__ missing create_segment_representations 2025-07-10 19:42:30 +05:00
Andrej730 05cb06a28e typing 2025-07-10 19:42:30 +05:00
Andrej730 8a6eb1b60f Update IfcApplication attributes export after 5866994 2025-07-10 19:42:30 +05:00
Andrej730 37d4ee97e7 alignment - fix missing imports 2025-07-10 19:42:29 +05:00
Andrej730 ef5d1448da Fix is_null not updating when adding new attribute subitems #6869 2025-07-10 19:42:29 +05:00
Massimo Fabbro eed42b8fac Fix #6711. Forgot to change the space elevation 2025-07-09 22:27:31 +02:00
Thomas Krijnen 1f0ac8fdb9 submodule 2025-07-09 20:45:39 +02:00
Andrej730 1b1a1f143a bim.attribute_search_values - enable it in more places
Since this feature can be generally useful, let's try it out to see the cases when it should be disabled.

Additionally to Materials UI, it's now enabled in object attributes, materials, profiles, styles and explorer ui.
2025-07-09 19:02:27 +05:00
Andrej730 9649c23a3b bim.attribute_search_values - more clear description 2025-07-09 19:02:27 +05:00
Andrej730 0ad5ace35f bim.attribute_search_values - support not just scene props 2025-07-09 19:02:27 +05:00
Andrej730 f7fdb8cc81 bim.attribute_search_values - rearrange props for readability 2025-07-09 19:02:27 +05:00
Andrej730 41e64781d8 Skip pyradiance import if it fails #6633 2025-07-09 19:02:27 +05:00
Andrej730 6ea7ee84a6 Remove prop.GlobalId
It seems it was unused since 6f55cd5
2025-07-09 19:02:27 +05:00
Andrej730 e7b86ae5b0 bim.add_group - use dynamic description to clarify when it's adding subgroups
It was kind of confusing since the icon and everything else was the same.
2025-07-09 19:02:27 +05:00
Andrej730 f6035609c9 Groups UI - use natsort 2025-07-09 19:02:27 +05:00
Andrej730 76feeaccc9 typing 2025-07-09 19:02:26 +05:00
Andrej730 af02a6b3a6 Fix UP037 (quoted-annotation) 2025-07-09 19:02:26 +05:00
Andrej730 832e68cb6f Fix Ruff UP034 (extraneous-parentheses) 2025-07-09 19:02:26 +05:00
Andrej730 a8268e8323 Replace deprecated typing.Sequence (40f92d15c) 2025-07-09 19:02:26 +05:00
Andrej730 5a4ad69bf7 black . 2025-07-09 19:02:26 +05:00
Richard Brice 40f92d15c3 Alignment api update (#6817)
* Some basic COGO survey points functions

* Update alignment api

Includes defining alignment segment by segment, automatic geometry definitions, and automatric stationing and referents

* Fixes bonsai import alignment from csv

* Adds DMS angle conversion functions to COGO api

* Updated per @civilx64 review comments

* Fixes problem with segment representations

* Fixes problem with segment transition codes

* Allows for compound vertical and horizontal curves

* Implements callbacks for referent naming

* Renames angle_from_bearing to bearing2dd for consistency with ifcopenshell.util.geolocation.dms2dd. Removes angle_from_dms because it duplicates dms2dd

* Documents register_referent_name_callback

* Fixes all sorts of problems with Cant/SegRefCurve implementation

* refactor referent unit tests to use a fixture for test setup

* lint with black

---------

Co-authored-by: Scott Lecher <civilx64@gmail.com>
2025-07-08 10:09:14 -07:00
Andrej730 ad8d02bfed Groups UI - support renaming groups directly from UI
Example - https://files.catbox.moe/u4b918.mp4
2025-07-08 21:20:59 +05:00
Andrej730 5866994b84 Intergrate explorer UI to support editing some props like #6869
Example - https://files.catbox.moe/ujhlwv.mp4
2025-07-08 21:20:59 +05:00
Andrej730 2ec2ac0e5c Attributes UI to support attributes with string subitems
Example - https://files.catbox.moe/oew1ue.mp4
2025-07-08 21:20:58 +05:00
Andrej730 c7555652d7 Bulk Rename Pset Props - show how many props were affected 2025-07-08 21:20:58 +05:00
Andrej730 5a7ce6d68b bim.bulk_remove_psets - show how many psets were removed
Also removed exception handling to catch that bug sneaking in, if it ever returns.
2025-07-08 21:20:58 +05:00
Andrej730 ad8913a979 Move bulk properties to GlobalPsetProperties 2025-07-08 21:20:58 +05:00
Andrej730 188b6a03a8 Attribute.is_uri - move to Attribute.special_type 2025-07-08 21:20:57 +05:00
Andrej730 7f3860b025 util.attribute - add tests 2025-07-08 21:20:57 +05:00
Andrej730 d08756d1b4 typing 2025-07-08 21:20:57 +05:00
Andrej730 525ef2b77b Explorer UI - add is_loaded toggle 2025-07-07 19:11:39 +05:00
Andrej730 fca2bf7aa8 Search filters - add example to criteria descriptions
To give users some default value to get familiar with the tool.
Example - https://files.catbox.moe/d1zh5n.png
2025-07-07 19:11:39 +05:00
Andrej730 ff82262490 Reuse get_active_sheet_item 2025-07-07 19:11:38 +05:00
Andrej730 c82351476a SheetBuilder - move references to local scope
Previously it was set as sheet builder attribute, but was never used as such.
2025-07-07 19:11:38 +05:00
Andrej730 d4f32802aa Show warnings if some sheets related files are missing
Previously when you would load/open/create sheets and some files were missing you would get traceback errors.

Also removed warnings for missing sheets svgs since they can go missing if someone is opening ifc project and want to regenerate sheets. We'll be able to return them in the future, if needed, but they will need some refinement.
2025-07-07 19:11:38 +05:00
Andrej730 d7668c3e3f drawing.get_sheet_identification 2025-07-07 19:11:38 +05:00
Andrej730 1445f91289 Fix very old bug with IfcPersonAndOrganization enum not loading (6ce8a36) 2025-07-07 19:11:37 +05:00
Andrej730 44e27f706a Owner UI - further decouple data.py from Blender entities 2025-07-07 19:11:37 +05:00
Andrej730 416ebde333 Fix names lists are not updated immediately
Similar to addresses problem in #6869
2025-07-07 19:11:37 +05:00
Andrej730 40dcd393c6 Fix addresses not updating immediately in IfcPerson UI
Found by @sboddy in #6869

Caused by trying to remove Blender entities from owner/data.py
2025-07-07 19:11:37 +05:00
Andrej730 4fda916c2f Fix duplicated assignments 2025-07-07 19:11:37 +05:00
Andrej730 acb9953ce3 dev_environment.py - add comments and small changes 2025-07-07 19:11:36 +05:00
Andrej730 4154356288 bim.operator - document resetting screen behaviour 2025-07-07 19:11:36 +05:00
Andrej730 54488bddbb bim.attribute_search_values - use enumproperty for data_type 2025-07-07 19:11:36 +05:00
Andrej730 8f21866248 typing 2025-07-07 19:11:36 +05:00
Thomas Krijnen f3b63aae76 Use should_use_presentation_style_assignment 2025-07-06 18:39:21 +02:00
Thomas Krijnen 3f05bab4e5 Shape builder: allow translate() on tesselated face sets 2025-07-06 18:35:19 +02:00
Thomas Krijnen 8534333092 Update doc comment 2025-07-06 18:34:50 +02:00
Ryan Schultz 5f2b4c96a6 fixes #6874 2025-07-06 10:26:12 -05:00
Massimo Fabbro 00836ec983 Fix #6736. Better use of non-SI units in creating spaces and coverings 2025-07-05 13:59:04 +02:00
Andrej730 fb78f75541 Basic IFC Entities explorer UI
A generic UI to allow editing simple entities like #6869 without creating an entire edit attributes UI inside edit attributes UI.
Still work in progress and should be connected to attributes UI.

Example - https://www.imgchest.com/p/agyv9lpq978
2025-07-04 19:48:12 +05:00
Andrej730 5948282ed0 bim.add_address - display it in more readable way
Before - https://cdn.imgchest.com/files/4gdcxp5j9l4.png
After - https://cdn.imgchest.com/files/yrgcnzgj5n4.png
2025-07-04 19:34:12 +05:00
Andrej730 47b81a6f89 bim.save_search - prefer only SEARCH IfcGroups by default #6857
Previously it was asking for a name to use to save search and users might type anything they find convenient at the moment, but it was possible that this name was used already by something important (for example drawing's IfcGroup) and would change it's description and assign objects there unexpectedly.

Now it will target IfcGroups with SEARCH ObjectType by default and only if user confirms, it will target all IfcGroups available (to keep the search a powerful tool for advanced users that allows them to populate any IfcGroup they want with items).

There's also a temporary patch in place  - when you load/save search Bonsai will try to find old search IfcGroups with ObjectType = None and update their ObjectType, this needed to make sure transition works seamlessly for the simple cases.
2025-07-04 19:34:12 +05:00
Andrej730 8ae08cb90e bim.save_search - not to fail silently 2025-07-04 19:34:11 +05:00
Andrej730 7796856119 typing 2025-07-04 19:34:11 +05:00
falken10vdl f75045f0c5 See #6570. Now it is possible to remove the csv cost file link.
This is a followup to that issue. In this particular case, a remove csv link button is added next to the refresh csv link button.
2025-07-04 12:35:17 +02:00
Andrej730 e52af2baad Iterator.validate_iterator_state to avoid crashes using next() and get()
Just to avoid accidental crashes using .get() / .next() when using them from Python - in cases when iterator was initialized but there were no elements or if iterator got exhausted.
2025-07-03 18:49:41 +05:00
Andrej730 da4ae4c2f2 Fix typo in 2a199cf 2025-07-03 18:48:30 +05:00
Andrej730 28043a8e5d ifc2sql - skip creating geometry if iterator failed to initialize 2025-07-03 18:48:30 +05:00
Andrej730 0e8fb44301 Remove unused Iterator.task_result_index_ (c78b289) 2025-07-03 18:48:08 +05:00
Andrej730 b97dca5d47 typing 2025-07-03 18:48:08 +05:00
Andrej730 207db459a7 Iterator.h - remove redundant typename (eeaf04c) 2025-07-03 18:48:07 +05:00
Andrej730 026eebf39b pyproject.toml - remove enableTypeIgnoreComments
As Pylance now allows inserting pyright ignore comments without it 🎉

https://github.com/microsoft/pylance-release/issues/7220
2025-07-03 18:48:07 +05:00
carlopav 5f5c0b41d5 Basic infrastructure for IfcCostSchedule PDF export with typst (#6860) 2025-07-03 13:18:03 +00:00
falken10 3055dfa1a6 clean up from developer's feedback + black formatting 2025-07-03 05:51:59 +02:00
falken10 b59bdb585b Refactor cost documents following developrs advice 2025-07-03 05:51:59 +02:00
falken10 ccd97c66ae refactor and black formatting 2025-07-03 05:51:59 +02:00
falken10 e6747b258c Remove CostSchedulesMapping and added csv_filepaths to data.py 2025-07-03 05:51:59 +02:00
falken10 6aa6803490 Added relative CSV file paths 2025-07-03 05:51:59 +02:00
falken10 72fbe3a6d9 Refactor cost schedule CSV filepath to use document references 2025-07-03 05:51:59 +02:00
Andrej730 68a602573b ifcopenshell.geom.iterator - more descriptive error in __init__ #6865 2025-07-02 18:14:34 +05:00
Andrej730 1e06dfcb76 Fix console errors when project is not loaded 2025-07-02 18:14:34 +05:00
Andrej730 3a0a912022 Use property_unset instead of in and del 2025-07-02 18:14:34 +05:00
Andrej730 b16fb7d917 bim.pip_install - add dynamic description 2025-07-02 18:14:34 +05:00
Andrej730 94d2d32d18 typing 2025-07-02 18:14:34 +05:00
falken10vdl e743db27f4 Draw attributes with search (#6849)
See example in #4325
2025-07-02 11:11:29 +05:00
Thomas Krijnen bb329affb8 settings: DeferProcessingFirstElement, MaxOffset, MaxOffsetDeviation, ApplyOffset; taxonomy: centroid funcs; iterator get_tasks + items() funcs; geom.map_shape() 2025-07-01 21:10:24 +02:00
Bruno Perdigão 6c4e4e5f91 Partial solution for #6775.
The issue happens when a wall has a "ATPATH" connection and has a
negative direction sense. It mixes the profile points in an incorrect
order. This commit seems to fix it, but maybe there is a better solution.
2025-06-30 19:50:41 -03:00
falken10 a31aa6053e black formatting 2025-06-30 18:25:41 -03:00
falken10 b90f146a68 update gizmo based on selected items 2025-06-30 18:25:41 -03:00
falken10 f67dca8de0 Enhance label rendering in BoundingBoxDecorator to prevent text overlaps 2025-06-30 18:25:41 -03:00
falken10 a686d70908 Updated based on feedback from @brunoperdigao 2025-06-30 18:25:41 -03:00
falken10vdl 38fe7fea65 Update workspace.py cosmetic remove nl from previous edits 2025-06-30 18:25:41 -03:00
falken10 b26890f74b black formating 2025-06-30 18:25:41 -03:00
falken10 2c16247943 moved to viewport overlays 2025-06-30 18:25:41 -03:00
falken10 53fc7bce0c clenaup 2025-06-30 18:25:41 -03:00
falken10 f5db905223 Separate decorator from operator to follow other modules in model / project 2025-06-30 18:25:41 -03:00
falken10 3557b11e64 same style as polyline texts 2025-06-30 18:25:41 -03:00
falken10 c420402334 black . formating 2025-06-30 18:25:41 -03:00
falken10 589c562cba refactor everyting within the class MeasureXYZDimensionsTool 2025-06-30 18:25:41 -03:00
falken10 d3f2711fe4 Moved everything to explore tool 2025-06-30 18:25:41 -03:00
falken10 8b4305360e updated to cope for multiple selection and better looking using trihedrons 2025-06-30 18:25:41 -03:00
falken10 1213bbdd73 set exact same color for bounding box edges as gizom axis 2025-06-30 18:25:41 -03:00
falken10 c3380daa19 better layout with colored bounding box 2025-06-30 18:25:41 -03:00
falken10 7aa87907d7 Add local orientation operator and UI toggle for colored dimensions 2025-06-30 18:25:41 -03:00
Andrej730 8cfd6b1754 Unify doc-strings (remove type/rtype)
As this information is already provided in type hints and we don't need to maintain it in two places.
2025-06-30 18:55:14 +05:00
Andrej730 fc10915d2b ifcopenshell.validate - remove simple_spf dependency (689289b)
Moved it to `TYPE_CHECKING` since it's currently used just for typing.

Was breaking validation in Bonsai since it doesn't provide all submodules with IfcOpenShell https://github.com/IfcOpenShell/IfcOpenShell/issues/6723#issuecomment-3016223786

Also added a simple test so github workflow would catch a simple error like this.
2025-06-30 18:08:34 +05:00
Andrej730 c23c21cb88 black . 2025-06-30 18:08:29 +05:00
Geert Hesselink 260bc80a4b update simple_spf submodule 2025-06-30 10:11:40 +02:00
Thomas Krijnen d7cf803fdf Don't use anonymous namespace for geometry_conversion_result 2025-06-29 20:04:44 +02:00
Thomas Krijnen 4488044a68 yum install xz 2025-06-29 19:58:37 +02:00
skemaikin 21a4db7823 serialize roughnessFactor
add roughnessFactor for gltf serialization
2025-06-29 14:24:52 +02:00
Ryan Schultz d300454118 Option to select only immediate children in Spatial Decomposition panel 2025-06-28 15:00:23 -05:00
Ryan Schultz 553003e664 Closes #6748: An option, when running bim.select_similar_container() to only select those objects in the immediate container 2025-06-28 13:16:51 -05:00
Ryan Schultz 524561a398 similar to #6748: option to select only immediate children, not recursively 2025-06-28 12:07:22 -05:00
Ryan Schultz 16f82683c8 closes #6843 - made merge by distance a little more tolerable 2025-06-28 10:07:54 -05:00
Ryan Schultz 4dbec59b9c Fix #6840: Add search to profile selection, when creating a new extrusion type 2025-06-28 09:17:40 -05:00
falken10vdl dcf69c46e5 Update PLAN_LEVEL annotation (#6844)
* Update PLAN_LEVEL annotation

* Length of line segment based on scale

---------

Co-authored-by: Ryan Schultz <ryan@openingdesign.com>
2025-06-27 09:48:12 -05:00
Andrej730 611e83ff1e Remove unused code for additional ifc files in the drawings
Code isn't used for over 3 years now and it's quite simple, so it's easy to restore if we'll need it again.
Noticed when started to investigate what was the use for `include_query` and `exclude_query` and turned out they basically had no effect for all this time.
2025-06-27 18:51:15 +05:00
Andrej730 bdcebd0f98 document.add_information - error handling in case project is not found 2025-06-27 18:51:15 +05:00
Andrej730 9278974d98 Reuse get_linked_models_document 2025-06-27 18:51:15 +05:00
Andrej730 b3a708c06c typing 2025-06-27 18:51:15 +05:00
falken10vdl 0b05c3bcfc Z-coordinate in PlanLevelDecorator relative to spatial container if exists. Absolute otherwise (#6845)
* Z-coordinate  in PlanLevelDecorator relative to spatial container if exists. Absolute otherwise
2025-06-27 08:20:50 -05:00
Piotr Smolira fef6ef76ed Update code_examples.rst
Added example to iterate through entire project.
2025-06-27 11:37:47 +02:00
Piotr Smolira e39d62b35a Update code_examples.rst
Added example about iterating entire model.
2025-06-27 11:37:47 +02:00
Thomas Krijnen f1827d02ca Retain non-uniform scaling by assigning to gtrsf mat directly #6841 2025-06-27 09:54:13 +02:00
Andrej730 65ccc635d4 Fix sheeter issue after 7440c321b 2025-06-27 10:58:28 +05:00
falken10vdl 2698ba69d5 Display TargetScale in Contexts UI / edit target scale denominator (#6816) 2025-06-27 05:48:14 +00:00
Andrej730 efb3b9d255 Add eevee shadows props to shading_styles.json #6847 2025-06-26 20:19:41 +05:00
Andrej730 b0ec185c34 Fix removing occurrences when removing type object #3931 2025-06-26 19:57:10 +05:00
Andrej730 0ca92f6a1d Bonsai preferences - add 'bsdd' label to make bsdd props more recognizable 2025-06-26 19:57:09 +05:00
Andrej730 6e4b784652 bsdd props ui - small tweaks 2025-06-26 19:57:09 +05:00
Andrej730 c764da3511 Status Filter - add missing row alignment in UI 2025-06-26 19:57:09 +05:00
Andrej730 c0d97051fd Status Filters operators - add undo step to prevent Blender crashes
In Blender if operator is editing any Blender data it must have `UNDO` option, otherwise undo might lead to a crash.
2025-06-26 19:57:09 +05:00
Andrej730 2a199cf8a7 Status Filters - add explicit button to refresh status filter
Button location - https://files.catbox.moe/yuk2e6.png
See the reasoning in https://github.com/IfcOpenShell/IfcOpenShell/pull/6826#issuecomment-3004090797
2025-06-26 19:57:08 +05:00
Andrej730 b5dacfd621 Fix console errors toggling ifc status (same as e92520b77) 2025-06-26 19:57:08 +05:00
Andrej730 1a67ce87a1 light.feature - more test fixes 2025-06-26 19:57:08 +05:00
Andrej730 b7db741474 typing 2025-06-26 19:57:08 +05:00
Andrej730 7b1aa207d8 Add a warning in case user set a large drawing resolution #6686
Example - https://files.catbox.moe/0ygukk.png
2025-06-26 16:28:52 +05:00
Stephen Boddy 9b58d4aa47 Minor refinements for 931d8e6 2025-06-26 10:12:55 +05:00
Stephen Boddy bc2d67220e Improve fix for #5563 based on feedback:
- Remove INFO - No statuses selected,
- Remember and set last status on disable-reenable
- Moved duplicated code into shared tool.Sequence class method
2025-06-26 10:12:55 +05:00
Stephen Boddy 7858cb128e Fix #5563 inconsistent behaviour of Status filter 2025-06-26 10:12:55 +05:00
Bruno Perdigão 0d859bd4ee See #6837. Fix handling of connections when splitting walls. 2025-06-25 19:00:25 -03:00
Andrej730 360afe68aa sheeter - replace by_id with by_guid
No functional changes, just to make it more idiomatic (both `by_id` and `by_guid` end up doing `ifc_file[xxx]` under the hood).
2025-06-25 19:01:41 +05:00
Andrej730 b6af68bfe6 data/templates/titleblocks/README.md
Basic readme about folder contents. Will need to move it more general documentation later.
2025-06-25 19:01:41 +05:00
Andrej730 7440c321b6 sheeter - use more strict data access where it's implied 2025-06-25 19:01:41 +05:00
Andrej730 73b5c2f1ae sheeter - simplify namespace access using constants 2025-06-25 19:01:41 +05:00
Andrej730 485ce22ca7 bim.select_all_sheets - fix a typo 2025-06-25 19:01:41 +05:00
Andrej730 e92520b777 Fix value errors in update callback
Blender is requiring update callback to return `None`, otherwise it prints `ValueError: the return value must be None`.
2025-06-25 19:01:40 +05:00
Andrej730 00414a1590 bim.assign_scene_units - expose operator to ui 2025-06-25 19:01:40 +05:00
Andrej730 6b6bf248ff typing 2025-06-25 19:01:40 +05:00
Andrej730 03fcd59c5a black . 2025-06-25 19:01:40 +05:00
Andrej730 5e89748ba6 unit - remove unnecessary ifc operators 2025-06-25 19:01:39 +05:00
Stephen Boddy c5987bca3d Fix #4588 fix broken rev cloud styles in sheets. 2025-06-25 18:59:42 +05:00
Bruno Perdigão 94f98b1e0e Fix issue where edge intersection snap were not being calculated. 2025-06-24 18:17:24 -03:00
Thomas Krijnen 38dcdde7ae submodule 2025-06-24 22:12:38 +02:00
Thomas Krijnen 95f8af01f0 submodule 2025-06-24 22:03:13 +02:00
Thomas Krijnen 8f0d053049 Factor out code into Iterator.cpp 2025-06-24 22:03:13 +02:00
Andrej730 caccfd2516 unit operators - add some descriptions 2025-06-24 19:08:43 +05:00
Andrej730 b4c4efcf5b unit.unassign_unit - smal doc-string fix 2025-06-24 19:08:43 +05:00
Andrej730 fe81dc7f21 Units UI - match icons in enum with icons in ui list
To build a visual association
Example (previously enum had no icons)
https://files.catbox.moe/0ks3jj.png
https://files.catbox.moe/y1yrv5.png
2025-06-24 19:08:42 +05:00
Andrej730 4f77c41bd5 calculate_unit_scale - small optimization 2025-06-24 19:08:42 +05:00
Andrej730 47b077b555 Fix sheets related tests 2025-06-24 19:08:42 +05:00
Andrej730 845e034614 test_feature - fix non-unique function name 2025-06-24 19:08:42 +05:00
Andrej730 82d020b20d bim light tests - fix after f684a6058
Since defaults were updated
2025-06-24 19:08:42 +05:00
Andrej730 cd73f80894 Fix tool test_drawing after 992ad8e 2025-06-24 19:08:41 +05:00
Andrej730 705a924f41 Fix context's TestImportAttributes after e4a50b3 2025-06-24 19:08:41 +05:00
Andrej730 097e03d6f5 Fix TestGenerateSheetIdentification (b20997d) 2025-06-24 19:08:41 +05:00
Andrej730 4a69df1f6b Classification source - automatically update on changes in bsdd ui 2025-06-24 19:08:41 +05:00
Andrej730 df69ca27da Remove duplicated active_dictionary prop (22f10e5) 2025-06-24 19:08:41 +05:00
Andrej730 b3778facda Fix core drawing tests after be65774 2025-06-24 19:08:41 +05:00
Andrej730 c333535ae5 Blender 4.5 - prevent infinite loading of previews
Related - https://projects.blender.org/blender/blender/issues/140903
2025-06-24 19:08:40 +05:00
Andrej730 fcaeb920ec typing 2025-06-24 19:08:40 +05:00
Andrej730 50d3cab1ec Fix regression with thumbnails update #6839 (be1836f) 2025-06-24 19:08:40 +05:00
Andrej730 4574351817 Light UI - detect Sun Position addon activation/deactivation more reliably 2025-06-23 19:06:16 +05:00
Andrej730 623997b40d Revert unintended prop_with_search change (98c479f1b) 2025-06-23 19:06:16 +05:00
Andrej730 8f7b965a58 Light UI - add our own 'coordinates' property
To ensure when it's changed - latitude and longitude update will follow. Previously we were just displaying sun position addon's `coordinates' prop.
2025-06-23 19:06:16 +05:00
Andrej730 c1e6e7d736 light module true_north prop - use Blender angle subtype 2025-06-23 19:06:16 +05:00
Andrej730 f684a6058c Light UI - quickly select current time/pick location from Google Maps
Location - https://files.catbox.moe/ew9tgd.png ("Now" and "Pick" buttons)
2025-06-23 19:06:15 +05:00
Andrej730 1a80e3f1a1 bim.enum_property_search - note on activate_init 2025-06-23 19:06:15 +05:00
Andrej730 a76af3c2ba validate IfcApplication.UR2 - check that both version and app name are not None (263d238f9)
Otherwise it will be caught by regular validation checks
2025-06-23 19:06:15 +05:00
Andrej730 28bee52dff Blender 4.5 - deprecate GPUShader constructor
Now it should be replaced with `gpu.shader.create_from_info`
2025-06-23 19:06:15 +05:00
Andrej730 58449368fb radiance_exporter_properties -> BIMRadianceExporeterProperies
For consistency with other props
2025-06-23 19:06:15 +05:00
Andrej730 30c33518f0 typing 2025-06-23 19:06:14 +05:00
Andrej730 3a06afae25 black . 2025-06-23 19:06:14 +05:00
falken10vdl 98c479f1bd Create instance context menu (#6830) 2025-06-22 07:09:57 -05:00
Ryan Schultz d875baefde #5709 - Partly addresses RCP camera flipping around. (still hacky though) 2025-06-21 10:14:09 -05:00
Ryan Schultz 9149bb0fdc whoops, accidently pushed. 2025-06-21 08:11:01 -05:00
Ryan Schultz f3c86d7fcc added a few to ifc_classes_suggestions.json 2025-06-21 08:08:50 -05:00
Ryan Schultz 64aeef7103 Automatically detect bonsai_path 2025-06-21 07:59:51 -05:00
Andrej730 cd1bda5311 regenerate_wall_representation - remove debug prints 2025-06-20 18:48:38 +05:00
Andrej730 67ae9fced0 bim.extend_walls_to_polyline_point - fix poll errors selecting non-ifc objects
E.g.
```
  File "\bonsai\bim\module\model\wall.py", line 142, in poll
    if not tool.Ifc.get_entity(obj).is_a("IfcWall"):
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'is_a'
```
2025-06-20 18:48:37 +05:00
Andrej730 22fdb324ac Restore daily builds 2025-06-20 18:48:37 +05:00
Andrej730 263d238f9c ifcopenshell.validate - fix IfcApplication.UR2 validation
Previously we were checking for just ApplicationFullName, without considering Version.

https://github.com/buildingSMART/IFC4.3.x-development/issues/966
2025-06-20 18:48:37 +05:00
Andrej730 c6fd02869b ifcopenshell.validate - check json_logger more explicitly 2025-06-20 18:48:37 +05:00
Andrej730 b4d41790a5 ifcopenshell.validate - fix mixed up rule names (IfcApplication.UR) 2025-06-20 18:48:37 +05:00
Andrej730 e18346d4ea bim.remove_application - remove unneccessery info from the tooltip
Since now user can edit applications and see that info there.
2025-06-20 18:48:36 +05:00
Andrej730 c724b2a888 Applications UI - also show app versions
So apps with different version would be identifiable by a glance.
2025-06-20 18:48:36 +05:00
Andrej730 d05f320665 Bonsai - prevent duplicating IfcOpenShell organization for new IfcApplications 2025-06-20 18:48:36 +05:00
Andrej730 6599f74d11 Make Bonsai IfcApplication identifier unique
To avoid validation errors.
2025-06-20 18:48:36 +05:00
Andrej730 4f03c7e03e OwnerData.can_add_user - remove as unnecessary 2025-06-20 18:48:36 +05:00
Andrej730 946cae4065 ifcgit.git_diff - save diff to temp file on alt-click 2025-06-20 18:48:35 +05:00
Andrej730 98145fbbd9 Purge/merge IfcPersonAndOrganizations 2025-06-20 18:48:35 +05:00
Andrej730 74598b805b Purge/merge IfcPerson entities 2025-06-20 18:48:35 +05:00
Andrej730 90ab846a18 typing 2025-06-20 18:48:34 +05:00
Andrej730 5ec77b1b1c black . 2025-06-20 18:48:34 +05:00
Bruno Perdigão 8d079ce0cb Remove unused code. 2025-06-19 16:38:22 -03:00
Bruno Perdigão fb9edb733b Refactor MeasureDecorator to use model PolylineDecorator code. 2025-06-19 16:33:51 -03:00
Andrej730 42b777b1f0 application ui - support changing organization 2025-06-19 19:28:34 +05:00
Andrej730 a968cad4cc core/tool tests for application methods 2025-06-19 19:28:34 +05:00
Andrej730 7ab03109b9 typing 2025-06-19 19:28:33 +05:00
Andrej730 6782729ed8 black . 2025-06-19 19:28:33 +05:00
Ryan Schultz 9dc75ece44 Fix #6593 - preference to turn on/off cut decorator 2025-06-18 21:36:04 -05:00
Andrej730 cbd78506ce Fix mistake in 96bdda305 - expose IfcApplication purge to UI
Noticed a mistake when merge was included twice for applications, instead of including purge+merge.
2025-06-18 19:13:59 +05:00
Andrej730 9af15aeb0b Simple application UI for editing attributes
Example - https://files.catbox.moe/i0wgid.png

Previously before 71d469a27 we produced a lot of IfcApplications on live-builds because of the version differences, now it's possible to edit them from UI - fix the differences and merge them using 106323847
2025-06-18 19:12:12 +05:00
Andrej730 934c9e1485 test_edit_organisation - add ifc4x3 tests 2025-06-18 19:12:12 +05:00
Andrej730 bbebdd56ca owner.edit_application 2025-06-18 19:12:12 +05:00
Andrej730 b1bfe5c3b3 tool.ifc.run - replace with direct api calls 2025-06-18 19:12:11 +05:00
Andrej730 a72e1231f2 test_ids - use more generic exception to fix tests on xmlschema>=4.0
In xmlschema 4.0 it started to throw a more generic XMLSchemaValidationError - I guess there are many things that can trigger validation check and in the new version some other issue gets there first.
2025-06-18 19:12:11 +05:00
Andrej730 47da4ef3ae Fix overlook in 67b539aba 2025-06-18 19:12:11 +05:00
Andrej730 08a8afba5b ifcopenshell-python - fix 3.9 support (replace match statements) 2025-06-18 19:12:10 +05:00
Andrej730 44e5f89119 ruff 0.12 - ignore UP045
Kind of acts the same as UP007 asking to convert all Optional[xxx] to X | None
2025-06-18 19:12:10 +05:00
Andrej730 c68b0c7280 typing 2025-06-18 19:12:10 +05:00
Andrej730 8c5affaae6 linter github workflow - use uv for acquiring black 2025-06-18 19:12:09 +05:00
Andrej730 bd4b80bdc2 linter github workflow - use correct minimum required versions
Previously it was using Python 3.12 for everything, therefore syntax errors from Python 3.9 (e.g. `match` statement in ifcopenshell-python) went under the radar.
2025-06-18 19:12:09 +05:00
Andrej730 21f5363c74 Bonsai - cache get_user and get_application for a speed-up 2025-06-17 18:44:32 +05:00
Andrej730 71d469a271 Bonsai - use main version part to avoid creating too many IfcApplications 2025-06-17 18:41:20 +05:00
Andrej730 96bdda305e Bonsai - purge unused IfcApplications and IfcOrganizations 2025-06-17 18:41:20 +05:00
Andrej730 1063238479 Bonsai - merge identical IfcOrganizations, IfcApplications
Added an utility to merge organizations and application that have exactly the same data. Noticed working with different files that sometimes they have these data duplicated - I guess reasons vary (revit exporter, our bug - c58b84d), but it's nice to be able to clean up redundant data.
Though still need to add some UI to change IfcApplications, so user could also get rid of them in case if they have small differences.

Feature location - https://files.catbox.moe/pwymx1.png

@theoryshaw in file from #6784 there were 2481 identical organizations and 1234 identical applications 😅
2025-06-17 18:41:20 +05:00
Andrej730 d1337cff02 Refresh applications UI on changes (bd1871bd4) 2025-06-17 18:41:19 +05:00
Andrej730 f26f2780e9 owner.ui - check props explicitly instead of relying on ifc data update 2025-06-17 18:41:19 +05:00
Andrej730 2e3c733c0d IfcParseWrapper - exclude instances_by_reference
As it does exactly the same as get_inverse()
2025-06-17 18:41:19 +05:00
Andrej730 ff09cd0a56 IfcParseWrapper - instance_by_guid -> by_guid
It seems they have exactly the same signatures and it makes sense to just rename instance_by_guid to by_guid, instead of implementing it separately.
2025-06-17 18:41:19 +05:00
Andrej730 df91f475c1 entity_instance constructor - skip setting None value 2025-06-17 18:41:19 +05:00
Andrej730 a733f36576 CMake - set C++17 as the minimum required version (34676a567)
Currently IfcOpenShell doesn't compile for C++14 (it stumbles upon `invoke_result_t` in XmlSerializer.cpp) and I'm not sure if it's needed.
2025-06-17 18:41:18 +05:00
Andrej730 9282ccabb3 typing 2025-06-17 18:41:18 +05:00
Thomas Krijnen 404c39873d Update WASM example app 2025-06-17 11:50:02 +02:00
Thomas Krijnen 34a1bc6f04 WASM build changes #6794 2025-06-16 21:18:14 +02:00
Andrej730 15112211c1 add_application not to add deprecated IfcTelecomAddress in ifc4x3 #6806 2025-06-16 19:57:00 +05:00
Andrej730 e3bbec6394 entity_instance constructor and destructor micro optimizations 2025-06-16 19:55:25 +05:00
Andrej730 bd1871bd44 Basic IfcApplications UI
Just so you can view IfcApplications in the project from UI

Example - https://files.catbox.moe/15a72n.png
2025-06-16 19:55:25 +05:00
Andrej730 a75f6a5804 typing 2025-06-16 19:55:25 +05:00
Andrej730 a12bb343ca Fix RUF015 (next() instead of list comprehensions[0])
https://docs.astral.sh/ruff/rules/-iterable-allocation-for-first-element/
2025-06-16 19:55:25 +05:00
Thomas Krijnen b486b32c8d WASM build changes 2025-06-14 18:05:29 +02:00
Thomas Krijnen 5e7893dbb3 Update build-all.py 2025-06-14 15:38:28 +02:00
Thomas Krijnen c8d74a560c Nix build retry downloads 2025-06-13 18:15:23 +02:00
Andrej730 9b8357c5ec entity_instance.__hash__ - reuse id() value for optimization 2025-06-13 19:33:30 +05:00
Andrej730 67b539aba1 remove_representation - optimization
In some cases remove_representation got 2x times faster (when there were a lot representation items that are not IfcTessellatedFaceSets).

getattr with attribute that might not be present on the element could be much slower then checking it's class explicitly.
2025-06-13 19:33:29 +05:00
Andrej730 176c5cc0fb typing 2025-06-13 19:33:29 +05:00
Thomas Krijnen 152c94a7d6 Update cgal for compilation on clang 19 https://github.com/CGAL/cgal/pull/8330 2025-06-13 16:06:08 +02:00
Thomas Krijnen 4b92d7d076 cgal: Dont proceed when not valid after triangulation #6735 2025-06-13 13:33:42 +02:00
Cristian Ritter d10c176bb6 re-adding u32string if ASCII instead of use only Boost.Locale 2025-06-13 10:39:59 +02:00
Bruno Postle 3503d41b3f Minor fix for the CMake package_source target 2025-06-13 09:11:09 +01:00
Esteban DUGUEPEROUX 34676a567e #6803: Update IfcOffsetCurveByDistance to be compitable on Windows with MSVC and C++20
In addition add option to be able to build IfcOpenShell against other C++ standard than C++17
2025-06-13 09:43:13 +02:00
Thomas Krijnen f5a46ffb7c pyodide: try 0.28.0a3 2025-06-12 20:48:52 +02:00
Cristian Ritter 94452b4a7a change deprecated wstring_convert to Boost.Locale and fix #6779 issue 2025-06-12 08:52:38 +02:00
Thomas Krijnen 2c0b1691ae -lexceptions.js #6794 2025-06-11 16:45:06 +02:00
Andrej 60c71c145b IfcFile::inverse_attr_record - add a note 2025-06-11 19:39:46 +05:00
Andrej d0f4dd00c7 getInverse -> get_inverses_by_declaration
Similar to getTotalInverses rename, to prevent confusing with get_inverse.
2025-06-11 19:39:46 +05:00
Andrej eee94f53a4 get_inverse, get_inverse_indices, get_total_inverses - prevent possible crashes
When passing entity without an id (e.g. IfcBaseType)
2025-06-11 19:39:46 +05:00
Andrej 6bb028696d getTotalInverses -> get_total_inverses_by_id
Rename method to avoid confusion with get_total_inverses
2025-06-11 19:39:46 +05:00
Andrej 3499c50d7d entity_instance.__getattr__ - small optimization for inverse attrs
Stop iterating early and don't build a list to make it a bit more optimized.

Though I'm unsure if anyone is really using `unpack_non_aggregate_inverses`
2025-06-11 19:39:46 +05:00
Andrej cd952d7e54 typing 2025-06-11 19:38:24 +05:00
Thomas Krijnen 2741acf4fe Fix compilation 2025-06-11 10:39:02 +02:00
Thomas Krijnen 69b6c41791 Use u32string throughout in character decoder 2025-06-10 20:33:21 +02:00
myoualid d1306d7a27 pyodide fatal error example #6794 2025-06-10 19:31:09 +01:00
Andrej 2bb373c1b2 bim.remove_representation - show operator time if it took more than 10 sec #6784 2025-06-10 18:54:37 +05:00
Andrej b4bc7ec371 remove_deep2 optimization for cases when the same entity is referenced by multiple other in the subgraph #6784 2025-06-10 18:54:37 +05:00
Andrej 1fecd77121 Fix a typo 2025-06-10 18:54:37 +05:00
Andrej c9b41a1499 Warning for joining non-mesh-like representation items #6793 2025-06-10 18:54:36 +05:00
Andrej b1ae213349 Fix bim.unassign_structural_load_case (redundant argument) 2025-06-10 18:54:36 +05:00
Andrej 5d632eae3d opencdeserver - fix bug with DocumentVersions initialization 2025-06-10 18:54:36 +05:00
Andrej 1608677d40 ifcopenshell.api.run to use explicit kwargs instead of unpacking 2025-06-10 18:54:35 +05:00
Andrej a37d46ca3d typing 2025-06-10 18:54:33 +05:00
Thomas Krijnen 862119834f Submodule 2025-06-09 21:37:13 +02:00
Thomas Krijnen 4d7453f9a4 Increase SVG precision to prevent self-intersections on detailed polygons 2025-06-09 21:31:55 +02:00
Thomas Krijnen a4f06f69cc Update pyodide to 0.27.7 #6794 2025-06-09 20:36:37 +02:00
Andrej 24f693e0bb ExpandedGroups - encapsulate in BIMGroupProperties 2025-06-09 18:03:25 +05:00
Andrej 32088fb95a Bonsai - fix cjio dependency (3d77fc7) 2025-06-09 18:03:22 +05:00
Andrej 812baa1c04 ifctester pyproject.toml - document flask dependency 2025-06-09 18:03:22 +05:00
Andrej 6862523c75 pyproject black - select all src folders by default, exclude explicitly 2025-06-09 18:03:22 +05:00
Andrej 8b4683aef1 replace api.run with static methods 2025-06-09 18:03:19 +05:00
Andrej 02d359d0e6 Fix Ruff UP035 (also deprecated annotation symbols) 2025-06-09 18:03:19 +05:00
Andrej 7d4176d105 Fix Ruff UP006 (deprecated annotation symbols) 2025-06-09 18:03:18 +05:00
Andrej 6eb45917fe fix typo 2025-06-09 18:03:15 +05:00
Andrej 3f4656600e typing 2025-06-09 18:03:15 +05:00
Andrej 7034c0b18a Fix typo (5ba82d7) 2025-06-09 18:03:14 +05:00
Andrej 97ece7c20b black . 2025-06-09 18:03:14 +05:00
Massimo Fabbro 5ba82d7764 See #5155. Update doc accordingly 2025-06-08 10:29:28 +02:00
falken10 8e8b44a744 Resource should not increment quantity in cost schedule 2025-06-08 10:23:00 +02:00
Massimo Fabbro 8b9927b77b See #6570. Better variable name 2025-06-08 10:22:12 +02:00
Andrej 09f6991565 cost items ui - update current index on item deletion
To make it less awkward to remove multiple items starting from the last.

Before - https://files.catbox.moe/wce56c.mp4
After - https://files.catbox.moe/wv1jhs.mp4
2025-06-06 19:06:47 +05:00
Andrej 14e8fad841 bim.contract / expand cost items - remove unused prop 2025-06-06 19:06:47 +05:00
Andrej c76785e02c tool.cost - refactor expand/contract rates methods
So they will be more similar
2025-06-06 19:06:46 +05:00
Andrej 7808dd535d Fix contracted cost item removal logic
It was removing checking for cost item index, though container is storing cost item ids.
2025-06-06 19:06:46 +05:00
Andrej 72cf77690e Prevent removing rel when is still valid after IfcCostItem removal #6769 2025-06-06 19:06:46 +05:00
Andrej bf4a3f074f typing 2025-06-06 19:06:46 +05:00
Andrej a374a7e3ff bonsai pyproject.toml - document dev dependencies 2025-06-06 13:11:59 +05:00
Andrej b6267e4ad1 Fix issue with occurrences when reassigning IfcTypeProduct class #6781 (977d814) 2025-06-06 13:11:59 +05:00
Andrej f21b34ffc1 reassign_class - safe check to prevent errors like #6781 2025-06-06 13:11:58 +05:00
Andrej 6de16bf658 bim.reassign_class - add simple test to cover cases like #6781 2025-06-06 13:11:58 +05:00
Andrej 9f17b9fd86 reassign_class - calculate occurrence_class just once for the entire loop 2025-06-06 13:11:58 +05:00
Andrej 781df9e3d8 Fix incorrect glob separator (8c9ccfd) 2025-06-06 11:57:46 +05:00
Andrej 8c9ccfdaae Fix #6760 2025-06-06 11:22:05 +05:00
Andrej 3892ac56a4 Skip 26b21dd test if non-standard schema is not available (e.g. in github ci) 2025-06-05 19:05:28 +05:00
Andrej ba6674299c ifocpenshell-python makefile - shortcut to get current build urls 2025-06-05 19:05:28 +05:00
Andrej fc0624692f test_sql - use Ifc2Sql patcher for readability 2025-06-05 19:05:28 +05:00
Andrej d6b5c73d4a ifcpatch - fix doc-string (b07b0e4be) 2025-06-05 19:05:28 +05:00
Andrej f622d2f961 ifc2sql - consider .ifcsqlite suffix 2025-06-05 19:05:27 +05:00
Andrej 3a66a1c754 geom settings - add new settings from 439f9c1 2025-06-05 19:05:27 +05:00
Andrej d0741cd495 Fix error using X hotkey on empty collection #6696 2025-06-05 19:05:27 +05:00
Andrej 0d39f8df28 Safer BlendData access
To avoid issues like #6771 when we by accident find an ID data-block from linked .blend file.
2025-06-05 19:05:27 +05:00
Andrej 87265d052b typing 2025-06-05 19:05:27 +05:00
Thomas Krijnen 5af7c9a32d Clarify Compound of Solids behaviour in create_solid_from_faces() #6780 2025-06-05 10:22:28 +02:00
Andrej 1ce9a4c68e ifc2sql - fix mysql errors inserting values into shape table 2025-06-04 19:10:52 +05:00
Andrej 930d58cf69 ifc2sql - fix issue inserting mysql json attributes
It seems mysql doesn't have automatic conversion from other types to json, so we stringify it for compatibility.
2025-06-04 19:10:51 +05:00
Andrej 926785c5ec ifc2sql - fix should_get_inverses for mysql 2025-06-04 19:10:51 +05:00
Andrej 274e19dcb9 ifc2sql - warn user if ifc database already exists 2025-06-04 19:10:51 +05:00
Andrej 7f0f93f726 ifc2sql - close cursor too
Important on Windows if someone is using Patcher explicitly and `self.c` is not garbage collected and file stays locked and cannot be moved.
2025-06-04 19:10:51 +05:00
Andrej b07b0e4bea ifc2sql - fix regression with missing resulting database filepath (9055f14) #6758 2025-06-04 19:10:50 +05:00
Andrej a0e4bdcbef ifcpatch - fix ifc2x3 bootstrap (c58b84d)
After c58b84d append_asset requires user to be a valid IFC entity.
2025-06-04 19:04:32 +05:00
Andrej 2da183bfa6 bpy._typing -> bpy.stub_internal
https://github.com/nutti/fake-bpy-module/issues/362
2025-06-04 19:04:31 +05:00
Andrej adf8a1f4e0 ci - run all ifcopenshell packages tests regardless of errors in some 2025-06-04 19:04:31 +05:00
Andrej e1ec422bb1 common.mk - explicitly specify sh as default shell 2025-06-04 19:04:31 +05:00
Andrej c51e36dbca ifcpatch - a static way to use ifcpatch 2025-06-04 19:04:31 +05:00
Andrej b393c120f1 ifcpatch pyproject - list optional dependencies 2025-06-04 19:04:31 +05:00
Andrej 3c7e938d90 IfcGeomWrapper - add missing property for edges_item_ids 2025-06-04 19:04:31 +05:00
Andrej a614fac71a typing 2025-06-04 19:04:31 +05:00
Andrej 5a211c67eb black . 2025-06-04 19:04:30 +05:00
Andrej 6ea53d54ba ruff to ignore src/ifcconvert/cityjson submodule 2025-06-04 11:02:08 +05:00
Dion Moult b9b5aefd5e Fix bSDD to properly use names and codes 2025-06-04 12:11:34 +10:00
Ryan Schultz e1c1b828ad Have save/cancel filter on top as well--helps with long filters. 2025-06-03 07:29:19 -05:00
Thomas Krijnen 3180004a28 WKT serializer: adapt section height to geometry height and emit height as geo:length 2025-06-02 13:30:06 +02:00
Thomas Krijnen 2ea2dc303c OSX action: try with pip's awscli 2025-06-02 11:28:19 +02:00
Thomas Krijnen 0aae57620f Temporary Python 3.9 compatibility 2025-06-02 08:42:47 +02:00
Thomas Krijnen 5c681d729c vc path 2025-06-01 13:55:53 +02:00
Thomas Krijnen 802ab8c296 vs2022 2025-06-01 13:39:00 +02:00
Thomas Krijnen 2ebe7046d3 windows-2022 2025-06-01 10:00:33 +02:00
Thomas Krijnen 3562d89f01 Win S3 upload: try without --recursive but loop in ps 2025-06-01 09:53:17 +02:00
Thomas Krijnen 1155b52375 Black 2025-06-01 09:23:10 +02:00
Thomas Krijnen 2d67440085 Draw.py don't assert just only take outer boundary in arrangement 2025-06-01 09:22:40 +02:00
Thomas Krijnen cc3f2aa28d Expose mirror-y in draw.py 2025-06-01 09:21:59 +02:00
Massimo Fabbro 390349aa7a See #6570. Better cost item rate identification
Now show cost item rate shows the Identification and not the ID for better understanding
2025-05-31 15:58:42 +02:00
Massimo Fabbro eade620581 Add calculating functions for Qto_CourseBaseQuantities for IfcCourse elements 2025-05-31 14:57:46 +02:00
Thomas Krijnen 88ac0a0f6c Wrong preprocessor statement #6765 2025-05-31 14:14:43 +02:00
Bruno Perdigão a531bd8c1f Fix. Uninstall Bonsai Decorators if they are not selected at start 2025-05-30 16:20:42 -03:00
Andrej f44769c138 ifc2sql - add example for should_expand 2025-05-30 18:13:18 +05:00
Andrej a6e3cc667c ifc2sql - add missing should_get_inverses description 2025-05-30 18:13:17 +05:00
Andrej 59b17fc593 ifcopenshell_wrapper.pyi 2025-05-30 18:13:17 +05:00
Andrej fb1b5bea51 ifcopenshell.file - use IFC4X3 instead of IFC4X3_ADD2 for simplicity 2025-05-30 18:13:11 +05:00
Andrej e8aa508405 Add 'advanced' optional ifcopenshell dependencies
To list all other optional dependencies.
2025-05-30 18:13:11 +05:00
Andrej 363083104b Add 'tabulate' to optional ifcopenshell dev dependencies
Used in test_rules and test_wall_opening.
2025-05-30 18:13:11 +05:00
Andrej 2798bf3b68 ifcopenshell.api.geometry to fail more gracefully with fake-bpy
If user has fake-bpy-module for type hints, it will fail with `ImportError: cannot import name 'Vector' from 'mathutils' (unknown location)` instead of `ModuleNotFoundError`.
2025-05-30 18:13:11 +05:00
Andrej e41c4bf7ae Separate mathutils tests 2025-05-30 18:13:11 +05:00
Andrej 3bdce4fbc3 Make zip strict argument optional to support Python 3.9 2025-05-30 18:13:11 +05:00
Andrej ad7473ff1f Make dataclasses args optional to support Python 3.9 #6725 2025-05-30 18:13:10 +05:00
Andrej 3d77fc7c7d ifccityjson - more specific depdendency
API in cjio 0.10 changed (`load` method was moved elsewhere) and for now we just specify that we require older cjio version.
2025-05-30 18:13:10 +05:00
Andrej 92a222cda7 typing 2025-05-30 18:13:10 +05:00
Andrej cc60794293 edit style for rep item - fallback in case user has no styles #6764
Or if style was removed in the process of editing. And it seems now UI will never appear broken that way.

Previously - https://files.catbox.moe/exhkln.png
Currently - https://files.catbox.moe/r36be8.png
2025-05-30 17:01:11 +05:00
Kristoffer Andersen c7b4033f6f Update conda_build_config.yaml
try to fix conda daily
2025-05-29 21:18:19 +02:00
Andrej 1ab288de0b ifc5d - fix <py3.11 compatibility issue (6499ac4) 2025-05-29 19:14:43 +05:00
Andrej 48f62c0b5b ifcopenshell_wrapper.pyi - update from the latest build
I guess previously I used some of my local builds, so some symbols were missing.
2025-05-29 19:14:43 +05:00
Andrej 5fc549453c validate_stub - add wrappers constants 2025-05-29 19:14:43 +05:00
Andrej bd5f5a30b3 validate_stub - also skip object 2025-05-29 19:14:43 +05:00
Andrej baf3edca13 ifcopenshell.validate - fix Python 3.9 support (25eecde) 2025-05-29 19:14:42 +05:00
Andrej c23e973575 Fix moved cost.remove_cost_item_value (b272210) 2025-05-29 19:14:42 +05:00
Andrej a5801e439f edit_attributes - fix Python 3.9 support (39fd9ce) 2025-05-29 19:14:42 +05:00
Andrej 4e65c33bae Bonsai make - use ifcjson master instead of fork
https://github.com/IFCJSON-Team/IFC2JSON_python/pull/3 got merged
2025-05-29 19:14:42 +05:00
Andrej c3652a56ee dev_environment.py #6754 2025-05-29 19:14:42 +05:00
Andrej e1b3b68a8b gitignore jquery.min.js (320a4cf) 2025-05-29 19:14:42 +05:00
Andrej dbce4c5d98 bcf - add missing requests dependency 2025-05-29 19:14:42 +05:00
Andrej 0663c5130a typing 2025-05-29 19:14:42 +05:00
Andrej ea7ced738a Introduce Ruff github CI with some basic linter rules
And also to help catching syntax errors on older Python versions.
2025-05-28 18:24:13 +05:00
Andrej 7cde9629f8 Fix Ruff UP034 (extraneous-parentheses)
https://docs.astral.sh/ruff/rules/extraneous-parentheses/
2025-05-28 18:24:13 +05:00
Andrej 5f5ba72919 Fix Ruff UP003 (taking type of primitive)
https://docs.astral.sh/ruff/rules/UP003
2025-05-28 18:24:13 +05:00
Andrej feebc701fa Fix Ruff UP008 (super-call-with-parameters)
https://docs.astral.sh/ruff/rules/super-call-with-parameters/
2025-05-28 18:24:13 +05:00
Andrej 5ab14f1e4e Fix Ruff UP022 (replace-stdout-stderr)
https://docs.astral.sh/ruff/rules/replace-stdout-stderr/
2025-05-28 18:24:12 +05:00
Andrej f289b55766 Fix Ruff UP010 (unnecessary-future-import)
https://docs.astral.sh/ruff/rules/unnecessary-future-import/
2025-05-28 18:24:12 +05:00
Andrej 3976b77ee1 Fix Ruff UP033 (lru-cache-with-maxsize-none)
https://docs.astral.sh/ruff/rules/lru-cache-with-maxsize-none/
2025-05-28 18:24:12 +05:00
Andrej 7259dd625f Fix Ruff UP013 (convert-typed-dict-functional-to-class)
https://docs.astral.sh/ruff/rules/convert-typed-dict-functional-to-class/
2025-05-28 18:24:12 +05:00
Andrej 03f5ee653b Fix Ruff UP017 (datetime-timezone-utc)
https://docs.astral.sh/ruff/rules/datetime-timezone-utc/
2025-05-28 18:24:12 +05:00
Andrej 62a0c258b2 Fix Ruff UP012 (unnecessary-encode-utf8)
https://docs.astral.sh/ruff/rules/unnecessary-encode-utf8/
2025-05-28 18:24:11 +05:00
Andrej c838139fdc Fix Ruff UP024 (os-error-alias)
https://docs.astral.sh/ruff/rules/os-error-alias/
2025-05-28 18:24:11 +05:00
Andrej a42a95adcb Fix Ruff UP037 (quoted-annotation)
https://docs.astral.sh/ruff/rules/quoted-annotation/
2025-05-28 18:24:11 +05:00
Andrej 3b9d6ed145 Fix Ruff UP009 (utf8-encoding-declaration)
https://docs.astral.sh/ruff/rules/utf8-encoding-declaration/
2025-05-28 18:24:11 +05:00
Andrej 3c4105cbc5 Fix Ruff UP011 (lru-cache-without-parameters)
https://docs.astral.sh/ruff/rules/lru-cache-without-parameters/
2025-05-28 18:24:11 +05:00
Andrej 302423bfdb Fix Ruff UP004 (useless-object-inheritance)
https://docs.astral.sh/ruff/rules/useless-object-inheritance/
2025-05-28 18:24:11 +05:00
Andrej 6dbf86382f Fix Ruff UP018 (native-literals)
https://docs.astral.sh/ruff/rules/native-literals/
2025-05-28 18:24:11 +05:00
Andrej 3b50993b21 ifc2ca, bcf, opencdeserver - add Python 3.9 support
Was breaking due to `|` operator used in annotations instead of `Union`
2025-05-28 18:24:11 +05:00
Andrej a8634a561a ifcopenshell.util.mvd_info - fix breaking Python 3.9 support (cbe8fc1)
due to `|` operator used in annotations instead of `Union`
2025-05-28 18:24:10 +05:00
Andrej a16b8fa376 Fill ifcopenshell_wrapper.pyi with symbols #6684
As described in #6744 partial stub wrapper is completely replacing the type information from the wrapper, so having partial stub is making things worse than they were before.
This commit is adding all available symbols from the wrapper to the stub and allows to gradually include type information to the stub.
It also adds a safe net to ensure stub and wrapper won't go out of sync.
2025-05-28 18:24:10 +05:00
Andrej a25698734a Address PR feedback from #6719 2025-05-28 18:24:10 +05:00
Andrej 1c1bf56893 typing 2025-05-28 18:24:10 +05:00
Andrej ceeb7ca355 black . 2025-05-28 18:24:10 +05:00
Martin15135215 6b77147d45 Create ifcopenshell_wrapper.pyi
stub file for swig-generated ifcopenshell_wrapper python file
2025-05-28 12:29:10 +00:00
Dion Moult a929ab0c4f Big bSDD overhaul for properties and classifications. See description.
* bSDD now supports a couple more args when querying the API.
 * Fetching properties via the classification UI has been removed
 * The bSDD dictionary selector has been merged into the classification
source dropdown
 * The bSDD dictionary selector merged into the property set name
dropdown
 * You can now search for properties in the bSDD directly when adding
properties
 * You can browse properties via groups or classifications (based on
assigned classifications)
 * You can browse properties via keyword (with some limitations due to
the API)
 * You can selectively choose which properties you then want to add.
Only basic support right now.
2025-05-28 20:51:08 +10:00
Iagoba Apellaniz e9eb5f892a Update installation.rst to point to the latest release 2025-05-28 07:25:26 +00:00
Dion Moult 72ef055946 Add tests for boundary 2025-05-27 10:38:38 +10:00
Ryan Schultz 32e4a06fe4 fix: #6749: use shift + click to remove that selection from the selection set.
changed bim.select_similar.calculate_sum to `ctrl + click` instead, since `shift + click` is a UI pattern used in vanilla blender when selecting/deselecting things.
Also refactored into helper methods.
2025-05-26 18:18:18 -05:00
Massimo Fabbro 02af46c99f Fix #6700. Cleanup 2025-05-26 17:56:13 +02:00
Massimo Fabbro 085fc8ca1c Fix #6700
It seems that with new shapely release the buffer behavior has changed so now it seems to work again
2025-05-26 17:56:13 +02:00
Dion Moult 93d36501a3 Fix #3994. Regression where space boundaries would not be created due to shape var being garbage cleaned. 2025-05-26 23:04:51 +10:00
Dion Moult a74f1c6e08 Fix #6513. Bug where OBJ not found when adding element.
This was actually fixed earlier already due to restructuring of
structural element authoring (i.e. the representation_template items now
always has OBJ unless it is a structural item)
2025-05-26 16:45:35 +10:00
Dion Moult 8766cad37e bSDD searches now work on type classes as well as predefined types 2025-05-26 16:34:31 +10:00
Sayanjyoti Das 320a4cf4fa Update installation scripts on documentation 2025-05-26 13:57:52 +10:00
Sayanjyoti Das 77ed64a900 Bundle jQuery at build time instead of fetching from a CDN 2025-05-26 13:57:52 +10:00
Sayanjyoti Das b17c6dde85 fix jquery CDN links 2025-05-26 13:57:52 +10:00
falken10vdl b0b7bbf95f Update data.py to get data sorted
Then later is easier to read in lists
2025-05-26 13:56:03 +10:00
Dion Moult 873fe6a87f Shift down grouping below spatial 2025-05-26 13:55:38 +10:00
talukderpushpak2000 31dc3fe002 Initial Commit 2025-05-26 13:53:58 +10:00
Martin15135215 697b034c22 Add annotation for file.header 2025-05-26 13:52:29 +10:00
Dion Moult 810e8df6fe Fix #6640. Fix invalid association of annotations to grids.
There was already some half written code with this intent it seems, so I
just finished it up.
2025-05-26 13:49:06 +10:00
Dion Moult d6cc1ff9e9 Fix #2999. You can now edit topology representation items. 2025-05-26 12:41:55 +10:00
Dion Moult 242d70c311 Fix set update in loop bug when trying to filter elements by spatial container 2025-05-26 10:47:05 +10:00
Massimo Fabbro 327899ca62 See #6570. Small fix for previuos commit 2025-05-25 08:00:54 +02:00
Massimo Fabbro 482512085c See #6570. Small fix 2025-05-25 08:00:54 +02:00
Massimo Fabbro 12774607f3 See #6570. Now it is possible to assign a cost rate from a non schedule of rate cost schedule 2025-05-25 08:00:54 +02:00
Massimo Fabbro 242d0ffb3f See #6570.Minor fix 2025-05-25 08:00:54 +02:00
Massimo Fabbro 2bef2a0219 See #6570. More consistent cost rate assignment
Now when a cost rate is assigned to a cost item, it creates also a relation between them, making the relation more consistent. It is also shown in the UI
2025-05-25 08:00:54 +02:00
Ryan Schultz 8cb871a6a2 Better convention: Using poll() method for b452348748
Thank you @Andrej730.
per https://github.com/IfcOpenShell/IfcOpenShell/commit/b452348748629a6e6dad6df24e21bfad2c203043#r157689504
2025-05-24 07:53:45 -05:00
Andrej730 fb5358e54e clash ui - add basic header for the found clashes
Example - https://i.imgur.com/JjKIUZX.png

Also show indices, so it will be easier to communicate a specific clash to someone.
2025-05-23 18:45:07 +05:00
Andrej730 deae4e659b clash ui - expose clash type to UI
Example - https://imgur.com/a/Wdyg3pn
2025-05-23 18:45:07 +05:00
Andrej730 29bd89324a ifcopenshell.geom.tree.get_clash_type 2025-05-23 18:42:55 +05:00
Andrej730 087608a5f5 clash ui - show clearance, tolerance using blender units for readibility 2025-05-23 18:42:55 +05:00
Andrej730 bc5a291321 ifcclash - remove 'objects' dict item
It seems to be part of legacy code that's no longer used since 18c38b3
2025-05-23 18:42:55 +05:00
Andrej730 88befea174 typing 2025-05-23 18:42:55 +05:00
Andrej730 441e2ddbd6 black . 2025-05-23 18:42:55 +05:00
Dion Moult 09e07c80c5 Implement recursive contract / expand of the spatial tree 2025-05-23 17:35:15 +10:00
Orion Sehn e3ddccdb11 updating documentation to make it clear to run the setup script on windows with blender closed. 2025-05-23 11:38:25 +05:00
Orion Sehn 621f6e7a31 fix to default directory on dev_environment.bat assuming you weren't using the daily build 2025-05-23 11:38:25 +05:00
Bruno Perdigão 0052570fdb See #6166. First implementation of polyline extend walls. 2025-05-22 23:47:11 -03:00
Bruno Perdigão 55e2171cac Remove print statement 2025-05-22 23:16:48 -03:00
Bruno Perdigão 1f8967d5b8 See #6240. Fix input handling for "close polyline". 2025-05-22 23:16:25 -03:00
falken10vdl 0410cec21b Added Quickstart guides for Linux and Windows IDEs using VSCode (#6494)
* Added Quickstart guides for Linux and Windows IDEs using VSCode

* added some tips to Linux QS

* Added point 15

* extra picture for step 15

* Added windows part

* updated windows vscode launch settings

* added info about branch rebase for PRs

* updated dev_environment to cope for some errors
2025-05-22 09:46:41 -05:00
Andrej730 08ab48887a clash - allow running a quick clash without saving results to some file explicitly
It's available on alt+click.
Example - https://imgur.com/a/wctjHnq
2025-05-22 18:09:28 +05:00
Andrej730 97bb587772 Bump bonsai build #6620
Besides the pending issues also resolves #6589 and error in #6196
2025-05-22 17:08:46 +05:00
Ryan Schultz b452348748 Select all instances that share the same attribute value as any of the selected objects 2025-05-21 22:58:34 -05:00
Andrej730 60c18628c5 clash ui - display "clashes are not loaded" to avoid confusion with "no clashes found"
Example - https://imgur.com/a/5a6cuEG
2025-05-21 18:43:03 +05:00
Andrej730 5bb769448d bim.execute_ifc_clash - show results in Bonsai UI for bcf file too
Previously results would show up only if you'd save .json, creating confusion since UI is showing "0 Clashes Found" though there were clashes.
2025-05-21 18:43:03 +05:00
Andrej730 ca890d2f9b clash ui - make filters expandable for readibility
Example - https://imgur.com/a/ZeVeF3u
2025-05-21 18:43:03 +05:00
Andrej730 99782a2813 bim.select_clash_source - hide internal props from user
to avoid confusion
2025-05-21 18:43:02 +05:00
Andrej730 d48406006f clash ui - move file selection button closer to filepath field
To make the type of the field more apparent.
2025-05-21 18:43:02 +05:00
Andrej730 0405b5c61f clash ui - deduplicate ui code 2025-05-21 18:43:02 +05:00
Andrej730 c9eea76e57 bim.execute_ifc_clash - more user-friendly error when one of the filepaths do not exist 2025-05-21 18:43:02 +05:00
Andrej730 cc73143ac1 clash - typing fixes and some refactor 2025-05-21 18:43:02 +05:00
Andrej730 84b0abfb60 ci-ifcopenshell-python - include submodules #6723 2025-05-21 18:43:02 +05:00
Andrej730 13ea2ba8d2 ifcopenshell-python makefile - set sh as the main shell explicitly 2025-05-21 18:43:02 +05:00
Andrej730 d148ab4f31 Fix error switching to custom title block #6685 2025-05-21 18:43:02 +05:00
Andrej730 c7a030cc2d typing 2025-05-21 18:43:01 +05:00
Andrej730 718254fe5e Fix regression parsing iso duration with floats (d1b1b08) #6715 2025-05-21 12:16:07 +05:00
Andrej730 41227b89c8 Fix subtle bug with operators precedence in eabdb5d
`+` has a higher precedence than `or`, so it resulted in the error below

```
Traceback (most recent call last):
  File "\bonsai\bim\module\spatial\prop.py", line 86, in update_active_container_index
    tool.Spatial.load_contained_elements()
  File "\bonsai\tool\spatial.py", line 312, in load_contained_elements
    cls.load_contained_elements_by_type(container)
  File "\bonsai\tool\spatial.py", line 321, in load_contained_elements_by_type
    results = cls.get_container_elements_grouped_by_type(container)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "\bonsai\tool\spatial.py", line 294, in get_container_elements_grouped_by_type
    element_type.is_a() + "/" + element_type.Name or "Unnamed"
    ~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~
TypeError: can only concatenate str (not "NoneType") to str
```
2025-05-20 19:06:25 +05:00
Andrej730 2547ee9ab5 bonsai.crash.txt
There seems to be cases when Blender might crash too unexpectedly.
E.g. #6686 - during viewport render system is running out of resources and crashing without leaving blender.crash.txt, leaving everyone clueless. Which is arguably a bug in Blender (both crash and lack of any crash report), but at least we'll have a fallback for this case.

What bonsai.crash.txt does - it's created in system temp folder with the current Python traceback, just before some dangerous operation. If operation didn't crashed Blender, then file will be unlinked. If crash did occurred, it might be the only clue for users and devs on what actually happened.
2025-05-20 19:06:25 +05:00
Andrej730 d22f1bcd71 'viewport' render type - add a note 2025-05-20 19:06:24 +05:00
Andrej730 0c2158089d disable update_has_underlay if update_props is disabled
Otherwise it was trying to load drawing styles too early, when drawing was not yet active. And then they were reloaded again during ActivateDrawingBase. Occurred after c2860e6, before this commit issue was kind of hidden.
2025-05-20 19:06:02 +05:00
Thomas Krijnen ccafc203b5 submodule 2025-05-20 10:05:21 +02:00
Andrej730 0efc2d25f1 Fix for assign_class and object with modifiers in Blender <4.4 #6701 2025-05-20 12:24:35 +05:00
Andrej730 ac50f002f0 Fix confused blender and pset props in 9586869 2025-05-19 19:28:55 +05:00
Andrej730 66f4c957ea black . 2025-05-19 19:28:55 +05:00
Dion Moult 67016d6f90 See #2999. Reimplement basic creation of structural items. 2025-05-19 17:36:12 +10:00
Thomas Krijnen 439f9c14ce OcctNoCleanTriangulation CacheShapes PermissiveShapeReuse setting; Cgal conditional copy during triangulation; occt cheaper check for triangulation existance #6712 2025-05-18 22:14:22 +02:00
Dion Moult 404bf13a18 Update colour by property test to reflect new behaviour of only showing geometry not empties
ALL (feature) TESTS PASS YESS
2025-05-18 19:23:34 +10:00
Dion Moult 3a37bcdcaa Update test cases to reflect new drawing activation UI 2025-05-18 19:09:29 +10:00
Dion Moult 992ad8e24a See #4699. Continue refactoring logic from drawing decorator into DecoratorData. 2025-05-18 15:32:55 +10:00
Dion Moult 1de2515fcd More bSDD tests to cover all current functionality 2025-05-18 12:05:33 +10:00
Thomas Krijnen 4bc88eac2f ifdefs around geometry handling for 4.3 rc schemas #6710 2025-05-17 16:01:28 +02:00
Dion Moult 22f10e5549 Fix #6669. bSDD now supports test dictionaries, refactor domain to dictionary, support zero length keyword searches, multi-dict support, and basic tests.
Feature tests can now have more sophisticated per UIList item spies.
2025-05-17 21:28:12 +10:00
Andrej730 8e34c1d7df Fix error using bim.updating_representation for object with material set
Possibly occurred after 4c255cc, but haven't investigated deeper.

@BIMvoice
The error you were reporting about Split Along Edge operator was caused by this issue, should be fixed now too.
See https://jmp.sh/hgJEHsgH

Example error traceback:
```
Error: Python: Traceback (most recent call last):
  File "C\bonsai\bim\ifc.py", line 487, in execute_ifc_operator
    result = getattr(operator, "_execute")(context)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C\bonsai\bim\module\geometry\operator.py", line 541, in _execute
    self.update_obj_mesh_representation(context, obj)
  File "C\bonsai\bim\module\geometry\operator.py", line 666, in update_obj_mesh_representation
    tool.Ifc.link(new_representation, data)
  File "C\bonsai\tool\ifc.py", line 230, in link
    IfcStore.link_element(element, obj)
  File "C\bonsai\bim\ifc.py", line 258, in link_element
    tool.Geometry.get_mesh_props(obj).ifc_definition_id = element.id()
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C\bonsai\tool\geometry.py", line 92, in get_mesh_props
    return mesh.BIMMeshProperties
           ^^^^^^^^^^^^^^^^^^^^^^
ReferenceError: StructRNA of type Mesh has been removed
```
2025-05-16 19:43:42 +05:00
Andrej730 4369baa226 bim.split_along_edge - improve descriptions, add error handling 2025-05-16 19:43:42 +05:00
Andrej730 7941d71feb SvgWriter - make required arguments more explicit 2025-05-16 19:43:42 +05:00
Andrej730 68d967c2e5 pyproject.toml - enableTypeIgnoreComments=false 2025-05-16 19:43:41 +05:00
Andrej730 4ea9a598c7 operator.drawing - remove unnecessary ifc operators 2025-05-16 19:43:41 +05:00
Andrej730 06d1c81954 black . 2025-05-16 19:43:41 +05:00
Andrej730 aa5f08880b typing 2025-05-16 19:43:41 +05:00
Ryan Schultz 9e6320d545 A preference to list those classes that are not affected by the 'cut decorator'. 2025-05-15 21:45:53 -05:00
Bruno Perdigão f39d3d4b9c Fix polyline plane method decorator to show properly.
The `plane_method` where not being cleared after canceling or confirming
the polyline operator.
2025-05-15 21:28:31 -03:00
Bruno Perdigão 5c72cf35bd See #6208. Change wall merge tolerance for more consistent behavior. 2025-05-15 19:08:04 -03:00
Bruno Perdigão b9a435e1c3 Fix polyline tool calculation when dealing with 180 degrees angles. 2025-05-15 17:41:32 -03:00
Bruno Perdigão 92f2559ce0 See #6536. Recalculate profile only for selected objects. 2025-05-15 17:41:32 -03:00
Bruno Perdigão acf9713943 Fix aggregate decorator after #6537. 2025-05-15 17:41:32 -03:00
Andrej730 a77125ff12 bim.add_drawing - document camera placement behaviour in description
See https://i.imgur.com/VfOjGHk.png
2025-05-15 20:01:02 +05:00
Andrej730 c8841bcb6a Add a test for be65774 2025-05-15 20:01:01 +05:00
Andrej730 b20997dea8 Fix tests missing assert statement - add assert and fix them 2025-05-15 19:45:11 +05:00
Andrej730 292aca6685 Remove unnecessary IFC operators 2025-05-15 19:45:11 +05:00
Andrej730 3a824ec969 Allow adding arrays to openings and spatial elements 0b8fb41 0e37528 2025-05-15 19:45:11 +05:00
Andrej730 bea9f42f87 Annotator.add_text - remove ancient dead code
It was deprecated a while ago in 43869c3, when we stopped using curves for text annotations. bim.resize_text was also using curve annotations and now also was unused
2025-05-15 19:45:11 +05:00
Andrej730 e4a50b32ce typing 2025-05-15 19:40:44 +05:00
Andrej730 76611024b6 Remove drawing styles attributes prop (a50c633cf) 2025-05-15 10:30:35 +05:00
Andrej730 92ee82374c EPset_Drawing.Metadata - add description
See https://i.imgur.com/0fR9fRj.png
2025-05-14 19:40:46 +05:00
Andrej730 af81aa1fae Reapply 06f1477 to fix not appearing IFC Description button
Not that skipping non-string properties was a bad idea, but inserting it right at the beginning was - non-string IFC properties were missing IFC descriptions because of it. Moved it past `Attribute` check to avoid this issue.

For anyone hearing about this feature for the first time - https://imgur.com/a/dNRao3R

I assume this was also the issue that was encountered in #5892
2025-05-14 19:40:45 +05:00
Andrej730 16f9f91561 util.doc - statically type json structures 2025-05-14 19:40:45 +05:00
Andrej730 3a52a848ae draw_custom_context_menu - small refactor to improve readibility 2025-05-14 19:40:45 +05:00
Andrej730 a50c633cf4 Remove drawing styles attributes
It's been awhile since they were unused (2c301d7) and now they're replaced with EPset_Drawing.Metadata
2025-05-14 19:40:45 +05:00
Andrej730 6251750133 common method for getting active drawing style 2025-05-14 19:40:45 +05:00
Andrej730 acef6379e8 drawing styles - add descriptions to render types, remove NONE type
NONE type removed as it was unused and basically meant the same as 'VIEWPORT'.

Example - https://i.imgur.com/7r6CJdg.png
2025-05-14 19:40:45 +05:00
Andrej730 c2860e6e25 drawing.operator not to use DrawingsData 2025-05-14 19:40:44 +05:00
Andrej730 b8e5cd740a Use common method for getting active drawing entity 2025-05-14 19:40:44 +05:00
Andrej730 9188dc60ab typing 2025-05-14 19:40:44 +05:00
Thomas Krijnen 8ce56db0c3 Align preprocessor flags #6706 2025-05-14 13:09:17 +02:00
Andrej730 b91b1401f4 util.get_aggregate - make it more readable (5597095) 2025-05-13 17:44:42 +05:00
Andrej730 0e37528915 Skip IfcOpeningElements arrays during base import, setup constraint on opening load #6688 2025-05-13 17:23:46 +05:00
Andrej730 9991c4aa56 IfcImporter.create_generic_elements - prevent argument mutation
as it may have unexpected consequences
2025-05-13 17:23:46 +05:00
Andrej730 0ef3983035 bim.show_opening - simplify code 2025-05-13 17:23:46 +05:00
Andrej730 0f6e6a48f4 bim.show_openings - expand description 2025-05-13 17:23:45 +05:00
Andrej730 3d225beafd Add notes on Tekla accommodations (7c3870595) 2025-05-13 16:59:12 +05:00
Andrej730 6a91bbb720 typing 2025-05-13 16:59:12 +05:00
Hannes 1f23e4ba18 Fixed typo in __init__.py
See https://github.com/IfcOpenShell/IfcOpenShell/issues/6625
2025-05-13 13:18:24 +02:00
Andrej730 2ebcffc7d4 Geometry changes will be lost - show warning only for IFC objects
Previously it would also occur for vanilla Blender objects not connected to IFC.
2025-05-13 13:26:54 +05:00
Andrej730 3d4321f713 Prevent class assignment in non-object mode #6697
Example - https://i.imgur.com/KQYQLQZ.png
2025-05-13 13:26:54 +05:00
Andrej730 f8799305e1 black . 2025-05-13 13:26:54 +05:00
Dion Moult 7c38705958 Accommodate invalid missing mapped representations and items coming from Tekla (2023) 2025-05-13 13:09:37 +10:00
Bruno Perdigão f6fc0cfa0f See #6537. Fix object display type when exiting aggregate mode. 2025-05-12 22:01:40 -03:00
Richard Brice 686e9c946b Adds object placement to stationing referent. 2025-05-12 16:14:53 -07:00
sboddy 3085072751 Improve drawing activation as per #6653 (#6659)
Drawings are now activated in a bit different way - not by the camera button located above the drawings list, but by using the camera indicator next to each drawing. Which allows do change active drawing very quickly. All activation options (shift+, alt+) are also available from this indicator.

Now - https://imgur.com/a/DmnWkul

Before - https://imgur.com/a/zjPDkPa
2025-05-12 19:46:34 +05:00
Andrej730 0328be4093 Drawing styles - remove deprecated properties / property values
Deprecated properties:
- scene.render.engine + BLENDER_EEVEE
- scene.render.simplify_shadows
- scene.render.simplify_shadows_render
- space.overlay.backwire_opacity
- space.overlay.show_edges
- space.overlay.show_occlude_wire
- scene.display.shading.wireframe_color_type + MATERIAL
2025-05-12 19:10:04 +05:00
Andrej730 6218d3902e bim.activate_drawing_style - show warnings if some property failed to be set
Otherwise errors were passing silently (just console errors) and remained unfixed. 'PYTEST_VERSION' is needed to be even more strict when we're running pytests, which allows us to catch outdated Blender properties in time.

Example - https://i.imgur.com/K7Trkxf.png
2025-05-12 19:10:04 +05:00
Andrej730 be1836fa48 Mark thumbnails as outdated instead of updating them right away
As user may never open them in type manager
2025-05-12 19:08:46 +05:00
Andrej730 22b5db8a1e ci-black-formatting - print diff 2025-05-12 19:08:46 +05:00
Andrej730 4268e1376c Address PR review changes #6660 2025-05-12 19:08:46 +05:00
Andrej730 e2c1a54e0a typing 2025-05-12 19:08:46 +05:00
falken10vdl d1be660469 Update type manager preview on updating paramteric door/window type (#6656) 2025-05-12 12:41:02 +05:00
Kristoffer Andersen 3ae5227b57 Update ci-ifcopenshell-conda-daily.yml
remove env var
2025-05-11 19:59:05 +02:00
Kristoffer Andersen cdc994ca76 Update ci-ifcopenshell-conda-daily.yml
move rattler build output dir outside of build dir
2025-05-11 19:58:27 +02:00
Kristoffer Andersen 0ad4087f0a Update ci-ifcopenshell-conda-daily.yml
remove dotnet to save space on ubuntu runner
2025-05-11 19:28:12 +02:00
Kristoffer Andersen 4b7ca648c6 Update ci-ifcopenshell-conda-daily.yml
try to get even more space on ubuntu ci runner
2025-05-11 19:23:28 +02:00
Kristoffer Andersen 17bd6e6b0c Update ci-ifcopenshell-conda-daily.yml
fix missing comma
2025-05-11 19:12:34 +02:00
Kristoffer Andersen 7b80803cbe separate osx-x86 and osx-arm64 for the conda daily builds (#6694)
* separate osx-x86 and osx-arm64 for the conda daily builds

* remove running this in PR. No need.
2025-05-11 18:59:52 +02:00
Kristoffer Andersen eaf67d83f9 Update ci-ifcopenshell-conda-daily.yml
strip daily conda build version date string
2025-05-11 16:04:21 +02:00
Kristoffer Andersen 41f754e15b Update ci-ifcopenshell-conda-daily.yml
Update conda ci
2025-05-11 15:53:29 +02:00
Kristoffer Andersen b7cdb86327 use rattler-build to speed up daily conda builds and fix deps (#6692)
* use rattler-build to speed up daily conda builds and add typing_extensions

* minor update
2025-05-11 09:37:02 +02:00
falken10vdl 809a20dc99 system info copied when clicking in Bonsai Version (#6660) 2025-05-09 16:09:37 -05:00
Andrej730 f5e41d18c6 drawing.operator - reuse blender.get_view3d_space 2025-05-09 19:26:28 +05:00
Andrej730 60a8c45ad7 Fix IFC operators issues for drawing styles operations (38cf88d) 2025-05-09 19:26:28 +05:00
Andrej730 73f98bfe4b Remove 2 years old fallback for missing ShadingStyles prop in drawing's pset (38cf88d) 2025-05-09 19:26:28 +05:00
Andrej730 958686958b drawing.prop - reuse generic pset update callback 2025-05-09 19:26:28 +05:00
Andrej730 e0b61c5794 Fix drawing styles update on underlay toggle after 9329390 2025-05-09 19:26:28 +05:00
Andrej730 5aa5552759 Fix drawing test after be657748f 2025-05-09 19:26:27 +05:00
Andrej730 82a19cee7b generate_drawing_matrix - simplify logic 2025-05-09 19:26:27 +05:00
Andrej730 43d4adbd9d Fix tests after specifying types more precisely in def9352ad 2025-05-09 19:26:27 +05:00
Andrej730 1d3c71985f typing 2025-05-09 19:26:27 +05:00
Andrej730 381fca0812 black . 2025-05-09 19:26:27 +05:00
Dion Moult 0f37eca333 Fix bug in IFC4X3 when convering properties to quantities 2025-05-09 15:24:30 +10:00
Dion Moult 6bf9b8b0c9 Fix bug where appending assets could accidentally create duplicate contexts 2025-05-09 15:24:17 +10:00
Bruno Perdigão 9361cfee28 See #6536. Update cardinal point when editing IfcMaterialProfileSetUsage. 2025-05-08 22:06:48 -03:00
Bruno Perdigão 1c205d494a See #6332. Fix issue where imperial precision were being used by metric formatting. 2025-05-08 19:15:51 -03:00
Ryan Schultz 4d1d224a34 fix #6683 - When placing Level (Plan) annotation, have it take on the 'z' dimension of the 3d cursor, verses the front plane of the drawing camera 2025-05-08 17:04:34 -05:00
Andrej730 4976af5e66 drawing.create_camera - remove setting location line that had no effect
Apparently it had no effect from the beginning in 074fe0d and lost any use in 6a8292d, when we started to base position on current cursor's location.
2025-05-08 19:23:24 +05:00
Andrej730 be657748f3 Align new Model drawings to viewport by default
Previously it was creating not very useful top-down camera at `(0,0,0)`, now it should be more intuitive.

Example - https://imgur.com/a/GHrvCSJ
2025-05-08 19:23:24 +05:00
Andrej730 def9352ade typing 2025-05-08 19:23:23 +05:00
Josef Wienerroither a20002ecca Update links and version info ifcmax.rst 2025-05-08 14:42:50 +02:00
Andrej730 b7a0f230ad Fix confused target_view callbacks (1619ae854)
Fix using the same function name twice leading to DocProperties using camera callback too.
2025-05-08 15:50:00 +05:00
Andrej730 76529a06bb Fix resetting perspective camera on drawing activation (1619ae854) 2025-05-08 15:41:34 +05:00
Andrej730 dcdae67a2d Fix typo in 1619ae854 2025-05-08 15:41:34 +05:00
Andrej730 5a7a368169 ifcopenshell.file.Transaction - add static typing 2025-05-08 15:41:34 +05:00
Thomas Krijnen ec13294430 python39 compatibility 2025-05-08 08:37:41 +02:00
Andrej730 653b88ba18 Bonsai - make undo errors visible to user
In theory there are possible undo errors, when some step wasn't registered in undo system and undo then won't be able to undo change from IFC. The problem is, those errors could be very dangerous but they occur in background, undetected by users, they're only visible as console traceback.

After this commit we'll try to detect them and set `last_error`, which will show usual Bonsai error panel in Properties.

@theoryshaw just in case, as you will probably find this kind of error 😁

Example traceback user might see in system console (or in their error report from "Copy Error Message").
```
last_error: Undo transaction operations:
- {'action': 'create', 'value': {'id': 1560, 'type': 'IfcGrid', 'GlobalId': '05Y8D_Ja12HfiEbmc$D3WN', 'OwnerHistory': None, 'Name': None, 'Description': None, 'ObjectType': None, 'ObjectPlacement': None, 'Representation': None, 'UAxes': None, 'VAxes': None, 'WAxes': None, 'PredefinedType': None}}
- {'action': 'edit', 'id': 1560, 'index': 2, 'old': None, 'new': 'Grid'}
- {'action': 'edit', 'id': 1221, 'index': 4, 'old': ({'id': 1297}, {'id': 1247}, {'id': 1220}, {'id': 1272}), 'new': ({'id': 1272}, {'id': 1247}, {'id': 1220}, {'id': 1560}, {'id': 1297})}
- {'action': 'create', 'value': {'id': 1561, 'type': 'IfcCartesianPoint', 'Coordinates': (0.0, 0.0, 0.0)}}
- {'action': 'create', 'value': {'id': 1562, 'type': 'IfcDirection', 'DirectionRatios': (0.0, 0.0, 1.0)}}
- {'action': 'create', 'value': {'id': 1563, 'type': 'IfcDirection', 'DirectionRatios': (1.0, 0.0, 0.0)}}
- {'action': 'create', 'value': {'id': 1564, 'type': 'IfcAxis2Placement3D', 'Location': {'id': 1561}, 'Axis': {'id': 1562}, 'RefDirection': {'id': 1563}}}
- {'action': 'create', 'value': {'id': 1565, 'type': 'IfcLocalPlacement', 'PlacementRelTo': None, 'RelativePlacement': {'id': 1564}}}
- {'action': 'edit', 'id': 1565, 'index': 0, 'old': None, 'new': {'id': 66}}
- {'action': 'edit', 'id': 1560, 'index': 5, 'old': None, 'new': {'id': 1565}}
- {'action': 'create', 'value': {'id': 1566, 'type': 'IfcGridAxis', 'AxisTag': 'A', 'AxisCurve': None, 'SameSense': True}}
- {'action': 'edit', 'id': 1560, 'index': 7, 'old': None, 'new': ({'id': 1566},)}
- {'action': 'create', 'value': {'id': 1567, 'type': 'IfcCartesianPoint', 'Coordinates': (-2.0, 0.0)}}
- {'action': 'create', 'value': {'id': 1568, 'type': 'IfcCartesianPoint', 'Coordinates': (22.0, 0.0)}}
- {'action': 'create', 'value': {'id': 1569, 'type': 'IfcPolyline', 'Points': ({'id': 1567}, {'id': 1568})}}
- {'action': 'edit', 'id': 1566, 'index': 1, 'old': None, 'new': {'id': 1569}}
- {'action': 'create', 'value': {'id': 1570, 'type': 'IfcGridAxis', 'AxisTag': 'B', 'AxisCurve': None, 'SameSense': True}}
- {'action': 'edit', 'id': 1560, 'index': 7, 'old': ({'id': 1566},), 'new': ({'id': 1566}, {'id': 1570})}
- {'action': 'create', 'value': {'id': 1571, 'type': 'IfcCartesianPoint', 'Coordinates': (-2.0, 10.0)}}
- {'action': 'create', 'value': {'id': 1572, 'type': 'IfcCartesianPoint', 'Coordinates': (22.0, 10.0)}}
- {'action': 'create', 'value': {'id': 1573, 'type': 'IfcPolyline', 'Points': ({'id': 1571}, {'id': 1572})}}
- {'action': 'edit', 'id': 1570, 'index': 1, 'old': None, 'new': {'id': 1573}}
- {'action': 'create', 'value': {'id': 1574, 'type': 'IfcGridAxis', 'AxisTag': 'C', 'AxisCurve': None, 'SameSense': True}}
- {'action': 'edit', 'id': 1560, 'index': 7, 'old': ({'id': 1566}, {'id': 1570}), 'new': ({'id': 1566}, {'id': 1570}, {'id': 1574})}
- {'action': 'create', 'value': {'id': 1575, 'type': 'IfcCartesianPoint', 'Coordinates': (-2.0, 20.0)}}
- {'action': 'create', 'value': {'id': 1576, 'type': 'IfcCartesianPoint', 'Coordinates': (22.0, 20.0)}}
- {'action': 'create', 'value': {'id': 1577, 'type': 'IfcPolyline', 'Points': ({'id': 1575}, {'id': 1576})}}
- {'action': 'edit', 'id': 1574, 'index': 1, 'old': None, 'new': {'id': 1577}}
- {'action': 'create', 'value': {'id': 1578, 'type': 'IfcGridAxis', 'AxisTag': '01', 'AxisCurve': None, 'SameSense': True}}
- {'action': 'edit', 'id': 1560, 'index': 8, 'old': None, 'new': ({'id': 1578},)}
- {'action': 'create', 'value': {'id': 1579, 'type': 'IfcCartesianPoint', 'Coordinates': (0.0, -2.0)}}
- {'action': 'create', 'value': {'id': 1580, 'type': 'IfcCartesianPoint', 'Coordinates': (0.0, 22.0)}}
- {'action': 'create', 'value': {'id': 1581, 'type': 'IfcPolyline', 'Points': ({'id': 1579}, {'id': 1580})}}
- {'action': 'edit', 'id': 1578, 'index': 1, 'old': None, 'new': {'id': 1581}}
- {'action': 'create', 'value': {'id': 1582, 'type': 'IfcGridAxis', 'AxisTag': '02', 'AxisCurve': None, 'SameSense': True}}
- {'action': 'edit', 'id': 1560, 'index': 8, 'old': ({'id': 1578},), 'new': ({'id': 1578}, {'id': 1582})}
- {'action': 'create', 'value': {'id': 1583, 'type': 'IfcCartesianPoint', 'Coordinates': (10.0, -2.0)}}
- {'action': 'create', 'value': {'id': 1584, 'type': 'IfcCartesianPoint', 'Coordinates': (10.0, 22.0)}}
- {'action': 'create', 'value': {'id': 1585, 'type': 'IfcPolyline', 'Points': ({'id': 1583}, {'id': 1584})}}
- {'action': 'edit', 'id': 1582, 'index': 1, 'old': None, 'new': {'id': 1585}}
- {'action': 'create', 'value': {'id': 1586, 'type': 'IfcGridAxis', 'AxisTag': '03', 'AxisCurve': None, 'SameSense': True}}
- {'action': 'edit', 'id': 1560, 'index': 8, 'old': ({'id': 1578}, {'id': 1582}), 'new': ({'id': 1578}, {'id': 1582}, {'id': 1586})}
- {'action': 'create', 'value': {'id': 1587, 'type': 'IfcCartesianPoint', 'Coordinates': (20.0, -2.0)}}
- {'action': 'create', 'value': {'id': 1588, 'type': 'IfcCartesianPoint', 'Coordinates': (20.0, 22.0)}}
- {'action': 'create', 'value': {'id': 1589, 'type': 'IfcPolyline', 'Points': ({'id': 1587}, {'id': 1588})}}
- {'action': 'edit', 'id': 1586, 'index': 1, 'old': None, 'new': {'id': 1589}}
Traceback (most recent call last):
  File "C:\Users\Andrej\AppData\Roaming\Blender Foundation\Blender\4.4\extensions\.local\lib\python3.11\site-packages\ifcopenshell\file.py", line 352, in undo
    transaction.rollback()
  File "C:\Users\Andrej\AppData\Roaming\Blender Foundation\Blender\4.4\extensions\.local\lib\python3.11\site-packages\ifcopenshell\file.py", line 167, in rollback
    element = self.file.by_id(operation["id"])
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\Andrej\AppData\Roaming\Blender Foundation\Blender\4.4\extensions\.local\lib\python3.11\site-packages\ifcopenshell\file.py", line 510, in by_id
    return self[id]
           ~~~~^^^^
  File "C:\Users\Andrej\AppData\Roaming\Blender Foundation\Blender\4.4\extensions\.local\lib\python3.11\site-packages\ifcopenshell\file.py", line 497, in __getitem__
    return entity_instance(self.wrapped_data.by_id(key), self)
                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\Andrej\AppData\Roaming\Blender Foundation\Blender\4.4\extensions\.local\lib\python3.11\site-packages\ifcopenshell\ifcopenshell_wrapper.py", line 8884, in by_id
    return _ifcopenshell_wrapper.file_by_id(self, id)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
RuntimeError: Instance #1565 not found

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "C:\Users\Andrej\AppData\Roaming\Blender Foundation\Blender\4.4\extensions\.local\lib\python3.11\site-packages\bonsai\bim\ifc.py", line 426, in callback
    tool.Ifc.get().undo()
  File "C:\Users\Andrej\AppData\Roaming\Blender Foundation\Blender\4.4\extensions\.local\lib\python3.11\site-packages\ifcopenshell\file.py", line 354, in undo
    raise UndoSystemError("Error during transaction undo.", transaction) from e
ifcopenshell.file.UndoSystemError: Error during transaction undo.
```
2025-05-07 18:54:57 +05:00
Andrej730 f8489779f6 Fix missing IFC undo for all activate drawing operations
Very dangerous bug that could create a situation when .blend will go out of sync with IFC leading to unpredictable issues.
Example issue:
1. Create a drawing and activate it.
2. Create a grid.
3. Activate the drawing again.
4. Undoing just drawing activation here would work fine - annotations would be still floating in IFC without objects, but that would be fixed on the next drawing activation

To make it more dangerous undo grid creation. Then you'll get the traceback similar to the one below. The most dangerous part of it, that user won't see an error, it will just pop up in the system console silently.
```
Traceback (most recent call last):
  File "\bonsai\bim\handler.py", line 244, in undo_post
    IfcStore.undo(until_key=props.last_transaction)
  File "\bonsai\bim\ifc.py", line 533, in undo
    transaction["rollback"](transaction["data"])
  File "\bonsai\bim\ifc.py", line 449, in <lambda>
    operator, rollback=lambda d: tool.Ifc.get().undo(), commit=lambda d: tool.Ifc.get().redo()
                                 ^^^^^^^^^^^^^^^^^^^^^
  File "\ifcopenshell\file.py", line 345, in undo
    transaction.rollback()
  File "\ifcopenshell\file.py", line 161, in rollback
    element = self.file.by_id(operation["id"])
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "\ifcopenshell\file.py", line 498, in by_id
    return self[id]
           ~~~~^^^^
  File "\ifcopenshell\file.py", line 485, in __getitem__
    return entity_instance(self.wrapped_data.by_id(key), self)
                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "\ifcopenshell\ifcopenshell_wrapper.py", line 8884, in by_id
    return _ifcopenshell_wrapper.file_by_id(self, id)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
RuntimeError: Instance 1565 not found
```
2025-05-07 18:54:57 +05:00
Andrej730 64df82a76b ifcopenshell.file - save last failed transaction for introspection 2025-05-07 18:54:57 +05:00
Andrej730 af4fbd6750 Document features available for perspective camera in it's description
See https://i.imgur.com/8ZPJ8ag.png
2025-05-07 18:54:56 +05:00
Andrej730 1619ae8543 Expose camera type to UI and automatically update IFC camera type on drawing activation
Noticed fixing #6670 that though example project in this issue had camera named "PERSPECTIVE" but it was saved to IFC as orthogonal and there is no straightforward way to change it, besides changing Blender camera props and updating representation.

Example - https://imgur.com/a/GQ9A3EK

It's kind of exposed to UI now, but there's still bunch of issues with it to address.
2025-05-07 18:54:56 +05:00
Andrej730 41eadf735e add_representation - more readable error on using panoramic camera 2025-05-07 18:54:56 +05:00
Andrej730 172775d4ed Fix constant camera representation update because of the float garbage values
Similar issue to 82f25f5

Basically matrix is a little bit different every time you activate drawing even if user made no changes to camera position. My guess is basically all float values stored by Blender somewhere deep as float32 but when you access matrix world, Blender converts them to Python floats (which are float64) and some garbage values introduced along the way creating this noise.
2025-05-07 18:54:56 +05:00
Andrej730 d539cad1c2 Fix mistake in fd30a6b #6670 2025-05-07 18:54:56 +05:00
Andrej730 94cf56e080 typing 2025-05-07 18:54:56 +05:00
Geert Hesselink cf253824ca update latest simple_spf submodue 2025-05-07 15:48:01 +02:00
Andrej730 9c73f17303 add pytest-bdd to bonsai daily cli 2025-05-06 20:08:09 +05:00
Andrej730 a6f523039b bonsai pytest.ini - specify plugins explicitly
To make it possible to run the tests with PYTEST_DISABLE_PLUGIN_AUTOLOAD set, which is very useful if you don't want to start Blender anytime you run any tests on your system.
2025-05-06 20:07:44 +05:00
Andrej730 c2f9bfeb8d Fix bim search tests after 0b22333 2025-05-06 20:07:44 +05:00
Andrej730 3ab1c2e0f9 outliner.delete - make errors from attempt to delete locked object less scary
Traceback errors give users an impression that some broke and maybe project now is in some invalid state and they should ctrl-z immediately, error reports are much more friendly.

Before - https://i.imgur.com/N4fUU1M.png
After - https://i.imgur.com/6On1Be0.png
2025-05-06 19:03:05 +05:00
Andrej730 0ed964daa2 object.delete, outliner.delete - move to common method is_batch logic 2025-05-06 19:03:04 +05:00
Andrej730 03f619a77c aggregate decorator indicator - make it more intuitive
Example - https://imgur.com/a/RoFQKa1
2025-05-06 19:03:04 +05:00
Andrej730 e5ad35e2c8 bim.add_aggregate - use more common way to get all selected objects 2025-05-06 19:03:04 +05:00
Andrej730 297f15737d bim.add_aggregate - remove unused 'obj' property (c51aa91) 2025-05-06 19:03:03 +05:00
Andrej730 374b95efdf bim.add_aggregate - update description 2025-05-06 19:03:03 +05:00
Andrej730 744ed522b6 Fix error duplicating just an IfcElementAssembly (d2b05ea)
Example error traceback:
```
Traceback (most recent call last):
  File "C:\Users\Andrej\AppData\Roaming\Blender Foundation\Blender\4.4\extensions\.local\lib\python3.11\site-packages\bonsai\bim\module\geometry\operator.py", line 1044, in execute
    return OverrideDuplicateMove.execute_duplicate_operator(self, context, linked=False)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\Andrej\AppData\Roaming\Blender Foundation\Blender\4.4\extensions\.local\lib\python3.11\site-packages\bonsai\bim\module\geometry\operator.py", line 1055, in execute_duplicate_operator
    IfcStore.execute_ifc_operator(operator, context)
  File "C:\Users\Andrej\AppData\Roaming\Blender Foundation\Blender\4.4\extensions\.local\lib\python3.11\site-packages\bonsai\bim\ifc.py", line 458, in execute_ifc_operator
    result = getattr(operator, "_execute")(context)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\Andrej\AppData\Roaming\Blender Foundation\Blender\4.4\extensions\.local\lib\python3.11\site-packages\bonsai\bim\module\geometry\operator.py", line 1047, in _execute
    return OverrideDuplicateMove.execute_ifc_duplicate_operator(self, context)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\Andrej\AppData\Roaming\Blender Foundation\Blender\4.4\extensions\.local\lib\python3.11\site-packages\bonsai\bim\module\geometry\operator.py", line 1085, in execute_ifc_duplicate_operator
    old_to_new, new_active_obj = tool.Geometry.duplicate_ifc_objects(
                                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\Andrej\AppData\Roaming\Blender Foundation\Blender\4.4\extensions\.local\lib\python3.11\site-packages\bonsai\tool\geometry.py", line 2131, in duplicate_ifc_objects
    tool.Root.recreate_aggregate(old_to_new)
  File "C:\Users\Andrej\AppData\Roaming\Blender Foundation\Blender\4.4\extensions\.local\lib\python3.11\site-packages\bonsai\tool\root.py", line 397, in recreate_aggregate
    tool.Blender.select_and_activate_single_object(bpy.context, tool.Ifc.get_object(new_aggregate[0]))
                                                                                    ~~~~~~~~~~~~~^^^
TypeError: 'NoneType' object is not subscriptable
```
2025-05-06 19:03:03 +05:00
Andrej730 f4bc3fffa3 bim.override_move - rename for clairty and add description 2025-05-06 19:03:02 +05:00
Andrej730 06a2cecb51 typing 2025-05-06 15:15:03 +05:00
Andrej730 b0f2785e99 black . 2025-05-06 11:11:07 +05:00
Thomas Krijnen c6c852c7f6 NB: fix get_cartesiantransformationoperator3d() 2025-05-05 21:47:57 +02:00
Geert Hesselink 5d920d5e31 header_entity type -> attr check 2025-05-05 20:54:19 +02:00
Geert Hesselink 2b522b71cb hasattr to isinstance() 2025-05-05 20:54:19 +02:00
Geert Hesselink d00c3735ec Update src/ifcopenshell-python/ifcopenshell/validate.py
Co-authored-by: Thomas Krijnen <t.krijnen@gmail.com>
2025-05-05 20:54:19 +02:00
Geert Hesselink bbdc9c45dd use repr(), compatible f.header 2025-05-05 20:54:19 +02:00
Geert Hesselink 5eb9cdf40d handle header entity string reversal 2025-05-05 20:54:19 +02:00
Geert Hesselink 689289bb60 validate ifcopenshell.simple_spf.file 2025-05-05 20:54:19 +02:00
Geert Hesselink d401e4c948 set set/get_description, add view_definitions & tests 2025-05-05 16:18:09 +02:00
Geert Hesselink cbe8fc1ebd mvd info to module, add testing, setattr & getattr 2025-05-05 16:18:09 +02:00
Geert Hesselink 9f11568aff Allow for dots in mvd value 2025-05-05 16:18:09 +02:00
Geert Hesselink 968e193f99 abort string when None, more explicit error handling 2025-05-05 16:18:09 +02:00
Geert Hesselink 2d06a79842 handle emptry description field 2025-05-05 16:18:09 +02:00
Geert Hesselink 808a16ba4a parsed mvd description in ifcopenshell.file 2025-05-05 16:18:09 +02:00
Andrej730 a67a6034a5 Use mesh.separate for vanilla Blender objects #6658 (0b22333)
mesh.separate works completely different in EDIT mode, previously we only supported it from object mode
2025-05-05 18:45:09 +05:00
Andrej730 8fe1a63339 Bonsai docs - add page to list hotkeys
See https://docs-unstable.bonsaibim.org/reference/hotkeys.html
2025-05-05 18:45:08 +05:00
Andrej730 1ce78f3c65 Override Shift-X and Shift-Del with IFC Delete
To prevent users from creating weird state of IFC model by using non-ifc `object.delete` triggered by those hotkeys by default.
2025-05-05 18:45:08 +05:00
Andrej730 57b35c68e4 IFC delete - just reuse object.delete if no IFC file active
So it will be possible for this operator to completely override object.delete
2025-05-05 18:45:08 +05:00
Andrej730 ff57b91691 typing 2025-05-05 18:45:08 +05:00
Andrej730 6e15219fad black . 2025-05-05 18:34:47 +05:00
Andrej730 979964f211 Merge branch 'falken10vdl-patch-2' into v0.8.0 2025-05-05 18:33:15 +05:00
falken10vdl 4ce2f81479 Skip non MESH and CURVE object listing elements for coloring #6657
So non MESH or CURVE objects are not listed in the Colour by property.

Now all unrelated empty objects are excluded from the coloring section as they were just taking space, see - https://imgur.com/sPA8I5V
2025-05-05 18:31:19 +05:00
Thomas Krijnen dbdde52435 validate.py Add test files 2025-05-05 14:21:19 +02:00
Thomas Krijnen 84568f34f0 validate.py be more specific in distinguishing type decl and entity refs 2025-05-05 14:20:47 +02:00
Geert Hesselink fe9c548ae0 Valid template header default (#6610)
* Valid template header default

* consistent version in tempalte file

* schema-dependent mvd
2025-05-05 13:56:40 +02:00
Andrej730 1714516a81 Fix bim.override_move issue with aggregates #6648 (c9fc543) 2025-05-05 14:59:31 +05:00
Andrej730 c4c3a2056b overridden delete operators - add descriptions 2025-05-02 19:58:24 +05:00
Andrej730 fad35b14f2 Fix dangerous bug with bim.override_outliner_delete
Essentialy outliner.delete should do the same as object.delete, but in our case the main implementation was in object.delete while outliner.delete was missing very important parts - e.g. handling arrays.

So if user could have remove part of an array without errors, leaving pset data invalid. See #6590, #6547.
Found this by trying to debug issues above.

Now outliner.delete will call object.delete explicitly, so the two stay connected.

I also had an idea that may be it somehow can produce #6632/#6619, but couldn't find a way to reproduce it. So it's still uncertain.
2025-05-02 19:58:24 +05:00
Andrej730 2382f8d7f8 color by property - add operators descriptions 2025-05-02 19:58:23 +05:00
Andrej730 9ae7a68c43 Display actual Bonsai version for live repo (reuse last commit date) 2025-05-02 19:58:23 +05:00
Andrej730 7a5e5fe602 Reuse get_last_commit_hash
Noticed that hash wasn't updating from live git repo because of this.
Also update commit data only when running the package, not extension.
2025-05-02 19:58:23 +05:00
Andrej730 839b2c9c1f typing 2025-05-02 19:58:23 +05:00
Andrej730 4aaaf6e8f5 Fix ifc2x3 errors editing IfcSurfaceStyleShading #6624 2025-05-02 12:32:49 +05:00
Andrej730 1b38b03532 new operator - bim.select_entity_by_guid
I find myself often having element guid copied (either from other viewer or from text editor or from elsewhere) and to find it in scene I needed to create a new search group, add a filter, paste guid and then search for it. So decided to add a shortcut for this that just takes guid from the clipboard and runs the search for it.

Location - https://i.imgur.com/3c5rm1N.png
2025-05-01 19:32:53 +05:00
Andrej730 cbf0fa88eb Reenable black formatting workflow
Since 3b1e893 black formatter workflow basically had no use since it wasn't reporting any issues (no one checks workflow logs unless workflow failed) and wasn't reliable for catching syntax errors either (black formatter catches syntax errors only when they get in the way of it's formatting the code, but now we delegate to python itself to catch syntax errors, see 406962287c).

And we also have some docs on how to apply black formatter - https://docs.bonsaibim.org/guides/development/code_style.html
2025-05-01 19:32:52 +05:00
Andrej730 6961ca8ce7 Fix bug creating a drawing if linked model schema doesn't match the main model schema
Co-Authored-By: Thomas Krijnen <1096535+aothms@users.noreply.github.com>
2025-05-01 19:32:52 +05:00
Andrej730 d758d4aef5 Windows - fix opening paths with special characters #6630 2025-05-01 19:32:52 +05:00
Andrej730 84240c870c More consistent Windows Bonsai .ifc file association
Rely on `winreg` instead of running powershell scripts with cmd commands + .reg file + weird workflow with the need to retype cmd command. Also use HKCU instead of HKCR because it doesn't require admin rights.

If someone hearing about this feature for the first time, here's a short demo - https://imgur.com/a/dfG44pY (icon appears too but after explorer restart).
2025-05-01 19:32:52 +05:00
Andrej730 4483d8e958 Fix typo 2025-05-01 19:32:52 +05:00
Thomas Krijnen 70440050db Bounds checking in variant array 2025-04-30 20:43:43 +02:00
Thomas Krijnen bc7527a73b Defensiveness against empty faces 2025-04-30 20:43:27 +02:00
Thomas Krijnen c8ad1ba0ee Fix faceset helper on advanced brep oriented edges #6604 2025-04-30 20:43:13 +02:00
Andrej730 c7a29d5604 Fix invalid IfcGrid cartesian points (774a770)
They were saved as 4d coordinates, not 2d.
2025-04-30 19:36:22 +05:00
Andrej730 814c16d3ee IfcGrid - add placement by default to avoid invalid IFC 2025-04-30 19:36:22 +05:00
Andrej730 3bedbfb82c Operator to run git diff on current ifc file
Short demo - https://imgur.com/a/TvnztNy
2025-04-30 19:36:22 +05:00
Andrej730 07f42b9126 typing 2025-04-30 19:36:21 +05:00
Andrej730 5b1da583d9 black . 2025-04-30 19:36:21 +05:00
Thomas Krijnen c624162f09 partial_success_is_success #6589 2025-04-30 14:53:53 +02:00
FabioPiccinini bc221cfa99 Use tool.Blender.get_view3d_area() to allow the use of separate windows (#6617) 2025-04-30 17:29:05 +05:00
c4rlosdias 5d02afa47e clear list of entities failed when execute Tester 2025-04-30 17:27:06 +05:00
Andrej730 406962287c ci-black-formatting - check syntax errors explicitly 2025-04-30 15:22:09 +05:00
Andrej730 241f7679b0 create_axis_curve - fix typo 2025-04-30 14:24:04 +05:00
Thomas Krijnen 0001071185 partial_success_is_success #6589 2025-04-29 22:03:18 +02:00
Thomas Krijnen 8d476c792d todo notes #6589 2025-04-29 21:43:49 +02:00
Thomas Krijnen 97c7aefad7 One more self-intersection check in cgal kernel #6589 2025-04-29 21:39:08 +02:00
Andrej730 34cafc761a Use actual text position instead of local pos rendering drawings text decorations
E.g. it's important if text is part of parametric array and it's local position may be constant, but it's constrainet by parent's position.
2025-04-29 19:26:43 +05:00
Andrej730 3fbde8db28 mesh.add_grid - display props in Blender project units
Example - https://i.imgur.com/koLiH7U.png
2025-04-29 19:26:42 +05:00
Andrej730 0b8fb41ac4 bim.add_array - guard from unexpected element types
So we can gradually expand it with the things we do know to support.
2025-04-29 19:26:18 +05:00
Andrej730 dc46afc227 Setup array constraints for annotations that use paramatric arrays
Previously you was able to add parametric array to annotations but constraint would be missing on file reload.
2025-04-29 19:26:18 +05:00
Andrej730 b8ee1d4d8d import_ifc - skip setup_arrays for annotations #6636 2025-04-29 19:26:18 +05:00
Andrej730 ecf461e1e0 typing 2025-04-29 19:26:17 +05:00
Andrej730 1856eeddeb Fix missed cases of loading unloaded linked files in 9316e0b (#6403) 2025-04-29 15:20:45 +05:00
Andrej730 afb2b9fdea Fix issues printing drawing with projects linked by relative path
The fix in f9bf889 wasn't related to this problem and was targetting another bug.
2025-04-29 15:20:45 +05:00
Andrej730 af2cb2896a Revert f9bf889 #6605
As this fix is no longer needed and covered by more general resolve_uri refactor
2025-04-29 15:20:45 +05:00
Andrej730 e792cf8fa0 get_uri, resolve_uri - refactor #6605
1) Use pathlib for consistency
2) Ensure consistent output - always use posix format and resolve paths (but keep the symlinks)
2025-04-29 15:20:45 +05:00
Thomas Krijnen 7b71e2ce2a Don't set xstep.cascade.unit as it appears to result in scaling during export #6623 2025-04-29 10:53:01 +02:00
Thomas Krijnen ce72337d3a Mark settings as internal #6623 2025-04-29 10:52:27 +02:00
Bruno Postle 0e9ca541c4 Linux escape double-quotes in filenames #6630
Special chars ('#{)$ were already escaped, but " wasn't
2025-04-28 21:34:05 +01:00
Andrej730 784576b249 IFC Join - support joining non-ifc Blender objects into representation items
Short demo - https://imgur.com/a/o9MJiIr
2025-04-28 19:14:21 +05:00
Andrej730 5e26e55a8c edit_meshlike_item - fix missing item object name update 2025-04-28 19:14:21 +05:00
Andrej730 1b110c4a84 reuse ifc.link for meshes 2025-04-28 19:14:20 +05:00
Andrej730 b94629133c geometry.name_item_object 2025-04-28 19:14:20 +05:00
Andrej730 55e270c135 IFC Join - fix error joining representation items (6ce4b5d)
It was trying to reaccess already removed ifc_definition_id leading to an error. Now we update id with a new item.
2025-04-28 19:14:20 +05:00
Andrej730 308d628b1e IFC Join - prevent joining rep items into non items
Because it was removing representation item object leaving Bonsai in confusing state, which may lead to errors later
2025-04-28 19:14:20 +05:00
Andrej730 5e16588eb7 IFC Join - prevent possible ghost ifc objects after join
Skip ifc objects of different type as they will be ignored by object.join either way and removing them produced ghost ifc objects.
2025-04-28 19:14:20 +05:00
Andrej730 e919a0643f IFC Join - add description 2025-04-28 19:14:19 +05:00
Andrej730 9835ef977f IFC Join - add poll check similar to object.join 2025-04-28 19:14:19 +05:00
Andrej730 10f84537de typing 2025-04-28 19:14:19 +05:00
Massimo Fabbro f9a0081b33 Fix bug in reload link ifc where in Windows gives an error if relative path is used 2025-04-28 06:22:13 +02:00
Massimo Fabbro 11462c388a Purge unecessary code 2025-04-28 06:22:13 +02:00
Ryan Schultz 4bcd9639f4 whoops 2025-04-27 16:55:45 -05:00
Ryan Schultz b856c40fe7 small tweak to previous commit 2025-04-27 16:54:34 -05:00
Ryan Schultz dd1c529544 Fix #6601 - If you duplicate something it makes the duplicate the active object.
also bulk deselect all objects_to_remove to speed things up a little.
2025-04-27 13:12:07 -05:00
Andrej730 c9fbec4d46 Revert "Bump IOS build in Bonsai" #6589
This reverts commit 50e7728f95.
2025-04-25 19:47:17 +05:00
Andrej730 c9437329ef IFC Mesh Separate - fix bug in 52af302bef separating multiple elements
Forgot to clear selection after processing each object, it could have led to incorrect results since mesh.separate is applied to the selection later.
2025-04-25 19:47:17 +05:00
Andrej730 a09e971674 IFC Mesh Separate - fix not updated items decorator after some item was separated
Noticed fixing bug from #6285
2025-04-25 19:47:17 +05:00
Andrej730 8ac04e32e5 bim.import_representation_items - add description 2025-04-25 19:47:16 +05:00
Andrej730 0634dc83d4 IFC Mesh Separate - fix bug when separating an item wouldn't change separated item
Mentioned by Ryan in #6285
2025-04-25 19:47:16 +05:00
Andrej730 5dc4b18d3f IFC Mesh Separate - reload representation just once if multiple items got separated 2025-04-25 19:47:16 +05:00
Andrej730 f84bca8017 IFC Mesh Separate - fix bug when new separated mesh wasn't relinked to a new ifc item 2025-04-25 19:47:16 +05:00
Andrej730 31a6c91c4b core.duplicate_drawing - remove debug print statements and unused code 2025-04-25 19:47:16 +05:00
Andrej730 30242d859e Fix core test for drawing duplication (0b1c049) 2025-04-25 19:47:16 +05:00
Andrej730 330799c799 core tests - very readable error for unpredicted return values
Previously:
E       TypeError: Object of type Prophecy is not JSON serializable

Now:
E           TypeError: Failed to serialize call: '{'name': 'duplicate_ifc_objects', 'args': ([<Prophecy for 'bonsai.core.tool.Ifc'>],), 'kwargs': {}}'.
E           Possibly due to unpredicted return value for some call.
E           See the list of the recent calls without return values:
E           - {'name': 'get_object', 'args': ('annotation',), 'kwargs': {}}
E           - {'name': 'run', 'args': ('group.assign_group',), 'kwargs': {'group': 'new_group', 'products': ['new_drawing']}}
E           - {'name': 'run', 'args': ('group.edit_group',), 'kwargs': {'group': 'new_group', 'attributes': {'Name': 'unique_name', 'ObjectType': 'DRAWING'}}}
E           - {'name': 'run', 'args': ('group.unassign_group',), 'kwargs': {'group': 'group', 'products': ['new_drawing']}}
2025-04-25 19:47:15 +05:00
Andrej730 e2c2234199 core tests - more readable errors
E.g. it shows now:
AttributeError: Interface 'bonsai.core.tool.Drawing' has no attribute 'weird_method'.

Instead of:
Prophecy <class 'abc.Drawing'> has no attribute weird_method
2025-04-25 19:47:15 +05:00
Andrej730 34efa9f459 typing 2025-04-25 19:47:15 +05:00
Andrej730 261de9b2be entity_instance.__getattr__ - document some attributes conversion to Python 2025-04-25 19:47:15 +05:00
Andrej730 52af302bef IFC Mesh Separate - add to object mode menu too for discoverability + support multiple selected elements
Short demo - https://imgur.com/a/PIuJ9Rq
2025-04-24 19:54:36 +05:00
Andrej730 529c74f6a9 create_camera - small optimization with util.shape 2025-04-24 19:54:36 +05:00
Andrej730 fd30a6b5eb create_camera - small optimization with early return 2025-04-24 19:54:35 +05:00
Andrej730 82f25f50cc Fix resaving camera to IFC on every drawing activation (32899eb)
`camera_props.update_representation` basically always returned True leading to `bim.update_representation` call on camera on every drawing activation. It was always True because `create_camera` wasn't setting `representation` and some other related camera props.

Changes:
- create_camera now setting representation
- ortho_scale, rather_x and raster_y now explicitly is tied to width, height and drawing scale props, not on depsgraph update. Depsgraph update wasn't allowing to set these props in time for create_camera to record representation correctly
- depsgraph update now only in charge of ortho_scale (just to be sure user won't touch it by hand) and scene.render resolution
2025-04-24 19:54:35 +05:00
Andrej730 d0dc31588d typing 2025-04-24 19:54:35 +05:00
Andrej730 68f71c715e Fix invalid cprops after camera data is recreated
Mentioned in  #5676, discoverd after 476604202a.

`bim.update_representation` removes previous `camera.data` and adds a new one making cprops invalid possibly leading to either invalid prop values or crashes (https://projects.blender.org/blender/blender/issues/137889).
2025-04-23 19:22:31 +05:00
Andrej730 6913060f0c bim.expand_target_view - remove trailing space from description 2025-04-23 19:22:31 +05:00
Andrej730 4427c73816 bim.override_mesh_separate - handle possible mode mismatch more gracefully
Other options LOOSE_PARTS and MATERIAL do support object mode.
2025-04-22 20:05:32 +05:00
Andrej730 d188155fac bim.override_mesh_separate - fix bug with skip_save
Previously you would use bim.override_mesh_separate from UI with any of the available `type` options, then running operator separately would leading to running this operator from F3 to always choose this options (possibly leading to errors in objects mode)
2025-04-22 20:05:31 +05:00
Andrej730 598dfd75e7 bim.override_origin_set - use operator_enum for menu 2025-04-22 20:05:31 +05:00
Andrej730 5c63cd7981 bim.override_mesh_separate - use operator_enum, hide Blender Separate
as it's now integrated in IFC separate and using Blender Separate for ifc elements is not safe
2025-04-22 20:05:31 +05:00
Andrej730 0b22333b69 bim.override_mesh_separate - integrate mesh.separate
mesh.separate will be used for all other selected non-ifc objects.

Included some code to support multple objects but there are still a few issue with it, I'll look into soon.
2025-04-22 20:05:31 +05:00
Andrej730 69481c2d63 bim.override_mesh_separate - fix bug separating elements (7bc4b51)
`obj` variable was used but never passed to `separate_element`

```
Error: Python: Traceback (most recent call last):
  File "\bonsai\bim\ifc.py", line 458, in execute_ifc_operator
    result = getattr(operator, "_execute")(context)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "\bonsai\bim\module\geometry\operator.py", line 105, in _execute
    self.separate_element(element)
  File "\bonsai\bim\module\geometry\operator.py", line 175, in separate_element
    new_objs = [obj]
                ^^^
NameError: name 'obj' is not defined
```
2025-04-22 20:05:31 +05:00
Andrej730 671c887201 bim.override_mesh_separate - invoke menu if called without argument
Mimicking default mesh.separate behaviour.

@Gorgious56 I don't know if you've seen this before, but you might be interested - I've found a way to figure if operator wasn't called without any arguments.
2025-04-22 20:05:30 +05:00
Andrej730 0a6fbeccb0 bim.override_mesh_separate - use original Blender enum and description 2025-04-22 20:05:30 +05:00
Andrej730 4b3ca11c12 typing 2025-04-22 20:05:30 +05:00
Andrej730 f9bf8897bc Fix error linking ifc files with relative filepaths going upwards
Mentioned in https://github.com/IfcOpenShell/IfcOpenShell/pull/6540#issuecomment-2820938852
2025-04-22 16:37:05 +05:00
Andrej730 932939099a Add target_view prop to camera
This property was already set in 50b41f5 but Blender property was missing. Though nowit's not very useful as it just writes value to pset.
2025-04-22 16:37:05 +05:00
Andrej730 8d31d93531 typing 2025-04-22 16:37:05 +05:00
Andrej730 5c4f157624 readable_ifc_duration - add doc-string and note 2025-04-21 19:00:25 +05:00
Andrej730 f87c694432 Fix issue with custom diagram scale (1d5891f)
It was referring to non-existent prop "custom_diagram_scale"
2025-04-21 19:00:25 +05:00
Andrej730 38eedffd80 tool.Ifc - add some doc-strings 2025-04-21 19:00:25 +05:00
Andrej730 305439d27e Link.name - fix description
After f8828d3 we store linked filepath in .ifc project, so we don't support path relative to .blend anymore.
2025-04-21 19:00:24 +05:00
Andrej730 908bf02098 ifc5d - strip " Cost" suffix when importing csv
" Cost" is a suffix is added on export, so it makes to get rid of it on import.
2025-04-21 19:00:24 +05:00
Andrej730 c6176d364f ifc5d - use filename for imported schedule name
So it will be in sync with how export works.

Removed non_sor_fields is unrelated - forgot to remove before.
2025-04-21 19:00:24 +05:00
Andrej730 068814c095 bim.assign_document - add to documents UI 2025-04-21 19:00:24 +05:00
Andrej730 476604202a typing 2025-04-21 19:00:24 +05:00
Andrej730 558bda1aea black . 2025-04-21 19:00:23 +05:00
Dion Moult 426f1b91f2 See #2783. Don't show 0 durations for clarity. 2025-04-21 22:59:14 +10:00
Dion Moult d1b1b08174 See #2783. Duration parsing no longer uses isodate to remain faithful to formatting. 2025-04-21 22:39:23 +10:00
Thomas Krijnen c1513190d4 Debug build_win.yml 2025-04-21 13:17:14 +02:00
Andrej730 50e7728f95 Bump IOS build in Bonsai
New build resolves #6529 #6576
2025-04-21 13:57:25 +05:00
Andrej730 4e00a8d596 bonsai Makefile - specify shell explicitly
To avoid Windows trying to use cmd
2025-04-21 13:57:25 +05:00
Massimo Fabbro 266cc32336 Add calculating functions for earthworks 2025-04-21 10:48:00 +02:00
Andrej730 f8ad05cb54 Use isodate to parse iso durations #2783
Related #6585

Co-Authored-By: sboddy <12862430+sboddy@users.noreply.github.com>
2025-04-21 12:45:40 +05:00
Andrej730 311f9b96e7 black . 2025-04-21 12:45:40 +05:00
Dion Moult 56bfe80b3b Fix #6501. Fix #6104. Fix #6189. You can now change wall offset convention after drawing walls.
People kept on getting confused with the align tool thinking it changed
the baseline to reference line offset whereas it actually just aligned
the object bodies. This tool now does exactly that.

Also start refactoring the "DumbWall" classes into the tools so we can
test them properly.
2025-04-21 17:17:09 +10:00
Dion Moult 2db6b860b2 See #6501. Fix bug where wall alignment didn't strictly check for poll requirements prior to executing operator. 2025-04-21 15:38:26 +10:00
Ryan Schultz 5af2c3914a When \n stopped the world. fix previous commit.
```
os: Windows
os_version: 10.0.19045
python_version: 3.11.7
architecture: ('64bit', 'WindowsPE')
machine: AMD64
processor: Intel64 Family 6 Model 158 Stepping 10, GenuineIntel
blender_version: 4.2.1 LTS
bonsai_version: 0.0.0
bonsai_commit_hash: f8ebc2d
bonsai_commit_date: 2025-04-20T10:30:03-05:00
last_actions:
last_error: Traceback (most recent call last):
  File "C:\Users\Ryan Schultz\AppData\Roaming\Blender Foundation\Blender\4.2\extensions\.local\lib\python3.11\site-packages\bonsai\bim\module\project\operator.py", line 1003, in finish_loading_project
    bpy.ops.bim.load_project_elements()
  File "c:\Program Files\Blender Foundation\Blender 4.2\4.2\scripts\modules\bpy\ops.py", line 109, in __call__
    ret = _op_call(self.idname_py(), kw)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
RuntimeError: Error: Python: Traceback (most recent call last):
  File "C:\Users\Ryan Schultz\AppData\Roaming\Blender Foundation\Blender\4.2\extensions\.local\lib\python3.11\site-packages\bonsai\bim\module\project\operator.py", line 1063, in execute
    bonsai.bim.schema.reload(self.file.schema_identifier)
  File "C:\Users\Ryan Schultz\AppData\Roaming\Blender Foundation\Blender\4.2\extensions\.local\lib\python3.11\site-packages\bonsai\bim\schema.py", line 67, in reload
    ifc = IfcSchema(schema_identifier)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\Ryan Schultz\AppData\Roaming\Blender Foundation\Blender\4.2\extensions\.local\lib\python3.11\site-packages\bonsai\bim\schema.py", line 43, in __init__
    self.load_pset_templates()
  File "C:\Users\Ryan Schultz\AppData\Roaming\Blender Foundation\Blender\4.2\extensions\.local\lib\python3.11\site-packages\bonsai\bim\schema.py", line 57, in load_pset_templates
    self.psetqto.templates.append(ifcopenshell.open(path))
                                  ^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\Ryan Schultz\AppData\Roaming\Blender Foundation\Blender\4.2\extensions\.local\lib\python3.11\site-packages\ifcopenshell\__init__.py", line 183, in open
    return file(f)
           ^^^^^^^
  File "C:\Users\Ryan Schultz\AppData\Roaming\Blender Foundation\Blender\4.2\extensions\.local\lib\python3.11\site-packages\ifcopenshell\file.py", line 295, in __init__
    raise exc(msg)
ifcopenshell.Error: Syntax error during parse, check logs
Location: c:\Program Files\Blender Foundation\Blender 4.2\4.2\scripts\modules\bpy\ops.py:109

ifc: projection lines.ifc
schema: IFC4
preprocessor_version: IfcOpenShell 0.0.0
originating_system: Bonsai 0.8.3-alpha250417-f3228c3
```
2025-04-20 11:20:05 -05:00
Ryan Schultz f8ebc2d156 Property in EPset_Drawing for SVG classes that 'rise to the top'. per: https://community.osarch.org/discussion/comment/22689/#Comment_22689 2025-04-20 10:30:03 -05:00
Thomas Krijnen f3228c31f8 Debug build_win.yml 2025-04-20 08:42:03 +02:00
Dion Moult 86d481f4f8 Revert "Fix #4130. Skip invalid topology."
This reverts commit bb598d848a.
2025-04-20 15:07:45 +10:00
Ryan Schultz 05f79c844a For cut lines through terrain. I still plan to make these a preference, however. 2025-04-19 12:46:52 -05:00
Thomas Krijnen b87a7b3d0a cgal kernel - conditionally reorient extrusion basis #6529 2025-04-19 18:50:51 +02:00
Massimo Fabbro e54acd83f8 See #5149
Now it is possible to assign a resource also to a cost item in a schedule of rates
2025-04-19 16:40:47 +02:00
Massimo Fabbro f8e52c8a12 See #6570. Add a button to refresh an imported csv file
Now the imported csv filepath is stored so it is possible to refresh the cost schedule without the need of reimporting it
2025-04-19 16:40:24 +02:00
Dion Moult 0608f92bc4 See #4699. Potentially fix crashes by only loading IFC data once and caching in decorator. 2025-04-19 21:19:18 +10:00
Dion Moult df18108406 Add tests for sheet creation and drawing / sheet generation 2025-04-19 17:36:21 +10:00
Thomas Krijnen b64dcfd4b8 Fix compilation on schemas prior to ifc4 #6549 2025-04-19 09:21:22 +02:00
Dion Moult d1493b4234 Fix #5571. No longer create "RASTER" document references. Process all images at sheet creation time.
I think this was overengineered and a bad decision. RASTER is a
technical detail, not a semantic label (in contrast with whether it's a
title or a drawing).
2025-04-19 16:53:14 +10:00
Dion Moult fb34af9162 Fix #6575. Regression in moving aggregates. 2025-04-19 15:30:46 +10:00
Dion Moult bb598d848a Fix #4130. Skip invalid topology. 2025-04-19 15:25:13 +10:00
Dion Moult 502cf6f439 Standalone script to generate drawings for debugging 2025-04-19 14:59:54 +10:00
Dion Moult 9875fce579 Revert "See #6574. Bump IOS."
This reverts commit 90198115b6.
2025-04-19 14:58:07 +10:00
Dion Moult 90198115b6 See #6574. Bump IOS. 2025-04-19 14:29:20 +10:00
Dion Moult 4896a69981 Add test cases for drawing duplication and shapely fill mode. 2025-04-19 13:42:36 +10:00
Dion Moult 3390f58c88 Fix failing tests 2025-04-19 08:19:25 +10:00
Andrej730 917d28a7b1 ifc5d cost schedule export - guess unit from quantity type 2025-04-18 19:25:46 +05:00
Andrej730 bebaa4d1bd ifc5d cost import - fix unexpected columns assigned as values
Mentioned in #5492 - id, index and lots of others were assigned as values at import.
2025-04-18 19:25:46 +05:00
Andrej730 93a4f4980a Revert ddd7dff75e
I guess after all it's not very reliable idea since it's possible to have a cost schedule without quantities (even non-SoR) and it's giving users false assumptions that they don't need to worry about "Is Schedule of Rates" flag in some cases.
2025-04-18 19:25:46 +05:00
Andrej730 1e30eae1d7 ifc5d - continue refactor 2025-04-18 19:25:46 +05:00
Andrej730 ff90396bcf typing 2025-04-18 19:25:46 +05:00
Dion Moult a030b84fc5 Typo 2025-04-18 19:50:03 +10:00
Dion Moult 0b1c049e81 Fix #5137. Fix bug where duplicated drawings didn't duplicate properly. Duplicating drawings now shares code with regular object duplication.
Previously code wasn't shared and duplication was reimplemented in an
incomplete way. Now it's shared which means that it's also more
sophisticated (i.e. does all the linked aggregate magic).

Also the duplicate code seems super complicated especially with arrays
and linked aggregates. Moving it into a tool makes it more reusable and
we can test it more granularly in the future.
2025-04-18 19:47:54 +10:00
Dion Moult 74390c0529 Fix bug where duplication outside IFC projects didn't use native Blender operators.
Historically we overrode the operator, so we had to reinvent native
functionality. Now we only override the hotkey, so we can reuse native
functionality. This also means that things we failed to reinvent like
duplicating unlinked animation data now works properly. This bit me when
I was doing animation work and Bonsai's duplicate messed things up.
2025-04-18 18:28:15 +10:00
Thomas Krijnen 21d39d8a23 Use exact topology points at parametric range bounds to guarantee welding #4487 2025-04-18 10:08:57 +02:00
Thomas Krijnen ad0f62f88f Don't terminate on invalid placement location #4883 2025-04-18 09:17:19 +02:00
Andrej730 85d8fa49ab ifc5d - add examples for other formats 2025-04-17 19:07:20 +05:00
Andrej730 c5f4b53555 bim.export_cost_schedules - fix bug with overridden prop value
E.g. if you would call this operator for 1 specific cost schedule, Blender would remember it's value and would keep on using it even if operator is going to be called outside this schedule
2025-04-17 18:55:42 +05:00
Andrej730 016cfa94d2 export_cost_schedules - fix confusing filename
Mentioned in #6545

Override filename to be always empty, so user won't have an expectation that csvs will be saved as a single file (currently they're always saved to a directory).
2025-04-17 18:55:41 +05:00
Andrej730 007af80234 ifc5d readme - add notes for possible missing columns 2025-04-17 18:55:41 +05:00
Andrej730 576a206d5b Fix typo 2025-04-17 18:55:41 +05:00
Andrej730 4cf6411a61 ifc5d.csv2ifc - add tests for sample files 2025-04-17 18:55:41 +05:00
Andrej730 37a0b3f6a9 Csv2Ifc - move arguments to init for a simpler setup 2025-04-17 18:55:41 +05:00
Andrej730 209caec49a csv2ifc import - rename required column from hierarchy to index #5492
To prevent confusion with 'Hierarchy' column that's added on the export and make current and export compatible.
Indices now always start from 1 for consistency.
2025-04-17 18:55:41 +05:00
Andrej730 bebb29f486 ifc5d csv export - rename Description column to Name #5492
Because actually it is a name and this is the column name that csv2ifc expects.
2025-04-17 18:55:40 +05:00
Andrej730 deb6e3779e ifc5d - refactor 2025-04-17 18:55:40 +05:00
Andrej730 a6c37771fc typing 2025-04-17 18:55:40 +05:00
Andrej730 95f754d2df black . 2025-04-17 18:55:40 +05:00
Thomas Krijnen 030dc6460a Prevent double free in iterator with output=native 2025-04-17 15:26:18 +02:00
Thomas Krijnen 360e08f268 SVG matrix #5676 2025-04-17 15:25:35 +02:00
Sebastian Friston dc84d85f7b ISSUE #6340 check if material is null before trying to get id 2025-04-17 14:33:07 +02:00
Dion Moult e16d8907f3 See #5676. Shapely fill calculation on drawings is 100x faster due to Blender raycasting instead of tree.select_ray. 2025-04-17 22:31:23 +10:00
Dion Moult 52cada180b Fix #4107. Bug where shapely used incorrect raycast coordinates causing fills to not show.
This has been around for 2 years, either it was never noticed or
something changed?
2025-04-17 19:38:31 +10:00
Gorgious56 1999216ea5 Use utilities to fetch collection hierarchy
I don't think this offers a noticeable performance gain but feels more idiomatic since it is available with the API.
2025-04-17 09:10:28 +02:00
Dion Moult 5a6476a57f Fix #3673. Bug where a split wall broke flipped doors.
Instead of regenerating fills upon splitting walls, placements are
preserved exactly.
2025-04-17 16:28:05 +10:00
Bruno Perdigão c45e3b0099 See #6537. Fix how OverrideMove works when dealing with multiple levels of aggregates. 2025-04-16 20:46:39 -03:00
Bruno Perdigão c9fc543e91 See #6537. Improve how aggregate mode behaves when dealing with multiple levels of aggregates. 2025-04-16 20:23:18 -03:00
Andrej730 9994fec571 ifc5d - fix bug exporting multiple csvs with the same name 2025-04-16 19:24:16 +05:00
Andrej730 b0aeabf2f6 ifc5d .csv cost schedule - save as unicode
Previously it was saved using Windows 1252 and importing it would fail with an error like so:
```
UnicodeDecodeError: 'utf-8' codec can't decode byte 0x92 in position 650: invalid start byte
```
2025-04-16 19:24:16 +05:00
Andrej730 ddd7dff75e ifc5d csv2ifc - autodetect schedule of rates by the header structure 2025-04-16 19:24:16 +05:00
Andrej730 7acd13cb82 ifc5d readme - mention format examples 2025-04-16 19:24:15 +05:00
Andrej730 6499ac41cf ifc5d typing and refactor
no functional changes
2025-04-16 19:24:15 +05:00
Dion Moult e8b74245ce Update VERSION 2025-04-16 16:27:16 +10:00
Andrej730 ecee3781e4 Fix more missing Blender descriptions leading to crashes #6420 2025-04-15 19:43:38 +05:00
Andrej730 52f949b2dc assign_class - avoid empty geometries and assigning shape reprs to structural items 2025-04-15 19:43:38 +05:00
Andrej730 cb7e04f61a assign_class - replace object with empty if no representation was added
Same goal as ffffcc576e - prevent user confusion when they reload IFC file and see the representation is gone.
2025-04-15 19:43:38 +05:00
Andrej730 b59e5b076d get_pset, get_psets - don't fail on ifc2x3 profile psets and partially handle ifc2x3 material psets #6538 2025-04-14 18:59:47 +05:00
Andrej730 1516237537 Allow Undefined topology representations for IfcStructuralPointConnection #6459
Because apparently it's not invalid IFC.
IfcStructuralPointConnection documentation says that they should have a topology representation with a IfcVertexPoint but it doesn't restict this representation from having Undefined type if it does have a IfcVertexPoint as it's item.
2025-04-14 17:49:17 +05:00
Andrej730 53cd5f834e add_mesh_representation - reuse shape builder + numpy input support 2025-04-14 16:17:59 +05:00
Andrej730 6dc6fddd0e add_mesh_representation - expand documentation and typing #6541 2025-04-14 16:17:59 +05:00
Dion Moult 52c8cc0a72 Fix #6140. You can configure the force snap setting.
I can't figure out a more elegant way of doing this. I.e. I don't know
how to detect whether or not the user has explicitly set their own snap
default.
2025-04-13 17:57:08 +10:00
Dion Moult 463473b959 Fix #6359. No longer sync materials at export sync we have a proper material / style manager now not Blender materials. 2025-04-13 16:09:36 +10:00
Dion Moult fd0c578995 No longer sync edited objects since we don't defer representation updates anymore due to item mode. 2025-04-13 16:06:15 +10:00
Dion Moult 74fbe94ed4 Update feature_request.yml 2025-04-13 12:10:28 +10:00
Dion Moult 38002e1ee2 Update and rename feature_request.md to feature_request.yml 2025-04-13 12:08:28 +10:00
Dion Moult 58b070d22d Update and rename bug_report.md to bug_report.yml 2025-04-13 12:06:46 +10:00
Dion Moult e2b119bf9b Update feature_request.md 2025-04-13 11:42:53 +10:00
Dion Moult 8cf3807c2c Update feature_request.md 2025-04-13 11:42:11 +10:00
Dion Moult ad3d0fecb2 Describe other channels in issue template 2025-04-13 11:38:33 +10:00
Dion Moult fcfe6f97c3 Update bug_report.md 2025-04-13 11:32:30 +10:00
Dion Moult c0190b5065 Fix #6452. Fix #6478. Disable camera clipping due to unpredictable Blender behaviour (reported upstream).
https://projects.blender.org/blender/blender/issues/137384
2025-04-13 11:20:57 +10:00
Andrej730 e596ac6b1b dev_environment.bat - ensure symlinks are loaded #6473 2025-04-11 19:55:27 +05:00
Andrej730 d9ed6a4950 Fix bim test after 1e3f1e7dfc 2025-04-11 19:55:26 +05:00
Andrej730 a49ccfcfc8 test_append_asset - fix skipped test
It wasn't appending library_material so test would never fail
2025-04-11 19:55:26 +05:00
Andrej730 7974d8a0d0 project.append_asset - test assume_asset_uniqueness_by_name=False 2025-04-11 19:55:26 +05:00
Andrej730 f00e77ef6c bim.validate_ifc_assets Bonsai operator #6243
There's now operator to check that all profiles, styles, materials in the current project have a unique name. Which is important since it's used when we append assets to ensure there are no duplicated assets.

Operator location - https://i.imgur.com/bvXjZ8Y.png

Example output:
```
IfcMaterial name 'KnownUnknown' is used by multiple assets:
- # 260=IfcMaterial('KnownUnknown',$,$)
- # 1220=IfcMaterial('KnownUnknown',$,$)

Found issues validating IfcProfileDef assets.
IfcProfileDef name 'DEMO-C' is used by multiple assets:
- # 266=IfcCShapeProfileDef(.AREA.,'DEMO-C',$,0.2,0.1,0.0015,0.03,0.005)
- # 261=IfcIShapeProfileDef(.AREA.,'DEMO-C',$,0.1,0.2,0.005,0.01,0.005,$,$)

Found issues validating IfcPresentationStyle assets.
IfcPresentationStyle name 'Frame' is used by multiple assets:
- # 363=IfcSurfaceStyle('Frame',.BOTH.,(# 362))
- # 1221=IfcSurfaceStyle('Frame',.BOTH.,(# 1222))
```
2025-04-11 19:55:26 +05:00
Andrej730 fc37cca1d0 ExtractElements - expose assume_asset_uniqueness_by_name option #6243
I'd much rather not expose this option to ifcpatch but I guess models are different and there should be a way to extract elements even if assets name uniqueness is not ensured (since it's not IFC requirement anyway). But it may cause duplicated assets.
2025-04-11 19:55:26 +05:00
Andrej730 53da00a5b7 append_asset - fix possible errors with assume_asset_uniqueness_by_name=False after f43e33b
If `assume_asset_uniqueness_by_name` is `False`, then we don't really need `SafeRemovalContext`.
2025-04-11 19:55:25 +05:00
Andrej730 b5bc53bdf6 Renaming style from UI not to add .xxx Blender suffix to style name
Set style name explicitly, this way what user does in UI is what they get in IFC.
Previously confusingly if you set style name to "Glass" and "Glass" already existed, you would get "Glass.001" style internally but "Glass" name in UI.
2025-04-11 19:55:25 +05:00
Andrej730 bc0fbf744a bim.search - set found object as active
As sometimes it's hard of find a selected object in outliner if it's not active.
2025-04-11 19:55:25 +05:00
Andrej730 53b26a9b66 typing 2025-04-11 19:55:25 +05:00
Thomas Krijnen 858e73c9d9 IfcPropertySetDefinitionSet in xml serializer #6330 2025-04-11 15:53:27 +02:00
Thomas Krijnen 952400bd58 Support defined types with instance references in parser (IfcPropertySetDefinitionSet; #6330) 2025-04-11 15:52:40 +02:00
Andrej730 ffffcc576e assign_class - reload representation after assignment
Just to make sure user will set what was actually saved to IFC right away. To prevent confusion when they see it only after model was reloaded completely.
2025-04-10 19:16:04 +05:00
Andrej730 d8102d848d Fix bug with the first object rename after class assignment
The problem was that during class assignment, it was setting the object name and `is_renaming` flag but only then linking to IFC. Since object wasn't linked to IFC, name callback wasn't triggered and `is_renaming` wasn't reset and first rename wouldn't work.
Now it's just not setting the flag if object is not linked to IFC, as there's no callback then and don't need to protect from it.
2025-04-10 17:44:06 +05:00
Andrej730 9407faf1f9 vs-cfg.md - support cmake 4.0.0 2025-04-10 17:44:06 +05:00
Andrej730 1e3f1e7dfc Show assign/reference container buttons if container has no elements #6514 2025-04-10 10:54:46 +05:00
Andrej730 8aad28c730 bim.assign_class - skip ifc objects to prevent errors 2025-04-09 18:26:55 +05:00
Andrej730 81943ec9e5 bim.assign_class - apply modifiers
Noticed working on #6500
2025-04-09 18:26:55 +05:00
Andrej730 5b15de50b6 bim.assign_class - use blender operators in a more safe way
Use temp_override or modify selection not to modify some object by accident, because this operator can also be called not from UI but by providing `object` argument.
2025-04-09 18:26:54 +05:00
Andrej730 4d50dce16d bim.assign_class - use get_selected_objects 2025-04-09 18:26:54 +05:00
Andrej730 69b5d0940f bim.assign_class - flip normals if matrix is negative #6500
object.transform_apply doesn't preserve normals (https://projects.blender.org/blender/blender/issues/102266)
2025-04-09 18:26:54 +05:00
Andrej730 273d6c1039 Experimental geo nodes integration 2025-04-09 18:26:54 +05:00
Andrej730 24ef03c96e bim.assign_container - skip aggregated elements #6514
New description - https://i.imgur.com/UeSPtEe.png
2025-04-09 18:26:54 +05:00
Andrej730 cc4553d3c9 bim.aggregate_assign_object - clarification 2025-04-09 18:26:54 +05:00
Andrej730 cf1f74e260 typing 2025-04-09 18:26:53 +05:00
Andrej730 3052c6e578 black . 2025-04-09 18:26:53 +05:00
Bruno Perdigão 0eb70b7fc3 See #6397 - Fix extrusion profile editing issue.
Profile transformations now correctly use the perpendicular value of the offset.
2025-04-08 20:07:46 -03:00
Bruno Perdigão eb863bb2aa Snap - Filter out 2D bounding boxes that are too far from the camera view. 2025-04-08 18:11:01 -03:00
Andrej730 345a6e72d9 Make referencing in structure more discoverable #6514
Didn't wanted to disrupt existing operators, but we probably should deprecate them.
2025-04-08 15:54:41 +05:00
Andrej730 8b775ea30f bim.assign_container - small clarification 2025-04-08 15:54:41 +05:00
Thomas Krijnen b712f30ad3 Don't assign material as style when it doesn't have any styles #6314 2025-04-07 21:25:33 +02:00
Andrej730 0bbe1aad5b Fix UI errors in Blender 4.2 (missing MOUSE_MMB_SCROLL icon)
Noticed #6515
2025-04-07 16:20:22 +05:00
Andrej730 29de264b43 ifc5d - remove Qto_BodyGeometryValidation from ifc4x3 base qtos (6c04669)
As it's not part of base quantities and it would be unexpected for user if some additional qto would start calculating for every IfcProduct.
2025-04-07 16:20:22 +05:00
Andrej730 f4714d2012 ifc5d - avoid overriding results from previous queries after f63d6b8 2025-04-07 16:20:21 +05:00
Andrej730 8bfdabab80 ifc5d IfcOpenShell.calculate - warn on missing formulas 2025-04-07 16:20:21 +05:00
Andrej730 50c1220730 typing 2025-04-07 16:20:21 +05:00
Bruno Perdigão c280550e30 Measure Tool - fix issue where the area highlight was incorrectly displayed in polyline mode. 2025-04-05 11:55:05 -03:00
Bruno Perdigão 2bf303686c Snap - Improve how viewport zoom affects snapping. 2025-04-05 11:45:56 -03:00
Bruno Perdigão bb91416c4e Snap - Fix creation of 2D bounding boxes
Previously, objects with 2D bounding boxes outside the view were still being added. This issue is now resolved.
Additionally, implement simple AABB detection to check if the bounding box is within the viewport.
2025-04-04 20:19:25 -03:00
Bruno Perdigão 47e8b978af Snap - filter visible objects before creating the 2d bouding box 2025-04-04 18:38:07 -03:00
Andrej730 50eae4ae19 Fix typo, expose Extend To Underside description 2025-04-04 19:04:03 +05:00
Andrej730 979183b64b Make adding openings more discoverable
1) It's back as Shift-O hotkey in BIM tool - https://i.imgur.com/XfmfqNY.png
2) It's always visible in Voids UI - https://i.imgur.com/rO6nOSw.png
2025-04-04 19:04:03 +05:00
Andrej730 dc2f788326 model/wall.py - poll messages 2025-04-04 19:04:03 +05:00
Andrej730 7d12880eb9 BIM Tool - add descriptions to "Align" section buttons 2025-04-04 19:03:08 +05:00
Andrej730 6676c7f62e typing 2025-04-04 19:03:08 +05:00
Thomas Krijnen c0e8c3d1b5 Update ci-ifcopenshell-docker.yml - action versions 2025-04-04 14:11:41 +02:00
Andrej730 e9245efd37 bim.browse_external_style - cache enum items #6496 #5673 2025-04-03 19:37:51 +05:00
Andrej730 de287395d2 Bump Blender build in tests to 4.4 2025-04-03 19:18:42 +05:00
Andrej730 938d72ebe3 Fix UI issue with props missing (65c99c5) 2025-04-03 18:06:49 +05:00
Andrej730 605c86afc5 Fix void bim tests after bim.add_potential_opening was removed 2025-04-03 18:06:48 +05:00
Andrej730 68cf8ee247 BIM tests - replace some class assignment with PanelSpy version 2025-04-03 18:06:48 +05:00
Andrej730 a452d1d361 Fix failing bim tests after 5b33c60 2025-04-03 18:06:48 +05:00
Andrej730 71067e4a14 Fix style test in Blender 4.4 2025-04-03 18:06:48 +05:00
Andrej730 7bacdbf8dd typing 2025-04-03 17:45:49 +05:00
Thomas Krijnen 54c1dfdb77 Update ci-ifcopenshell-docker.yml 2025-04-03 09:29:49 +02:00
Thomas Krijnen 834123ad14 Update release.yml - add workflow_dispatch 2025-04-03 09:27:32 +02:00
Andrej730 3cbab975d5 Fix tests after ea84f82 2025-04-02 19:29:43 +05:00
Andrej730 0315542237 geometry.remove_representation_item - add element argument
Use explicit element argument instead of props.representation_obj as representation_obj may not be there (e.g. removing rep items without item mode) or some other object might be in item mode and then booleans won't be unmarked.

Found the issue because of the failing test 🥳
2025-04-02 19:29:43 +05:00
Andrej730 156cca5932 representation_obj - add a note 2025-04-02 19:29:43 +05:00
Andrej730 4672fc341f Fix representation items / booleans UI prioritizing item mode object over active obj
It led to confusion when object A would be in Item Mode and you open properties for object B and try to show rep items for B and UI would be unresponsive.
2025-04-02 19:29:43 +05:00
Andrej730 f69b02cad9 bim.override_object_join - a bit more readable error message
Use name instead of bpy_struct repr, add a line split.
2025-04-02 19:29:42 +05:00
Andrej730 40af3f0495 Fix bug adding brick namespace in ed0b1c5
brick graph has bool evaluation too
2025-04-02 19:29:42 +05:00
Andrej730 0dd42c1fb2 fix core system tests after 5b9ef23 2025-04-02 19:29:42 +05:00
Andrej730 0990dfd372 biim.load_link - error if file does not exist
Noticed investigating #6403
2025-04-02 12:42:02 +05:00
Andrej730 9316e0bff0 Skip loading linked models in the drawing if they're unloaded #6403
As it's probably unintuitive (user can't see the linked model in viewport but we still load it) and user then have no option to keep model in Links but keep it unloaded.

Also producing traceback below if linked file is missing.
```
Error: Python: Traceback (most recent call last):
  File "\bonsai\bim\module\drawing\operator.py", line 299, in execute
    linework_svg = self.generate_linework(context)
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "\bonsai\bim\module\drawing\operator.py", line 828, in generate_linework
    IfcStore.session_files[link.name] = ifcopenshell.open(link.name)
                                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "\ifcopenshell\__init__.py", line 162, in open
    raise FileNotFoundError(f"File does not exist: '{path}'.")
FileNotFoundError: File does not exist: 'D:\Dropbox\GitLab\OD_Library\BIM\OD_Revit_Template - 2020 - IFC4 - Reference View.ifc'.
```
2025-04-02 12:42:02 +05:00
Andrej730 bc709f06f7 Fix Blender 4.4 crashing on Mac (decorators issues) #6461 2025-04-01 19:16:16 +05:00
Andrej730 cc031a23cb black . 2025-04-01 19:16:16 +05:00
Andrej730 614b71e0c6 gitignore compiled ifcopenshell_wrapper files on mac/linux #4686 2025-04-01 19:10:05 +05:00
Andrej730 66329f07cb dev_environment.sh - validate input paths 2025-04-01 19:10:05 +05:00
986 changed files with 32700 additions and 12578 deletions
-30
View File
@@ -1,30 +0,0 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Error Description and Steps to Reproduce**
<!--
Describe what problem occurred and what you expected to happen instead.
1. To reproduce this, open file '...'
2. Click on '....'
3. See error
-->
**Attachments**
<!--
If applicable, add screenshots to help explain your problem. Please also drag-drop any files necessary to show the error (rename the file extension from .ifc to .txt to upload). Private files can be uploaded to https://ifcopenshell.org/upload.html - only viewed by core developers and will be deleted afterwards.
-->
**Debug information**
<!--
If this is in Bonsai, paste the output from the Copy Debug Information option in Bonsai. It can be found under Quality and Coordination -> Quality Control -> Debug. If this is a general software issue, if relevant include details about IfcOpenShell version, operating system, Python version, etc.
-->
+23
View File
@@ -0,0 +1,23 @@
name: Bug Report
description: Crashes, error messages, and broken features
type: bug
body:
- type: textarea
attributes:
label: Bug Description
placeholder: |
Describe what problem occurred and what you expected to happen instead.
1. To reproduce this, open file '...'
2. Click on '....'
3. See error
- type: textarea
attributes:
label: Attachments
description: "Private files can be uploaded to https://ifcopenshell.org/upload.html - only viewed by core developers and will be deleted afterwards. Please submit your report first then upload private files afterwards."
placeholder: "If applicable, add screenshots to help explain your problem. Please also drag-drop any files necessary to show the error (rename the file extension from .ifc to .txt to upload)."
- type: textarea
attributes:
label: Debug and Error Output
description: "If this is in Bonsai, paste the output from the Copy Debug Information option in Bonsai. It can be found under Quality and Coordination -> Quality Control -> Debug. If this is a general software issue, if relevant include details about IfcOpenShell version, operating system, Python version, etc."
render: yes
+8
View File
@@ -0,0 +1,8 @@
blank_issues_enabled: false
contact_links:
- name: Community Forums
url: https://community.osarch.org/
about: Have a question? Want to discuss an idea? Try the OSArch forums instead.
- name: Live Chat
url: https://osarch.org/chat
about: Have a really confusing issue? Want real-time support?
-15
View File
@@ -1,15 +0,0 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
**Feature Description**
<!--
Describe a feature you'd like us to add. If it's not obvious, explain why this feature is awesome. Note that feature requests must be specific and measurable.
-->
@@ -0,0 +1,8 @@
name: Feature Request
description: Suggest a new feature or improvement
type: Feature
body:
- type: textarea
attributes:
label: Feature Description
placeholder: "Describe a feature you'd like us to add, or a change to the user interface, design, or workflow for usability. If it's not obvious, explain why this feature is awesome. Note that feature requests must be specific and measurable."
+5 -15
View File
@@ -44,7 +44,11 @@ jobs:
brew update
brew install git bison autoconf automake libffi cmake findutils
echo "$(brew --prefix findutils)/libexec/gnubin" >> $GITHUB_PATH
- name: Install aws cli
run: |
python -m pip install awscli
- name: Unpack Dependencies
run: |
install_root=$(find ./build -maxdepth 4 -name install)
@@ -117,20 +121,6 @@ jobs:
aws-secret-access-key: ${{ secrets.AWS_UPLOAD_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- name: Overwrite aws cli
run: |
# Error: The `brew link` step did not complete successfully
# The formula built, but is not symlinked into /usr/local
# Could not symlink bin/idle3
# Target /usr/local/bin/idle3
# already exists. You may want to remove it:
# rm '/usr/local/bin/idle3'
#
# To force the link and overwrite all conflicting files:
# brew link --overwrite python@3.13
# https://github.com/rust-lang/rustup/pull/3989/files
brew install --overwrite awscli | true
- name: Upload .zip archives to S3
run: |
aws s3 cp ~/output s3://ifcopenshell-builds/ --recursive
+5 -5
View File
@@ -19,7 +19,7 @@ jobs:
with:
submodules: recursive
repository: pyodide/pyodide
ref: '0.26.4'
ref: '0.28.0a3'
token: ${{ secrets.BUILD_REPO_TOKEN }}
path: pyodide
@@ -29,18 +29,18 @@ jobs:
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 && pip install ./pyodide-build' >> 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/20.18.0_64bit/bin:/src/pyodide/emsdk/emsdk/upstream/emscripten:$PATH \' >> 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}-cp312-cp312-emscripten_3_1_58_wasm32.whl
mv dist/ifcopenshell-$VERSION-py3-none-any.whl dist/ifcopenshell-$VERSION+${GITHUB_SHA:0:7}-cp313-cp313-emscripten_4_0_9_wasm32.whl
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
+1 -1
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
findutils xz
git config --global --add safe.directory '*'
- name: Install aws cli
+1 -1
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
findutils xz
git config --global --add safe.directory '*'
- name: Install aws cli
+19 -11
View File
@@ -5,7 +5,7 @@ on:
jobs:
build_ifcopenshell:
runs-on: windows-2019
runs-on: windows-2022
strategy:
fail-fast: false
matrix:
@@ -21,7 +21,7 @@ jobs:
uses: actions/checkout@v3
with:
repository: IfcOpenShell/build-outputs
path: _deps-vs2019-x64-installed
path: _deps-vs2022-x64-installed
ref: windows-${{ matrix.arch }}
lfs: true
token: ${{ secrets.BUILD_REPO_TOKEN }}
@@ -40,7 +40,7 @@ jobs:
- name: Unpack Dependencies
run: |
cd _deps-vs2019-x64-installed
cd _deps-vs2022-x64-installed
Get-ChildItem -Path . -Filter 'cache-*.zip' | ForEach-Object {
7z x $_.FullName
}
@@ -53,17 +53,17 @@ jobs:
for /f "tokens=1,2,3 delims=." %%a in ("%PYTHON_VERSION%") do (
set PY_VER_MAJOR_MINOR=%%a%%b
)
call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvars64.bat"
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 vs2019-x64 Release
echo y | call build-deps.cmd vs2022-x64 Release
SET PYTHONHOME=C:\Python\${{ matrix.python }}
call run-cmake.bat vs2019-x64 -DENABLE_BUILD_OPTIMIZATIONS=On -DGLTF_SUPPORT=ON -DADD_COMMIT_SHA=ON -DVERSION_OVERRIDE=ON
call install-ifcopenshell.bat vs2019-x64 Release
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
- name: Pack Dependencies
run: |
cd _deps-vs2019-x64-installed
cd _deps-vs2022-x64-installed
Get-ChildItem -Path . -Directory | ForEach-Object {
$cacheFile = "cache-$($_.Name).zip"
echo $cacheFile
@@ -74,7 +74,7 @@ jobs:
- name: Commit and Push Changes to Build Repository
run: |
cd _deps-vs2019-x64-installed
cd _deps-vs2022-x64-installed
git config user.name "IfcOpenBot"
git config user.email "ifcopenbot@ifcopenshell.org"
git add *.zip
@@ -90,7 +90,7 @@ jobs:
if ("${{ matrix.python }}" -eq "3.9.11") {
# only for the first python version the executables are assembled for upload
cd _installed-vs2019-x64/bin
cd _installed-vs2022-x64/bin
Get-ChildItem -Path . | ForEach-Object {
echo $_
$exe = $_.Name
@@ -118,5 +118,13 @@ jobs:
aws-region: us-east-1
- name: Upload .zip Archives to S3
env:
AWS_DEBUG: 1
AWS_RETRY_MODE: standard
AWS_MAX_ATTEMPTS: 3
run: |
aws s3 cp $env:USERPROFILE\output s3://ifcopenshell-builds/ --recursive
dir "$env:USERPROFILE\output"
foreach ($zip in Get-ChildItem -Path "$env:USERPROFILE\output" -Filter *.zip) {
aws s3 cp "$($zip.FullName)" s3://ifcopenshell-builds/ --debug
Start-Sleep -Seconds 5
}
+40 -49
View File
@@ -4,9 +4,6 @@ on:
push:
pull_request:
env:
PYTHON_VERSION: "3.12"
jobs:
lint-formatting:
runs-on: ubuntu-latest
@@ -17,57 +14,51 @@ jobs:
- name: Action - install python
uses: actions/setup-python@v5.3.0
with:
python-version: "${{ env.PYTHON_VERSION }}"
python-version: "3.9"
- name: Step 1 - install dependencies
shell: bash
- name: Action - install python
uses: actions/setup-python@v5.3.0
with:
python-version: "3.11"
- name: Install dependencies
run: |
python3 -m pip install --upgrade pip
python3 -m pip install 'black>=24.10.0'
curl -LsSf https://astral.sh/uv/install.sh | sh
uv tool install ruff
uv tool install black
# NOTE: This would suffice, however it is less informative in terms of the 3 possible outcomes
# - name: QA Step - check linting
# shell: bash
# id: linting
# run: |
# python3 -m black .
# QA STEP
- name: QA Step - check linting
shell: bash
id: linting
# black doesn't catch all syntax errors, so we check them explicitly.
- name: Check syntax errors
id: syntax-errors
run: |
python3 -m black --check . \
&& exit 0 \
|| (echo "exit_code=$?" >> "$GITHUB_OUTPUT" && exit 1);
ERROR=0
python3.9 -W error -m compileall -q src/ifcopenshell-python || ERROR=1
python3.11 -W error -m compileall -q src/bonsai || ERROR=1
exit $ERROR
continue-on-error: true
# OUTCOME 1 of QA STEP
- name: QA Step - no linting errors
if: steps.linting.outcome == 'success'
shell: bash
run: |-
echo "::notice::QA step linting succeeded"
exit 0;
- name: Black formatter
id: black
run: |
uvx black --diff --check .
continue-on-error: true
# OUTCOME 2i of QA STEP
- name: QA Step - unprettified code with no syntax errors
if: steps.linting.outputs.exit_code == 1
shell: bash
run: |-
echo "::group::QA step succeeded with warnings"
echo "::warning::one or more files contains unformatted code but no syntax errors";
echo "::notice::please run the linter before pushing!";
echo "::endgroup::"
exit 0;
- name: Ruff check
id: ruff
run: |
uvx ruff check
continue-on-error: true
# OUTCOME 2ii of QA STEP
- name: QA Step - code contains syntax errors
if: steps.linting.outputs.exit_code == 123
shell: bash
run: |-
echo "::group::QA step failed"
echo "::error::one or more files contains syntax errors";
echo "::notice::please run the linter and fix syntax errors before pushing!";
echo "::endgroup::"
exit 1;
- name: Final check
run: |
ERROR=0
if [ "${{ steps.syntax-errors.outcome }}" != "success" ]; then
echo "::error::Syntax errors check failed, see 'syntax-errors' step for the details." && ERROR=1
fi
if [ "${{ steps.black.outcome }}" != "success" ]; then
echo "::error::Black formatting check failed, see 'black' step for the details." && ERROR=1
fi
if [ "${{ steps.ruff.outcome }}" != "success" ]; then
echo "::error::Ruff check failed, see 'ruff' step for the details." && ERROR=1
fi
exit $ERROR
+3 -2
View File
@@ -96,7 +96,7 @@ jobs:
uses: actions/checkout@v2
with:
repository: IfcOpenShell/bonsai_unstable_repo
token: ${{ secrets.IOS_TO_BLENDER_REPO }}
token: ${{ secrets.IFCOPENBOT_TOKEN }}
path: bonsai_unstable_repo
- name: Download Blender and run critical tests
@@ -104,7 +104,7 @@ jobs:
# Ensure Bonsai and ifcsverchok enable/disable works before uploading to extensions repo.
# Download Blender.
wget -q -O blender.tar.xz https://download.blender.org/release/Blender4.3/blender-4.3.2-linux-x64.tar.xz
wget -q -O blender.tar.xz https://download.blender.org/release/Blender4.5/blender-4.5.0-linux-x64.tar.xz
tar -xf blender.tar.xz
# Setup Blender.
@@ -175,6 +175,7 @@ jobs:
cd IfcOpenShell/src/bonsai
pip install pytest-blender
pip install pytest-bdd
blender --background --python scripts/setup_pytest.py
blender --python-expr "import bonsai; print(bonsai.bbim_semver); import ifcopenshell; print(ifcopenshell.version)" --background
make test
@@ -35,11 +35,11 @@ jobs:
- name: Version + date str
id: verdate
run: echo "verdate='${{ steps.version.outputs.version }}alpha${{ steps.date.outputs.date }}'" >> $GITHUB_OUTPUT
run: echo "verdate=${{ steps.version.outputs.version }}alpha${{ steps.date.outputs.date }}" >> $GITHUB_OUTPUT
test:
name: ${{ matrix.platform.distver }}-${{ matrix.pyver.name }}-${{ matrix.variant }}
name: ${{ matrix.platform.distver }}-${{ matrix.pyver.name }}
needs: activate
runs-on: ${{ matrix.platform.distver }}
defaults:
@@ -48,57 +48,67 @@ jobs:
strategy:
fail-fast: false
matrix:
variant: [novtk, all]
pyver: [
# { name: py311, distver: '3.11'},
{ name: py312, distver: '3.12'}
]
platform: [
{ name: win, distver: windows-latest, pkg_dir: 'win-64' },
{ name: linux, distver: ubuntu-latest, pkg_dir: 'linux-64' },
{ name: macOS, distver: macos-latest, pkg_dir: 'osx-64' }
{ name: macOS-arm, distver: macos-latest, pkg_dir: 'osx-arm64' },
{ name: macOS-x86, distver: macos-13, pkg_dir: 'osx-64' }
]
steps:
- name: Set up swap space
- name: Set Swap Space
if: runner.os == 'Linux'
uses: pierotofy/set-swap-space@v1.0
uses: pierotofy/set-swap-space@master
with:
swap-size-gb: 10
- name: set ARTIFACTS ENV vars
shell: bash
run: |
pwd
if [[ "$RUNNER_OS" == "Windows" ]]; then
echo "ARTIFACTS_DIR=D:/a/artifacts" >> $GITHUB_ENV
elif [[ "$RUNNER_OS" == "macOS" ]]; then
echo "ARTIFACTS_DIR=/Users/runner/work/artifacts" >> $GITHUB_ENV
elif [[ "$RUNNER_OS" == "Linux" ]]; then
echo "ARTIFACTS_DIR=/home/runner/work/artifacts" >> $GITHUB_ENV
fi
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Download and extract MacOSX SDK
if: ${{ runner.os == 'macOS' }}
if: ${{ matrix.platform.name == 'macOS-x86' }}
run: |
curl -L https://github.com/phracker/MacOSX-SDKs/releases/download/11.3/MacOSX10.13.sdk.tar.xz | tar -xvJf - -C /Users/runner/work/
- uses: mamba-org/setup-micromamba@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: >-
python=3.11
python=3.12
anaconda-client
boa
rattler-build
- name: create conda package dist dir
run: |
mkdir -p ${{ github.workspace }}/dist
mkdir -p ${{ env.ARTIFACTS_DIR }}
- name: build & test ifcopenshell
run: |
conda mambabuild . --python ${{ matrix.pyver.distver }} --no-remove-work-dir --output-folder '${{ github.workspace }}/dist' --variants='{variant: ${{ matrix.variant }}}'
working-directory: ./conda
rattler-build build -r conda/recipe.yaml --output-dir '${{ env.ARTIFACTS_DIR }}'
env:
VERSION_OVERRIDE: ${{ needs.activate.outputs.verdate }}
- name: upload to anaconda
if: ${{ matrix.platform.name == 'win' }}
run: |
anaconda -t ${{ secrets.ANACONDA_TOKEN }} upload --force --user ifcopenshell '${{ github.workspace }}/dist/${{ matrix.platform.pkg_dir }}/*.tar.bz2'
anaconda -t ${{ secrets.ANACONDA_TOKEN }} upload --force --user ifcopenshell '${{ env.ARTIFACTS_DIR }}/${{ matrix.platform.pkg_dir }}/*.conda'
- name: upload to anaconda
if: ${{ matrix.platform.name != 'win' }}
run: |
anaconda -t ${{ secrets.ANACONDA_TOKEN }} upload --force --user ifcopenshell ${{ github.workspace }}/dist/${{ matrix.platform.pkg_dir }}/*.tar.bz2
anaconda -t ${{ secrets.ANACONDA_TOKEN }} upload --force --user ifcopenshell ${{ env.ARTIFACTS_DIR }}/${{ matrix.platform.pkg_dir }}/*.conda
+7 -6
View File
@@ -9,7 +9,7 @@ on:
jobs:
activate:
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
if: |
github.repository == 'IfcOpenShell/IfcOpenShell' &&
!startsWith(github.event.head_commit.message, 'Release ') &&
@@ -18,7 +18,7 @@ jobs:
- run: echo ok go
build:
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
needs: activate
steps:
- uses: actions/checkout@v2
@@ -35,7 +35,7 @@ jobs:
-
name: ccache
uses: hendrikmuhs/ccache-action@v1
uses: hendrikmuhs/ccache-action@v1.2
-
name: Build ifcopenshell
@@ -44,6 +44,7 @@ jobs:
cmake \
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
-DCMAKE_CXX_STANDARD=17 \
-DCMAKE_INSTALL_PREFIX=$PWD/install/ \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_PREFIX_PATH=/usr \
@@ -75,7 +76,7 @@ jobs:
make package
working-directory: build
- name: Upload
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v4
with:
# Artifact name
name: ifcos-artifacts
@@ -83,7 +84,7 @@ jobs:
path: build/assets/Ifc*
deliver:
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
needs: build
name: Docker Build, Tag, Push
@@ -118,6 +119,6 @@ jobs:
repository: aecgeeks/ifcopenshell
# Since the dispatch is set to `tag`, `github.ref_name` should evaluate to the pushed tag
# On a workflow dispatch, `ref_name` will take on the value from the dispatch payload
tags: aecgeeks/ifcopenshell:${{ github.ref_name }}${{ github.ref_name == github.event.repository.default_branch && ',aecgeeks/ifcopenshell:latest' }}
tags: aecgeeks/ifcopenshell:${{ github.ref_name }}${{ github.ref_name == github.event.repository.default_branch && ',aecgeeks/ifcopenshell:22.04' }}
file: ./Dockerfile
push: true
@@ -44,6 +44,8 @@ jobs:
}
steps:
- uses: actions/checkout@v2
with:
submodules: recursive
- uses: actions/setup-python@v2 # 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
@@ -39,6 +39,8 @@ jobs:
}
steps:
- uses: actions/checkout@v2
with:
submodules: recursive
- uses: actions/setup-python@v2 # 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
+16 -6
View File
@@ -82,6 +82,7 @@ jobs:
cmake \
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
-DCMAKE_CXX_STANDARD=17 \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_PREFIX_PATH=/usr \
-DCMAKE_SYSTEM_PREFIX_PATH=/usr \
@@ -127,12 +128,21 @@ jobs:
cd ../src/ifcopenshell-python
mv ifcopenshell ifcopenshell-local # Force testing on installed module
pip install -e ../ifcpatch --no-deps # Needed for sql.py tests.
make test
cd ../bcf && make test
ERROR=0
make test || ERROR=1
cd ../bcf && make test || ERROR=1
pip install requests
cd ../bsdd && make test
cd ../bsdd && make test || ERROR=1
pip install deepdiff
cd ../ifcdiff && make test
cd ../ifcpatch && make test
cd ../ifcdiff && make test || ERROR=1
cd ../ifcpatch && make test || ERROR=1
pip install -e ../ifctester --no-deps
cd ../ifctester && make test
cd ../ifctester && make test || ERROR=1
# Run mathutils related tests at the end to ensure no other code is relying on mathutils.
cd ../ifcopenshell-python
pip install mathutils
make test-mathutils || ERROR=1
if [ $ERROR -ne 0 ]; then
echo "One or more tests failed";
exit 1;
fi
+2
View File
@@ -4,6 +4,7 @@ on:
push:
tags:
- 'v[0-9].[0-9].[0-9]*'
workflow_dispatch:
jobs:
activate:
@@ -45,6 +46,7 @@ jobs:
mkdir build && cd build
cmake \
-DCMAKE_INSTALL_PREFIX=$PWD/install/ \
-DCMAKE_CXX_STANDARD=17 \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_PREFIX_PATH=/usr \
-DCMAKE_SYSTEM_PREFIX_PATH=/usr \
+3 -2
View File
@@ -86,12 +86,13 @@ src/bonsai/bonsai/bim/data/build/
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
# ifcopenshell swig and compiled files
src/ifcopenshell-python/ifcopenshell/_ifcopenshell_wrapper.so
src/ifcopenshell-python/ifcopenshell/_ifcopenshell_wrapper*.so
src/ifcopenshell-python/ifcopenshell/ifcopenshell_wrapper.py
# apple
@@ -106,4 +107,4 @@ src/ifcopenshell-python/ifcopenshell/ifcopenshell_wrapper.py
src/bonsai/bonsai/bim/schema/Brick.ttl
bonsaiDecoratorForLoads.code-workspace
dev_environment.bat
+1 -1
View File
@@ -1 +1 @@
0.8.2
0.8.4
+8 -1
View File
@@ -18,7 +18,12 @@
################################################################################
cmake_minimum_required(VERSION 3.21)
set(CMAKE_CXX_STANDARD 17)
if (NOT DEFINED CMAKE_CXX_STANDARD)
set(CMAKE_CXX_STANDARD 17)
endif()
if (CMAKE_CXX_STANDARD LESS 17)
message(FATAL_ERROR "C++17 or newer is required.")
endif()
set(CMAKE_CXX_STANDARD_REQUIRED ON) # not necessary, but encouraged
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
@@ -1289,6 +1294,8 @@ endif()
list(APPEND CPACK_SOURCE_IGNORE_FILES
"/\\\\.git"
"/build/"
"/.pytest_cache/"
"/__pycache__/"
)
set(CPACK_SOURCE_INSTALLED_DIRECTORIES "${CMAKE_SOURCE_DIR}/..;/")
set(CPACK_PACKAGE_NAME "${PROJECT_NAME}-${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}${EXTRA_VERSION}")
+5
View File
@@ -9,6 +9,7 @@ set LIBXML2="%LIBRARY_PREFIX%/lib/libxml2.lib"
cmake -G "Ninja" ^
-D SCHEMA_VERSIONS="2x3;4;4x1;4x3_add2" ^
-D CMAKE_BUILD_TYPE:STRING=Release ^
-D CMAKE_CXX_STANDARD=17 ^
-D CMAKE_INSTALL_PREFIX:FILEPATH="%LIBRARY_PREFIX%" ^
-D CMAKE_PREFIX_PATH:FILEPATH="%LIBRARY_PREFIX%" ^
-D CMAKE_SYSTEM_PREFIX_PATH:FILEPATH="%LIBRARY_PREFIX%" ^
@@ -49,3 +50,7 @@ if errorlevel 1 exit 1
ninja install -j 1
if errorlevel 1 exit 1
python %RECIPE_DIR%/update_version_init.py %PKG_VERSION% %SP_DIR%/ifcopenshell/__init__.py
if errorlevel 1 exit 1
+4 -1
View File
@@ -16,6 +16,7 @@ cmake ${CMAKE_ARGS} -G Ninja \
-DSCHEMA_VERSIONS="2x3;4;4x1;4x3_add2" \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX=$PREFIX \
-DCMAKE_CXX_STANDARD=17 \
${CMAKE_PLATFORM_FLAGS[@]} \
-DCMAKE_PREFIX_PATH=$PREFIX \
-DCMAKE_SYSTEM_PREFIX_PATH=$PREFIX \
@@ -46,4 +47,6 @@ cmake ${CMAKE_ARGS} -G Ninja \
ninja
ninja install -j 1
ninja install -j 1
python "${RECIPE_DIR}/update_version_init.py" "${PKG_VERSION}" "${SP_DIR}/ifcopenshell/__init__.py"
+16 -16
View File
@@ -1,6 +1,5 @@
variant:
- novtk
- all
python:
- 3.12
occt:
- 7.8.1
@@ -24,14 +23,15 @@ cxx_compiler_version:
- '12' # [linux]
- '16' # [osx]
c_stdlib_version:
- '2.12' # [linux]
- '10.13' # [osx]
- 2.17 # [linux]
- 10.13 # [osx and x86_64]
- 11.0 # [osx and arm64]
hdf5:
- 1.14.3
- 1.14.6
libboost_devel:
- '1.84'
- '1.86'
libxml2:
- '2'
- 2.13
mpfr:
- '4'
gmp:
@@ -47,12 +47,12 @@ target_platform:
- win-64 # [win]
- linux-64 # [linux]
- osx-64 # [osx]
macos_machine:
- x86_64-apple-darwin13.4.0
MACOSX_DEPLOYMENT_TARGET:
- '10.13'
MACOSX_SDK_VERSION:
- '10.13'
macos_machine: # [osx]
- x86_64-apple-darwin13.4.0 # [osx and x86_64]
- arm64-apple-darwin20.0.0 # [osx and arm64]
MACOSX_DEPLOYMENT_TARGET: # [osx]
- 11.0 # [osx and arm64]
- 10.13 # [osx and x86_64]
CONDA_BUILD_SYSROOT:
- "/Users/runner/work/MacOSX10.13.sdk" # [osx]
CONDA_BUILD_SYSROOT: # [osx]
- "/Users/runner/work/MacOSX10.13.sdk" # [osx and x86_64]
+48 -33
View File
@@ -1,64 +1,78 @@
{% set version = environ.get('VERSION_OVERRIDE', '0.8.0') %}
{% set build = 0 %}
# Higher number -> Always prioritize "novtk" variant over "all" variant
{% set build = build + 200 %} # [variant == "novtk"]
{% set build = build + 100 %} # [variant == "all"]
context:
version: ${{ env.get("VERSION_OVERRIDE", default="0.8.2.1") }}
build: 1
package:
name: ifcopenshell
version: {{ version }}
version: ${{ version }}
source:
path: ../
- path: ..
build:
number: {{ build }}
skip: true # [py<39]
binary_relocation: false # [osx]
string: py{{ CONDA_PY }}_{{ variant }}_h{{ PKG_HASH }}_{{ build }}
run_exports:
- {{ pin_subpackage('ifcopenshell', max_pin='x.x.x') }} *{{ variant }}*
number: ${{ build }}
dynamic_linking:
binary_relocation: ${{ true if osx }}
requirements:
build:
- cmake
- if: build_platform != target_platform
then:
- python
- cross-python_${{ target_platform }}
- cmake <4
- ninja
- swig >=4.1.1
- {{ stdlib("c") }}
- {{ compiler('c') }}
- {{ compiler('cxx') }}
- ${{ stdlib("c") }}
- ${{ compiler('c') }}
- ${{ compiler('cxx') }}
host:
- python
- libboost-devel
- occt *=*{{ variant }}*
- occt
- libxml2
- cgal-cpp
- hdf5
- eigen
- mpfr
- nlohmann_json
- gmp # [unix]
- mpir # [win]
- gmp
- zlib
run:
- python
- {{ pin_compatible('occt', max_pin='x.x.x') }} *{{ variant }}*
- {{ pin_compatible('cgal-cpp', max_pin='x.x') }}
test:
imports:
- ifcopenshell
- shapely
- typing_extensions
- ${{ pin_compatible('occt', upper_bound='x.x.x') }}
- ${{ pin_compatible('cgal-cpp', upper_bound='x.x') }}
run_exports:
- ${{ pin_subpackage('ifcopenshell', upper_bound='x.x.x') }}
tests:
- python:
imports:
- ifcopenshell
pip_check: false
- script:
- python -c "import ifcopenshell; assert ifcopenshell.version == '${{ version }}', 'print(ifcopenshell.version)'"
requirements:
run:
- occt * *novtk* # Ensure that even though compiled against OCCT with VTK, it can still run with OCCT without VTK
- pytest
- python-dateutil
- xmlschema
- xsdata
- lxml
- isodate
- lark
- networkx
- tabulate
- shapely
about:
home: https://ifcopenshell.org
license: LGPL-3.0-or-later
license_file: COPYING
summary: 'IfcOpenShell is a library to support the IFC file format'
summary: IfcOpenShell is a library to support the IFC file format
description: |
IfcOpenShell
============
@@ -293,8 +307,9 @@ about:
[MSYS2]: https://msys2.github.io/ "MSYS2"
[win/readme.md]: https://github.com/IfcOpenShell/IfcOpenShell/tree/master/win/readme.md "win/readme.md"
[nix/build-all.py]: https://github.com/IfcOpenShell/IfcOpenShell/tree/master/nix/build-all.py "nix/build-all.py"
doc_url: https://ifcopenshell.org/
dev_url: https://github.com/IfcOpenShell/IfcOpenShell
homepage: https://ifcopenshell.org
repository: https://github.com/IfcOpenShell/IfcOpenShell
documentation: https://ifcopenshell.org/
extra:
recipe-maintainers:
+32
View File
@@ -0,0 +1,32 @@
import re
import argparse
from pathlib import Path
def update_version(file_path: str, version: str) -> None:
"""Update the version string in the given __init__.py file."""
file_path = Path(file_path)
# Read the file and replace the version
file_contents = file_path.read_text(encoding="utf-8")
new_contents = re.sub(r'version = "0\.0\.0"', f'version = "{version}"', file_contents)
# Write the updated contents back to the file
file_path.write_text(new_contents, encoding="utf-8")
print(f"Updated version in {file_path} to {version}")
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Update the version string in a Python file.")
parser.add_argument(
"version",
type=str,
help="The version string to replace '0.0.0' with (e.g., '1.2.3')."
)
parser.add_argument(
"file",
type=str,
help="The path to the __init__.py file where the version will be updated."
)
args = parser.parse_args()
update_version(args.file, args.version)
+40 -13
View File
@@ -69,6 +69,7 @@ import sysconfig
import ssl
ssl._create_default_https_context = ssl._create_unverified_context
import time
from urllib.request import urlretrieve
@@ -88,14 +89,14 @@ OCE_VERSION = "0.18.3"
OCCT_VERSION = "7.8.1"
BOOST_VERSION = "1.86.0"
PCRE_VERSION = "8.41"
LIBXML2_VERSION = "2.9.11"
LIBXML2_VERSION = "2.13.8"
SWIG_VERSION = "4.0.2"
OPENCOLLADA_VERSION = "v1.6.68"
HDF5_VERSION = "1.12.1"
GMP_VERSION = "6.2.1"
MPFR_VERSION = "3.1.6" # latest is 4.1.0
CGAL_VERSION = "5.3"
CGAL_VERSION = "5.6.x-branch"
USD_VERSION = "23.05"
TBB_VERSION = "2021.9.0"
@@ -323,7 +324,7 @@ def run_autoconf(arg1, configure_args, cwd):
if "wasm" in flags:
wasm.append("emconfigure")
run([*wasm, "/bin/sh", "../configure"] + configure_args + [f"--prefix={prefix}"], cwd=cwd)
run([*wasm, "/bin/sh", "../configure", *(["--host=wasm32"] if "wasm" in flags and not any(s.startswith('--host') for s in configure_args) else []), *configure_args, f"--prefix={prefix}"], cwd=cwd)
def run_cmake(arg1, cmake_args, cmake_dir=None, cwd=None):
@@ -336,7 +337,7 @@ def run_cmake(arg1, cmake_args, cmake_dir=None, cwd=None):
if "wasm" in flags:
wasm.append("emcmake")
run([*wasm, "cmake", P, *cmake_args, f"-DCMAKE_BUILD_TYPE={BUILD_CFG}", f"-DBUILD_SHARED_LIBS={OFF_ON[not BUILD_STATIC]}"], cwd=cwd)
run([*wasm, "cmake", P, *cmake_args, f"-DCMAKE_BUILD_TYPE={BUILD_CFG}", f"-DBUILD_SHARED_LIBS={OFF_ON[not BUILD_STATIC]}", f"-DCMAKE_CXX_FLAGS='{os.environ['CXXFLAGS']}'", f"-DCMAKE_C_FLAGS='{os.environ['CFLAGS']}'", f"-DCMAKE_SHARED_LINKER_FLAGS={os.environ['LDFLAGS']}"], cwd=cwd)
def git_clone_or_pull_repository(clone_url, target_dir, revision=None):
@@ -362,7 +363,7 @@ def git_clone_or_pull_repository(clone_url, target_dir, revision=None):
run([git, "checkout", revision], cwd=target_dir)
def build_dependency(name, mode, build_tool_args, download_url, download_name, download_tool=download_tool_default, revision=None, patch=None, additional_files={}, no_append_name=False, **kwargs):
def build_dependency(name, mode, build_tool_args, download_url, download_name, download_tool=download_tool_default, revision=None, patch=None, shell=None, pre_compile_subs=[], additional_files={}, no_append_name=False, **kwargs):
"""Handles building of dependencies with different tools (which are
distinguished with the `mode` argument. `build_tool_args` is expected to be
a list which is necessary in order to not mess up quoting of compiler and
@@ -385,7 +386,14 @@ def build_dependency(name, mode, build_tool_args, download_url, download_name, d
download_path = os.path.join(build_dir, download_name)
if not os.path.exists(download_path):
urlretrieve(url, os.path.join(build_dir, download_path))
for _ in range(3):
try:
urlretrieve(url, os.path.join(build_dir, download_path))
break
except ConnectionError as e:
print(e, "... retrying...")
time.sleep(30.)
continue
else:
logger.info(f"Download '{download_path}' already exists, assuming it's an undamaged download and that it has been extracted if possible, skipping")
elif download_tool == download_tool_git:
@@ -404,6 +412,8 @@ def build_dependency(name, mode, build_tool_args, download_url, download_name, d
compr = "gz"
elif download_name.endswith(".tar.bz2"):
compr = "bz2"
elif download_name.endswith(".tar.xz"):
compr = "xz"
else:
raise RuntimeError("fix source for new download type")
download_tarfile = tarfile.open(name=download_tarfile_path, mode=f"r:{compr}")
@@ -430,6 +440,9 @@ def build_dependency(name, mode, build_tool_args, download_url, download_name, d
except Exception as e:
# Assert that the patch has already been applied
run(["patch", "-p1", "--batch", "--reverse", "--dry-run", "-i", patch_abs], cwd=extract_dir)
if shell is not None:
sp.run(shell, shell=True, check=True, cwd=extract_dir)
if mode == "ctest":
run(["ctest", "-S", "HDF5config.cmake,BUILD_GENERATOR=Unix", "-C", BUILD_CFG, "-V", "-O", "hdf5.log"], cwd=extract_dir)
@@ -451,6 +464,12 @@ def build_dependency(name, mode, build_tool_args, download_url, download_name, d
run_cmake(name, build_tool_args, cwd=extract_build_dir)
else:
raise ValueError()
for fn, before, after in pre_compile_subs:
with open(os.path.join(extract_dir, fn), 'r') as f:
s = f.read()
s = s.replace(before, after)
with open(os.path.join(extract_dir, fn), 'w') as f:
f.write(s)
logger.info(f"\rBuilding {name}... ")
run([make, f"-j{IFCOS_NUM_BUILD_PROCS}", "VERBOSE=1"], cwd=extract_build_dir)
logger.info(f"\rInstalling {name}... ")
@@ -478,9 +497,6 @@ ADDITIONAL_ARGS = []
if platform.system() == "Darwin":
ADDITIONAL_ARGS = [f"-mmacosx-version-min={TOOLSET}"] + ADDITIONAL_ARGS
if "wasm" in flags:
ADDITIONAL_ARGS.extend(("-sWASM_BIGINT", "-fwasm-exceptions"))
# If the linker supports GC sections, set it up to reduce binary file size
# -fPIC is required for the shared libraries to work
@@ -491,7 +507,11 @@ CFLAGS = os.environ.get("CFLAGS", "")
LDFLAGS = os.environ.get("LDFLAGS", "")
ADDITIONAL_ARGS_STR = " ".join(ADDITIONAL_ARGS)
if "wasm" not in flags and sp.call([bash, "-c", "ld --gc-sections 2>&1 | grep -- --gc-sections &> /dev/null"]) != 0:
if "wasm" in flags:
CFLAGS_MINIMAL = CXXFLAGS_MINIMAL = CFLAGS = CXXFLAGS = os.environ['SIDE_MODULE_CFLAGS']
LDFLAGS = os.environ['SIDE_MODULE_LDFLAGS']
elif sp.call([bash, "-c", "ld --gc-sections 2>&1 | grep -- --gc-sections &> /dev/null"]) != 0:
CXXFLAGS_MINIMAL = f"{CXXFLAGS} {PIC} {ADDITIONAL_ARGS_STR}"
CFLAGS_MINIMAL = f"{CFLAGS} {PIC} {ADDITIONAL_ARGS_STR}"
if BUILD_STATIC:
@@ -517,6 +537,7 @@ if "lto" in flags:
locals()[f] += f" -flto={IFCOS_NUM_BUILD_PROCS}"
os.environ["CXXFLAGS"] = CXXFLAGS
os.environ["CPPFLAGS"] = CXXFLAGS
os.environ["CFLAGS"] = CFLAGS
os.environ["LDFLAGS"] = LDFLAGS
@@ -653,8 +674,8 @@ if "libxml2" in targets:
"--without-iconv",
"--without-lzma"
],
download_url="ftp://xmlsoft.org/libxml2/",
download_name=f"libxml2-{LIBXML2_VERSION}.tar.gz"
download_url=f"https://download.gnome.org/sources/libxml2/{'.'.join(LIBXML2_VERSION.split('.')[0:2])}/",
download_name=f"libxml2-{LIBXML2_VERSION}.tar.xz"
)
if "OpenCOLLADA" in targets:
@@ -687,9 +708,11 @@ if "OpenCOLLADA" in targets:
if "python" in targets and not USE_CURRENT_PYTHON_VERSION and "wasm" not in flags:
# Python should not be built with -fvisibility=hidden, from experience that introduces segfaults
OLD_CPP_FLAGS = os.environ["CPPFLAGS"]
OLD_CXX_FLAGS = os.environ["CXXFLAGS"]
OLD_C_FLAGS = os.environ["CFLAGS"]
os.environ["CXXFLAGS"] = CXXFLAGS_MINIMAL
os.environ["CPPFLAGS"] = CXXFLAGS_MINIMAL
os.environ["CFLAGS"] = CFLAGS_MINIMAL
# On OSX a dynamic python library is built or it would not be compatible
@@ -719,6 +742,7 @@ if "python" in targets and not USE_CURRENT_PYTHON_VERSION and "wasm" not in flag
):
raise e
os.environ["CPPFLAGS"] = OLD_CPP_FLAGS
os.environ["CXXFLAGS"] = OLD_CXX_FLAGS
os.environ["CFLAGS"] = OLD_C_FLAGS
@@ -764,6 +788,7 @@ if "cgal" in targets:
name=f"gmp-{GMP_VERSION}",
mode="autoconf",
build_tool_args=[ENABLE_FLAG, DISABLE_FLAG, "--with-pic", *gmp_args],
pre_compile_subs=([("build/config.h", "HAVE_OBSTACK_VPRINTF 1", "HAVE_OBSTACK_VPRINTF 0")] if "wasm" in flags else []),
download_url="https://ftp.gnu.org/gnu/gmp/",
download_name=f"gmp-{GMP_VERSION}.tar.bz2"
)
@@ -792,7 +817,7 @@ if "cgal" in targets:
download_url="https://github.com/CGAL/cgal.git",
download_name="cgal",
download_tool=download_tool_git,
revision=f"v{CGAL_VERSION}"
revision=CGAL_VERSION
)
if "usd" in targets:
@@ -842,6 +867,7 @@ os.makedirs(executables_dir, exist_ok=True)
cmake_args = [
"-DCMAKE_CXX_STANDARD=" "17",
"-DUSE_MMAP=" "OFF",
"-DBUILD_EXAMPLES=" "OFF",
"-DBUILD_SHARED_LIBS=" +OFF_ON[not BUILD_STATIC],
@@ -947,6 +973,7 @@ if "IfcOpenShell-Python" in targets:
if "wasm" in flags:
ADDITIONAL_ARGS = f"-Wl,-undefined,suppress -sSIDE_MODULE=2 -sEXPORTED_FUNCTIONS=_PyInit__ifcopenshell_wrapper"
os.environ["CPPFLAGS"] = f"{CXXFLAGS_MINIMAL} {ADDITIONAL_ARGS}"
os.environ["CXXFLAGS"] = f"{CXXFLAGS_MINIMAL} {ADDITIONAL_ARGS}"
os.environ["CFLAGS"] = f"{CFLAGS_MINIMAL} {ADDITIONAL_ARGS}"
os.environ["LDFLAGS"] = f"{LDFLAGS} {ADDITIONAL_ARGS}"
+1 -1
View File
@@ -7,7 +7,7 @@ source:
build:
script: |
BUILD_CFG=Release python nix/build-all.py --without-hdf5 --without-opencollada --without-swig --without-pcre -v --wasm --py312 IfcOpenShell-Python
BUILD_CFG=Release python nix/build-all.py --without-hdf5 --without-opencollada --without-swig --without-pcre -v --wasm --py313 IfcOpenShell-Python
mv package/ifcopenshell .
cp pyodide/setup.py .
+49 -24
View File
@@ -1,37 +1,62 @@
[tool.black]
line-length = 120
include = '''
src/(
bcf
|bcfserver
|bonsai
|bsdd
|foundationserver
|ifc2ca
|ifc4d
|ifc5d
|ifcbimtester
|ifcblender
|ifccityjson
|ifcclash
|ifccsv
|ifcdiff
|ifcfm
|ifcpatch
|ifctester
|ifcopenshell-python
|ifcsverchok
|opencdeserver
)/.*.py$
'''
include = "src/.*.pyi?$"
extend-exclude = '''
src/ifcopenshell-python/ifcopenshell/express/*
|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]
reportInvalidTypeForm = false
disableBytesTypePromotions = true
reportUnnecessaryTypeIgnoreComment = true
# Define here general ruff settings,
# then they will be inherited 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",
"src/svgfill",
#
# Unformatted.
"src/exterior-shell-extractor",
# Incompatible with linter.
"src/ifc2ca/templates",
]
[tool.ruff.lint]
select = [
# Default Ruff rules.
# "E4", # imports
# "E7", # statements
"E9", # io errors
# "F", # pyflakes
#
"FA", # future annotations
"UP", # pyupgrade
"RUF015", # next() > list_comprehension[0]
]
ignore = [
"FA100", # Conflicts with Blender using annotations for props definitions.
# Maybe will enable later:
"UP007", # Optional to X | Y
"UP045", # Optional to X | None
"UP015", # Unnecessary mode argument
"UP028", # yield for -> yield from
"UP030", # implicit references for positional format fields
"UP031", # Replace % with .format
"UP032", # Replace .format with f-string
# Deprecated Ruff rules.
"UP038", # Use X | Y in isinstance
]
+1 -1
View File
@@ -20,7 +20,7 @@ def get_extensions_attributes(extensions: Extensions) -> dict[str, AttributeData
- subattribute name"""
possible_attributes = {}
for field in fields(type(extensions)):
field_type = field.type.__args__[0] # type: ignore [reportAttributeAccessIssue]
field_type = field.type.__args__[0] # pyright: ignore [reportAttributeAccessIssue]
subfield = next(iter(fields(field_type)))
xsd_name = subfield.metadata["name"]
possible_attributes[field.name] = AttributeData(field_type, subfield.name, xsd_name)
+2 -1
View File
@@ -6,6 +6,7 @@ Copyright (c) 2017-2020 Anthon van der Neut, Ruamel bvba
original idea from https://stackoverflow.com/a/19722365/1307905
"""
from __future__ import annotations
import zipfile
from io import BytesIO
from os import PathLike
@@ -47,7 +48,7 @@ class InMemoryZipFile:
def data(self) -> bytes:
return self.in_memory_data.getvalue()
def __enter__(self) -> "InMemoryZipFile":
def __enter__(self) -> InMemoryZipFile:
return self
def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> None:
+6 -5
View File
@@ -1,5 +1,6 @@
"""BCF XML V2 handler."""
from __future__ import annotations
import uuid
import warnings
import zipfile
@@ -31,7 +32,7 @@ class BcfXml:
self._extension_schema: Optional[bytes] = None
self._zip_file = self._load_zip_file()
def __enter__(self) -> "BcfXml":
def __enter__(self) -> BcfXml:
return self
def __exit__(self, *args: Any) -> None:
@@ -99,7 +100,7 @@ class BcfXml:
extensions = mdl_extensions.Extensions()
xs = "{http://www.w3.org/2001/XMLSchema}"
root = etree.parse(io.BytesIO((self.extension_schema)))
root = etree.parse(io.BytesIO(self.extension_schema))
attrs = bcf.agnostic.extensions.get_extensions_attributes(extensions)
xsd_to_attrs = {v.subattr_xsd_name: k for k, v in attrs.items()}
@@ -148,7 +149,7 @@ class BcfXml:
return topics
@classmethod
def load(cls, filename: Path, xml_handler: Optional[AbstractXmlParserSerializer] = None) -> Optional["BcfXml"]:
def load(cls, filename: Path, xml_handler: Optional[AbstractXmlParserSerializer] = None) -> Optional[BcfXml]:
"""
Create a BcfXml object from a file.
@@ -172,7 +173,7 @@ class BcfXml:
cls,
project_name: Optional[str] = None,
xml_handler: Optional[AbstractXmlParserSerializer] = None,
) -> "BcfXml":
) -> BcfXml:
"""
Create a new BcfXml object.
@@ -261,7 +262,7 @@ class BcfXml:
)
# region Deprecated methods
def new_project(self) -> "BcfXml":
def new_project(self) -> BcfXml:
"""Deprecated method."""
warnings.warn("new_project is deprecated, use create_new instead.", DeprecationWarning)
return self.create_new()
+20 -16
View File
@@ -14,16 +14,20 @@
# Currently extensions support for v2 is only read-only.
import sys
from dataclasses import dataclass, field, fields
from typing import List, NamedTuple, Optional
from typing import Optional
@dataclass(slots=True, kw_only=True)
DATACLASS_KWARGS = {} if sys.version_info < (3, 10) else {"slots": True, "kw_only": True}
@dataclass(**DATACLASS_KWARGS)
class ExtensionsPriorities:
class Meta:
global_type = False
priority: List[str] = field(
priority: list[str] = field(
default_factory=list,
metadata={
"name": "Priority",
@@ -35,12 +39,12 @@ class ExtensionsPriorities:
)
@dataclass(slots=True, kw_only=True)
@dataclass(**DATACLASS_KWARGS)
class ExtensionsSnippetTypes:
class Meta:
global_type = False
snippet_type: List[str] = field(
snippet_type: list[str] = field(
default_factory=list,
metadata={
"name": "SnippetType",
@@ -52,12 +56,12 @@ class ExtensionsSnippetTypes:
)
@dataclass(slots=True, kw_only=True)
@dataclass(**DATACLASS_KWARGS)
class ExtensionsStages:
class Meta:
global_type = False
stage: List[str] = field(
stage: list[str] = field(
default_factory=list,
metadata={
"name": "Stage",
@@ -69,12 +73,12 @@ class ExtensionsStages:
)
@dataclass(slots=True, kw_only=True)
@dataclass(**DATACLASS_KWARGS)
class ExtensionsTopicLabels:
class Meta:
global_type = False
topic_label: List[str] = field(
topic_label: list[str] = field(
default_factory=list,
metadata={
"name": "TopicLabel",
@@ -86,12 +90,12 @@ class ExtensionsTopicLabels:
)
@dataclass(slots=True, kw_only=True)
@dataclass(**DATACLASS_KWARGS)
class ExtensionsTopicStatuses:
class Meta:
global_type = False
topic_status: List[str] = field(
topic_status: list[str] = field(
default_factory=list,
metadata={
"name": "TopicStatus",
@@ -103,12 +107,12 @@ class ExtensionsTopicStatuses:
)
@dataclass(slots=True, kw_only=True)
@dataclass(**DATACLASS_KWARGS)
class ExtensionsTopicTypes:
class Meta:
global_type = False
topic_type: List[str] = field(
topic_type: list[str] = field(
default_factory=list,
metadata={
"name": "TopicType",
@@ -120,12 +124,12 @@ class ExtensionsTopicTypes:
)
@dataclass(slots=True, kw_only=True)
@dataclass(**DATACLASS_KWARGS)
class ExtensionsUsers:
class Meta:
global_type = False
user: List[str] = field(
user: list[str] = field(
default_factory=list,
metadata={
"name": "UserIdType",
@@ -137,7 +141,7 @@ class ExtensionsUsers:
)
@dataclass(slots=True, kw_only=True)
@dataclass(**DATACLASS_KWARGS)
class Extensions:
topic_types: Optional[ExtensionsTopicTypes] = field(
default=None,
+21 -18
View File
@@ -1,10 +1,13 @@
import sys
from dataclasses import dataclass, field
from typing import List, Optional
from typing import Optional
from xsdata.models.datatype import XmlDateTime
DATACLASS_KWARGS = {} if sys.version_info < (3, 10) else {"slots": True, "kw_only": True}
@dataclass(slots=True, kw_only=True)
@dataclass(**DATACLASS_KWARGS)
class BimSnippet:
reference: str = field(
metadata={
@@ -38,7 +41,7 @@ class BimSnippet:
)
@dataclass(slots=True, kw_only=True)
@dataclass(**DATACLASS_KWARGS)
class CommentViewpoint:
class Meta:
global_type = False
@@ -53,7 +56,7 @@ class CommentViewpoint:
)
@dataclass(slots=True, kw_only=True)
@dataclass(**DATACLASS_KWARGS)
class HeaderFile:
class Meta:
global_type = False
@@ -109,7 +112,7 @@ class HeaderFile:
)
@dataclass(slots=True, kw_only=True)
@dataclass(**DATACLASS_KWARGS)
class TopicDocumentReference:
class Meta:
global_type = False
@@ -147,7 +150,7 @@ class TopicDocumentReference:
)
@dataclass(slots=True, kw_only=True)
@dataclass(**DATACLASS_KWARGS)
class TopicRelatedTopic:
class Meta:
global_type = False
@@ -162,7 +165,7 @@ class TopicRelatedTopic:
)
@dataclass(slots=True, kw_only=True)
@dataclass(**DATACLASS_KWARGS)
class ViewPoint:
viewpoint: Optional[str] = field(
default=None,
@@ -198,7 +201,7 @@ class ViewPoint:
)
@dataclass(slots=True, kw_only=True)
@dataclass(**DATACLASS_KWARGS)
class Comment:
date: XmlDateTime = field(
metadata={
@@ -258,9 +261,9 @@ class Comment:
)
@dataclass(slots=True, kw_only=True)
@dataclass(**DATACLASS_KWARGS)
class Header:
file: List[HeaderFile] = field(
file: list[HeaderFile] = field(
default_factory=list,
metadata={
"name": "File",
@@ -271,9 +274,9 @@ class Header:
)
@dataclass(slots=True, kw_only=True)
@dataclass(**DATACLASS_KWARGS)
class Topic:
reference_link: List[str] = field(
reference_link: list[str] = field(
default_factory=list,
metadata={
"name": "ReferenceLink",
@@ -305,7 +308,7 @@ class Topic:
"namespace": "",
},
)
labels: List[str] = field(
labels: list[str] = field(
default_factory=list,
metadata={
"name": "Labels",
@@ -385,7 +388,7 @@ class Topic:
"namespace": "",
},
)
document_reference: List[TopicDocumentReference] = field(
document_reference: list[TopicDocumentReference] = field(
default_factory=list,
metadata={
"name": "DocumentReference",
@@ -393,7 +396,7 @@ class Topic:
"namespace": "",
},
)
related_topic: List[TopicRelatedTopic] = field(
related_topic: list[TopicRelatedTopic] = field(
default_factory=list,
metadata={
"name": "RelatedTopic",
@@ -425,7 +428,7 @@ class Topic:
)
@dataclass(slots=True, kw_only=True)
@dataclass(**DATACLASS_KWARGS)
class Markup:
header: Optional[Header] = field(
default=None,
@@ -443,7 +446,7 @@ class Markup:
"required": True,
}
)
comment: List[Comment] = field(
comment: list[Comment] = field(
default_factory=list,
metadata={
"name": "Comment",
@@ -451,7 +454,7 @@ class Markup:
"namespace": "",
},
)
viewpoints: List[ViewPoint] = field(
viewpoints: list[ViewPoint] = field(
default_factory=list,
metadata={
"name": "Viewpoints",
+5 -2
View File
@@ -1,8 +1,11 @@
import sys
from dataclasses import dataclass, field
from typing import Optional
DATACLASS_KWARGS = {} if sys.version_info < (3, 10) else {"slots": True, "kw_only": True}
@dataclass(slots=True, kw_only=True)
@dataclass(**DATACLASS_KWARGS)
class Project:
name: Optional[str] = field(
default=None,
@@ -21,7 +24,7 @@ class Project:
)
@dataclass(slots=True, kw_only=True)
@dataclass(**DATACLASS_KWARGS)
class ProjectExtension:
project: Optional[Project] = field(
default=None,
+4 -1
View File
@@ -1,8 +1,11 @@
import sys
from dataclasses import dataclass, field
from typing import Optional
DATACLASS_KWARGS = {} if sys.version_info < (3, 10) else {"slots": True, "kw_only": True}
@dataclass(slots=True, kw_only=True)
@dataclass(**DATACLASS_KWARGS)
class Version:
detailed_version: Optional[str] = field(
default=None,
+29 -26
View File
@@ -1,6 +1,9 @@
import sys
from dataclasses import dataclass, field
from enum import Enum
from typing import List, Optional
from typing import Optional
DATACLASS_KWARGS = {} if sys.version_info < (3, 10) else {"slots": True, "kw_only": True}
class BitmapFormat(Enum):
@@ -8,7 +11,7 @@ class BitmapFormat(Enum):
JPG = "JPG"
@dataclass(slots=True, kw_only=True)
@dataclass(**DATACLASS_KWARGS)
class Component:
originating_system: Optional[str] = field(
default=None,
@@ -35,7 +38,7 @@ class Component:
)
@dataclass(slots=True, kw_only=True)
@dataclass(**DATACLASS_KWARGS)
class Direction:
x: float = field(
metadata={
@@ -60,7 +63,7 @@ class Direction:
)
@dataclass(slots=True, kw_only=True)
@dataclass(**DATACLASS_KWARGS)
class Point:
x: float = field(
metadata={
@@ -85,7 +88,7 @@ class Point:
)
@dataclass(slots=True, kw_only=True)
@dataclass(**DATACLASS_KWARGS)
class ViewSetupHints:
spaces_visible: Optional[bool] = field(
default=None,
@@ -110,7 +113,7 @@ class ViewSetupHints:
)
@dataclass(slots=True, kw_only=True)
@dataclass(**DATACLASS_KWARGS)
class ClippingPlane:
location: Point = field(
metadata={
@@ -128,12 +131,12 @@ class ClippingPlane:
)
@dataclass(slots=True, kw_only=True)
@dataclass(**DATACLASS_KWARGS)
class ComponentColoringColor:
class Meta:
global_type = False
component: List[Component] = field(
component: list[Component] = field(
default_factory=list,
metadata={
"name": "Component",
@@ -151,9 +154,9 @@ class ComponentColoringColor:
)
@dataclass(slots=True, kw_only=True)
@dataclass(**DATACLASS_KWARGS)
class ComponentSelection:
component: List[Component] = field(
component: list[Component] = field(
default_factory=list,
metadata={
"name": "Component",
@@ -163,12 +166,12 @@ class ComponentSelection:
)
@dataclass(slots=True, kw_only=True)
@dataclass(**DATACLASS_KWARGS)
class ComponentVisibilityExceptions:
class Meta:
global_type = False
component: List[Component] = field(
component: list[Component] = field(
default_factory=list,
metadata={
"name": "Component",
@@ -178,7 +181,7 @@ class ComponentVisibilityExceptions:
)
@dataclass(slots=True, kw_only=True)
@dataclass(**DATACLASS_KWARGS)
class Line:
start_point: Point = field(
metadata={
@@ -196,7 +199,7 @@ class Line:
)
@dataclass(slots=True, kw_only=True)
@dataclass(**DATACLASS_KWARGS)
class OrthogonalCamera:
"""
Attributes
@@ -236,7 +239,7 @@ class OrthogonalCamera:
)
@dataclass(slots=True, kw_only=True)
@dataclass(**DATACLASS_KWARGS)
class PerspectiveCamera:
"""
Attributes
@@ -281,7 +284,7 @@ class PerspectiveCamera:
)
@dataclass(slots=True, kw_only=True)
@dataclass(**DATACLASS_KWARGS)
class VisualizationInfoBitmap:
class Meta:
global_type = False
@@ -330,9 +333,9 @@ class VisualizationInfoBitmap:
)
@dataclass(slots=True, kw_only=True)
@dataclass(**DATACLASS_KWARGS)
class ComponentColoring:
color: List[ComponentColoringColor] = field(
color: list[ComponentColoringColor] = field(
default_factory=list,
metadata={
"name": "Color",
@@ -342,7 +345,7 @@ class ComponentColoring:
)
@dataclass(slots=True, kw_only=True)
@dataclass(**DATACLASS_KWARGS)
class ComponentVisibility:
exceptions: Optional[ComponentVisibilityExceptions] = field(
default=None,
@@ -360,12 +363,12 @@ class ComponentVisibility:
)
@dataclass(slots=True, kw_only=True)
@dataclass(**DATACLASS_KWARGS)
class VisualizationInfoClippingPlanes:
class Meta:
global_type = False
clipping_plane: List[ClippingPlane] = field(
clipping_plane: list[ClippingPlane] = field(
default_factory=list,
metadata={
"name": "ClippingPlane",
@@ -374,12 +377,12 @@ class VisualizationInfoClippingPlanes:
)
@dataclass(slots=True, kw_only=True)
@dataclass(**DATACLASS_KWARGS)
class VisualizationInfoLines:
class Meta:
global_type = False
line: List[Line] = field(
line: list[Line] = field(
default_factory=list,
metadata={
"name": "Line",
@@ -389,7 +392,7 @@ class VisualizationInfoLines:
)
@dataclass(slots=True, kw_only=True)
@dataclass(**DATACLASS_KWARGS)
class Components:
view_setup_hints: Optional[ViewSetupHints] = field(
default=None,
@@ -421,7 +424,7 @@ class Components:
)
@dataclass(slots=True, kw_only=True)
@dataclass(**DATACLASS_KWARGS)
class VisualizationInfo:
"""
VisualizationInfo documentation.
@@ -462,7 +465,7 @@ class VisualizationInfo:
"type": "Element",
},
)
bitmap: List[VisualizationInfoBitmap] = field(
bitmap: list[VisualizationInfoBitmap] = field(
default_factory=list,
metadata={
"name": "Bitmap",
+2 -1
View File
@@ -1,5 +1,6 @@
"""BCF XML V2 Topic handler."""
from __future__ import annotations
import datetime
import tempfile
import uuid
@@ -152,7 +153,7 @@ class TopicHandler:
topic_type: str = "",
topic_status: str = "",
xml_handler: Optional[AbstractXmlParserSerializer] = None,
) -> "TopicHandler":
) -> TopicHandler:
"""
Create a new BCF topic.
+2 -1
View File
@@ -1,6 +1,7 @@
import uuid
import zipfile
from typing import Any, Iterable, Optional, Literal, Union
from typing import Any, Optional, Literal, Union
from collections.abc import Iterable
import numpy as np
from ifcopenshell import entity_instance
+21 -21
View File
@@ -21,11 +21,11 @@ import http.server
import os
import tempfile
import time
import urllib
import urllib.parse
import uuid
import webbrowser
from re import A
from typing import Any, Optional, Tuple
from typing import Any, Optional
import requests
@@ -40,7 +40,7 @@ class OAuthReceiver(http.server.BaseHTTPRequestHandler):
self.send_response(200)
self.send_header("Content-type", "text/plain")
self.end_headers()
self.wfile.write("You have now authenticated :) You may now close this browser window.".encode("utf-8"))
self.wfile.write(b"You have now authenticated :) You may now close this browser window.")
class FoundationClient:
@@ -174,7 +174,7 @@ class BcfClient:
except requests.exceptions.HTTPError as e:
print(f"message: {response.reason}' '{response.status_code}' '{ e }")
def post(self, endpoint: str, data: Any = None, params: Any = None) -> Tuple[int, str]:
def post(self, endpoint: str, data: Any = None, params: Any = None) -> tuple[int, str]:
headers = {
"Authorization": f"Bearer {self.foundation_client.get_access_token()}",
"Content-type": "application/json",
@@ -192,7 +192,7 @@ class BcfClient:
print(f"message: {response.reason}' '{response.status_code}, {errh}")
return response.status_code, response.reason
def put(self, endpoint: str, data: Any = None, params: Any = None) -> Tuple[int, str]:
def put(self, endpoint: str, data: Any = None, params: Any = None) -> tuple[int, str]:
headers = {
"Authorization": f"Bearer {self.foundation_client.get_access_token()}",
"Content-type": "application/json",
@@ -210,7 +210,7 @@ class BcfClient:
print(f"message: {response.reason}' '{response.status_code}, {errh}")
return response.status_code, response.reason
def delete(self, endpoint: str, params: Any = None) -> Tuple[int, str]:
def delete(self, endpoint: str, params: Any = None) -> tuple[int, str]:
headers = {
"Authorization": f"Bearer {self.foundation_client.get_access_token()}",
"Content-type": "application/json",
@@ -237,7 +237,7 @@ class BcfClient:
},
)
def update_project(self, project_id: str = "", data: Any = None) -> Tuple[int, str]:
def update_project(self, project_id: str = "", data: Any = None) -> tuple[int, str]:
url = f"{self.baseurl}/projects/{project_id}"
headers = {"Authorization": f"Bearer {self.foundation_client.get_access_token()}"}
resp = requests.put(url, headers=headers, data=data)
@@ -276,16 +276,16 @@ class BcfClient:
},
)
def create_topic(self, project_id: str = "", data: Any = None) -> Tuple[int, str]:
def create_topic(self, project_id: str = "", data: Any = None) -> tuple[int, str]:
return self.post(f"/projects/{project_id}/topics", data=data)
def update_topic(self, project_id: str = "", topic_id: str = "", data: Any = None) -> Tuple[int, str]:
def update_topic(self, project_id: str = "", topic_id: str = "", data: Any = None) -> tuple[int, str]:
return self.put(f"/projects/{project_id}/topics/{topic_id}", data=data)
def delete_topic(self, project_id: str = "", topic_id: str = "") -> Tuple[int, str]:
def delete_topic(self, project_id: str = "", topic_id: str = "") -> tuple[int, str]:
return self.delete(f"/projects/{project_id}/topics/{topic_id}")
def get_snippet(self, project_id: str = "", topic_id: str = "") -> Tuple[int, str]:
def get_snippet(self, project_id: str = "", topic_id: str = "") -> tuple[int, str]:
headers = {
"Authorization": f"Bearer {self.foundation_client.get_access_token()}",
"Content-type": "application/octet-stream",
@@ -332,7 +332,7 @@ class BcfClient:
topic_id: str = "",
data: Any = None,
params: Any = None,
) -> Tuple[int, str]:
) -> tuple[int, str]:
return self.put(
f"/projects/{project_id}/topics/{topic_id}/files",
data=data,
@@ -347,7 +347,7 @@ class BcfClient:
topic_id: str = "",
data: Any = None,
params: Any = None,
) -> Tuple[int, str]:
) -> tuple[int, str]:
return self.post(
f"/projects/{project_id}/topics/{topic_id}/comments",
data=data,
@@ -363,7 +363,7 @@ class BcfClient:
},
)
def delete_comment(self, project_id: str = "", topic_id: str = "", comment_id: str = "") -> Tuple[int, str]:
def delete_comment(self, project_id: str = "", topic_id: str = "", comment_id: str = "") -> tuple[int, str]:
return self.delete(f"/projects/{project_id}/topics/{topic_id}/comments/{comment_id}")
def update_comment(
@@ -372,7 +372,7 @@ class BcfClient:
topic_id: str = "",
comment_id: str = "",
data: Any = None,
) -> Tuple[int, str]:
) -> tuple[int, str]:
return self.put(
f"/projects/{project_id}/topics/{topic_id}/comments/{comment_id}",
data=data,
@@ -387,7 +387,7 @@ class BcfClient:
},
)
def create_viewpoints(self, project_id: str = "", topic_id: str = "", data: Any = None) -> Tuple[int, str]:
def create_viewpoints(self, project_id: str = "", topic_id: str = "", data: Any = None) -> tuple[int, str]:
return self.post(
f"/projects/{project_id}/topics/{topic_id}/viewpoints",
data=data,
@@ -408,7 +408,7 @@ class BcfClient:
project_id: str = "",
topic_id: str = "",
viewpoint_id: str = "",
) -> Tuple[int, str]:
) -> tuple[int, str]:
return self.delete(
f"/projects/{project_id}/topics/{topic_id}/viewpoints/{viewpoint_id}",
)
@@ -478,7 +478,7 @@ class BcfClient:
project_id: str = "",
topic_id: str = "",
data: Any = None,
) -> Tuple[int, str]:
) -> tuple[int, str]:
return self.put(
f"/projects/{project_id}/topics/{topic_id}/related_topics",
data=data,
@@ -498,7 +498,7 @@ class BcfClient:
project_id: str = "",
topic_id: str = "",
data: Any = None,
) -> Tuple[int, str]:
) -> tuple[int, str]:
return self.post(
f"/projects/{project_id}/topics/{topic_id}/document_references",
data=data,
@@ -510,7 +510,7 @@ class BcfClient:
topic_id: str = "",
document_reference_id: str = "",
data: Any = None,
) -> Tuple[int, str]:
) -> tuple[int, str]:
return self.put(
f"/projects/{project_id}/topics/{topic_id}/document_references/{document_reference_id}",
data=data,
@@ -543,7 +543,7 @@ class BcfClient:
return response.status_code
def get_document(self, project_id: str = "", topic_id: str = "", document_id: str = "") -> Tuple[int, str]:
def get_document(self, project_id: str = "", topic_id: str = "", document_id: str = "") -> tuple[int, str]:
headers = {
"Authorization": f"Bearer {self.foundation_client.get_access_token()}",
"Content-type": "application/octet-stream",
+5 -4
View File
@@ -1,5 +1,6 @@
"""BCF XML V3 handlers."""
from __future__ import annotations
import uuid
import warnings
import zipfile
@@ -30,7 +31,7 @@ class BcfXml:
self._documents: Optional[DocumentsHandler] = None
self._zip_file = self._load_zip_file()
def __enter__(self) -> "BcfXml":
def __enter__(self) -> BcfXml:
return self
def __exit__(self, *args: Any) -> None:
@@ -116,7 +117,7 @@ class BcfXml:
return self._documents
@classmethod
def load(cls, filename: Path, xml_handler: Optional[AbstractXmlParserSerializer] = None) -> Optional["BcfXml"]:
def load(cls, filename: Path, xml_handler: Optional[AbstractXmlParserSerializer] = None) -> Optional[BcfXml]:
"""
Create a BcfXml object from a file.
@@ -141,7 +142,7 @@ class BcfXml:
project_name: Optional[str] = None,
extensions: Optional[mdl.Extensions] = None,
xml_handler: Optional[AbstractXmlParserSerializer] = None,
) -> "BcfXml":
) -> BcfXml:
"""
Create a new BcfXml object.
@@ -239,7 +240,7 @@ class BcfXml:
)
# region Deprecated methods
def new_project(self) -> "BcfXml":
def new_project(self) -> BcfXml:
"""Deprecated method."""
warnings.warn("new_project is deprecated, use create_new instead.", DeprecationWarning)
return self.create_new()
+8 -5
View File
@@ -1,8 +1,11 @@
import sys
from dataclasses import dataclass, field
from typing import List, Optional
from typing import Optional
DATACLASS_KWARGS = {} if sys.version_info < (3, 10) else {"slots": True, "kw_only": True}
@dataclass(slots=True, kw_only=True)
@dataclass(**DATACLASS_KWARGS)
class Document:
filename: str = field(
metadata={
@@ -34,12 +37,12 @@ class Document:
)
@dataclass(slots=True, kw_only=True)
@dataclass(**DATACLASS_KWARGS)
class DocumentInfoDocuments:
class Meta:
global_type = False
document: List[Document] = field(
document: list[Document] = field(
default_factory=list,
metadata={
"name": "Document",
@@ -49,7 +52,7 @@ class DocumentInfoDocuments:
)
@dataclass(slots=True, kw_only=True)
@dataclass(**DATACLASS_KWARGS)
class DocumentInfo:
documents: Optional[DocumentInfoDocuments] = field(
default=None,
+19 -16
View File
@@ -1,13 +1,16 @@
import sys
from dataclasses import dataclass, field
from typing import List, Optional
from typing import Optional
DATACLASS_KWARGS = {} if sys.version_info < (3, 10) else {"slots": True, "kw_only": True}
@dataclass(slots=True, kw_only=True)
@dataclass(**DATACLASS_KWARGS)
class ExtensionsPriorities:
class Meta:
global_type = False
priority: List[str] = field(
priority: list[str] = field(
default_factory=list,
metadata={
"name": "Priority",
@@ -19,12 +22,12 @@ class ExtensionsPriorities:
)
@dataclass(slots=True, kw_only=True)
@dataclass(**DATACLASS_KWARGS)
class ExtensionsSnippetTypes:
class Meta:
global_type = False
snippet_type: List[str] = field(
snippet_type: list[str] = field(
default_factory=list,
metadata={
"name": "SnippetType",
@@ -36,12 +39,12 @@ class ExtensionsSnippetTypes:
)
@dataclass(slots=True, kw_only=True)
@dataclass(**DATACLASS_KWARGS)
class ExtensionsStages:
class Meta:
global_type = False
stage: List[str] = field(
stage: list[str] = field(
default_factory=list,
metadata={
"name": "Stage",
@@ -53,12 +56,12 @@ class ExtensionsStages:
)
@dataclass(slots=True, kw_only=True)
@dataclass(**DATACLASS_KWARGS)
class ExtensionsTopicLabels:
class Meta:
global_type = False
topic_label: List[str] = field(
topic_label: list[str] = field(
default_factory=list,
metadata={
"name": "TopicLabel",
@@ -70,12 +73,12 @@ class ExtensionsTopicLabels:
)
@dataclass(slots=True, kw_only=True)
@dataclass(**DATACLASS_KWARGS)
class ExtensionsTopicStatuses:
class Meta:
global_type = False
topic_status: List[str] = field(
topic_status: list[str] = field(
default_factory=list,
metadata={
"name": "TopicStatus",
@@ -87,12 +90,12 @@ class ExtensionsTopicStatuses:
)
@dataclass(slots=True, kw_only=True)
@dataclass(**DATACLASS_KWARGS)
class ExtensionsTopicTypes:
class Meta:
global_type = False
topic_type: List[str] = field(
topic_type: list[str] = field(
default_factory=list,
metadata={
"name": "TopicType",
@@ -104,12 +107,12 @@ class ExtensionsTopicTypes:
)
@dataclass(slots=True, kw_only=True)
@dataclass(**DATACLASS_KWARGS)
class ExtensionsUsers:
class Meta:
global_type = False
user: List[str] = field(
user: list[str] = field(
default_factory=list,
metadata={
"name": "User",
@@ -121,7 +124,7 @@ class ExtensionsUsers:
)
@dataclass(slots=True, kw_only=True)
@dataclass(**DATACLASS_KWARGS)
class Extensions:
topic_types: Optional[ExtensionsTopicTypes] = field(
default=None,
+28 -25
View File
@@ -1,10 +1,13 @@
import sys
from dataclasses import dataclass, field
from typing import List, Optional
from typing import Optional
from xsdata.models.datatype import XmlDateTime
DATACLASS_KWARGS = {} if sys.version_info < (3, 10) else {"slots": True, "kw_only": True}
@dataclass(slots=True, kw_only=True)
@dataclass(**DATACLASS_KWARGS)
class BimSnippet:
reference: str = field(
metadata={
@@ -44,7 +47,7 @@ class BimSnippet:
)
@dataclass(slots=True, kw_only=True)
@dataclass(**DATACLASS_KWARGS)
class CommentViewpoint:
class Meta:
global_type = False
@@ -59,7 +62,7 @@ class CommentViewpoint:
)
@dataclass(slots=True, kw_only=True)
@dataclass(**DATACLASS_KWARGS)
class DocumentReference:
document_guid: Optional[str] = field(
default=None,
@@ -100,7 +103,7 @@ class DocumentReference:
)
@dataclass(slots=True, kw_only=True)
@dataclass(**DATACLASS_KWARGS)
class File:
filename: Optional[str] = field(
default=None,
@@ -157,12 +160,12 @@ class File:
)
@dataclass(slots=True, kw_only=True)
@dataclass(**DATACLASS_KWARGS)
class TopicLabels:
class Meta:
global_type = False
label: List[str] = field(
label: list[str] = field(
default_factory=list,
metadata={
"name": "Label",
@@ -174,12 +177,12 @@ class TopicLabels:
)
@dataclass(slots=True, kw_only=True)
@dataclass(**DATACLASS_KWARGS)
class TopicReferenceLinks:
class Meta:
global_type = False
reference_link: List[str] = field(
reference_link: list[str] = field(
default_factory=list,
metadata={
"name": "ReferenceLink",
@@ -191,7 +194,7 @@ class TopicReferenceLinks:
)
@dataclass(slots=True, kw_only=True)
@dataclass(**DATACLASS_KWARGS)
class TopicRelatedTopicsRelatedTopic:
class Meta:
global_type = False
@@ -206,7 +209,7 @@ class TopicRelatedTopicsRelatedTopic:
)
@dataclass(slots=True, kw_only=True)
@dataclass(**DATACLASS_KWARGS)
class ViewPoint:
viewpoint: Optional[str] = field(
default=None,
@@ -246,7 +249,7 @@ class ViewPoint:
)
@dataclass(slots=True, kw_only=True)
@dataclass(**DATACLASS_KWARGS)
class Comment:
date: XmlDateTime = field(
metadata={
@@ -312,12 +315,12 @@ class Comment:
)
@dataclass(slots=True, kw_only=True)
@dataclass(**DATACLASS_KWARGS)
class HeaderFiles:
class Meta:
global_type = False
file: List[File] = field(
file: list[File] = field(
default_factory=list,
metadata={
"name": "File",
@@ -327,12 +330,12 @@ class HeaderFiles:
)
@dataclass(slots=True, kw_only=True)
@dataclass(**DATACLASS_KWARGS)
class TopicDocumentReferences:
class Meta:
global_type = False
document_reference: List[DocumentReference] = field(
document_reference: list[DocumentReference] = field(
default_factory=list,
metadata={
"name": "DocumentReference",
@@ -342,12 +345,12 @@ class TopicDocumentReferences:
)
@dataclass(slots=True, kw_only=True)
@dataclass(**DATACLASS_KWARGS)
class TopicRelatedTopics:
class Meta:
global_type = False
related_topic: List[TopicRelatedTopicsRelatedTopic] = field(
related_topic: list[TopicRelatedTopicsRelatedTopic] = field(
default_factory=list,
metadata={
"name": "RelatedTopic",
@@ -357,12 +360,12 @@ class TopicRelatedTopics:
)
@dataclass(slots=True, kw_only=True)
@dataclass(**DATACLASS_KWARGS)
class TopicViewpoints:
class Meta:
global_type = False
view_point: List[ViewPoint] = field(
view_point: list[ViewPoint] = field(
default_factory=list,
metadata={
"name": "ViewPoint",
@@ -372,7 +375,7 @@ class TopicViewpoints:
)
@dataclass(slots=True, kw_only=True)
@dataclass(**DATACLASS_KWARGS)
class Header:
files: Optional[HeaderFiles] = field(
default=None,
@@ -384,12 +387,12 @@ class Header:
)
@dataclass(slots=True, kw_only=True)
@dataclass(**DATACLASS_KWARGS)
class TopicComments:
class Meta:
global_type = False
comment: List[Comment] = field(
comment: list[Comment] = field(
default_factory=list,
metadata={
"name": "Comment",
@@ -399,7 +402,7 @@ class TopicComments:
)
@dataclass(slots=True, kw_only=True)
@dataclass(**DATACLASS_KWARGS)
class Topic:
reference_links: Optional[TopicReferenceLinks] = field(
default=None,
@@ -596,7 +599,7 @@ class Topic:
)
@dataclass(slots=True, kw_only=True)
@dataclass(**DATACLASS_KWARGS)
class Markup:
header: Optional[Header] = field(
default=None,
+5 -2
View File
@@ -1,8 +1,11 @@
import sys
from dataclasses import dataclass, field
from typing import Optional
DATACLASS_KWARGS = {} if sys.version_info < (3, 10) else {"slots": True, "kw_only": True}
@dataclass(slots=True, kw_only=True)
@dataclass(**DATACLASS_KWARGS)
class Project:
name: Optional[str] = field(
default=None,
@@ -25,7 +28,7 @@ class Project:
)
@dataclass(slots=True, kw_only=True)
@dataclass(**DATACLASS_KWARGS)
class ProjectInfo:
project: Project = field(
metadata={
+4 -1
View File
@@ -1,7 +1,10 @@
import sys
from dataclasses import dataclass, field
DATACLASS_KWARGS = {} if sys.version_info < (3, 10) else {"slots": True, "kw_only": True}
@dataclass(slots=True, kw_only=True)
@dataclass(**DATACLASS_KWARGS)
class Version:
version_id: str = field(
metadata={
+32 -28
View File
@@ -1,6 +1,10 @@
import sys
from dataclasses import dataclass, field
from enum import Enum
from typing import List, Optional
from typing import Optional
DATACLASS_KWARGS = {} if sys.version_info < (3, 10) else {"slots": True, "kw_only": True}
class BitmapFormat(Enum):
@@ -8,7 +12,7 @@ class BitmapFormat(Enum):
JPG = "jpg"
@dataclass(slots=True, kw_only=True)
@dataclass(**DATACLASS_KWARGS)
class Component:
originating_system: Optional[str] = field(
default=None,
@@ -39,7 +43,7 @@ class Component:
)
@dataclass(slots=True, kw_only=True)
@dataclass(**DATACLASS_KWARGS)
class Direction:
x: float = field(
metadata={
@@ -64,7 +68,7 @@ class Direction:
)
@dataclass(slots=True, kw_only=True)
@dataclass(**DATACLASS_KWARGS)
class Point:
x: float = field(
metadata={
@@ -89,7 +93,7 @@ class Point:
)
@dataclass(slots=True, kw_only=True)
@dataclass(**DATACLASS_KWARGS)
class ViewSetupHints:
spaces_visible: bool = field(
default=False,
@@ -114,7 +118,7 @@ class ViewSetupHints:
)
@dataclass(slots=True, kw_only=True)
@dataclass(**DATACLASS_KWARGS)
class Bitmap:
format: BitmapFormat = field(
metadata={
@@ -162,7 +166,7 @@ class Bitmap:
)
@dataclass(slots=True, kw_only=True)
@dataclass(**DATACLASS_KWARGS)
class ClippingPlane:
location: Point = field(
metadata={
@@ -180,12 +184,12 @@ class ClippingPlane:
)
@dataclass(slots=True, kw_only=True)
@dataclass(**DATACLASS_KWARGS)
class ComponentColoringColorComponents:
class Meta:
global_type = False
component: List[Component] = field(
component: list[Component] = field(
default_factory=list,
metadata={
"name": "Component",
@@ -195,9 +199,9 @@ class ComponentColoringColorComponents:
)
@dataclass(slots=True, kw_only=True)
@dataclass(**DATACLASS_KWARGS)
class ComponentSelection:
component: List[Component] = field(
component: list[Component] = field(
default_factory=list,
metadata={
"name": "Component",
@@ -206,12 +210,12 @@ class ComponentSelection:
)
@dataclass(slots=True, kw_only=True)
@dataclass(**DATACLASS_KWARGS)
class ComponentVisibilityExceptions:
class Meta:
global_type = False
component: List[Component] = field(
component: list[Component] = field(
default_factory=list,
metadata={
"name": "Component",
@@ -220,7 +224,7 @@ class ComponentVisibilityExceptions:
)
@dataclass(slots=True, kw_only=True)
@dataclass(**DATACLASS_KWARGS)
class Line:
start_point: Point = field(
metadata={
@@ -238,7 +242,7 @@ class Line:
)
@dataclass(slots=True, kw_only=True)
@dataclass(**DATACLASS_KWARGS)
class OrthogonalCamera:
"""
Attributes
@@ -288,7 +292,7 @@ class OrthogonalCamera:
)
@dataclass(slots=True, kw_only=True)
@dataclass(**DATACLASS_KWARGS)
class PerspectiveCamera:
"""
Attributes
@@ -344,7 +348,7 @@ class PerspectiveCamera:
)
@dataclass(slots=True, kw_only=True)
@dataclass(**DATACLASS_KWARGS)
class ComponentColoringColor:
class Meta:
global_type = False
@@ -366,7 +370,7 @@ class ComponentColoringColor:
)
@dataclass(slots=True, kw_only=True)
@dataclass(**DATACLASS_KWARGS)
class ComponentVisibility:
view_setup_hints: Optional[ViewSetupHints] = field(
default=None,
@@ -391,12 +395,12 @@ class ComponentVisibility:
)
@dataclass(slots=True, kw_only=True)
@dataclass(**DATACLASS_KWARGS)
class VisualizationInfoBitmaps:
class Meta:
global_type = False
bitmap: List[Bitmap] = field(
bitmap: list[Bitmap] = field(
default_factory=list,
metadata={
"name": "Bitmap",
@@ -405,12 +409,12 @@ class VisualizationInfoBitmaps:
)
@dataclass(slots=True, kw_only=True)
@dataclass(**DATACLASS_KWARGS)
class VisualizationInfoClippingPlanes:
class Meta:
global_type = False
clipping_plane: List[ClippingPlane] = field(
clipping_plane: list[ClippingPlane] = field(
default_factory=list,
metadata={
"name": "ClippingPlane",
@@ -419,12 +423,12 @@ class VisualizationInfoClippingPlanes:
)
@dataclass(slots=True, kw_only=True)
@dataclass(**DATACLASS_KWARGS)
class VisualizationInfoLines:
class Meta:
global_type = False
line: List[Line] = field(
line: list[Line] = field(
default_factory=list,
metadata={
"name": "Line",
@@ -433,9 +437,9 @@ class VisualizationInfoLines:
)
@dataclass(slots=True, kw_only=True)
@dataclass(**DATACLASS_KWARGS)
class ComponentColoring:
color: List[ComponentColoringColor] = field(
color: list[ComponentColoringColor] = field(
default_factory=list,
metadata={
"name": "Color",
@@ -444,7 +448,7 @@ class ComponentColoring:
)
@dataclass(slots=True, kw_only=True)
@dataclass(**DATACLASS_KWARGS)
class Components:
selection: Optional[ComponentSelection] = field(
default=None,
@@ -469,7 +473,7 @@ class Components:
)
@dataclass(slots=True, kw_only=True)
@dataclass(**DATACLASS_KWARGS)
class VisualizationInfo:
"""
VisualizationInfo documentation.
+4 -3
View File
@@ -1,5 +1,6 @@
"""BCF XML V3 Topic handler."""
from __future__ import annotations
import datetime
import uuid
import zipfile
@@ -91,12 +92,12 @@ class TopicHandler:
self._bim_snippet = value
@property
def viewpoints(self) -> dict[str, "VisualizationInfoHandler"]:
def viewpoints(self) -> dict[str, VisualizationInfoHandler]:
if self._viewpoints is None:
self._viewpoints = self._load_viewpoints()
return self._viewpoints
def _load_viewpoints(self) -> dict[str, "VisualizationInfoHandler"]:
def _load_viewpoints(self) -> dict[str, VisualizationInfoHandler]:
if self._topic_dir and self.topic.viewpoints and (viewpoints := self.topic.viewpoints.view_point):
return VisualizationInfoHandler.from_topic_viewpoints(self._topic_dir, viewpoints)
return {}
@@ -139,7 +140,7 @@ class TopicHandler:
topic_type: str = "",
topic_status: str = "",
xml_handler: Optional[AbstractXmlParserSerializer] = None,
) -> "TopicHandler":
) -> TopicHandler:
"""
Create a new BCF topic.
+2 -1
View File
@@ -1,6 +1,7 @@
import uuid
import zipfile
from typing import Any, Iterable, Optional, Literal, Union
from typing import Any, Optional, Literal, Union
from collections.abc import Iterable
import numpy as np
from ifcopenshell import entity_instance
+3 -3
View File
@@ -1,6 +1,6 @@
"""XML Parser and Serializer factories."""
from typing import Optional, Protocol, Type, TypeVar
from typing import Optional, Protocol, TypeVar
from xsdata.formats.dataclass.context import XmlContext
from xsdata.formats.dataclass.parsers import XmlParser
@@ -29,7 +29,7 @@ T = TypeVar("T")
class AbstractXmlParserSerializer(Protocol):
"""XML Parser and serializer wrapper."""
def parse(self, xml: bytes, clazz: Type[T]) -> T:
def parse(self, xml: bytes, clazz: type[T]) -> T:
"""
Parse an XML file to an object.
@@ -61,7 +61,7 @@ class XmlParserSerializer:
self.parser = build_xml_parser(self.context)
self.serializer = build_serializer(self.context)
def parse(self, xml: bytes, clazz: Type[T]) -> T:
def parse(self, xml: bytes, clazz: type[T]) -> T:
"""
Parse an XML file to an object.
+1
View File
@@ -16,6 +16,7 @@ dependencies = [
"xsdata>=24.4",
"numpy",
"ifcopenshell",
"requests",
]
version = "0.0.0"
classifiers = [
+7 -4
View File
@@ -16,6 +16,7 @@
# You should have received a copy of the GNU General Public License
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
SHELL := sh
PYTHON:=python3.11
PIP:=pip3.11
PATCH:=patch
@@ -86,7 +87,7 @@ BLENDER_PLATFORM:=windows-x64
endif
# Current build commit hash.
OLD:=c49ca69
OLD:=ec13294
.PHONY: bump
bump:
ifndef NEW
@@ -137,7 +138,7 @@ endif
# odfpy doesn't come with its own wheel, so whee'l (get it?) create it ourselves!
cd build && . env/$(VENV_ACTIVATE) && $(PIP) wheel odfpy --wheel-dir=./wheels
# Required by IFCCityJSON
cd build && . env/$(VENV_ACTIVATE) && $(PIP) download cjio --dest=./wheels
cd build && . env/$(VENV_ACTIVATE) && $(PIP) download "cjio >=0.8, <0.10" --dest=./wheels
# Required in general for sorting all sorts of stuff in a nice way
cd build && . env/$(VENV_ACTIVATE) && $(PIP) download natsort --dest=./wheels
# Provides express rule validation for ifcopenshell.validate
@@ -192,13 +193,15 @@ else
cd build && . env/$(VENV_ACTIVATE) && $(PIP) download pyradiance $(PYPI_PLATFORM) --python-version $(PYPI_VERSION) --implementation $(PYPI_IMP) --only-binary=:all: --dest=./wheels
endif
# jQuery is required for web UI functionality
cd build/bonsai/bim/data/webui/static/js/ && wget https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js
# Provides jsgantt-improved supports for web-based construction sequencing gantt charts
cd build/bonsai/bim/data/gantt/ && wget https://raw.githubusercontent.com/jsGanttImproved/jsgantt-improved/master/dist/jsgantt.js
cd build/bonsai/bim/data/gantt/ && wget https://raw.githubusercontent.com/jsGanttImproved/jsgantt-improved/master/dist/jsgantt.css
# Provides IFCJSON functionality
# TODO: replace with main repo if https://github.com/IFCJSON-Team/IFC2JSON_python/pull/3 is merged.
cd build && wget -O ifc2json.zip https://github.com/Moult/IFC2JSON_python/archive/refs/heads/feature-ios-v0.8.0.zip
cd build && wget -O ifc2json.zip https://github.com/IFCJSON-Team/IFC2JSON_python/archive/refs/heads/master.zip
cd build && unzip ifc2json.zip && rm ifc2json.zip
# IFCJSON doesn't have pyproject.toml, so we use python command.
cd build && . env/$(VENV_ACTIVATE) && cd IFC2JSON_python-*/file_converters && \
+27 -11
View File
@@ -28,6 +28,10 @@ IN_BLENDER = sys.modules.get("bpy", None)
if IN_BLENDER:
import bpy
# This file is executed twice - first as a bonsai-extension
# and then as a bonsai-package.
IN_PACKAGE = __package__ == "bonsai"
import re
import platform
import traceback
@@ -36,7 +40,8 @@ import uuid
import shutil
from collections import deque
from pathlib import Path
from typing import Union, Any, Generator
from typing import Union, Any
from collections.abc import Generator
last_commit_hash = "8888888"
@@ -218,17 +223,21 @@ if IN_BLENDER:
info["binary_python_version"] = version
return info
try:
import git
def update_commit_data() -> None:
try:
import git
# We can't just use __file__ as bonsai/__init__.py is typically not symlinked
# as Blender have errors symlinking main addon package file.
path = Path(__file__).resolve().parent
repo = git.Repo(str(path), search_parent_directories=True)
last_commit_hash = repo.head.object.hexsha
last_commit_date = repo.head.object.committed_datetime.isoformat()
except:
pass
global last_commit_hash
global last_commit_date
path = Path(__file__).resolve().parent
repo = git.Repo(str(path), search_parent_directories=True)
last_commit_hash = repo.head.object.hexsha
last_commit_date = repo.head.object.committed_datetime.isoformat()
except:
pass
if IN_PACKAGE:
update_commit_data()
try:
import ifcopenshell.api
@@ -244,6 +253,12 @@ if IN_BLENDER:
ifcopenshell.api.add_pre_listener("*", "action_logger", log_api)
def purge_cache():
"""Purge cache left from previous session (e.g. after reload or update)."""
import bonsai.tool as tool
tool.Blender.get_bonsai_version.cache_clear()
def register():
if platform.system() == "Windows":
clean_up_dlls_safe_links()
@@ -261,6 +276,7 @@ if IN_BLENDER:
bonsai.REINSTALLED_BBIM_VERSION = current_version
bonsai.bim.register()
purge_cache()
def unregister():
if platform.system() == "Windows":
+15 -6
View File
@@ -20,8 +20,10 @@ import os
import bpy
import bpy.utils.previews
import importlib
from bpy_extras.io_utils import ImportHelper, ExportHelper
from . import handler, ui, prop, operator
from typing import Callable, Union
from typing import Union
from collections.abc import Callable
try:
from bonsai.translations import translations_dict
@@ -95,14 +97,16 @@ for name in modules.keys():
classes = [
operator.AddIfcFile,
operator.BIM_OT_add_section_plane,
operator.BIM_OT_delete_object,
operator.BIM_OT_remove_section_plane,
operator.BIM_OT_select_entity,
operator.BIM_OT_select_entity_by_guid,
operator.BIM_OT_select_object,
operator.BIM_OT_show_description,
operator.BIM_OT_multiple_file_selector,
operator.BIM_OT_attribute_add_subitem,
operator.BIM_OT_attribute_remove_subitem,
operator.ClippingPlaneCutWithCappings,
operator.CloseBlendWarning,
operator.CloseError,
@@ -114,15 +118,16 @@ classes = [
operator.OpenUpstream,
operator.OpenUri,
operator.ReloadIfcFile,
operator.RemoveIfcFile,
operator.RevertClippingPlaneCut,
operator.SelectDir,
operator.SelectIfcFile,
operator.SelectURIAttribute,
operator.SetTab,
operator.SwitchTab,
operator.ShowSystemInfo,
prop.StrProperty,
operator.BIM_OT_enum_property_search, # /!\ Register AFTER prop.StrProperty
operator.BIM_OT_attribute_search_values,
prop.ObjProperty,
prop.MultipleFileSelect,
prop.Attribute,
@@ -131,7 +136,6 @@ classes = [
prop.BIMProperties,
prop.IfcParameter,
prop.PsetQto,
prop.GlobalId,
prop.BIMObjectProperties,
prop.BIMCollectionProperties,
prop.BIMMeshProperties,
@@ -141,7 +145,7 @@ classes = [
prop.BIMSnapGroups,
ui.BIM_UL_clipping_plane,
ui.BIM_UL_generic,
ui.BIM_UL_topics,
ui.DocPreferences,
ui.BIM_ADDON_preferences,
# Tabs panel
ui.BIM_PT_tabs,
@@ -149,10 +153,10 @@ classes = [
ui.BIM_PT_tab_new_project_wizard,
ui.BIM_PT_tab_project_info,
ui.BIM_PT_tab_spatial,
ui.BIM_PT_tab_grouping_and_filtering,
ui.BIM_PT_tab_project_setup,
ui.BIM_PT_tab_geometry,
ui.BIM_PT_tab_stakeholders,
ui.BIM_PT_tab_grouping_and_filtering,
# Object information
ui.BIM_PT_tab_object_metadata,
ui.BIM_PT_tab_misc,
@@ -221,7 +225,12 @@ def on_register(scene):
def register():
for cls in classes:
# 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)
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)
@@ -47,8 +47,6 @@
"scene.render.simplify_gpencil_shader_fx": true,
"scene.render.simplify_gpencil_tint": true,
"scene.render.simplify_gpencil_view_fill": true,
"scene.render.simplify_shadows": 1.0,
"scene.render.simplify_shadows_render": 1.0,
"scene.render.simplify_subdivision": 6,
"scene.render.simplify_subdivision_render": 6,
"scene.render.simplify_volumes": 1.0,
@@ -147,7 +145,7 @@
"scene.display.shading.use_scene_world_render": false,
"scene.display.shading.use_studiolight_view_rotation": true,
"scene.display.shading.use_world_space_lighting": false,
"scene.display.shading.wireframe_color_type": "MATERIAL",
"scene.display.shading.wireframe_color_type": "THEME",
"scene.display.shading.xray_alpha": 0.5,
"scene.display.shading.xray_alpha_wireframe": 0.0,
"scene.display.light_direction": [
@@ -162,7 +160,6 @@
"scene.display.shadow_focus": 0.0,
"scene.display.shadow_shift": 0.10000000149011612,
"scene.display.viewport_aa": "FXAA",
"space.overlay.backwire_opacity": 0.5,
"space.overlay.bone_wire_alpha": 1.0,
"space.overlay.display_handle": "SELECTED",
"space.overlay.fade_inactive_alpha": 0.4000000059604645,
@@ -189,7 +186,6 @@
"space.overlay.show_edge_crease": true,
"space.overlay.show_edge_seams": true,
"space.overlay.show_edge_sharp": true,
"space.overlay.show_edges": false,
"space.overlay.show_extra_edge_angle": false,
"space.overlay.show_extra_edge_length": false,
"space.overlay.show_extra_face_angle": false,
@@ -208,7 +204,6 @@
"space.overlay.show_motion_paths": true,
"space.overlay.show_object_origins": false,
"space.overlay.show_object_origins_all": false,
"space.overlay.show_occlude_wire": false,
"space.overlay.show_onion_skins": false,
"space.overlay.show_ortho_grid": true,
"space.overlay.show_outline_selected": true,
@@ -298,8 +293,6 @@
"scene.render.simplify_gpencil_shader_fx": true,
"scene.render.simplify_gpencil_tint": true,
"scene.render.simplify_gpencil_view_fill": true,
"scene.render.simplify_shadows": 1.0,
"scene.render.simplify_shadows_render": 1.0,
"scene.render.simplify_subdivision": 6,
"scene.render.simplify_subdivision_render": 6,
"scene.render.simplify_volumes": 1.0,
@@ -398,7 +391,7 @@
"scene.display.shading.use_scene_world_render": false,
"scene.display.shading.use_studiolight_view_rotation": true,
"scene.display.shading.use_world_space_lighting": false,
"scene.display.shading.wireframe_color_type": "MATERIAL",
"scene.display.shading.wireframe_color_type": "THEME",
"scene.display.shading.xray_alpha": 0.5,
"scene.display.shading.xray_alpha_wireframe": 0.0,
"scene.display.light_direction": [
@@ -413,7 +406,6 @@
"scene.display.shadow_focus": 0.0,
"scene.display.shadow_shift": 0.10000000149011612,
"scene.display.viewport_aa": "FXAA",
"space.overlay.backwire_opacity": 0.5,
"space.overlay.bone_wire_alpha": 1.0,
"space.overlay.display_handle": "SELECTED",
"space.overlay.fade_inactive_alpha": 0.4000000059604645,
@@ -440,7 +432,6 @@
"space.overlay.show_edge_crease": true,
"space.overlay.show_edge_seams": true,
"space.overlay.show_edge_sharp": true,
"space.overlay.show_edges": false,
"space.overlay.show_extra_edge_angle": false,
"space.overlay.show_extra_edge_length": false,
"space.overlay.show_extra_face_angle": false,
@@ -459,7 +450,6 @@
"space.overlay.show_motion_paths": true,
"space.overlay.show_object_origins": false,
"space.overlay.show_object_origins_all": false,
"space.overlay.show_occlude_wire": false,
"space.overlay.show_onion_skins": false,
"space.overlay.show_ortho_grid": true,
"space.overlay.show_outline_selected": true,
@@ -509,6 +499,10 @@
0.05087608844041824,
0.05087608844041824
],
"scene.eevee.use_shadows": true,
"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",
@@ -520,7 +514,7 @@
"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",
"scene.render.engine": "BLENDER_EEVEE_NEXT",
"scene.render.film_transparent": false,
"scene.render.filter_size": 1.5,
"scene.render.fps": 24,
@@ -549,8 +543,6 @@
"scene.render.simplify_gpencil_shader_fx": true,
"scene.render.simplify_gpencil_tint": true,
"scene.render.simplify_gpencil_view_fill": true,
"scene.render.simplify_shadows": 1.0,
"scene.render.simplify_shadows_render": 1.0,
"scene.render.simplify_subdivision": 6,
"scene.render.simplify_subdivision_render": 6,
"scene.render.simplify_volumes": 1.0,
@@ -649,7 +641,7 @@
"scene.display.shading.use_scene_world_render": false,
"scene.display.shading.use_studiolight_view_rotation": true,
"scene.display.shading.use_world_space_lighting": false,
"scene.display.shading.wireframe_color_type": "MATERIAL",
"scene.display.shading.wireframe_color_type": "THEME",
"scene.display.shading.xray_alpha": 0.5,
"scene.display.shading.xray_alpha_wireframe": 0.0,
"scene.display.light_direction": [
@@ -664,7 +656,6 @@
"scene.display.shadow_focus": 0.0,
"scene.display.shadow_shift": 0.10000000149011612,
"scene.display.viewport_aa": "FXAA",
"space.overlay.backwire_opacity": 0.5,
"space.overlay.bone_wire_alpha": 1.0,
"space.overlay.display_handle": "SELECTED",
"space.overlay.fade_inactive_alpha": 0.4000000059604645,
@@ -691,7 +682,6 @@
"space.overlay.show_edge_crease": true,
"space.overlay.show_edge_seams": true,
"space.overlay.show_edge_sharp": true,
"space.overlay.show_edges": false,
"space.overlay.show_extra_edge_angle": false,
"space.overlay.show_extra_edge_length": false,
"space.overlay.show_extra_face_angle": false,
@@ -710,7 +700,6 @@
"space.overlay.show_motion_paths": true,
"space.overlay.show_object_origins": true,
"space.overlay.show_object_origins_all": false,
"space.overlay.show_occlude_wire": false,
"space.overlay.show_onion_skins": false,
"space.overlay.show_ortho_grid": true,
"space.overlay.show_outline_selected": true,
@@ -1,11 +1,11 @@
ISO-10303-21;
HEADER;
FILE_DESCRIPTION((),'2;1');
FILE_NAME('EPset_Drawing.ifc','2020-01-01T00:00:00',(),(),'EPset_Drawing','EPset_Drawing',$);
FILE_DESCRIPTION($,'2;1');
FILE_NAME('EPset_Drawing.ifc','2020-01-01T00:00:00',$,$,'EPset_Drawing','EPset_Drawing',$);
FILE_SCHEMA(('IFC4'));
ENDSEC;
DATA;
#1=IFCPROPERTYSETTEMPLATE('2JhNIvqZrFnAgxfhK0XVQX',$,'EPset_Drawing','',.PSET_OCCURRENCEDRIVEN.,'IfcAnnotation/DRAWING',(#2,#3,#4,#5,#6,#7,#8,#9,#10,#11,#12,#13,#14,#15,#16,#17,#18,#19,#20,#21,#22,#23,#24,#25,#26,#27));
#1=IFCPROPERTYSETTEMPLATE('2JhNIvqZrFnAgxfhK0XVQX',$,'EPset_Drawing','',.PSET_OCCURRENCEDRIVEN.,'IfcAnnotation/DRAWING',(#23,#22,#27,#24,#19,#12,#26,#9,#8,#7,#6,#4,#18,#11,#5,#20,#25,#14,#10,#17,#28,#16,#3,#21,#13,#15,#2));
#2=IFCSIMPLEPROPERTYTEMPLATE('23JavTMk98ZxXhrUEnjAcf',$,'TargetView','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
#3=IFCSIMPLEPROPERTYTEMPLATE('1yVWUt5H9DAOuu0OaMMLpe',$,'Scale','The scale of this drawing represented as a numerator and denominator, such as 1/100',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
#4=IFCSIMPLEPROPERTYTEMPLATE('3gsuPBtU93b8f0gg1pjkq6',$,'HumanScale','The scale of this drawing in human readable format, such as 1:100',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
@@ -14,7 +14,7 @@ DATA;
#7=IFCSIMPLEPROPERTYTEMPLATE('2j2ZEZR8X5tONm7kli5hM6',$,'HasLinework','',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
#8=IFCSIMPLEPROPERTYTEMPLATE('1ttChRysH9UuEX2FeMj5Hu',$,'HasAnnotation','',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
#9=IFCSIMPLEPROPERTYTEMPLATE('2NPPxuABv1huDTVh32TFgw',$,'GlobalReferencing','',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
#10=IFCSIMPLEPROPERTYTEMPLATE('10hT_1zrzEbRRKMXYAWvtD',$,'Metadata','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.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.);
#13=IFCSIMPLEPROPERTYTEMPLATE('0c1$8NpYDEaBiJrj16jHIo',$,'Stylesheet','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
@@ -32,5 +32,6 @@ DATA;
#25=IFCSIMPLEPROPERTYTEMPLATE('3LHwCrOcb6Y8ozfJZ7Ay$c',$,'LineworkMode','Method to use for line work',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
#26=IFCSIMPLEPROPERTYTEMPLATE('2iwERDOW55Pf4hCbuFRe1Q',$,'FillMode','Method to fill areas seen in projection',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
#27=IFCSIMPLEPROPERTYTEMPLATE('1YF$qLzBzF19Io8aB2N8cE',$,'CutMode','Method for cutting geometry',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
#28=IFCSIMPLEPROPERTYTEMPLATE('1YSnFzurrEyRNtoLdmmddP',$,'BringToFront','The objects with these SVG classes will render in front of all other objects.Ex: IfcBeam, IfcColumn',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
ENDSEC;
END-ISO-10303-21;
@@ -0,0 +1 @@
SVG [mustache](https://mustache.github.io/) templates that will be used for sheets and fill be filled with infromation from the sheet's IfcDocumentInformation attributes (e.g. Identification, Name, Revision, etc).
@@ -21,7 +21,7 @@
/>
<script
type="text/javascript"
src="https://code.jquery.com/jquery-3.6.0.min.js"
src="/static/js/jquery.min.js"
></script>
<script
type="text/javascript"
@@ -19,7 +19,7 @@
/>
<script
type="text/javascript"
src="https://code.jquery.com/jquery-3.6.0.min.js"
src="/static/js/jquery.min.js"
></script>
<script
type="text/javascript"
@@ -24,7 +24,7 @@
/>
<script
type="text/javascript"
src="https://code.jquery.com/jquery-3.6.0.min.js"
src="/static/js/jquery.min.js"
></script>
<script
type="text/javascript"
@@ -17,7 +17,7 @@
/>
<script
type="text/javascript"
src="https://code.jquery.com/jquery-3.6.0.min.js"
src="/static/js/jquery.min.js"
></script>
<script
type="text/javascript"
@@ -21,7 +21,7 @@
/>
<script
type="text/javascript"
src="https://code.jquery.com/jquery-3.6.0.min.js"
src="/static/js/jquery.min.js"
></script>
<script
type="text/javascript"
+5 -35
View File
@@ -47,7 +47,6 @@ class IfcExporter:
self.set_header()
IfcStore.update_cache()
self.sync_all_objects()
self.sync_edited_objects()
tool.Project.save_linked_models_to_ifc()
extension = self.ifc_export_settings.output_file.split(".")[-1].lower()
if extension == "ifczip":
@@ -77,11 +76,7 @@ class IfcExporter:
def set_header(self):
self.file.wrapped_data.header.file_name.name = os.path.basename(self.ifc_export_settings.output_file)
self.file.wrapped_data.header.file_name.time_stamp = (
datetime.datetime.utcnow()
.replace(tzinfo=datetime.timezone.utc)
.astimezone()
.replace(microsecond=0)
.isoformat()
datetime.datetime.utcnow().replace(tzinfo=datetime.UTC).astimezone().replace(microsecond=0).isoformat()
)
self.file.wrapped_data.header.file_name.preprocessor_version = "IfcOpenShell {}".format(ifcopenshell.version)
self.file.wrapped_data.header.file_name.originating_system = "{} {}".format(
@@ -101,41 +96,12 @@ class IfcExporter:
result = self.sync_object_placement(obj)
if result:
results.append(result)
result = self.sync_object_material(obj)
# TODO: sync_object_material always returns None
# so it's never really appended
if result:
results.append(result)
except ReferenceError:
pass # The object is likely deleted
return results
def sync_edited_objects(self) -> list[ifcopenshell.entity_instance]:
results: list[ifcopenshell.entity_instance] = []
for obj in IfcStore.edited_objs.copy():
if not obj:
continue
if not tool.Blender.is_valid_data_block(obj):
continue
element = tool.Ifc.get_entity(obj)
if element:
results.append(element)
bpy.ops.bim.update_representation(obj=obj.name)
IfcStore.edited_objs.clear()
return results
def sync_object_material(self, obj: bpy.types.Object) -> None:
if not obj.data or not isinstance(obj.data, bpy.types.Mesh):
return
if not self.has_changed_materials(obj):
return
bpy.ops.bim.update_representation(obj=obj.name)
def has_changed_materials(self, obj: bpy.types.Object) -> bool:
mprops = tool.Geometry.get_mesh_props(obj.data)
checksum = mprops.material_checksum
return checksum != tool.Geometry.get_material_checksum(obj)
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):
@@ -173,6 +139,10 @@ class IfcExporter:
class IfcExportSettings:
"""
Initialize only using `IfcExportSettings.factory()`.
"""
def __init__(self):
self.logger: Logger = None
self.output_file: str = None
+67 -14
View File
@@ -24,16 +24,18 @@ import ifcopenshell.util.unit
import ifcopenshell.api.owner.settings
import bonsai.bim
import bonsai.tool as tool
import weakref
from bpy.app.handlers import persistent
from bonsai.bim.ifc import IfcStore
from bonsai.bim.module.model.data import AuthoringData
from bonsai.bim.module.aggregate.decorator import AggregateDecorator
from bonsai.bim.module.georeference.decorator import GeoreferenceDecorator
from bonsai.bim.module.model.decorator import WallAxisDecorator, SlabDirectionDecorator
from bonsai.bim.module.model.decorator import WallAxisDecorator, SlabDirectionDecorator, BoundingBoxDecorator
from bonsai.bim.module.nest.decorator import NestDecorator
from mathutils import Vector
from math import cos
from typing import Union, Callable
from typing import Union
from collections.abc import Callable
cwd = os.path.dirname(os.path.realpath(__file__))
@@ -256,31 +258,73 @@ def redo_post(scene: bpy.types.Scene) -> None:
tool.Ifc.rebuild_element_maps()
# Cache is important as those entities will be retrieved very often,
# for every IfcOwnerHistory creation or update.
class SettingsCache:
APPLICATION_ID: Union[int, None] = None
USER_ID: Union[int, None] = None
_file: Union[weakref.ReferenceType[ifcopenshell.file], None] = None
@classmethod
def get_file(cls) -> Union[ifcopenshell.file, None]:
if cls._file is None:
return None
return cls._file()
@classmethod
def set_file(cls, file: ifcopenshell.file) -> None:
cls._file = weakref.ref(file)
def get_application(ifc: ifcopenshell.file) -> ifcopenshell.entity_instance:
# TODO: cache this for even faster application retrieval. It honestly makes a difference on long scripts.
version = tool.Blender.get_bonsai_version()
if SettingsCache.get_file() is ifc and SettingsCache.APPLICATION_ID is not None:
try:
app = ifc.by_id(SettingsCache.APPLICATION_ID)
return app
except RuntimeError:
pass
# Use only main part from the version to avoid flooding advanced users projects with IfcApplications.
version = tool.Blender.get_bonsai_version().split("-")[0]
identifier = f"Bonsai-{version}"
for element in ifc.by_type("IfcApplication"):
if element.ApplicationIdentifier == "Bonsai" and element.Version == version:
if element.ApplicationIdentifier == identifier:
return element
return ifcopenshell.api.run(
"owner.add_application",
application_developer = next((org for org in ifc.by_type("IfcOrganization") if org.Name == "IfcOpenShell"), None)
application = ifcopenshell.api.owner.add_application(
ifc,
application_developer=application_developer,
version=version,
application_full_name="Bonsai",
application_identifier="Bonsai",
application_identifier=identifier,
)
SettingsCache.APPLICATION_ID = application.id()
SettingsCache.set_file(ifc)
return application
def get_user(ifc: ifcopenshell.file) -> Union[ifcopenshell.entity_instance, None]:
# TODO: cache this for even faster application retrieval. It honestly makes a difference on long scripts.
if SettingsCache.get_file() is ifc and SettingsCache.USER_ID is not None:
try:
user = ifc.by_id(SettingsCache.USER_ID)
return user
except RuntimeError:
pass
if pao := next(iter(ifc.by_type("IfcPersonAndOrganization")), None):
SettingsCache.USER_ID = pao.id()
SettingsCache.set_file(ifc)
return pao
elif ifc.schema == "IFC2X3":
if (person := next(iter(ifc.by_type("IfcPerson")), None)) is None:
person = tool.Ifc.run("owner.add_person")
person = ifcopenshell.api.owner.add_person(ifc)
if (organization := next(iter(ifc.by_type("IfcOrganization")), None)) is None:
organization = tool.Ifc.run("owner.add_organisation")
pao = tool.Ifc.run("owner.add_person_and_organisation", person=person, organisation=organization)
organization = ifcopenshell.api.owner.add_organisation(ifc)
pao = ifcopenshell.api.owner.add_person_and_organisation(ifc, person=person, organisation=organization)
SettingsCache.USER_ID = pao.id()
SettingsCache.set_file(ifc)
return pao
@@ -347,9 +391,14 @@ def load_post(scene):
# Bonsai overlays
georeference_props = tool.Georeference.get_georeference_props()
aggregate_props = bpy.context.scene.BIMAggregateProperties
nest_props = bpy.context.scene.BIMNestProperties
aggregate_props = tool.Aggregate.get_aggregate_props()
nest_props = tool.Nest.get_nest_props()
model_props = tool.Model.get_model_props()
GeoreferenceDecorator.uninstall()
AggregateDecorator.uninstall()
NestDecorator.uninstall()
WallAxisDecorator.uninstall()
SlabDirectionDecorator.uninstall()
if georeference_props.should_visualise:
GeoreferenceDecorator.install(bpy.context)
if aggregate_props.aggregate_decorator:
@@ -360,9 +409,13 @@ def load_post(scene):
WallAxisDecorator.install(bpy.context)
if model_props.show_slab_direction:
SlabDirectionDecorator.install(bpy.context)
if model_props.show_bounding_box:
BoundingBoxDecorator.install(bpy.context)
if scene := bpy.context.scene:
if preferences.should_use_snap and (scene := bpy.context.scene):
# Snapping is off by default in Blender, but in BIM, it's more useful to be on
scene.tool_settings.use_snap = True
# Match default Bonsai snaps
scene.tool_settings.snap_elements_base = {"EDGE", "EDGE_PERPENDICULAR", "VERTEX", "EDGE_MIDPOINT", "FACE"}
tool.Blender.sync_old_preferences()
+114 -25
View File
@@ -27,7 +27,9 @@ import ifcopenshell.util.element
import ifcopenshell.util.unit
from ifcopenshell.util.doc import get_attribute_doc, get_predefined_type_doc, get_property_doc
import bonsai.tool as tool
from typing import Optional, Callable, Any, Union, Iterable, TYPE_CHECKING
from types import EllipsisType
from typing import Optional, Any, Union, TYPE_CHECKING
from collections.abc import Callable, Iterable
if TYPE_CHECKING:
import bonsai.bim.prop
@@ -38,6 +40,9 @@ if TYPE_CHECKING:
# - None - property should be imported by default workflow
# - True - setting value for imported attribute should be skipped
# - False - property should be skipped entirely from import
# Second argument is optional,
# because ImportCallback might be called for attributes that are not created by default
# (e.g. IFC entity attributes).
ImportCallback = Callable[[str, Optional[bonsai.bim.prop.Attribute], dict[str, Any]], Union[bool, None]]
# ExportCallback return values:
# - True - property should be skipped entirely from export
@@ -51,30 +56,61 @@ def draw_attributes(
copy_operator: Optional[str] = None,
popup_active_attribute: Optional[bonsai.bim.prop.Attribute] = None,
callback: Optional[Callable[[bonsai.bim.prop.Attribute, bpy.types.UILayout], None]] = None,
*,
enable_search: Union[bool, EllipsisType] = ...,
) -> None:
"""Draw editable UI for prop.Attributes.
You can set attribute active in popup with `active_attribute`
meaning you will be able to type into attribute's field without having to click
on it first
:param enable_search: Add search button to string, integer, and float attributes
Possible values:
- ``...`` (default value) -
add search if possible. If it's not possible, there will be no warnings or errors.
- ``True`` - always add search, if it's not possible it will result in errors.
- ``False`` - never add search.
"""
for attribute in props:
row = layout.row(align=True)
if attribute == popup_active_attribute:
row.activate_init = True
draw_attribute(attribute, row, copy_operator)
draw_attribute(attribute, row, copy_operator, enable_search=enable_search)
if callback:
callback(attribute, row)
def draw_attribute(
attribute: bonsai.bim.prop.Attribute, layout: bpy.types.UILayout, copy_operator: Optional[str] = None
attribute: bonsai.bim.prop.Attribute,
layout: bpy.types.UILayout,
copy_operator: Optional[str] = None,
enable_search: Union[bool, EllipsisType] = ...,
) -> None:
value_name = attribute.get_value_name(display_only=True)
if value_name == "enum_value":
prop_with_search(layout, attribute, "enum_value", text=attribute.name)
elif value_name == "filepath_value":
attribute.filepath_value.layout_file_select(layout, filter_glob=attribute.filter_glob, text=attribute.name)
elif value_name == "subitems_values":
col = layout.column()
layout = col.row(align=True)
layout.label(text=f"{attribute.name}:")
data_path = tool.Blender.get_full_data_path(attribute, value_name)
for i, item in enumerate(attribute.subitems_values, 1):
row = col.row(align=True)
row.alignment = "EXPAND"
row.prop(item, "name", text=f"# {i}")
op = row.operator("bim.attribute_remove_subitem", text="", icon="X")
op.data_path = data_path
op.index = i - 1
op = layout.operator("bim.attribute_add_subitem", icon="ADD", text="")
op.data_path = data_path
elif attribute.name in ("ScheduleDuration", "ActualDuration", "FreeFloat", "TotalFloat"):
props = tool.Sequence.get_work_schedule_props()
for item in props.durations_attributes:
@@ -95,17 +131,34 @@ def draw_attribute(
text=attribute.display_name,
)
if attribute.is_uri:
if attribute.special_type == "URI":
op = layout.operator("bim.select_uri_attribute", text="", icon="FILE_FOLDER")
op.data_path = attribute.path_from_id("string_value")
op.attribute_data_path = tool.Blender.get_full_data_path(attribute)
elif attribute.special_type in ("DATE", "DATETIME"):
op = layout.operator("bim.datepicker", text="", icon="TIME")
op.target_prop = attribute.path_from_id("string_value")
op.include_time = attribute.special_type == "DATETIME"
if attribute.data_type in ("string", "integer", "float") and (
enable_search is True or (enable_search is ... and attribute.ifc_class)
):
op = layout.operator("bim.attribute_search_values", text="", icon="VIEWZOOM")
op.attribute_name = attribute.name
op.attribute_ifc_class = attribute.ifc_class
op.data_path = tool.Blender.get_full_data_path(attribute, value_name)
op.data_type = attribute.data_type
if attribute.is_optional:
layout.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
if attribute.use_explorer_ui:
op = layout.operator("bim.explorer_show_ui_popup", text="", icon="ZOOM_SELECTED")
op.ifc_class = attribute.ifc_class
op.attribute_name = attribute.name
op.data_path = tool.Blender.get_full_data_path(attribute, value_name)
if ifc_id := attribute.get_value():
op.preselect_ifc_id = int(ifc_id)
if attribute.name == "GlobalId":
layout.operator("bim.generate_global_id", icon="FILE_REFRESH", text="")
elif copy_operator:
@@ -114,28 +167,22 @@ def draw_attribute(
def import_attributes(
ifc_class: str,
props: bpy.types.bpy_prop_collection_idprop[Attribute],
data: dict[str, Any],
callback: Optional[ImportCallback] = None,
) -> None:
schema = tool.Ifc.schema()
for attribute in schema.declaration_by_name(ifc_class).all_attributes():
import_attribute(attribute, props, data, callback=callback)
# A more elegant attribute importer signature, intended to supersede import_attributes
def import_attributes2(
element: Union[str, ifcopenshell.entity_instance],
props: bpy.types.bpy_prop_collection_idprop[Attribute],
callback: Optional[ImportCallback] = None,
) -> None:
"""
:param element: Entity or IFC class string.
"""
info: dict[str, Any]
if isinstance(element, str):
attributes = tool.Ifc.schema().declaration_by_name(element).as_entity().all_attributes()
assert (entity := tool.Ifc.schema().declaration_by_name(element).as_entity())
attributes = entity.all_attributes()
info = {a.name(): None for a in attributes}
info["type"] = element
else:
attributes = element.wrapped_data.declaration().as_entity().all_attributes()
assert (entity := element.wrapped_data.declaration().as_entity())
attributes = entity.all_attributes()
info = element.get_info()
for attribute in attributes:
import_attribute(attribute, props, info, callback=callback)
@@ -149,15 +196,19 @@ def import_attribute(
) -> None:
data_type = ifcopenshell.util.attribute.get_primitive_type(attribute)
# Complex data types (aggregates and entities) are handled only by callback.
if data_type == ("list", "string"):
data_type = "list[string]"
if isinstance(data_type, tuple) or data_type == "entity":
callback(attribute.name(), None, data) if callback else None
return
new: bonsai.bim.prop.Attribute = props.add()
new = props.add()
new.name = attribute.name()
new.is_null = data[attribute.name()] is None
new.is_optional = attribute.optional()
new.data_type = data_type if isinstance(data_type, str) else ""
new.ifc_class = data["type"]
is_handled_by_callback = callback(attribute.name(), new, data) if callback else None
data_type = new.data_type # Allow callback to override data type.
@@ -167,8 +218,9 @@ def import_attribute(
props.remove(len(props) - 1)
elif data_type == "string":
new.string_value = "" if new.is_null else str(data[attribute.name()]).replace("\n", "\\n")
if attribute.type_of_attribute().declared_type().name() == "IfcURIReference":
new.is_uri = True
attribute_type = attribute.type_of_attribute()
if attribute_type._is("IfcURIReference"):
new.special_type = "URI"
elif attribute.type_of_attribute()._is("IfcDate"):
new.special_type = "DATE"
elif attribute.type_of_attribute()._is("IfcDateTime"):
@@ -202,6 +254,12 @@ def import_attribute(
if enum_value is not None:
new.enum_value = enum_value
elif data_type == "list[string]":
value: Union[list[str], None] = data[attribute.name()]
if value:
for item in value:
new.subitems_values.add().name = str(item).replace("\n", "\\n")
add_attribute_description(new, data)
add_attribute_min_max(attribute, new)
@@ -269,6 +327,16 @@ def export_attributes(
return attributes
def process_exported_entity_attribute(attributes: dict[str, Any], attribute_names: list[str]) -> None:
for attribute_name in attribute_names:
entity_id = attributes[attribute_name]
if entity_id is None:
# Maybe it was removed by now and enum is invalid.
del attributes[attribute_name]
else:
attributes[attribute_name] = tool.Ifc.get().by_id(int(entity_id))
ENUM_ITEMS_DATA = Union[bpy.types.PropertyGroup, bpy.types.ID, bpy.types.Operator, bpy.types.OperatorProperties]
@@ -351,8 +419,23 @@ def get_enum_items(
return items
def draw_expandable_panel(layout, context, label: str, ui_func, default_closed: bool = True):
header, panel = layout.panel(label, default_closed=default_closed)
def draw_expandable_panel(
layout: bpy.types.UILayout,
context: bpy.types.Context,
label: str,
ui_func: Callable[[bpy.types.UILayout, bpy.types.Context], None],
default_closed: bool = True,
*,
panel_id: str = "",
) -> None:
"""
:param panel_id: Optional unique identifier for the panel.
By default is matching ``label``, but if more than one panel with the same name is used,
then ``panel_id`` can be provided explicitly to ensure panels can be expanded/collapsed separately.
"""
if not panel_id:
panel_id = label
header, panel = layout.panel(panel_id, default_closed=default_closed)
header.label(text=label)
if panel:
ui_func(panel, context)
@@ -392,10 +475,16 @@ def draw_filter(
row = layout.row(align=True)
row.label(text=f"{len(data.data['saved_searches'])} Saved Searches")
row.operator("bim.select_entity_by_guid", text="", icon="CON_OBJECTSOLVER")
if data.data["saved_searches"]:
row.operator("bim.load_search", text="", icon="IMPORT").module = module
row.operator("bim.save_search", text="", icon="EXPORT").module = module
if module != "search":
if module == "drawing_include":
row.operator("bim.edit_element_filter", icon="CHECKMARK", text="").filter_mode = "INCLUDE"
if module == "drawing_exclude":
row.operator("bim.edit_element_filter", icon="CHECKMARK", text="").filter_mode = "EXCLUDE"
row.operator("bim.enable_editing_element_filter", icon="CANCEL", text="").filter_mode = "NONE"
row = layout.row(align=True)
row.operator("bim.add_filter_group", text="Add Search Group", icon="ADD").module = module
row.operator("bim.edit_filter_query", text="", icon="FILTER").module = module
+45 -15
View File
@@ -31,9 +31,11 @@ import ifcopenshell.ifcopenshell_wrapper
import bonsai
import bonsai.bim.handler
import bonsai.tool as tool
from ifcopenshell.file import UndoSystemError
from pathlib import Path
from bonsai.tool.brick import BrickStore
from typing import Set, Union, Optional, TypedDict, Callable, NotRequired
from typing import Union, Optional, TypedDict, NotRequired, Literal
from collections.abc import Callable
IFC_CONNECTED_TYPE = Union[bpy.types.Material, bpy.types.Object]
@@ -68,7 +70,7 @@ class IfcStore:
cache_path: Optional[str] = None
id_map: dict[int, IFC_CONNECTED_TYPE] = {}
guid_map: dict[str, IFC_CONNECTED_TYPE] = {}
edited_objs: Set[bpy.types.Object] = set()
edited_objs: set[bpy.types.Object] = set()
pset_template_path: str = ""
pset_template_file: Optional[ifcopenshell.file] = None
classification_path: str = ""
@@ -103,7 +105,7 @@ class IfcStore:
IfcStore.session_files = {}
@staticmethod
def get_file():
def get_file() -> ifcopenshell.file | None:
if IfcStore.file is None:
props = tool.Blender.get_bim_props()
IfcStore.set_path(props.ifc_file)
@@ -115,20 +117,20 @@ class IfcStore:
return IfcStore.file
@staticmethod
def set_path(value):
def set_path(value: str) -> None:
IfcStore.path = value
# Interpret relative paths as relative to .blend file.
if IfcStore.path and not os.path.isabs(IfcStore.path):
IfcStore.path = os.path.abspath(os.path.join(bpy.path.abspath("//"), IfcStore.path))
@staticmethod
def get_cache():
def get_cache() -> ifcopenshell.geom.serializers.hdf5 | None:
if IfcStore.cache is None and IfcStore.path:
props = tool.Blender.get_bim_props()
prefs = tool.Blender.get_addon_preferences()
ifc_key = IfcStore.path + IfcStore.file.wrapped_data.header.file_name.time_stamp
ifc_hash = hashlib.md5(ifc_key.encode("utf-8")).hexdigest()
os.makedirs(props.cache_dir, exist_ok=True)
IfcStore.cache_path = os.path.join(props.cache_dir, f"{ifc_hash}.h5")
os.makedirs(prefs.cache_dir, exist_ok=True)
IfcStore.cache_path = os.path.join(prefs.cache_dir, f"{ifc_hash}.h5")
cache_path = Path(IfcStore.cache_path)
cache_settings = ifcopenshell.geom.settings()
serializer_settings = ifcopenshell.geom.serializer_settings()
@@ -161,15 +163,15 @@ class IfcStore:
return IfcStore.cache
@staticmethod
def update_cache():
def update_cache() -> None:
if not IfcStore.cache:
return
assert IfcStore.cache_path
assert IfcStore.file
ifc_key = IfcStore.path + IfcStore.file.wrapped_data.header.file_name.time_stamp
ifc_hash = hashlib.md5(ifc_key.encode("utf-8")).hexdigest()
props = tool.Blender.get_bim_props()
new_cache_path = os.path.join(props.cache_dir, f"{ifc_hash}.h5")
prefs = tool.Blender.get_addon_preferences()
new_cache_path = os.path.join(prefs.cache_dir, f"{ifc_hash}.h5")
IfcStore.cache = None
try:
shutil.move(IfcStore.cache_path, new_cache_path)
@@ -181,11 +183,11 @@ class IfcStore:
IfcStore.get_cache()
@staticmethod
def load_file(path) -> None:
def load_file(path: str) -> None:
if not os.path.isfile(path):
return
extension = path.split(".")[-1]
props = tool.Project.get_project_props()
prefs = tool.Blender.get_addon_preferences()
if extension.lower() == "ifczip":
with tempfile.TemporaryDirectory() as unzipped_path:
with zipfile.ZipFile(path, "r") as zip_ref:
@@ -195,7 +197,7 @@ class IfcStore:
return
elif extension.lower() == "ifcxml":
IfcStore.file = ifcopenshell.file(ifcopenshell.ifcopenshell_wrapper.parse_ifcxml(path))
elif props.should_stream:
elif prefs.should_stream:
IfcStore.file = ifcopenshell.open(path, should_stream=True)
else:
IfcStore.file = ifcopenshell.open(path)
@@ -417,6 +419,32 @@ class IfcStore:
props = tool.Blender.get_object_bim_props(obj)
props.ifc_definition_id = 0
@staticmethod
def get_ifc_file_undo_callback(callback_type: Literal["UNDO", "REDO"]):
def callback(_) -> None:
try:
if callback_type == "UNDO":
tool.Ifc.get().undo()
else:
tool.Ifc.get().redo()
except Exception as e:
# Persistent callbacks errors are not visible from UI and we set `last_error`
# to make it visible.
error_msg = ""
# In theory it should always be UndoSystemError, but just to be safe.
if isinstance(e, UndoSystemError):
error_msg += "Undo transaction operations:\n"
transaction = e.transaction
for operation in transaction.operations:
error_msg += f"- {str(operation)}\n"
# Show it in system console too, not just in last message.
print(error_msg)
error_msg += traceback.format_exc()
bonsai.last_error = error_msg
raise
return callback
@staticmethod
def execute_ifc_operator(
operator: tool.Ifc.Operator,
@@ -446,7 +474,9 @@ class IfcStore:
if tool.Ifc.get():
tool.Ifc.get().end_transaction()
IfcStore.add_transaction_operation(
operator, rollback=lambda d: tool.Ifc.get().undo(), commit=lambda d: tool.Ifc.get().redo()
operator,
rollback=IfcStore.get_ifc_file_undo_callback("UNDO"),
commit=IfcStore.get_ifc_file_undo_callback("REDO"),
)
if BrickStore.graph is not None: # `if BrickStore.graph` by itself takes ages.
BrickStore.end_transaction()
+75 -30
View File
@@ -29,6 +29,7 @@ import numpy.typing as npt
import multiprocessing
import ifcopenshell
import ifcopenshell.geom
import ifcopenshell.ifcopenshell_wrapper as W
import ifcopenshell.util.unit
import ifcopenshell.util.element
import ifcopenshell.util.geolocation
@@ -38,7 +39,8 @@ import ifcopenshell.util.shape
import bonsai.tool as tool
from bonsai.bim.ifc import IfcStore, IFC_CONNECTED_TYPE
from bonsai.tool.loader import OBJECT_DATA_TYPE
from typing import Dict, Union, Optional, Any, Literal
from typing import Union, Optional, Any, Literal
from collections.abc import Iterable
from ifcopenshell.util.shape import MatrixType
@@ -47,7 +49,7 @@ class MaterialCreator:
obj: bpy.types.Object
def __init__(self, ifc_import_settings: IfcImportSettings, ifc_importer: IfcImporter):
self.styles: Dict[int, bpy.types.Material] = {}
self.styles: dict[int, bpy.types.Material] = {}
self.parsed_meshes: set[str] = set()
self.ifc_import_settings = ifc_import_settings
self.ifc_importer = ifc_importer
@@ -198,11 +200,16 @@ class MaterialCreator:
class IfcImporter:
file: ifcopenshell.file
"""Either provided by user as an attribute or will be loaded from ``input_file`` during ``execute()``."""
elements: set[ifcopenshell.entity_instance]
"""Set of IfcElements to import. Excluding ``gross_elements`` and ```native_elements``."""
def __init__(self, ifc_import_settings: IfcImportSettings):
self.ifc_import_settings = ifc_import_settings
tool.Loader.set_settings(ifc_import_settings)
self.diff = None
self.file: ifcopenshell.file = None
self.project = None
self.has_existing_project = False
# element guids to blender collections mapping
@@ -224,7 +231,8 @@ class IfcImporter:
self.progress = 0
self.material_creator = MaterialCreator(ifc_import_settings, self)
classes_to_wireframe_str = tool.Drawing.get_document_props().classes_to_wireframe
prefs = tool.Blender.get_addon_preferences()
classes_to_wireframe_str = prefs.doc.classes_to_wireframe
self.classes_to_wireframe_list = [word.strip() for word in classes_to_wireframe_str.split(",")]
def profile_code(self, message: str) -> None:
@@ -295,11 +303,11 @@ class IfcImporter:
tool.Spatial.run_spatial_import_spatial_decomposition()
if default_container := tool.Spatial.guess_default_container():
tool.Spatial.set_default_container(default_container)
tool.Loader.setup_active_bsdd_classification()
self.update_progress(100)
bpy.context.window_manager.progress_end()
def process_context_filter(self) -> None:
"""Setup contexts. Necessary for importing elements representations."""
contexts = self.file.by_type("IfcGeometricRepresentationContext")
if len(contexts) > 100: # Probably something strange happening. Encountered from Revizto.
print("Warning! Excessive contexts were found and merged where applicable.")
@@ -319,16 +327,15 @@ class IfcImporter:
tool.Loader.settings.gross_context_settings = tool.Loader.create_settings(is_gross=True)
def process_element_filter(self) -> None:
elements: list[ifcopenshell.entity_instance]
if self.ifc_import_settings.has_filter:
self.elements = self.ifc_import_settings.elements
if isinstance(self.elements, set):
self.elements = list(self.elements)
elements = list(self.ifc_import_settings.elements)
# TODO: enable filtering for annotations
else:
if self.file.schema in ("IFC2X3", "IFC4"):
self.elements = self.file.by_type("IfcElement") + self.file.by_type("IfcProxy")
elements = self.file.by_type("IfcElement") + self.file.by_type("IfcProxy")
else:
self.elements = self.file.by_type("IfcElement")
elements = self.file.by_type("IfcElement")
drawing_groups = [g for g in self.file.by_type("IfcGroup") if g.ObjectType == "DRAWING"]
drawing_annotations = set()
@@ -338,16 +345,16 @@ class IfcImporter:
self.annotations = set([a for a in self.file.by_type("IfcAnnotation")])
self.annotations -= drawing_annotations
self.elements = [e for e in self.elements if not e.is_a("IfcFeatureElement") or e.is_a("IfcSurfaceFeature")]
elements = [e for e in elements if not e.is_a("IfcFeatureElement") or e.is_a("IfcSurfaceFeature")]
if self.ifc_import_settings.element_limit_mode == "UNLIMITED":
self.elements = set(self.elements)
self.elements = set(elements)
else:
offset = self.ifc_import_settings.element_offset
offset_limit = offset + self.ifc_import_settings.element_limit
self.elements = set(self.elements[offset:offset_limit])
self.elements = set(elements[offset:offset_limit])
if self.ifc_import_settings.has_filter or self.ifc_import_settings.element_limit_mode != "UNLIMITED":
self.element_types = set([ifcopenshell.util.element.get_type(e) for e in self.elements])
self.element_types = {t for e in self.elements if (t := ifcopenshell.util.element.get_type(e))}
else:
self.element_types = set(self.file.by_type("IfcTypeProduct"))
@@ -385,6 +392,7 @@ class IfcImporter:
return results
def parse_native_elements(self) -> None:
# TODO: move to `process_element_filter` to incapsulate all `self.elements` logic.
if not self.ifc_import_settings.should_load_geometry:
return
if not self.file.by_type("IfcSweptDiskSolid"):
@@ -422,7 +430,8 @@ class IfcImporter:
rep = representation
while True:
if len(rep.Items) == 1 and rep.Items[0].is_a("IfcMappedItem"):
# Tekla 2023 has missing items, though it's invalid IFC.
if rep.Items and len(rep.Items) == 1 and rep.Items[0].is_a("IfcMappedItem"):
rep_matrix = ifcopenshell.util.placement.get_mappeditem_transformation(rep.Items[0])
if not np.allclose(rep_matrix, np.eye(4)):
matrix = rep_matrix @ matrix
@@ -589,6 +598,7 @@ class IfcImporter:
return self.create_generic_sqlite_elements(elements)
if self.ifc_import_settings.should_load_geometry:
elements = elements.copy() # Prevent argument mutation.
context_settings = (
tool.Loader.settings.gross_context_settings if is_gross else tool.Loader.settings.context_settings
)
@@ -653,7 +663,7 @@ class IfcImporter:
settings: Optional[ifcopenshell.geom.main.settings] = None,
) -> set[ifcopenshell.entity_instance]:
checkpoint = time.time()
results = set()
results: set[ifcopenshell.entity_instance] = set()
if not products:
return results
@@ -717,9 +727,15 @@ class IfcImporter:
def create_structural_point_connections(self):
for product in self.file.by_type("IfcStructuralPointConnection"):
# TODO: make this based off ifcopenshell. See #1409
representation: ifcopenshell.entity_instance = next(
rep for rep in product.Representation.Representations if rep.RepresentationType == "Vertex"
)
representation = tool.Structural.get_vertex_representation(product)
if not representation:
print(
"WARNING. Skipping invalid IfcStructuralPointConnection - "
f"element has no valid representation:\n{product}."
)
continue
mesh = tool.Loader.create_structural_point_connection_mesh(representation)
if mesh is None:
continue
@@ -843,7 +859,7 @@ class IfcImporter:
return obj
def load_existing_meshes(self) -> None:
self.meshes.update({m.name: m for m in bpy.data.meshes})
self.meshes.update({m.name: m for m in bpy.data.meshes if m.library is None})
def merge_materials_by_colour(self):
cleaned_materials = {}
@@ -1018,9 +1034,9 @@ class IfcImporter:
def create_curve(
self,
element: ifcopenshell.entity_instance,
shape: Union[ifcopenshell.geom.ShapeElementType, ifcopenshell.geom.ShapeType],
shape: Union[W.Triangulation, W.TriangulationElement],
) -> bpy.types.Curve:
if hasattr(shape, "geometry"):
if isinstance(shape, W.TriangulationElement):
geometry = shape.geometry
else:
geometry = shape
@@ -1053,7 +1069,7 @@ class IfcImporter:
cartesian_point_offset: Union[npt.NDArray[np.float64], Literal[False]] = None,
) -> Union[bpy.types.Mesh, None]:
try:
if hasattr(shape, "geometry"):
if isinstance(shape, ifcopenshell.geom.ShapeElementType):
# shape is ShapeElementType
geometry = shape.geometry
else:
@@ -1062,7 +1078,7 @@ class IfcImporter:
# Mesh may already exists (e.g. during representation reimport)
# and we assign some suffix to it to prevent Blender from adding '.001' suffix to the new mesh.
mesh_name = tool.Loader.get_mesh_name_from_shape(geometry)
if old_mesh := bpy.data.meshes.get(mesh_name):
if old_mesh := bpy.data.meshes.get((mesh_name, None)):
old_mesh.name = mesh_name + ".old"
mesh = bpy.data.meshes.new(mesh_name)
@@ -1124,7 +1140,22 @@ class IfcImporter:
bpy.ops.view3d.view_selected()
bpy.ops.object.select_all(action="DESELECT")
def setup_arrays(self):
def setup_arrays(
self,
*,
annotations_to_import: Optional[set[ifcopenshell.entity_instance]] = None,
openings_to_import: Optional[set[ifcopenshell.entity_instance]] = None,
) -> None:
# During base import some elements are skipped.
is_base_import = True
elements_to_import: set[ifcopenshell.entity_instance] = set()
if annotations_to_import is not None:
is_base_import = False
elements_to_import = annotations_to_import
elif openings_to_import is not None:
is_base_import = False
elements_to_import = openings_to_import
for pset in self.file.by_type("IfcPropertySet"):
if pset.Name != "BBIM_Array":
continue
@@ -1132,16 +1163,28 @@ class IfcImporter:
continue
data = json.loads(data)
for rel in pset.DefinesOccurrence:
element: ifcopenshell.entity_instance
for element in rel.RelatedObjects:
if is_base_import:
if element.is_a("IfcAnnotation") or element.is_a("IfcOpeningElement"):
continue
else:
if element not in elements_to_import:
continue
for i in range(len(data)):
tool.Blender.Modifier.Array.set_children_lock_state(element, i, True)
tool.Blender.Modifier.Array.constrain_children_to_parent(element)
class IfcImportSettings:
"""
Initialize only using `IfcImportSettings.factory()`.
"""
input_file: Union[str, None] = None
logger: logging.Logger
def __init__(self):
self.logger: logging.Logger = None
self.input_file = None
self.diff_file = None
self.geometry_library = "opencascade"
self.should_use_cpu_multiprocessing = True
@@ -1162,17 +1205,19 @@ class IfcImportSettings:
self.element_limit_mode = "UNLIMITED"
self.element_offset = 0
self.element_limit = 30000
self.has_filter = None
self.has_filter = False
self.should_filter_spatial_elements = True
self.should_setup_viewport_camera = True
self.contexts: list[ifcopenshell.entity_instance] = []
self.context_settings: list[ifcopenshell.geom.main.settings] = []
self.gross_context_settings: list[ifcopenshell.geom.main.settings] = []
self.elements: set[ifcopenshell.entity_instance] = set()
self.elements: Iterable[ifcopenshell.entity_instance] = set()
self.load_indexed_maps = False
@staticmethod
def factory(context=None, input_file=None, logger=None):
def factory(
context=None, input_file: Optional[str] = None, logger: Optional[logging.Logger] = None
) -> IfcImportSettings:
scene_diff = tool.Blender.get_diff_props()
props = tool.Project.get_project_props()
settings = IfcImportSettings()
@@ -71,11 +71,14 @@ class AggregateData:
@classmethod
def total_linked_aggregate(cls) -> Union[int, None]:
element = tool.Ifc.get_entity(bpy.context.active_object)
aggregate = ifcopenshell.util.element.get_aggregate(element)
if not aggregate:
return
if not element:
return
aggregate = ifcopenshell.util.element.get_aggregate(element)
parts = ifcopenshell.util.element.get_parts(element)
if not aggregate and not parts:
return
if parts:
aggregate = element
product_linked_agg_group = next(
(
@@ -20,6 +20,7 @@ import blf
import bpy
import gpu
import ifcopenshell
import ifcopenshell.util.element
import bonsai.tool as tool
from bpy.types import SpaceView3D
from bpy_extras import view3d_utils
@@ -131,6 +132,9 @@ class AggregateDecorator:
return shader
def draw_custom_batch(self, coords, color):
indices = None
if not tool.Blender.validate_shader_batch_data(coords, indices):
return
shader = self.dotted_line_shader()
arc_lengths = [0]
@@ -141,6 +145,7 @@ class AggregateDecorator:
shader,
"LINE_STRIP",
{"position": coords, "arcLength": arc_lengths},
indices=indices,
)
matrix = bpy.context.region_data.perspective_matrix
@@ -150,14 +155,15 @@ class AggregateDecorator:
batch.draw(shader)
def draw_batch(self, shader_type, content_pos, color, indices=None):
if not tool.Blender.validate_shader_batch_data(content_pos, indices):
return
shader = self.line_shader if shader_type == "LINES" else self.shader
batch = batch_for_shader(shader, shader_type, {"pos": content_pos}, indices=indices)
shader.uniform_float("color", color)
batch.draw(shader)
def draw_aggregate(self, context):
if context.scene.BIMAggregateProperties.in_aggregate_mode:
return
props = tool.Aggregate.get_aggregate_props()
self.addon_prefs = tool.Blender.get_addon_preferences()
decorator_color_special = self.addon_prefs.decorator_color_special
decorator_color_selected = self.addon_prefs.decorator_color_selected
@@ -186,7 +192,13 @@ class AggregateDecorator:
aggregates.append(obj)
continue
aggregate = ifcopenshell.util.element.get_aggregate(element)
aggregates_list = tool.Aggregate.get_aggregates_recursively(element)
if props.in_aggregate_mode and props.editing_aggregate:
index = aggregates_list.index(tool.Ifc.get_entity(props.editing_aggregate))
if index > 0:
aggregate = aggregates_list[index - 1]
else:
aggregate = aggregates_list[-1]
if aggregate:
aggregates.append(tool.Ifc.get_object(aggregate))
@@ -204,9 +216,7 @@ class AggregateDecorator:
self.draw_batch("LINES", line_y, color, [(0, 1)])
line_z = (location - Vector((0.0, 0.0, size)), location + Vector((0.0, 0.0, size)))
self.draw_batch("LINES", line_z, color, [(0, 1)])
if context.scene.BIMAggregateProperties.in_aggregate_mode:
return
parts = ifcopenshell.util.element.get_parts(tool.Ifc.get_entity(aggregate))
parts = tool.Aggregate.get_parts_recursively(tool.Ifc.get_entity(aggregate))
parts_objs = [tool.Ifc.get_object(p) for p in parts]
indices, edges = create_bounding_box(parts_objs)
@@ -243,6 +253,8 @@ class AggregateModeDecorator:
cls.is_installed = False
def draw_batch(self, shader_type, content_pos, color, indices=None):
if not tool.Blender.validate_shader_batch_data(content_pos, indices):
return
shader = self.line_shader if shader_type == "LINES" else self.shader
batch = batch_for_shader(shader, shader_type, {"pos": content_pos}, indices=indices)
shader.uniform_float("color", color)
@@ -253,7 +265,7 @@ class AggregateModeDecorator:
return
region = context.region
rv3d = region.data
props = context.scene.BIMAggregateProperties
props = tool.Aggregate.get_aggregate_props()
aggregate_obj = props.editing_aggregate
if not aggregate_obj:
@@ -284,7 +296,7 @@ class AggregateModeDecorator:
def draw_aggregate_empty(self, context):
if context.mode == "EDIT_MESH":
return
props = context.scene.BIMAggregateProperties
props = tool.Aggregate.get_aggregate_props()
aggregate_obj = props.editing_aggregate
if not aggregate_obj:
return
@@ -323,7 +335,7 @@ class AggregateModeDecorator:
self.draw_batch("LINES", line_y, color, [(0, 1)])
line_z = (location - Vector((0.0, 0.0, size)), location + Vector((0.0, 0.0, size)))
self.draw_batch("LINES", line_z, color, [(0, 1)])
parts = ifcopenshell.util.element.get_parts(tool.Ifc.get_entity(aggregate_obj))
parts = tool.Aggregate.get_parts_recursively(tool.Ifc.get_entity(aggregate_obj))
color = self.addon_prefs.decorator_color_selected
parts_objs = [tool.Ifc.get_object(p) for p in parts]
@@ -19,10 +19,13 @@
import bpy
import ifcopenshell
import ifcopenshell.api
import ifcopenshell.api.group
import ifcopenshell.api.pset
import ifcopenshell.util.element
import bonsai.tool as tool
import bonsai.core.aggregate as core
import bonsai.core.spatial
from typing import TYPE_CHECKING
class BIM_OT_aggregate_assign_object(bpy.types.Operator, tool.Ifc.Operator):
@@ -30,23 +33,30 @@ class BIM_OT_aggregate_assign_object(bpy.types.Operator, tool.Ifc.Operator):
bl_label = "Assign Object To Aggregation"
bl_description = (
"Assign object as an aggregate to the selected IFC elements.\n\n"
"If called from Bonsai UI, then either 'Relating Whole' or 'Related Part' must be provided.\n"
"If called from Object Aggregates UI, then either 'Relating Whole' or 'Related Part' must be provided.\n"
"If called directly, active object will be considered a relating whole"
)
bl_options = {"REGISTER", "UNDO"}
relating_object: bpy.props.IntProperty()
related_object: bpy.props.IntProperty()
if TYPE_CHECKING:
relating_object: int
related_object: int
def _execute(self, context):
relating_obj = None
if self.relating_object:
relating_obj = tool.Ifc.get_object(tool.Ifc.get().by_id(self.relating_object))
assert relating_obj
elif self.related_object:
aggregate = ifcopenshell.util.element.get_aggregate(tool.Ifc.get().by_id(self.related_object))
if aggregate:
relating_obj = tool.Ifc.get_object(aggregate)
elif context.active_object:
assert relating_obj
else:
relating_obj = context.active_object
if not relating_obj:
self.report({"ERROR"}, "No relating object is provided.")
return
@@ -66,7 +76,7 @@ class BIM_OT_aggregate_assign_object(bpy.types.Operator, tool.Ifc.Operator):
relating_obj=relating_obj,
related_obj=obj,
)
props = context.scene.BIMAggregateProperties
props = tool.Aggregate.get_aggregate_props()
if relating_obj == props.editing_aggregate and props.in_aggregate_mode:
new_editing_obj = props.editing_objects.add()
new_editing_obj.obj = obj
@@ -104,7 +114,7 @@ class BIM_OT_aggregate_unassign_object(bpy.types.Operator, tool.Ifc.Operator):
pset = ifcopenshell.util.element.get_pset(element, "BBIM_Linked_Aggregate")
if pset:
pset = tool.Ifc.get().by_id(pset["id"])
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), product=element, pset=pset)
ifcopenshell.api.pset.remove_pset(tool.Ifc.get(), product=element, pset=pset)
class BIM_OT_enable_editing_aggregate(bpy.types.Operator):
@@ -132,12 +142,11 @@ class BIM_OT_disable_editing_aggregate(bpy.types.Operator):
class BIM_OT_add_aggregate(bpy.types.Operator, tool.Ifc.Operator):
"""Add aggregate to IFC element"""
"""Add aggregate to selected IFC elements."""
bl_idname = "bim.add_aggregate"
bl_label = "Add Aggregate"
bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
ifc_class: bpy.props.StringProperty(name="IFC Class", default="IfcElementAssembly")
aggregate_name: bpy.props.StringProperty(name="Name", default="Default_Name")
@@ -157,7 +166,7 @@ class BIM_OT_add_aggregate(bpy.types.Operator, tool.Ifc.Operator):
return
aggregate = self.create_aggregate(context, ifc_class, self.aggregate_name)
for obj in context.selected_objects:
for obj in tool.Blender.get_selected_objects():
element = tool.Ifc.get_entity(obj)
if not element:
continue
@@ -182,7 +191,7 @@ class BIM_OT_add_aggregate(bpy.types.Operator, tool.Ifc.Operator):
)
core.assign_object(tool.Ifc, tool.Aggregate, tool.Collector, relating_obj=aggregate, related_obj=obj)
def create_aggregate(self, context, ifc_class, aggregate_name):
def create_aggregate(self, context: bpy.types.Context, ifc_class: str, aggregate_name: str) -> bpy.types.Object:
aggregate = bpy.data.objects.new(aggregate_name, None)
aggregate.location = context.scene.cursor.location
bpy.ops.bim.assign_class(obj=aggregate.name, ifc_class=ifc_class)
@@ -216,16 +225,25 @@ class BIM_OT_select_aggregate(bpy.types.Operator):
bl_idname = "bim.select_aggregate"
bl_label = "Select Aggregate"
bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
select_parts: bpy.props.BoolProperty(default=False)
one_level_deep: bpy.props.BoolProperty(
name="One Level Deep", description="Select only immediate children, not recursively", default=False
)
@classmethod
def description(cls, context, properties):
if properties.select_parts:
return "Select Aggregate and Parts"
return "Select Aggregate and Parts.\n\nCtrl+click to select only one level deep"
else:
return "Select Aggregate"
def invoke(self, context, event):
if event.type == "LEFTMOUSE" and event.ctrl:
self.one_level_deep = True
return self.execute(context)
def execute(self, context):
all_parts = []
for obj in context.selected_objects:
@@ -241,14 +259,27 @@ class BIM_OT_select_aggregate(bpy.types.Operator):
obj.select_set(False)
if self.select_parts:
all_objs = []
selected_parts = []
for part in all_parts:
if part.IsDecomposedBy:
for subpart in part.IsDecomposedBy[0].RelatedObjects:
all_parts.append(subpart)
all_objs.append(part)
for rel in part.IsDecomposedBy:
for subpart in rel.RelatedObjects:
selected_parts.append(subpart)
for element in all_objs:
# If not limited to one level, traverse deeper
if not self.one_level_deep:
def add_descendants(elem):
if elem.IsDecomposedBy:
for rel in elem.IsDecomposedBy:
for deeper in rel.RelatedObjects:
selected_parts.append(deeper)
add_descendants(deeper)
add_descendants(subpart)
for element in set(selected_parts + all_parts):
obj = tool.Ifc.get_object(element)
if obj:
obj.select_set(True)
@@ -256,9 +287,11 @@ class BIM_OT_select_aggregate(bpy.types.Operator):
else:
for aggregate_element in all_parts:
aggregate_obj = tool.Ifc.get_object(aggregate_element)
aggregate_obj.select_set(True)
bpy.context.view_layer.objects.active = aggregate_obj
if aggregate_obj:
aggregate_obj.select_set(True)
bpy.context.view_layer.objects.active = aggregate_obj
self.one_level_deep = False # <-- forcibly reset
return {"FINISHED"}
@@ -293,6 +326,7 @@ class BIM_OT_break_link_to_other_aggregates(bpy.types.Operator, tool.Ifc.Operato
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
ifc_file = tool.Ifc.get()
element = tool.Ifc.get_entity(bpy.context.active_object)
aggregate = ifcopenshell.util.element.get_aggregate(element)
if not aggregate:
@@ -304,8 +338,8 @@ class BIM_OT_break_link_to_other_aggregates(bpy.types.Operator, tool.Ifc.Operato
for part in parts:
pset = ifcopenshell.util.element.get_pset(part, "BBIM_Linked_Aggregate")
pset = tool.Ifc.get().by_id(pset["id"])
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), product=part, pset=pset)
pset = ifc_file.by_id(pset["id"])
ifcopenshell.api.pset.remove_pset(ifc_file, product=part, pset=pset)
linked_aggregate_group = next(
r.RelatingGroup
@@ -313,7 +347,7 @@ class BIM_OT_break_link_to_other_aggregates(bpy.types.Operator, tool.Ifc.Operato
if r.is_a("IfcRelAssignsToGroup")
if "BBIM_Linked_Aggregate" in r.RelatingGroup.Name
)
tool.Ifc.run("group.unassign_group", group=linked_aggregate_group, products=[aggregate])
ifcopenshell.api.group.unassign_group(ifc_file, group=linked_aggregate_group, products=[aggregate])
return {"FINISHED"}
@@ -378,7 +412,7 @@ class BIM_OT_disable_aggregate_mode(bpy.types.Operator):
def execute(self, context):
bpy.ops.object.select_all(action="DESELECT")
bonsai.core.aggregate.disable_aggregate_mode(tool.Aggregate)
core.exit_aggregate_mode(tool.Aggregate)
return {"FINISHED"}
@@ -388,7 +422,7 @@ class BIM_OT_toggle_aggregate_mode_local_view(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
props = context.scene.BIMAggregateProperties
props = tool.Aggregate.get_aggregate_props()
objs = [o.obj for o in props.editing_objects]
if props.in_aggregate_mode:
if context.space_data.local_view:
@@ -33,6 +33,7 @@ from bpy.props import (
CollectionProperty,
)
from bonsai.bim.module.aggregate.decorator import AggregateDecorator, AggregateModeDecorator
from typing import TYPE_CHECKING, Union
def can_aggregate(relating_obj: bpy.types.Object, related_obj: bpy.types.Object) -> bool:
@@ -98,16 +99,27 @@ class BIMObjectAggregateProperties(PropertyGroup):
poll=poll_related_object,
)
if TYPE_CHECKING:
is_editing: bool
relating_object: Union[bpy.types.Object, None]
related_object: Union[bpy.types.Object, None]
class Objects(bpy.types.PropertyGroup):
obj: PointerProperty(type=bpy.types.Object)
previous_display_type: bpy.props.StringProperty(default="TEXTURED")
previous_hide_select: bpy.props.BoolProperty(default=False)
if TYPE_CHECKING:
obj: Union[bpy.types.Object, None]
previous_display_type: str
previous_hide_select: bool
class BIMAggregateProperties(PropertyGroup):
in_aggregate_mode: BoolProperty(name="In Edit Mode", update=update_aggregate_mode_decorator)
editing_aggregate: PointerProperty(name="Editing Aggregate", type=bpy.types.Object)
previous_editing_aggregate: PointerProperty(name="Editing Aggregate", type=bpy.types.Object)
editing_objects: CollectionProperty(type=Objects)
not_editing_objects: CollectionProperty(type=Objects)
aggregate_decorator: BoolProperty(
@@ -115,3 +127,16 @@ class BIMAggregateProperties(PropertyGroup):
default=False,
update=update_aggregate_decorator,
)
previous_state: BoolProperty(
name="True if it was previously in aggregate mode",
default=False,
)
if TYPE_CHECKING:
in_aggregate_mode: bool
editing_aggregate: Union[bpy.types.Object, None]
previous_editing_aggregate: Union[bpy.types.Object, None]
editing_objects: bpy.types.bpy_prop_collection_idprop[Objects]
not_editing_objects: bpy.types.bpy_prop_collection_idprop[Objects]
aggregate_decorator: bool
previous_state: bool
+8 -8
View File
@@ -49,11 +49,14 @@ class BIM_PT_aggregate(Panel):
layout = self.layout
row = layout.row()
row.label(text="Aggregate Decorator")
row.prop(context.scene.BIMAggregateProperties, "aggregate_decorator", icon="HIDE_OFF", text="")
props = tool.Aggregate.get_aggregate_props()
icon = "HIDE_OFF" if props.aggregate_decorator else "HIDE_ON"
row.prop(props, "aggregate_decorator", icon=icon, text="")
if not AggregateData.is_loaded:
AggregateData.load()
props = context.active_object.BIMObjectAggregateProperties
assert (obj := context.active_object)
props = tool.Aggregate.get_object_aggregate_props(obj)
if props.is_editing:
row = layout.row()
@@ -76,10 +79,8 @@ class BIM_PT_aggregate(Panel):
if AggregateData.data["has_relating_object"]:
row.label(text=AggregateData.data["relating_object_label"], icon="TRIA_UP")
op = row.operator("bim.select_aggregate", icon="OBJECT_DATA", text="")
op.obj = context.active_object.name
op.select_parts = False
op = row.operator("bim.select_aggregate", icon="RESTRICT_SELECT_OFF", text="")
op.obj = context.active_object.name
op.select_parts = True
row.operator("bim.enable_editing_aggregate", icon="GREASEPENCIL", text="")
row.operator("bim.add_aggregate", icon="ADD", text="")
@@ -131,12 +132,11 @@ class BIM_PT_linked_aggregate(Panel):
if not AggregateData.is_loaded:
AggregateData.load()
obj = context.active_object
element = tool.Ifc.get_entity(obj)
props = obj.BIMObjectAggregateProperties
assert (obj := context.active_object)
assert (element := tool.Ifc.get_entity(obj))
row = layout.row(align=True)
if element.Decomposes:
if element.Decomposes or element.IsDecomposedBy:
Number_Linked_Aggregates = AggregateData.data["total_linked_aggregate"]
if not Number_Linked_Aggregates:
row.label(text="Not a Linked Aggregate")
@@ -21,7 +21,6 @@
import os
import ifcopenshell.api.alignment
import ifcopenshell.api.alignment.add_stationing_to_alignment
import bpy
import json
@@ -38,13 +37,12 @@ import ifcopenshell.util.selector
from datetime import datetime
from dateutil import parser, relativedelta
from bpy_extras.io_utils import ImportHelper
from typing import get_args, TYPE_CHECKING
from typing_extensions import assert_never
class ImportAlignmentCSV(bpy.types.Operator, tool.Ifc.Operator, ImportHelper):
bl_idname = "bim.import_alignment_csv"
bl_label = "Import Alignment CSV"
bl_description = " Import alignment from the provided .csv file."
bl_options = {"REGISTER", "UNDO"}
filename_ext = ".csv"
filter_glob: bpy.props.StringProperty(default="*.csv", options={"HIDDEN"})
@@ -63,9 +61,7 @@ class ImportAlignmentCSV(bpy.types.Operator, tool.Ifc.Operator, ImportHelper):
def _execute(self, context):
self.file = tool.Ifc.get()
start = time.time()
alignment = ifcopenshell.api.alignment.create_alignment_from_csv(self.file, self.filepath)
ifcopenshell.api.alignment.create_geometric_representation(self.file, alignment)
ifcopenshell.api.alignment.add_stationing_to_alignment(self.file, alignment=alignment, start_station=0.0)
alignment = ifcopenshell.api.alignment.create_from_csv(self.file, self.filepath)
# IFC 4.1.5.1 alignments cannot be contained in spatial structures, but can be referenced into them
sites = self.file.by_type("IfcSite")
@@ -25,13 +25,23 @@ classes = (
operator.EditAttributes,
operator.GenerateGlobalId,
operator.CopyAttributeToSelection,
operator.ExplorerAddEntity,
operator.ExplorerEnableEditingEntity,
operator.ExplorerDisableEditingEntity,
operator.ExplorerEditEntity,
operator.ExplorerShowUIPopup,
prop.BIMAttributeProperties,
prop.ExplorerEntity,
prop.BIMExplorerProperties,
ui.BIM_PT_object_attributes,
ui.BIM_PT_explorer,
ui.BIM_UL_explorer,
)
def register():
bpy.types.Object.BIMAttributeProperties = bpy.props.PointerProperty(type=prop.BIMAttributeProperties)
bpy.types.Scene.BIMExplorerProperties = bpy.props.PointerProperty(type=prop.BIMExplorerProperties)
def unregister():
@@ -26,30 +26,46 @@ import bonsai.bim.helper
import bonsai.tool as tool
import bonsai.core.attribute as core
import bonsai.core.spatial
from typing import TYPE_CHECKING, Any, Union, Literal
if TYPE_CHECKING:
from bonsai.bim.prop import Attribute
import bpy.stub_internal.rna_enums as rna_enums
def get_objs_for_operation(operator_properties, context):
def get_objs_for_operation(
operator_properties: "AttributesOperator", context: bpy.types.Context
) -> list[bpy.types.Object]:
if operator_properties.obj:
return [bpy.data.objects[operator_properties.obj]]
if operator_properties.mass_operation:
return context.selected_objects[:]
return [context.active_object]
obj = context.active_object
assert obj
return [obj]
class EnableEditingAttributes(bpy.types.Operator):
bl_idname = "bim.enable_editing_attributes"
bl_label = "Enable Editing Attributes"
bl_description = "ALT + Left Click to enable editing attributes on all selected objects"
bl_options = {"REGISTER", "UNDO"}
class AttributesOperator:
obj: bpy.props.StringProperty(options={"SKIP_SAVE"})
mass_operation: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"})
if TYPE_CHECKING:
obj: str
mass_operation: bool
def invoke(self, context, event):
self.mass_operation = event.alt
return self.execute(context)
def enable_editing_attribute_on_obj(self, obj):
props = obj.BIMAttributeProperties
class EnableEditingAttributes(bpy.types.Operator, AttributesOperator):
bl_idname = "bim.enable_editing_attributes"
bl_label = "Enable Editing Attributes"
bl_description = "ALT + Left Click to enable editing attributes on all selected objects"
bl_options = {"REGISTER", "UNDO"}
def enable_editing_attribute_on_obj(self, obj: bpy.types.Object) -> None:
props = tool.Blender.get_object_attribute_props(obj)
props.attributes.clear()
element = tool.Ifc.get_entity(obj)
@@ -63,7 +79,9 @@ class EnableEditingAttributes(bpy.types.Operator):
None,
)
def callback(name, prop, data):
lookup_attrs = tool.Attribute.does_ifc_class_support_explorer_lookup(element.is_a())
def callback(name: str, prop: Union["Attribute", None], data: dict[str, Any]) -> None | Literal[True]:
if name in ("RefLatitude", "RefLongitude"):
new = props.attributes.add()
new.name = name
@@ -77,8 +95,19 @@ class EnableEditingAttributes(bpy.types.Operator):
if name in ("PredefinedType", "ObjectType") and has_inherited_predefined_type:
props.attributes.remove(len(props.attributes) - 1)
return True
if lookup_attrs and (name in lookup_attrs):
new = props.attributes.add()
new.name = name
new.ifc_class = data["type"]
new.data_type = "enum"
new.is_optional = True
new.enum_items_dynamic = lookup_attrs[name]
new.use_explorer_ui = True
value: Union[ifcopenshell.entity_instance, None] = data[name]
if value is not None:
new.enum_value = str(value.id())
bonsai.bim.helper.import_attributes2(element, props.attributes, callback=callback)
bonsai.bim.helper.import_attributes(element, props.attributes, callback=callback)
props.is_editing_attributes = True
def execute(self, context):
@@ -87,21 +116,16 @@ class EnableEditingAttributes(bpy.types.Operator):
return {"FINISHED"}
class DisableEditingAttributes(bpy.types.Operator):
class DisableEditingAttributes(bpy.types.Operator, AttributesOperator):
bl_idname = "bim.disable_editing_attributes"
bl_label = "Disable Editing Attributes"
bl_description = "ALT + Left Click to disable editing attributes on all selected objects"
bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty(options={"SKIP_SAVE"})
mass_operation: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"})
def invoke(self, context, event):
self.mass_operation = event.alt
return self.execute(context)
def disable_editing_attributes_on_obj(self, obj):
props = obj.BIMAttributeProperties
props.is_editing_attributes = False
def disable_editing_attributes_on_obj(self, obj: bpy.types.Object) -> None:
props = tool.Blender.get_object_attribute_props(obj)
props.attributes.clear()
props.property_unset("is_editing_attributes")
def execute(self, context):
for obj in get_objs_for_operation(self, context):
@@ -118,10 +142,10 @@ class EditAttributes(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
self.file = tool.Ifc.get()
obj = tool.Blender.get_active_object(is_selected=False)
if not (element := tool.Ifc.get_entity(obj)):
if not obj or not (element := tool.Ifc.get_entity(obj)):
return
def callback(attributes, prop):
def callback(attributes: dict[str, Any], prop: "Attribute") -> None | Literal[True]:
if prop.name in ("RefLatitude", "RefLongitude"):
if not prop.is_null:
try:
@@ -130,8 +154,11 @@ class EditAttributes(bpy.types.Operator, tool.Ifc.Operator):
attributes[prop.name] = None
return True
props = obj.BIMAttributeProperties
props = tool.Blender.get_object_attribute_props(obj)
attributes = bonsai.bim.helper.export_attributes(props.attributes, callback=callback)
lookup_attrs = tool.Attribute.does_ifc_class_support_explorer_lookup(element.is_a())
if lookup_attrs:
bonsai.bim.helper.process_exported_entity_attribute(attributes, list(lookup_attrs))
ifcopenshell.api.attribute.edit_attributes(self.file, product=element, attributes=attributes)
tool.Root.set_object_name(obj, element)
@@ -165,13 +192,12 @@ class GenerateGlobalId(bpy.types.Operator, tool.Ifc.Operator):
element.GlobalId = ifcopenshell.guid.new()
obj = context.active_object
if not obj or not obj.BIMAttributeProperties.is_editing_attributes:
if not obj or not (props := tool.Blender.get_object_attribute_props(obj)).is_editing_attributes:
return {"FINISHED"}
props = obj.BIMAttributeProperties
element = tool.Ifc.get_entity(obj)
if not element.is_a("IfcRoot"):
if not element or not element.is_a("IfcRoot"):
return {"FINISHED"}
if self.use_selected and obj in context.selected_objects:
@@ -191,8 +217,147 @@ class CopyAttributeToSelection(bpy.types.Operator, tool.Ifc.Operator):
name: bpy.props.StringProperty()
def _execute(self, context):
value = tool.Blender.get_active_object().BIMAttributeProperties.attributes.get(self.name).get_value()
obj = tool.Blender.get_active_object()
assert obj
props = tool.Blender.get_object_attribute_props(obj)
value = props.attributes[self.name].get_value()
total = core.copy_attribute_to_selection(
tool.Ifc, tool.Blender, tool.Root, tool.Spatial, name=self.name, value=value
)
self.report({"INFO"}, f"Attribute was successfully copied to {total} elements.")
class ExplorerAddEntity(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.explorer_add_entity"
bl_label = "Add Entity"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context) -> None:
props = tool.Attribute.get_explorer_props()
ifc_file = tool.Ifc.get()
entity = ifc_file.create_entity(props.ifc_class)
tool.Attribute.refresh_uilist_entities()
tool.Attribute.enable_editing_entity(entity)
tool.Attribute.import_entity_attributes(entity)
class ExplorerEnableEditingEntity(bpy.types.Operator):
bl_idname = "bim.explorer_enable_editing_entity"
bl_label = "Enable Editing Entity"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context) -> "set[rna_enums.OperatorReturnItems]":
props = tool.Attribute.get_explorer_props()
ifc_file = tool.Ifc.get()
assert (active_entity := props.active_entity)
entity = ifc_file.by_id(active_entity.ifc_definition_id)
tool.Attribute.disable_editing_entity()
tool.Attribute.enable_editing_entity(entity)
tool.Attribute.import_entity_attributes(entity)
return {"FINISHED"}
class ExplorerDisableEditingEntity(bpy.types.Operator):
bl_idname = "bim.explorer_disable_editing_entity"
bl_label = "Disable Editing Entity"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context) -> set["rna_enums.OperatorReturnItems"]:
tool.Attribute.disable_editing_entity()
return {"FINISHED"}
class ExplorerEditEntity(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.explorer_edit_entity"
bl_label = "Edit Entity"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context) -> None:
ifc_file = tool.Ifc.get()
props = tool.Attribute.get_explorer_props()
entity = ifc_file.by_id(props.editing_entity_id)
attrs = tool.Attribute.export_entity_attributes()
for attr, value in attrs.items():
setattr(entity, attr, value)
tool.Attribute.refresh_uilist_entities()
tool.Attribute.disable_editing_entity()
class ExplorerShowUIPopup(bpy.types.Operator):
bl_idname = "bim.explorer_show_ui_popup"
bl_label = "Show Explorer UI"
bl_description = "Show Explorer UI to select element as attribute value or edit it."
bl_options = {"REGISTER", "UNDO"}
ifc_class: bpy.props.StringProperty() # pyright: ignore[reportRedeclaration]
"""Element IFC class."""
attribute_name: bpy.props.StringProperty() # pyright: ignore[reportRedeclaration]
"""IFC class attribute name."""
data_path: bpy.props.StringProperty() # pyright: ignore[reportRedeclaration]
"""Full data path"""
preselect_ifc_id: bpy.props.IntProperty(options={"SKIP_SAVE"}) # pyright: ignore[reportRedeclaration]
"""IFC id to preselect in the popup."""
if TYPE_CHECKING:
ifc_class: str
attribute_name: str
data_path: str
preselect_ifc_id: int
def invoke(self, context, event) -> "set[rna_enums.OperatorReturnItems]":
assert context.window_manager
assert self.ifc_class and self.attribute_name and self.data_path
props = tool.Attribute.get_explorer_props()
props.is_loaded = True
props.ifc_class = self.get_attribute_type()
if self.preselect_ifc_id:
props.active_entity_index = next(
i for i, e in enumerate(props.entities) if e.ifc_definition_id == self.preselect_ifc_id
)
return context.window_manager.invoke_props_dialog(self, width=400)
def get_attribute_type(self) -> str:
schema = tool.Ifc.schema()
entity = schema.declaration_by_name(self.ifc_class).as_entity()
assert entity
i = entity.attribute_index(self.attribute_name)
attr = entity.all_attributes()[i]
named_type = attr.type_of_attribute().as_named_type()
assert named_type
declared = named_type.declared_type()
return declared.name()
def draw(self, context) -> None:
from bonsai.bim.module.attribute.ui import BIM_PT_explorer
BIM_PT_explorer.draw(self, context, is_popup=True)
def execute(self, context) -> "set[rna_enums.OperatorReturnItems]":
props = tool.Attribute.get_explorer_props()
active_entity = props.active_entity
if active_entity is None:
self.report({"WARNING"}, "No entity selected.")
return {"FINISHED"}
# Apply pending changes for convenience.
if props.editing_entity_id:
if props.editing_entity_id == active_entity.ifc_definition_id:
bpy.ops.bim.explorer_edit_entity()
else:
bpy.ops.bim.explorer_disable_editing_entity()
# Very important to do it after changes applied, otherwise enum might update
# and index will be pointing to a different element.
exec(f"{self.data_path} = '{active_entity.ifc_definition_id}'")
return {"FINISHED"}
def cancel(self, context: bpy.types.Context) -> None:
props = tool.Attribute.get_explorer_props()
if props.editing_entity_id:
bpy.ops.bim.explorer_disable_editing_entity()
@@ -17,6 +17,7 @@
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
import bpy
import bonsai.tool as tool
from bonsai.bim.prop import StrProperty, Attribute
from bpy.types import PropertyGroup
from bpy.props import (
@@ -29,8 +30,72 @@ from bpy.props import (
FloatVectorProperty,
CollectionProperty,
)
from typing import TYPE_CHECKING, Union
class BIMAttributeProperties(PropertyGroup):
attributes: CollectionProperty(name="Attributes", type=Attribute)
is_editing_attributes: BoolProperty(name="Is Editing Attributes")
if TYPE_CHECKING:
attributes: bpy.types.bpy_prop_collection_idprop[Attribute]
is_editing_attributes: bool
class ExplorerEntity(PropertyGroup):
ifc_definition_id: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
if TYPE_CHECKING:
ifc_definition_id: int
class BIMExplorerProperties(PropertyGroup):
def update_is_loaded(self, context: object) -> None:
if self.is_loaded:
# Trigger refresh.
self.ifc_class = self.ifc_class
else:
self.property_unset("is_loaded")
self.property_unset("ifc_class")
self.entities.clear()
self.property_unset("active_entity_index")
self.property_unset("editing_entity_id")
self.entity_attributes.clear()
is_loaded: BoolProperty( # pyright: ignore[reportRedeclaration]
name="Toggle Explorer UI",
update=update_is_loaded,
)
def get_ifc_class(self, context: object) -> tool.Blender.BLENDER_ENUM_ITEMS:
# TODO: Add more entities.
classes = [
"IfcPostalAddress",
"IfcTelecomAddress",
]
return [(c, c, "") for c in classes]
def update_ifc_class(self, context: object) -> None:
tool.Attribute.refresh_uilist_entities()
ifc_class: EnumProperty( # pyright: ignore[reportRedeclaration]
name="IFC Class To Search",
items=get_ifc_class,
update=update_ifc_class,
)
entities: CollectionProperty(type=ExplorerEntity) # pyright: ignore[reportRedeclaration]
active_entity_index: IntProperty() # pyright: ignore[reportRedeclaration]
editing_entity_id: IntProperty() # pyright: ignore[reportRedeclaration]
entity_attributes: CollectionProperty(type=Attribute) # pyright: ignore[reportRedeclaration]
if TYPE_CHECKING:
is_loaded: bool
ifc_class: str
entities: bpy.types.bpy_prop_collection_idprop[ExplorerEntity]
active_entity_index: int
editing_entity_id: int
entity_attributes: bpy.types.bpy_prop_collection_idprop[Attribute]
@property
def active_entity(self) -> Union[ExplorerEntity, None]:
return tool.Blender.get_active_uilist_element(self.entities, self.active_entity_index)
+82 -3
View File
@@ -16,22 +16,31 @@
# You should have received a copy of the GNU General Public License
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
from __future__ import annotations
import bonsai.bim.helper
import bpy.types
from bpy.types import Panel
from bonsai.bim.module.attribute.data import AttributesData
import bonsai.tool as tool
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from bonsai.bim.module.attribute.prop import BIMExplorerProperties, ExplorerEntity
def draw_ui(context, layout, attributes):
def draw_ui(context: bpy.types.Context, layout: bpy.types.UILayout, attributes) -> None:
obj = context.active_object
props = obj.BIMAttributeProperties
assert obj
props = tool.Blender.get_object_attribute_props(obj)
if props.is_editing_attributes:
row = layout.row(align=True)
row.operator("bim.edit_attributes", icon="CHECKMARK", text="Save Attributes")
row.operator("bim.disable_editing_attributes", icon="CANCEL", text="")
bonsai.bim.helper.draw_attributes(props.attributes, layout, copy_operator="bim.copy_attribute_to_selection")
bonsai.bim.helper.draw_attributes(
props.attributes, layout, copy_operator="bim.copy_attribute_to_selection", enable_search=True
)
else:
row = layout.row()
op = row.operator("bim.enable_editing_attributes", icon="GREASEPENCIL", text="Edit")
@@ -64,3 +73,73 @@ class BIM_PT_object_attributes(Panel):
if not AttributesData.is_loaded:
AttributesData.load()
draw_ui(context, self.layout, AttributesData.data["attributes"])
class BIM_PT_explorer(Panel):
bl_label = "Explorer"
bl_idname = "BIM_PT_explorer"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_tab_project_setup"
def draw(self, context, *, is_popup=False):
assert (layout := self.layout)
props = tool.Attribute.get_explorer_props()
if is_popup:
layout.label(text=props.ifc_class)
else:
if not props.is_loaded:
row = layout.row(align=True)
row.label(text="Explorer UI is not Loaded.")
row.prop(props, "is_loaded", text="", icon="IMPORT")
return
row = layout.row(align=True)
row.prop(props, "ifc_class", text="")
row.prop(props, "is_loaded", text="", icon="CANCEL")
active_entity = props.active_entity
row = layout.row(align=True)
row.label(text=f"{len(props.entities)} entities found")
row.operator("bim.explorer_add_entity", text="", icon="ADD")
if active_entity:
if active_entity.ifc_definition_id == props.editing_entity_id:
row.operator("bim.explorer_disable_editing_entity", icon="CANCEL", text="")
else:
row.operator("bim.explorer_enable_editing_entity", icon="GREASEPENCIL", text="")
# TODO: 'Remove' button?
layout.template_list("BIM_UL_explorer", "", props, "entities", props, "active_entity_index")
if props.editing_entity_id:
box = self.layout.box()
# In popup we accept edits automatically for the convenience.
# Othrewise it seems very unintuitive, when you need to click confirmation twice.
if not is_popup:
row = box.row(align=True)
row.operator("bim.explorer_edit_entity", icon="CHECKMARK")
row.operator("bim.explorer_disable_editing_entity", icon="CANCEL", text="")
bonsai.bim.helper.draw_attributes(props.entity_attributes, box, enable_search=True)
class BIM_UL_explorer(bpy.types.UIList):
def draw_item(
self,
context: bpy.types.Context,
layout: bpy.types.UILayout,
data: BIMExplorerProperties,
item: ExplorerEntity,
icon,
active_data,
active_propname,
) -> None:
row = layout.row(align=True)
if item.ifc_definition_id == data.editing_entity_id:
row.label(text=item.name, icon="GREASEPENCIL")
else:
row.label(text=item.name)
@@ -71,6 +71,7 @@ classes = (
ui.BIM_PT_bcf,
ui.BIM_PT_bcf_metadata,
ui.BIM_PT_bcf_comments,
ui.BIM_UL_topics,
)
+9 -2
View File
@@ -397,7 +397,7 @@ class AddBcfBimSnippet(bpy.types.Operator):
def poll(cls, context):
props = tool.Bcf.get_bcf_props()
props_are_filled = all(
(getattr(props, attr) for attr in ("bim_snippet_reference", "bim_snippet_schema", "bim_snippet_type"))
getattr(props, attr) for attr in ("bim_snippet_reference", "bim_snippet_schema", "bim_snippet_type")
)
if not props_are_filled:
cls.poll_message_set("Some BIM snippet fields are empty.")
@@ -1208,9 +1208,11 @@ class ActivateBcfViewpoint(bpy.types.Operator):
self.file = tool.Ifc.get()
bcfxml = bcfstore.BcfStore.get_bcfxml()
assert bcfxml
assert context.scene
props = tool.Bcf.get_bcf_props()
blender_topic = props.active_topic
assert blender_topic
topic = bcfxml.topics[blender_topic.name]
if self.viewpoint_guid:
viewpoint_guid = self.viewpoint_guid
@@ -1234,6 +1236,7 @@ class ActivateBcfViewpoint(bpy.types.Operator):
cam_height = context.scene.render.resolution_y
cam_aspect = cam_width / cam_height
assert isinstance(obj.data, bpy.types.Camera)
obj.data.background_images.clear()
if viewpoint.snapshot:
obj.data.show_background_images = True
@@ -1273,7 +1276,7 @@ class ActivateBcfViewpoint(bpy.types.Operator):
if tool.Bcf.get_viewpoint_bitmaps(viewpoint):
self.create_bitmaps(bcfxml, viewpoint, topic)
self.setup_camera(viewpoint, obj, cam_aspect, context)
self.setup_camera(viewpoint, obj, cam_aspect, context, cam_height, cam_width)
return {"FINISHED"}
def setup_camera(
@@ -1282,7 +1285,10 @@ class ActivateBcfViewpoint(bpy.types.Operator):
obj: bpy.types.Object,
cam_aspect: float,
context: bpy.types.Context,
cam_height: float,
cam_width: float,
) -> None:
assert isinstance(obj.data, bpy.types.Camera)
if viewpoint.visualization_info.orthogonal_camera:
camera = viewpoint.visualization_info.orthogonal_camera
obj.data.type = "ORTHO"
@@ -1400,6 +1406,7 @@ class ActivateBcfViewpoint(bpy.types.Operator):
self.hide_spaces(context)
def hide_spaces(self, context: bpy.types.Context) -> None:
assert context.area
old = context.area.type
context.area.type = "VIEW_3D"
bpy.ops.object.select_all(action="DESELECT")
-3
View File
@@ -31,9 +31,6 @@ from bpy.props import (
FloatVectorProperty,
CollectionProperty,
)
from functools import partial
from typing import Literal
from typing_extensions import assert_never
from bcf.agnostic.extensions import get_extensions_attributes
from typing import TYPE_CHECKING, Union
+26
View File
@@ -16,11 +16,16 @@
# You should have received a copy of the GNU General Public License
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
from __future__ import annotations
import os
import bpy
import bonsai.tool as tool
from . import bcfstore
from bpy.types import Panel
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from bonsai.bim.module.bcf.prop import BcfTopic, BCFProperties
class BIM_PT_bcf(Panel):
@@ -33,6 +38,7 @@ class BIM_PT_bcf(Panel):
bl_parent_id = "BIM_PT_tab_collaboration"
def draw(self, context):
assert self.layout
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
@@ -125,6 +131,7 @@ class BIM_PT_bcf_metadata(Panel):
# without adding them to specific topic.
# Currently we just handle it the same way as in v2
# documents are accessed and managed from topics.
assert self.layout
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
@@ -139,6 +146,7 @@ class BIM_PT_bcf_metadata(Panel):
bcf_v3 = bcf_verison.startswith("3")
topic = props.active_topic
assert topic
bcf_topic = bcfxml.topics[topic.name]
layout.label(text="Header Files:")
@@ -303,6 +311,7 @@ class BIM_PT_bcf_comments(Panel):
row.prop(props, "comment_text_width")
topic = props.active_topic
assert topic
for comment in topic.comments:
box = self.layout.box()
@@ -341,3 +350,20 @@ class BIM_PT_bcf_comments(Panel):
row.prop(props, "has_related_viewpoint")
row = layout.row()
row.operator("bim.add_bcf_comment")
class BIM_UL_topics(bpy.types.UIList):
def draw_item(
self,
context,
layout: bpy.types.UILayout,
data: BCFProperties,
item: BcfTopic,
icon,
active_data,
active_propname,
) -> None:
if item:
layout.prop(item, "title", text="", emboss=False)
else:
layout.label(text="", translate=False)
@@ -43,6 +43,8 @@ class BoundaryDecorator:
cls.installed = None
def draw_batch(self, shader_type, content_pos, color, indices=None):
if not tool.Blender.validate_shader_batch_data(content_pos, indices):
return
shader = self.line_shader if shader_type == "LINES" else self.shader
batch = batch_for_shader(shader, shader_type, {"pos": content_pos}, indices=indices)
shader.uniform_float("color", color)
@@ -404,12 +404,13 @@ class EditBoundaryAttributes(bpy.types.Operator, tool.Ifc.Operator):
assert obj
bprops = tool.Boundary.get_object_boundary_props(obj)
boundary = tool.Ifc.get_entity(obj)
assert boundary
attributes = dict()
for ifc_attribute, blender_property in EDITABLE_ATTRIBUTES.items():
obj = getattr(bprops, blender_property, None)
entity = tool.Ifc.get_entity(obj)
attributes[blender_property] = entity
ifcopenshell.api.run("boundary.edit_attributes", tool.Ifc.get(), entity=boundary, **attributes)
ifcopenshell.api.boundary.edit_attributes(tool.Ifc.get(), entity=boundary, **attributes)
bpy.ops.bim.disable_editing_boundary()
return {"FINISHED"}
@@ -605,7 +606,12 @@ class AddBoundary(bpy.types.Operator, tool.Ifc.Operator):
parent_boundaries: list[ifcopenshell.entity_instance] = []
objs = tool.Blender.get_selected_objects()
ifc_objects = {element: obj for obj in objs if (element := tool.Ifc.get_entity(obj))}
ifc_objects = {
element: obj
for obj in objs
if (element := tool.Ifc.get_entity(obj)) and isinstance(obj.data, bpy.types.Mesh) and obj.data.vertices
}
if len(ifc_objects) not in (1, 2):
self.report({"ERROR"}, "Please select one or two IFC elements.")
return {"CANCELLED"}
@@ -647,46 +653,6 @@ class AddBoundary(bpy.types.Operator, tool.Ifc.Operator):
return {"FINISHED"}
parent_boundaries.extend(res)
elif len(objs) == 1 and False:
# Optionally the user may select just the space, and the building element shall be auto-detected
# TODO : refactor to be able to generate all boundaries for selected space automatically or with an option
relating_space = None
related_building_element = None
relating_space_obj = None
related_building_element_obj = None
def msg(self, context):
self.layout.label(text="Please set an active container to detect space boundaries from.")
element = tool.Ifc.get_entity(objs[0])
if element.is_a("IfcSpace"):
relating_space = element
relating_space_obj = objs[0]
if not (container := tool.Root.get_default_container()):
bpy.context.window_manager.popup_menu(msg, title="Error", icon="ERROR")
return
for subelement in ifcopenshell.util.element.get_decomposition(container):
if not (
subelement.is_a("IfcWall") or subelement.is_a("IfcSlab") or subelement.is_a("IfcVirtualElement")
):
continue
obj = tool.Ifc.get_object(subelement)
if obj:
related_building_element = subelement
related_building_element_obj = obj
parent_boundary = self.create_element_boundary(
context,
relating_space,
relating_space_obj,
related_building_element,
related_building_element_obj,
)
if parent_boundary:
parent_boundaries.append(parent_boundary)
bpy.ops.bim.show_boundaries()
for parent_boundary in parent_boundaries:
obj = tool.Ifc.get_object(parent_boundary)
@@ -711,6 +677,14 @@ class AddBoundary(bpy.types.Operator, tool.Ifc.Operator):
+ tool.Ifc.get().by_type("IfcVirtualElement")
)
for building_element in building_elements:
if obj := tool.Ifc.get_object(building_element):
if tool.Ifc.is_moved(obj):
bonsai.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj)
if tool.Ifc.is_moved(space_obj):
bonsai.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=space_obj)
# Don't generate boundaries of building elements that we've already got bounaries for.
for boundary in space.BoundedBy:
if boundary.RelatedBuildingElement in building_elements:
@@ -727,7 +701,10 @@ class AddBoundary(bpy.types.Operator, tool.Ifc.Operator):
while True:
tree.add_element(iterator.get_native())
shape = iterator.get()
shapes[shape.id] = shape
shapes[shape.id] = {
"verts": ifcopenshell.util.shape.get_vertices(shape.geometry),
"faces": ifcopenshell.util.shape.get_faces(shape.geometry),
}
if not iterator.next():
break
@@ -748,13 +725,11 @@ class AddBoundary(bpy.types.Operator, tool.Ifc.Operator):
bm = bmesh.new()
shape = shapes[building_element.id()]
verts = ifcopenshell.util.shape.get_vertices(shape.geometry)
for vert in verts:
for vert in shape["verts"]:
bm.verts.new(Vector(vert))
bm.verts.ensure_lookup_table()
faces = ifcopenshell.util.shape.get_faces(shape.geometry)
for face in faces:
for face in shape["faces"]:
bm.faces.new([bm.verts[i] for i in face])
bm.verts.ensure_lookup_table()
bm.faces.ensure_lookup_table()
@@ -1009,7 +984,7 @@ class AddBoundary(bpy.types.Operator, tool.Ifc.Operator):
continue
connection_geometry = self.create_connection_geometry_from_polygon(opening_polygon, target_face_matrix)
boundary = tool.Ifc.run("root.create_entity", ifc_class=props.boundary_class)
boundary = ifcopenshell.api.root.create_entity(ifc_file, ifc_class=props.boundary_class)
boundary.RelatingSpace = space
boundary.RelatedBuildingElement = filling
boundary.ConnectionGeometry = connection_geometry
+10 -6
View File
@@ -20,16 +20,20 @@ import bpy
from . import ui, prop, operator
classes = (
operator.GetBSDDClassificationProperties,
operator.LoadBSDDDomains,
operator.SearchBSDDClass,
operator.SetActiveBSDDDictionary,
prop.BSDDDomain,
operator.AddBSDDProperties,
operator.ImportBSDDClasses,
operator.LoadBSDDDictionaries,
operator.SearchBSDDClassifications,
operator.SearchBSDDProperties,
prop.BSDDDictionary,
prop.BSDDClassification,
prop.BSDDProperty,
prop.BSDDPset,
prop.BIMBSDDProperties,
ui.BIM_UL_bsdd_domains,
ui.BIM_UL_bsdd_classifications,
ui.BIM_UL_bsdd_dictionaries,
ui.BIM_UL_bsdd_classes,
ui.BIM_UL_bsdd_properties,
ui.BIM_PT_bsdd,
)
+44
View File
@@ -0,0 +1,44 @@
# Bonsai - OpenBIM Blender Add-on
# Copyright (C) 2022 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/>.
import bpy
import ifcopenshell
import ifcopenshell.util.date
import ifcopenshell.util.classification
import bonsai.tool as tool
from bonsai.bim.ifc import IfcStore
def refresh():
BSDDData.is_loaded = False
class BSDDData:
data = {}
is_loaded = False
@classmethod
def load(cls):
cls.is_loaded = True
cls.data["active_dictionary"] = cls.active_dictionary()
@classmethod
def active_dictionary(cls) -> tool.Blender.BLENDER_ENUM_ITEMS:
results = [("ALL", "All Dictionaries", "All active dictionaries")]
results.extend(tool.Bsdd.get_active_bsdd_enum_items())
return results
+63 -35
View File
@@ -19,61 +19,89 @@
import bpy
import bsdd
import bonsai.tool as tool
import ifcopenshell.api.pset
import ifcopenshell.util.element
from bonsai.core import bsdd as core
from typing import Any
class LoadBSDDDomains(bpy.types.Operator):
bl_idname = "bim.load_bsdd_domains"
class LoadBSDDDictionaries(bpy.types.Operator):
bl_idname = "bim.load_bsdd_dictionaries"
bl_label = "Load bSDD Dictionaries"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
core.load_bsdd(bsdd.Client(), tool.Bsdd)
core.load_bsdd(tool.Bsdd)
return {"FINISHED"}
class SetActiveBSDDDictionary(bpy.types.Operator):
bl_idname = "bim.set_active_bsdd_domain"
bl_label = "Set bSDD Dictionary as active"
bl_options = {"REGISTER", "UNDO"}
name: bpy.props.StringProperty()
uri: bpy.props.StringProperty()
def execute(self, context):
core.set_active_bsdd_dictionary(self.name, self.uri, tool.Bsdd)
return {"FINISHED"}
class SearchBSDDClass(bpy.types.Operator):
class SearchBSDDClassifications(bpy.types.Operator):
bl_idname = "bim.search_bsdd_classifications"
bl_label = "Search bSDD Class"
bl_description = "Search for bSDD classes by the provided keyword"
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
# Requirement by buildingSMART bSDD api.
if len(context.scene.BIMBSDDProperties.keyword) < 3:
cls.poll_message_set("Search query has to be at least 3 characters long.")
return False
return True
def execute(self, context):
keyword = context.scene.BIMBSDDProperties.keyword
classes_found = core.search_class(keyword, bsdd.Client(), tool.Bsdd)
self.report({"INFO"}, f"{classes_found} bSDD classes found for '{keyword}'.")
total = core.search_bsdd_class(tool.Bsdd, tool.Bsdd.get_bsdd_props().keyword)
self.report({"INFO"}, f"{total} bSDD classes found.")
return {"FINISHED"}
class GetBSDDClassificationProperties(bpy.types.Operator):
bl_idname = "bim.get_bsdd_classification_properties"
bl_label = "Search bSDD Class Properties"
bl_description = "Search for bSDD class properties for the currently selected class"
class ImportBSDDClasses(bpy.types.Operator):
bl_idname = "bim.import_bsdd_classes"
bl_label = "Import bSDD Classes"
bl_description = "Load bSDD classes that apply to the current element"
bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
obj_type: bpy.props.StringProperty()
def execute(self, context):
pset_data = core.get_class_properties(bsdd.Client(), tool.Bsdd)
psets_found = len(pset_data)
props_found = sum(len(pset) for pset in pset_data.values())
self.report({"INFO"}, f"{psets_found} psets found ({props_found} props) for the active classification.")
props = tool.Bsdd.get_bsdd_props()
core.import_bsdd_classes(tool.Bsdd, self.obj, self.obj_type)
self.report({"INFO"}, f"{len(props.classes)} bSDD classes found.")
return {"FINISHED"}
class SearchBSDDProperties(bpy.types.Operator):
bl_idname = "bim.search_bsdd_properties"
bl_label = "Search bSDD Properties"
bl_description = "Search for bSDD properties that apply to the current element"
bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
obj_type: bpy.props.StringProperty()
def execute(self, context):
props = tool.Bsdd.get_bsdd_props()
core.search_bsdd_properties(tool.Bsdd, tool.Bsdd.get_bsdd_props().keyword, self.obj, self.obj_type)
self.report({"INFO"}, f"{len(props.properties)} bSDD properties found.")
return {"FINISHED"}
class AddBSDDProperties(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.add_bsdd_properties"
bl_label = "Add bSDD Properties"
bl_description = "Add selected bSDD properties to the active object"
bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
obj_type: bpy.props.StringProperty()
def _execute(self, context):
self.file = tool.Ifc.get()
bprops = tool.Bsdd.get_bsdd_props()
psets: dict[str, dict[str, Any]] = {}
for selected_property in bprops.selected_properties:
psets.setdefault(selected_property.metadata, {})[selected_property.name] = selected_property.get_value()
props = tool.Pset.get_pset_props(self.obj, self.obj_type)
ifc_definition_id = tool.Blender.get_obj_ifc_definition_id(self.obj, self.obj_type, context)
element = tool.Ifc.get().by_id(ifc_definition_id)
properties = {}
current_psets = ifcopenshell.util.element.get_psets(element, verbose=True)
for pset_name, properties in psets.items():
if pset := current_psets.get(pset_name, None):
pset = self.file.by_id(pset["id"])
else:
pset = ifcopenshell.api.pset.add_pset(self.file, product=element, name=pset_name)
ifcopenshell.api.pset.edit_pset(self.file, pset=pset, properties=properties)
+123 -14
View File
@@ -17,7 +17,10 @@
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
import bpy
import bonsai.tool as tool
from bpy.types import PropertyGroup
from bonsai.bim.module.bsdd.data import BSDDData
from bonsai.bim.module.classification.data import ClassificationsData
from bonsai.bim.prop import Attribute, StrProperty
from bpy.props import (
PointerProperty,
@@ -29,37 +32,123 @@ from bpy.props import (
FloatVectorProperty,
CollectionProperty,
)
from typing import Union, TYPE_CHECKING, Literal
class BSDDDomain(PropertyGroup):
name: StringProperty(name="Name")
def get_active_dictionary(self: "BIMBSDDProperties", context: object) -> tool.Blender.BLENDER_ENUM_ITEMS:
if not BSDDData.is_loaded:
BSDDData.load()
return BSDDData.data["active_dictionary"]
def update_is_active(self: "BSDDDictionary", context: bpy.types.Context) -> None:
tool.Bsdd.save_active_bsdd_to_ifc()
BSDDData.data["active_dictionary"] = BSDDData.active_dictionary()
if ClassificationsData.is_loaded:
props = tool.Classification.get_classification_props()
# Preserve original enum value.
classification_source = props.classification_source
ClassificationsData.data["classification_source"] = ClassificationsData.classification_source()
# Try to restore enum value.
if "classification_source" not in props:
# It's already on the default value, nothing to restore.
return
try:
props.classification_source = classification_source
except TypeError:
# Item is no longer active and not present in enum, fallback to the default.
del props["classification_source"]
def update_is_selected(self: "BSDDProperty", context: bpy.types.Context) -> None:
tool.Bsdd.import_selected_properties()
def update_active_class_index(self: "BIMBSDDProperties", context: bpy.types.Context) -> None:
tool.Bsdd.import_class_properties()
BSDDData.data["active_dictionary"] = BSDDData.active_dictionary()
class BSDDDictionary(PropertyGroup):
uri: StringProperty(name="URI")
default_language_code: StringProperty(name="Language")
organization_name_owner: StringProperty(name="Organization")
status: StringProperty(name="Status")
version: StringProperty(name="Version")
is_active: BoolProperty(
name="Is Active",
description="Enable to search with this dictionary",
default=False,
update=update_is_active,
)
if TYPE_CHECKING:
uri: str
default_language_code: str
organization_name_owner: str
status: str
version: str
is_active: bool
class BSDDClassification(PropertyGroup):
name: StringProperty(name="Name")
reference_code: StringProperty(name="Reference Code")
uri: StringProperty(name="Namespace URI")
domain_name: StringProperty(name="Domain Name")
domain_namespace_uri: StringProperty(name="Domain Namespace URI")
uri: StringProperty(name="URI")
dictionary_name: StringProperty(name="Dictionary Name")
dictionary_namespace_uri: StringProperty(name="Dictionary Namespace URI")
if TYPE_CHECKING:
reference_code: str
uri: str
dictionary_name: str
dictionary_namespace_uri: str
class BSDDProperty(PropertyGroup):
code: StringProperty(name="Code")
uri: StringProperty(name="URI")
pset: StringProperty(name="Pset")
is_selected: BoolProperty(
name="Is Selected", description="Select to add or edit this property", default=False, update=update_is_selected
)
if TYPE_CHECKING:
code: str
uri: str
pset: str
is_selected: bool
class BSDDPset(PropertyGroup):
name: StringProperty(name="Name")
properties: CollectionProperty(name="Properties", type=Attribute)
if TYPE_CHECKING:
properties: bpy.types.bpy_prop_collection_idprop[Attribute]
class BIMBSDDProperties(PropertyGroup):
active_domain: StringProperty(name="Active Domain")
# TODO: `active_dictionary` is not used anywhere?
active_dictionary: EnumProperty(items=get_active_dictionary, name="Active Dictionary")
active_uri: StringProperty(name="Active URI")
domains: CollectionProperty(name="Domains", type=BSDDDomain)
active_domain_index: IntProperty(name="Active Domain Index")
dictionaries: CollectionProperty(name="Dictionaries", type=BSDDDictionary)
active_dictionary_index: IntProperty(name="Active Dictionary Index")
classifications: CollectionProperty(name="Classifications", type=BSDDClassification)
active_classification_index: IntProperty(name="Active Classification Index")
property_filter_mode: EnumProperty(
name="Property Filter Mode",
items=[
("CLASS", "By Class", "Browse properties by class or group"),
("KEYWORD", "By Keyword", "Search properties directly using a keyword"),
],
default="CLASS",
)
classes: CollectionProperty(name="Classes", type=BSDDClassification)
active_class_index: IntProperty(name="Active Class Index", update=update_active_class_index)
properties: CollectionProperty(name="Properties", type=BSDDProperty)
active_property_index: IntProperty(name="Active Property Index")
selected_properties: CollectionProperty(name="Selected Properties", type=Attribute)
keyword: StringProperty(name="Keyword", description="Query for bsdd classes search, case and accent insensitive")
should_filter_ifc_class: BoolProperty(
name="Filter Active IFC Class",
@@ -69,9 +158,29 @@ class BIMBSDDProperties(PropertyGroup):
use_only_ifc_properties: BoolProperty(
name="Only IFC Properties",
description="Whether to display and assign only properties from IFC dictionary",
default=True,
)
load_preview_domains: BoolProperty(
name="Load Preview Domains", description="Whether it should load preview and inactive domains", default=False
default=False,
)
classification_psets: CollectionProperty(name="Classification Psets", type=BSDDPset)
if TYPE_CHECKING:
active_dictionary: str
active_dictionary: str
active_uri: str
dictionaries: bpy.types.bpy_prop_collection_idprop[BSDDDictionary]
active_dictionary_index: int
classifications: bpy.types.bpy_prop_collection_idprop[BSDDClassification]
active_classification_index: int
property_filter_mode: Literal["CLASS", "KEYWORD"]
classes: bpy.types.bpy_prop_collection_idprop[BSDDClassification]
active_class_index: int
properties: bpy.types.bpy_prop_collection_idprop[BSDDProperty]
active_property_index: int
selected_properties: bpy.types.bpy_prop_collection_idprop[Attribute]
keyword: str
should_filter_ifc_class: bool
use_only_ifc_properties: bool
classification_psets: bpy.types.bpy_prop_collection_idprop[BSDDPset]
@property
def active_class(self) -> Union[BSDDClassification, None]:
return tool.Blender.get_active_uilist_element(self.classes, self.active_class_index)
+100 -36
View File
@@ -16,8 +16,15 @@
# You should have received a copy of the GNU General Public License
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
from __future__ import annotations
import bpy
import bonsai.tool as tool
from bonsai.bim.module.bsdd.data import BSDDData
from bpy.types import Panel, UIList
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from bonsai.bim.module.bsdd.prop import BIMBSDDProperties, BSDDDictionary, BSDDClassification, BSDDProperty
class BIM_PT_bsdd(Panel):
@@ -30,71 +37,128 @@ class BIM_PT_bsdd(Panel):
bl_parent_id = "BIM_PT_tab_project_setup"
def draw(self, context):
props = context.scene.BIMBSDDProperties
if not BSDDData.is_loaded:
BSDDData.load()
props = tool.Bsdd.get_bsdd_props()
assert self.layout
layout = self.layout
row = self.layout.row(align=True)
row.prop(props, "load_preview_domains")
if len(props.domains):
row.operator("bim.load_bsdd_domains", text="", icon="FILE_REFRESH")
if props.active_domain:
if len(props.dictionaries):
row = self.layout.row()
row.label(text="Active: " + props.active_domain, icon="URL")
else:
row = self.layout.row()
row.label(text="No Active bSDD Domain", icon="ERROR")
row.operator("bim.load_bsdd_dictionaries", icon="FILE_REFRESH")
if len(props.domains):
if len(props.dictionaries):
self.layout.template_list(
"BIM_UL_bsdd_domains",
"BIM_UL_bsdd_dictionaries",
"",
props,
"domains",
"dictionaries",
props,
"active_domain_index",
"active_dictionary_index",
)
if 0 <= props.active_domain_index < len(props.domains):
selected_domain = props.domains[props.active_domain_index]
if 0 <= props.active_dictionary_index < len(props.dictionaries):
selected_dictionary = props.dictionaries[props.active_dictionary_index]
else:
selected_domain = None
selected_dictionary = None
if selected_domain:
layout.label(text="Selected domain:")
if selected_dictionary:
layout.label(text="Selected Dictionary:")
box = layout.box()
row = box.row(align=True)
row.label(text="Language")
row.label(text=selected_domain.default_language_code)
row.label(text=selected_dictionary.default_language_code)
row = box.row(align=True)
row.label(text="Version")
row.label(text=selected_domain.version)
box.operator("bim.open_uri", text="Open bSDD In Browser", icon="URL").uri = selected_domain.uri
row.label(text=selected_dictionary.version)
box.operator("bim.open_uri", text="Open bSDD In Browser", icon="URL").uri = selected_dictionary.uri
else:
row = self.layout.row()
row.operator("bim.load_bsdd_domains")
row.operator("bim.load_bsdd_dictionaries")
class BIM_UL_bsdd_domains(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
class BIM_UL_bsdd_dictionaries(UIList):
def draw_item(
self,
context,
layout: bpy.types.UILayout,
data: BIMBSDDProperties,
item: BSDDDictionary,
icon,
active_data,
active_propname,
) -> None:
if item:
props = context.scene.BIMBSDDProperties
row = layout.row(align=True)
if item.status != "Active":
row.label(text=f"{item.name} ({item.organization_name_owner}) - {item.status}", icon="ERROR")
row.label(
text=f"{item.name} ({item.organization_name_owner}) v{item.version} - {item.status}", icon="ERROR"
)
else:
row.label(text=f"{item.name} ({item.organization_name_owner})")
if item.uri == props.active_uri:
row.label(text="", icon="URL")
else:
op = row.operator("bim.set_active_bsdd_domain", text="", icon="RESTRICT_SELECT_OFF")
op.name = item.name
op.uri = item.uri
row.label(text=f"{item.name} ({item.organization_name_owner}) v{item.version}")
row.prop(
item, "is_active", icon="CHECKBOX_HLT" if item.is_active else "CHECKBOX_DEHLT", text="", emboss=False
)
class BIM_UL_bsdd_classifications(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
def draw_item(
self,
context,
layout: bpy.types.UILayout,
data: BIMBSDDProperties,
item: BSDDClassification,
icon,
active_data,
active_propname,
) -> None:
if item:
row = layout.row(align=True)
row.label(text=item.reference_code)
row.label(text=item.name)
row.operator("bim.open_uri", text="", icon="URL").uri = item.uri
class BIM_UL_bsdd_classes(UIList):
def draw_item(
self,
context,
layout: bpy.types.UILayout,
data: BIMBSDDProperties,
item: BSDDClassification,
icon,
active_data,
active_propname,
) -> None:
if item:
row = layout.row(align=True)
row.label(text=item.name)
row.operator("bim.open_uri", text="", icon="URL").uri = item.uri
class BIM_UL_bsdd_properties(UIList):
def draw_item(
self,
context,
layout: bpy.types.UILayout,
data: BIMBSDDProperties,
item: BSDDProperty,
icon,
active_data,
active_propname,
) -> None:
if item:
row = layout.row(align=True)
name = item.name
if name != item.code:
name += f" ({item.code})"
row.label(text=name)
if item.pset:
row.label(text=item.pset)
row.operator("bim.open_uri", text="", icon="URL").uri = item.uri
row.prop(
item,
"is_selected",
icon="CHECKBOX_HLT" if item.is_selected else "CHECKBOX_DEHLT",
text="",
emboss=False,
)
+4 -4
View File
@@ -177,7 +177,7 @@ class CadFillet(bpy.types.Operator):
selected_edges.append(cut_edges[1])
# Calculate the distance to slide each edge to make space for the fillet arc
shared_vert = list(set(selected_edges[0].verts) & set(selected_edges[1].verts))[0]
shared_vert = next(iter(set(selected_edges[0].verts) & set(selected_edges[1].verts)))
v1 = selected_edges[0].other_vert(shared_vert)
v2 = selected_edges[1].other_vert(shared_vert)
dir1 = (v1.co - shared_vert.co).normalized()
@@ -205,7 +205,7 @@ class CadFillet(bpy.types.Operator):
# If faces are involved, then the chamfer edge protects existing faces from the newly created fillet.
# If no faces are involved, we delete the chamfer edge.
if is_wire:
chamfer_edge = [e for e in bm.edges if shared_vert in e.verts and v2 in e.verts][0]
chamfer_edge = next(e for e in bm.edges if shared_vert in e.verts and v2 in e.verts)
bm.edges.remove(chamfer_edge)
bm.verts.index_update()
@@ -675,7 +675,7 @@ class AddIfcArcIndexFillet(bpy.types.Operator):
bm = bmesh.from_edit_mesh(self.mesh)
edges = [e for e in bm.edges if e.select and not e.hide]
mid = list(set(edges[0].verts) & set(edges[1].verts))[0]
mid = next(iter(set(edges[0].verts) & set(edges[1].verts)))
v1 = edges[0].other_vert(mid)
v2 = edges[1].other_vert(mid)
assert v1 and v2
@@ -738,7 +738,7 @@ class AddIfcArcIndexFillet(bpy.types.Operator):
bmesh.ops.remove_doubles(bm, verts=all_verts, dist=1e-4)
# Calculate the distance to slide each edge to make space for the fillet arc
shared_vert = list(set(selected_edges[0].verts) & set(selected_edges[1].verts))[0]
shared_vert = next(iter(set(selected_edges[0].verts) & set(selected_edges[1].verts)))
v1 = selected_edges[0].other_vert(shared_vert)
v2 = selected_edges[1].other_vert(shared_vert)
assert v1 and v2
@@ -49,6 +49,8 @@ class ClashDecorator:
cls.is_installed = False
def draw_batch(self, shader_type, content_pos, color, indices=None):
if not tool.Blender.validate_shader_batch_data(content_pos, indices):
return
shader = self.line_shader if shader_type == "LINES" else self.shader
batch = batch_for_shader(shader, shader_type, {"pos": content_pos}, indices=indices)
shader.uniform_float("color", color)
+96 -26
View File
@@ -19,16 +19,19 @@
import os
import bpy
import json
import tempfile
import bmesh
import logging
import numpy as np
import ifcopenshell
import bonsai.tool as tool
from pathlib import Path
from bpy_extras.io_utils import ExportHelper, ImportHelper
from math import radians
from mathutils import Matrix, Vector
from bonsai.bim.ifc import IfcStore
from bonsai.bim.module.clash.decorator import ClashDecorator
from typing import TYPE_CHECKING
class ExportClashSets(bpy.types.Operator, ExportHelper):
@@ -127,7 +130,8 @@ class AddClashSource(bpy.types.Operator):
def execute(self, context):
props = tool.Clash.get_clash_props()
clash_set = props.active_clash_set
source = getattr(clash_set, self.group).add()
assert clash_set
clash_set.get_clash_sources_group(self.group).add()
return {"FINISHED"}
@@ -142,7 +146,8 @@ class RemoveClashSource(bpy.types.Operator):
def execute(self, context):
props = tool.Clash.get_clash_props()
clash_set = props.active_clash_set
getattr(clash_set, self.group).remove(self.index)
assert clash_set
clash_set.get_clash_sources_group(self.group).remove(self.index)
return {"FINISHED"}
@@ -152,14 +157,16 @@ class SelectClashSource(bpy.types.Operator, ImportHelper):
bl_options = {"REGISTER", "UNDO"}
bl_description = "Select an IFC file to add as a clash source"
filter_glob: bpy.props.StringProperty(default="*.ifc", options={"HIDDEN"})
index: bpy.props.IntProperty()
group: bpy.props.StringProperty()
index: bpy.props.IntProperty(options={"HIDDEN"})
group: bpy.props.StringProperty(options={"HIDDEN"})
filename_ext = ".ifc"
def execute(self, context):
props = tool.Clash.get_clash_props()
clash_set = props.active_clash_set
getattr(clash_set, self.group)[self.index].name = self.filepath
assert clash_set
clash_source = clash_set.get_clash_sources_group(self.group)[self.index]
clash_source.name = self.filepath
return {"FINISHED"}
@@ -190,16 +197,39 @@ class SelectSmartGroupedClashesPath(bpy.types.Operator, ImportHelper):
class ExecuteIfcClash(bpy.types.Operator, ExportHelper):
bl_idname = "bim.execute_ifc_clash"
bl_label = "Execute IFC Clash"
bl_description = "Execute clash detection and save the information to a .bcf or .json file"
filter_glob: bpy.props.StringProperty(default="*.bcf;*.json", options={"HIDDEN"})
format: bpy.props.EnumProperty(name="Format", items=[(i, i, "") for i in ("bcf", "json")])
filepath: bpy.props.StringProperty(subtype="FILE_PATH", options={"SKIP_SAVE"})
bl_description = (
"Execute clash detection and save the information to a .bcf or .json file.\n\n"
"ALT+click to run a quick clash without selecting a file to save."
)
filter_glob: bpy.props.StringProperty( # pyright: ignore[reportRedeclaration]
default="*.bcf;*.json", options={"HIDDEN"}
)
format: bpy.props.EnumProperty( # pyright: ignore[reportRedeclaration]
name="Format", items=[(i, i, "") for i in ("bcf", "json")]
)
filepath: bpy.props.StringProperty( # pyright: ignore[reportRedeclaration]
subtype="FILE_PATH", options={"SKIP_SAVE"}
)
quick_clash: bpy.props.BoolProperty( # pyright: ignore[reportRedeclaration]
options={"SKIP_SAVE"},
)
if TYPE_CHECKING:
filter_glob: str
format: str
filepath: str
quick_clash: bool
@property
def filename_ext(self) -> str:
return f".{self.format.lower()}"
def invoke(self, context, event):
if event.alt:
self.quick_clash = True
return self.execute(context)
if self.filepath:
return self.execute(context)
return ExportHelper.invoke(self, context, event)
@@ -209,14 +239,32 @@ class ExecuteIfcClash(bpy.types.Operator, ExportHelper):
self.props = tool.Clash.get_clash_props()
_, extension = os.path.splitext(self.filepath)
if extension != ".bcf":
self.filepath = bpy.path.ensure_ext(self.filepath, ".json")
# TODO Temporarily until BCF support comes back
if extension != ".json":
self.filepath = bpy.path.ensure_ext(self.filepath, ".bcf")
for clash_set in self.props.clash_sets:
for clash_sources in clash_set.get_clash_sources().values():
for clash_source in clash_sources:
if not Path(clash_source.name).is_file():
self.report(
{"ERROR"},
f"One of the provided clash source filepaths do not exist: '{clash_source.name}'.",
)
return {"CANCELLED"}
temp_file = None
if self.quick_clash:
temp_file = tempfile.NamedTemporaryFile(suffix=".json", delete=False, mode="w")
temp_file.close()
extension = ".json"
self.filepath = temp_file.name
else:
extension = Path(self.filepath).suffix.lower()
if extension != ".bcf":
self.filepath = bpy.path.ensure_ext(self.filepath, ".json")
# TODO Temporarily until BCF support comes back
if extension != ".json":
self.filepath = bpy.path.ensure_ext(self.filepath, ".bcf")
assert extension in (".bcf", ".json")
self.props.export_path = self.filepath
self.props.export_path = self.filepath
settings = ifcclash.ClashSettings()
settings.output = self.filepath
settings.logger = logging.getLogger("Clash")
@@ -225,11 +273,14 @@ class ExecuteIfcClash(bpy.types.Operator, ExportHelper):
if self.props.should_create_clash_snapshots:
def get_viewpoint_snapshot(viewpoint):
def get_viewpoint_snapshot(viewpoint) -> tuple[str, bytes]:
assert context.scene
camera = bpy.data.objects.get("IFC Clash Camera")
if not camera:
camera = bpy.data.objects.new("IFC Clash Camera", bpy.data.cameras.new("IFC Clash Camera"))
context.scene.collection.objects.link(camera)
assert isinstance(camera.data, bpy.types.Camera)
bcf_camera = viewpoint.visualization_info.perspective_camera
p = bcf_camera.camera_view_point
@@ -238,6 +289,7 @@ class ExecuteIfcClash(bpy.types.Operator, ExportHelper):
y = bcf_camera.camera_up_vector
y = Vector([y.x, y.y, y.z])
x = y.cross(z)
assert isinstance(x, Vector)
mat = Matrix(
[
@@ -251,9 +303,9 @@ class ExecuteIfcClash(bpy.types.Operator, ExportHelper):
camera.matrix_world = mat
context.scene.camera = camera
camera.data.angle = radians(60)
area = next(area for area in context.screen.areas if area.type == "VIEW_3D")
area.spaces[0].region_3d.view_perspective = "CAMERA"
area.spaces[0].shading.show_xray = True
assert (space := tool.Blender.get_view3d_space()) and space.region_3d
space.region_3d.view_perspective = "CAMERA"
space.shading.show_xray = True
context.scene.render.resolution_x = 480
context.scene.render.resolution_y = 270
context.scene.render.image_settings.file_format = "PNG"
@@ -268,10 +320,26 @@ class ExecuteIfcClash(bpy.types.Operator, ExportHelper):
clasher.clash()
clasher.export()
if extension == ".json":
# Load clash results to UI.
if extension == ".bcf":
with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as tmp:
try:
tmp.close()
settings.output = tmp.name
clasher.export()
tool.Clash.load_clash_sets(tmp.name)
finally:
Path(tmp.name).unlink()
else:
tool.Clash.load_clash_sets(self.filepath)
tool.Clash.import_active_clashes()
self.report({"INFO"}, "Finished IFC clash.")
tool.Clash.import_active_clashes()
if self.quick_clash:
assert temp_file is not None
Path(temp_file.name).unlink()
self.report({"INFO"}, "IFC Clash completed and results are loaded.")
else:
self.report({"INFO"}, f"IFC Clash results are saved to '{Path(self.filepath).name}'.")
return {"FINISHED"}
@@ -348,14 +416,16 @@ class SelectClash(bpy.types.Operator):
def execute(self, context):
self.props = tool.Clash.get_clash_props()
clash_set = tool.Clash.get_clash_set(self.props.active_clash_set.name)
active_clash = self.props.active_clash
assert (active_clash := self.props.active_clash)
assert (active_clash_set := self.props.active_clash_set)
clash_set = tool.Clash.get_clash_set(active_clash_set.name)
assert clash_set
clash = tool.Clash.get_clash(clash_set, active_clash.a_global_id, active_clash.b_global_id)
if not clash:
return {"FINISHED"}
products = []
products: list[ifcopenshell.entity_instance] = []
for global_id in (clash["a_global_id"], clash["b_global_id"]):
try:
+42 -6
View File
@@ -30,14 +30,18 @@ from bpy.props import (
FloatVectorProperty,
CollectionProperty,
)
from ifcopenshell.geom.main import ClashType, CLASH_TYPE_ITEMS
from mathutils import Vector
from typing import TYPE_CHECKING, Literal, Union
class ClashSource(PropertyGroup):
name: StringProperty(name="File")
filter_groups: CollectionProperty(type=BIMFilterGroup, name="Filter Groups")
mode: EnumProperty(
name: StringProperty( # pyright: ignore[reportRedeclaration]
name="File",
description="Absolute filepath to existing .ifc file to use as a clash source.",
)
filter_groups: CollectionProperty(type=BIMFilterGroup, name="Filter Groups") # pyright: ignore[reportRedeclaration]
mode: EnumProperty( # pyright: ignore[reportRedeclaration]
items=[
("a", "All Elements", "All elements will be used for clashing"),
("i", "Include", "Only the selected elements are included for clashing"),
@@ -47,6 +51,7 @@ class ClashSource(PropertyGroup):
)
if TYPE_CHECKING:
name: str
filter_groups: bpy.types.bpy_prop_collection_idprop[BIMFilterGroup]
mode: Literal["a", "i", "e"]
@@ -56,16 +61,31 @@ class Clash(PropertyGroup):
b_global_id: StringProperty(name="B")
a_name: StringProperty(name="A Name")
b_name: StringProperty(name="B Name")
status: BoolProperty(name="Status", default=False)
clash_type: EnumProperty( # pyright: ignore[reportRedeclaration]
name="Clash Type",
items=tuple((i, i, "") for i in CLASH_TYPE_ITEMS),
)
status: BoolProperty(
name="Status",
description="Clash status, not stored anywhere - currently just displayed in UI for convenience.",
default=False,
)
if TYPE_CHECKING:
a_global_id: str
b_global_id: str
a_name: str
b_name: str
clash_type: ClashType
status: bool
def clashes_loaded_update(self: "ClashSet", context: bpy.types.Context) -> None:
if self.clashes_loaded:
return
tool.Clash.clear_active_clash_set_results()
class ClashSet(PropertyGroup):
mode: EnumProperty(
items=[
@@ -81,13 +101,18 @@ class ClashSet(PropertyGroup):
],
name="Mode",
)
tolerance: FloatProperty(name="Tolerance", default=0.002)
clearance: FloatProperty(name="Clearance", default=0.01)
tolerance: FloatProperty(name="Tolerance", default=0.002, subtype="DISTANCE")
clearance: FloatProperty(name="Clearance", default=0.01, subtype="DISTANCE")
allow_touching: BoolProperty(name="Allow Touching", default=False)
check_all: BoolProperty(name="Check All", default=False)
a: CollectionProperty(name="Group A", type=ClashSource)
b: CollectionProperty(name="Group B", type=ClashSource)
clashes: CollectionProperty(name="Clashes", type=Clash)
clashes_loaded: BoolProperty(
name="Clash Results Are Loaded",
description="Click to unload clash results for the clash set.",
update=clashes_loaded_update,
)
if TYPE_CHECKING:
mode: Literal["intersection", "collision", "clearance"]
@@ -98,6 +123,17 @@ class ClashSet(PropertyGroup):
a: bpy.types.bpy_prop_collection_idprop[ClashSource]
b: bpy.types.bpy_prop_collection_idprop[ClashSource]
clashes: bpy.types.bpy_prop_collection_idprop[Clash]
clashes_loaded: bool
def get_clash_sources_group(
self, group: tool.Clash.ClashSourceGroup
) -> "bpy.types.bpy_prop_collection_idprop[ClashSource]":
return getattr(self, group)
def get_clash_sources(
self,
) -> "dict[tool.Clash.ClashSourceGroup, bpy.types.bpy_prop_collection_idprop[ClashSource]]":
return {g: self.get_clash_sources_group(g) for g in tool.Clash.CLASH_SOURCE_GROUP_LITERALS}
class SmartClashGroup(PropertyGroup):
+117 -44
View File
@@ -16,11 +16,16 @@
# You should have received a copy of the GNU General Public License
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
from __future__ import annotations
import bpy
import bonsai.tool as tool
import bonsai.bim.helper
from bpy.types import Panel
from bonsai.bim.module.clash.data import ClashData
from typing import TYPE_CHECKING, assert_never
if TYPE_CHECKING:
from bonsai.bim.module.clash.prop import BIMClashProperties, ClashSet, SmartClashGroup, Clash
class BIM_PT_ifcclash(Panel):
@@ -36,6 +41,7 @@ class BIM_PT_ifcclash(Panel):
if not ClashData.is_loaded:
ClashData.load()
assert self.layout
layout = self.layout
props = tool.Clash.get_clash_props()
@@ -74,46 +80,62 @@ class BIM_PT_ifcclash(Panel):
row.prop(clash_set, "clearance")
row = layout.row()
row.prop(clash_set, "check_all")
else:
assert_never(clash_set.mode)
row = layout.row(align=True)
row.label(text="Group A:", icon="OUTLINER_OB_POINTCLOUD")
row.operator("bim.add_clash_source", icon="ADD", text="").group = "a"
for index, source in enumerate(clash_set.a):
def draw_clash_set_group(group: tool.Clash.ClashSourceGroup) -> None:
row = layout.row(align=True)
row.prop(source, "name", text="")
row.prop(source, "mode", text="")
op = row.operator("bim.select_clash_source", icon="FILE_FOLDER", text="")
op.index = index
op.group = "a"
op = row.operator("bim.remove_clash_source", icon="X", text="")
op.index = index
op.group = "a"
row.label(text=f"Group {group.upper()}:", icon="OUTLINER_OB_POINTCLOUD")
row.operator("bim.add_clash_source", icon="ADD", text="").group = group
if source.mode != "a":
bonsai.bim.helper.draw_filter(
layout, source.filter_groups, ClashData, f"clash_{props.active_clash_set_index}_a_{index}"
)
sources = clash_set.get_clash_sources_group(group)
if not sources:
return
layout_ = layout.box()
for index, source in enumerate(sources):
row = layout_.row(align=True)
row.column().label(text="", icon="POINTCLOUD_POINT")
row = layout.row(align=True)
row.label(text="Group B:", icon="OUTLINER_OB_POINTCLOUD")
row.operator("bim.add_clash_source", icon="ADD", text="").group = "b"
# Draw user attention to empty filepaths.
col = row.column()
col.alert = not bool(source.name)
col.prop(source, "name", text="", placeholder="source.ifc")
for index, source in enumerate(clash_set.b):
row = layout.row(align=True)
row.prop(source, "name", text="")
row.prop(source, "mode", text="")
op = row.operator("bim.select_clash_source", icon="FILE_FOLDER", text="")
op.index = index
op.group = "b"
op = row.operator("bim.remove_clash_source", icon="X", text="")
op.index = index
op.group = "b"
op = row.operator("bim.select_clash_source", icon="FILE_FOLDER", text="")
op.index = index
op.group = group
# Make sure file selection sticks to "name" field.
row.label(text="", icon="BLANK1")
if source.mode != "a":
bonsai.bim.helper.draw_filter(
layout, source.filter_groups, ClashData, f"clash_{props.active_clash_set_index}_b_{index}"
)
row.prop(source, "mode", text="")
op = row.operator("bim.remove_clash_source", icon="X", text="")
op.index = index
op.group = group
if source.mode != "a":
def draw_filter(layout: bpy.types.UILayout, context: bpy.types.Context) -> None:
bonsai.bim.helper.draw_filter(
layout,
source.filter_groups,
ClashData,
f"clash_{props.active_clash_set_index}_{group}_{index}",
)
bonsai.bim.helper.draw_expandable_panel(
layout_,
context,
"Filter",
draw_filter,
default_closed=True,
panel_id=f"filter_{group}_{index}",
)
layout.separator()
draw_clash_set_group("a")
layout.separator()
draw_clash_set_group("b")
layout.separator()
row = layout.row()
row.prop(props, "should_create_clash_snapshots")
@@ -125,11 +147,26 @@ class BIM_PT_ifcclash(Panel):
op.filepath = props.export_path
row = layout.row()
row.label(text=f"{len(clash_set.clashes)} Clashes Found", icon="PIVOT_CURSOR")
if clash_set.clashes_loaded:
row.column().label(text=f"{len(clash_set.clashes)} Clashes Found", icon="PIVOT_CURSOR")
layout.template_list("BIM_UL_clashes", "", props.active_clash_set, "clashes", props, "active_clash_index")
row = layout.row()
row.operator("bim.select_clash")
col = row.column()
col.alignment = "RIGHT"
col.prop(clash_set, "clashes_loaded", text="", icon="TRASH", invert_checkbox=True)
split = layout.split(factor=0.07, align=True)
split.label(text="#")
row = split.row(align=True)
row.label(text="Group A Element")
row.label(text="Group B Element")
row.label(text="Type")
layout.template_list("BIM_UL_clashes", "", props.active_clash_set, "clashes", props, "active_clash_index")
row = layout.row()
row.operator("bim.select_clash")
else:
row.label(text="Clashes Are Not Loaded", icon="PIVOT_CURSOR")
class BIM_PT_clash_manager(Panel):
@@ -155,6 +192,7 @@ class BIM_PT_smart_clash_manager(Panel):
bl_parent_id = "BIM_PT_clash_manager"
def draw(self, context):
assert self.layout
layout = self.layout
props = tool.Clash.get_clash_props()
@@ -188,8 +226,16 @@ class BIM_PT_smart_clash_manager(Panel):
class BIM_UL_clash_sets(bpy.types.UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
ob = data
def draw_item(
self,
context,
layout: bpy.types.UILayout,
data: BIMClashProperties,
item: ClashSet,
icon,
active_data,
active_propname,
) -> None:
if item:
layout.prop(item, "name", text="", emboss=False)
else:
@@ -197,8 +243,16 @@ class BIM_UL_clash_sets(bpy.types.UIList):
class BIM_UL_smart_groups(bpy.types.UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
ob = data
def draw_item(
self,
context,
layout: bpy.types.UILayout,
data: BIMClashProperties,
item: SmartClashGroup,
icon,
active_data,
active_propname,
) -> None:
if item:
layout.label(text=str(item.number), translate=False, icon="NONE", icon_value=0)
else:
@@ -206,11 +260,30 @@ class BIM_UL_smart_groups(bpy.types.UIList):
class BIM_UL_clashes(bpy.types.UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
def draw_item(
self,
context,
layout: bpy.types.UILayout,
data: BIMClashProperties,
item: Clash,
icon,
active_data,
active_propname,
index,
fit_flag,
) -> None:
if item:
row = layout.row(align=True)
split = layout.split(factor=0.05, align=True)
split.label(text=str(index + 1))
row = split.row(align=False)
row.label(text=str(item.a_name), translate=False, icon="NONE", icon_value=0)
row.label(text=str(item.b_name), translate=False, icon="NONE", icon_value=0)
col = row.column()
col.enabled = False
col.prop(item, "clash_type", text="")
row.prop(item, "status", text="")
else:
layout.label(text="", translate=False)
@@ -21,6 +21,7 @@ import ifcopenshell
import ifcopenshell.util.date
import ifcopenshell.util.classification
import bonsai.tool as tool
from typing import Any
from bonsai.bim.ifc import IfcStore
@@ -32,7 +33,7 @@ def refresh():
class ClassificationsData:
data = {}
data: dict[str, Any] = {}
is_loaded = False
@classmethod
@@ -41,6 +42,7 @@ class ClassificationsData:
cls.data["has_classification_file"] = cls.has_classification_file()
cls.data["classifications"] = cls.classifications()
cls.data["available_classifications"] = cls.available_classifications()
cls.data["classification_source"] = cls.classification_source()
@classmethod
def has_classification_file(cls):
@@ -62,13 +64,25 @@ class ClassificationsData:
return []
return [(str(e.id()), e.Name, "") for e in IfcStore.classification_file.by_type("IfcClassification")]
@classmethod
def classification_source(cls) -> tool.Blender.BLENDER_ENUM_ITEMS:
items = [
("FILE", "IFC File", ""),
("MANUAL", "Manual Entry", ""),
]
dictionaries = tool.Bsdd.get_active_bsdd_enum_items()
if dictionaries:
items.append(("BSDD", "All Active bSDDs", ""))
items.extend(dictionaries)
return items
class ReferencesData:
@classmethod
def active_classification_library(cls):
if not IfcStore.classification_file or not IfcStore.classification_file.by_type("IfcClassification"):
return False
props = bpy.context.scene.BIMClassificationProperties
props = tool.Classification.get_classification_props()
name = IfcStore.classification_file.by_id(int(props.available_classifications)).Name
if name in [e.Name for e in tool.Ifc.get().by_type("IfcClassification")]:
return name
@@ -79,7 +93,7 @@ class ReferencesData:
class ClassificationReferencesData(ReferencesData):
data = {}
data: dict[str, Any] = {}
is_loaded = False
@classmethod
@@ -103,7 +117,7 @@ class ClassificationReferencesData(ReferencesData):
class MaterialClassificationsData(ReferencesData):
data = {}
data: dict[str, Any] = {}
is_loaded = False
@classmethod
@@ -119,8 +133,7 @@ class MaterialClassificationsData(ReferencesData):
results = []
props = tool.Material.get_material_props()
if props.materials and props.active_material_index < len(props.materials):
material = props.materials[props.active_material_index]
if material := props.active_material:
if material.ifc_definition_id:
element = tool.Ifc.get().by_id(material.ifc_definition_id)
for reference in ifcopenshell.util.classification.get_references(element):
@@ -131,7 +144,7 @@ class MaterialClassificationsData(ReferencesData):
class CostClassificationsData(ReferencesData):
data = {}
data: dict[str, Any] = {}
is_loaded = False
@classmethod
@@ -143,10 +156,13 @@ class CostClassificationsData(ReferencesData):
cls.data["object_type"] = "Cost"
@classmethod
def references(cls):
results = []
def references(cls) -> list[dict[str, Any]]:
results: list[dict[str, Any]] = []
props = tool.Cost.get_cost_props()
element = tool.Ifc.get().by_id(props.cost_items[props.active_cost_item_index].ifc_definition_id)
active_cost_item = props.active_cost_item
if not active_cost_item:
return results
element = tool.Ifc.get().by_id(active_cost_item.ifc_definition_id)
if element:
for reference in ifcopenshell.util.classification.get_references(element):
data = reference.get_info()
@@ -20,6 +20,7 @@ import bpy
import json
import ifcopenshell
import ifcopenshell.api
import ifcopenshell.api.pset
import ifcopenshell.api.classification
import ifcopenshell.util.classification
import ifcopenshell.util.element
@@ -46,9 +47,8 @@ class AddClassification(bpy.types.Operator, tool.Ifc.Operator):
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
props = context.scene.BIMClassificationProperties
ifcopenshell.api.run(
"classification.add_classification",
props = tool.Classification.get_classification_props()
ifcopenshell.api.classification.add_classification(
tool.Ifc.get(),
classification=IfcStore.classification_file.by_id(int(props.available_classifications)),
)
@@ -60,13 +60,11 @@ class AddManualClassification(bpy.types.Operator, tool.Ifc.Operator):
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
props = context.scene.BIMClassificationProperties
props = tool.Classification.get_classification_props()
attributes = bonsai.bim.helper.export_attributes(props.classification_attributes)
classification = ifcopenshell.api.run(
"classification.add_classification", tool.Ifc.get(), classification="Unnamed"
)
ifcopenshell.api.run(
"classification.edit_classification", tool.Ifc.get(), classification=classification, attributes=attributes
classification = ifcopenshell.api.classification.add_classification(tool.Ifc.get(), classification="Unnamed")
ifcopenshell.api.classification.edit_classification(
tool.Ifc.get(), classification=classification, attributes=attributes
)
props.is_adding = False
@@ -86,7 +84,7 @@ class AddManualClassificationReference(bpy.types.Operator, tool.Ifc.Operator):
objects = [context.active_object.name]
else:
objects = [self.obj]
props = context.scene.BIMClassificationReferenceProperties
props = tool.Classification.get_classification_reference_props()
attributes = bonsai.bim.helper.export_attributes(props.reference_attributes)
products = [
tool.Ifc.get().by_id(ifc_definition_id)
@@ -95,17 +93,14 @@ class AddManualClassificationReference(bpy.types.Operator, tool.Ifc.Operator):
]
if products:
classification = tool.Ifc.get().by_id(int(props.classifications))
reference = ifcopenshell.api.run(
"classification.add_reference",
reference = ifcopenshell.api.classification.add_reference(
tool.Ifc.get(),
products=products,
classification=classification,
identification="X",
name="Unnamed",
)
ifcopenshell.api.run(
"classification.edit_reference", tool.Ifc.get(), reference=reference, attributes=attributes
)
ifcopenshell.api.classification.edit_reference(tool.Ifc.get(), reference=reference, attributes=attributes)
props.is_adding = False
@@ -115,29 +110,30 @@ class AddClassificationFromBSDD(bpy.types.Operator, tool.Ifc.Operator):
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
props = context.scene.BIMBSDDProperties
domain = next((d for d in props.domains if d.name == props.active_domain and d.uri == props.active_uri), None)
# Maybe user loaded preview domains, set it as active
# and then reloaded them without preview domains.
if not domain:
self.report(
{"INFO"},
f"Couldn't find domain '{props.active_domain}' ({props.active_uri}). Try to reload bSDD dictionaries.",
cprops = tool.Classification.get_classification_props()
bprops = tool.Bsdd.get_bsdd_props()
if cprops.classification_source == "BSDD":
dictionaries = [d.uri for d in bprops.dictionaries if d.is_active]
else:
dictionaries = [cprops.classification_source]
for uri in dictionaries:
if not (dictionary := tool.Bsdd.get_dictionary(uri)):
continue
has_classification = False
for element in tool.Ifc.get().by_type("IfcClassification"):
if element.Name == dictionary["name"] or (
tool.Classification.get_location(element) == dictionary["uri"]
):
self.report({"INFO"}, f"Classification '{dictionary['name']}' is already added to the project.")
has_classification = True
if has_classification:
continue
classification = ifcopenshell.api.classification.add_classification(
tool.Ifc.get(), classification=dictionary["name"]
)
return
for element in tool.Ifc.get().by_type("IfcClassification"):
if element.Name == props.active_domain or (tool.Classification.get_location(element) == domain.uri):
self.report({"INFO"}, f"Classification '{props.active_domain}' is already added to the project.")
return
classification = ifcopenshell.api.run(
"classification.add_classification", tool.Ifc.get(), classification=props.active_domain
)
classification.Source = domain.organization_name_owner
classification.Edition = domain.version
tool.Classification.set_location(classification, domain.uri)
classification.Source = dictionary["organizationNameOwner"]
classification.Edition = dictionary["version"]
tool.Classification.set_location(classification, dictionary["uri"])
class EnableAddingManualClassification(bpy.types.Operator):
@@ -146,11 +142,11 @@ class EnableAddingManualClassification(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
props = context.scene.BIMClassificationProperties
props = tool.Classification.get_classification_props()
props.is_adding = True
props.active_classification_id = 0
props.classification_attributes.clear()
bonsai.bim.helper.import_attributes2("IfcClassification", props.classification_attributes)
bonsai.bim.helper.import_attributes("IfcClassification", props.classification_attributes)
return {"FINISHED"}
@@ -160,7 +156,7 @@ class DisableAddingManualClassification(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
props = context.scene.BIMClassificationProperties
props = tool.Classification.get_classification_props()
props.is_adding = False
return {"FINISHED"}
@@ -171,10 +167,10 @@ class EnableAddingManualClassificationReference(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
props = context.scene.BIMClassificationReferenceProperties
props = tool.Classification.get_classification_reference_props()
props.is_adding = True
props.reference_attributes.clear()
bonsai.bim.helper.import_attributes2("IfcClassificationReference", props.reference_attributes)
bonsai.bim.helper.import_attributes("IfcClassificationReference", props.reference_attributes)
return {"FINISHED"}
@@ -184,7 +180,7 @@ class DisableAddingManualClassificationReference(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
props = context.scene.BIMClassificationReferenceProperties
props = tool.Classification.get_classification_reference_props()
props.is_adding = False
return {"FINISHED"}
@@ -207,9 +203,9 @@ class EnableEditingClassification(bpy.types.Operator):
new.string_value = "" if new.is_null else json.dumps(data[name])
return True
props = context.scene.BIMClassificationProperties
props = tool.Classification.get_classification_props()
props.classification_attributes.clear()
bonsai.bim.helper.import_attributes2(
bonsai.bim.helper.import_attributes(
tool.Ifc.get().by_id(self.classification), props.classification_attributes, callback
)
props.active_classification_id = self.classification
@@ -222,7 +218,8 @@ class DisableEditingClassification(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
context.scene.BIMClassificationProperties.active_classification_id = 0
props = tool.Classification.get_classification_props()
props.active_classification_id = 0
return {"FINISHED"}
@@ -238,8 +235,7 @@ class RemoveClassification(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
self.file = tool.Ifc.get()
ifcopenshell.api.run(
"classification.remove_classification",
ifcopenshell.api.classification.remove_classification(
tool.Ifc.get(),
classification=self.file.by_id(self.classification),
)
@@ -251,7 +247,7 @@ class EditClassification(bpy.types.Operator, tool.Ifc.Operator):
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
props = context.scene.BIMClassificationProperties
props = tool.Classification.get_classification_props()
def callback(attributes, prop):
if prop.name == "ReferenceTokens":
@@ -276,9 +272,9 @@ class EnableEditingClassificationReference(bpy.types.Operator):
obj: bpy.props.StringProperty()
def execute(self, context):
props = context.scene.BIMClassificationReferenceProperties
props = tool.Classification.get_classification_reference_props()
props.reference_attributes.clear()
bonsai.bim.helper.import_attributes2(tool.Ifc.get().by_id(self.reference), props.reference_attributes)
bonsai.bim.helper.import_attributes(tool.Ifc.get().by_id(self.reference), props.reference_attributes)
props.active_reference_id = self.reference
return {"FINISHED"}
@@ -290,7 +286,7 @@ class DisableEditingClassificationReference(bpy.types.Operator):
obj: bpy.props.StringProperty()
def execute(self, context):
context.scene.BIMClassificationReferenceProperties.active_reference_id = 0
tool.Classification.get_classification_reference_props().active_reference_id = 0
return {"FINISHED"}
@@ -328,8 +324,7 @@ class RemoveClassificationReference(bpy.types.Operator, tool.Ifc.Operator):
if elements_by_references:
for reference, products in elements_by_references.items():
ifcopenshell.api.run(
"classification.remove_reference",
ifcopenshell.api.classification.remove_reference(
tool.Ifc.get(),
reference=reference,
products=products,
@@ -343,7 +338,7 @@ class EditClassificationReference(bpy.types.Operator, tool.Ifc.Operator):
obj: bpy.props.StringProperty()
def _execute(self, context):
props = context.scene.BIMClassificationReferenceProperties
props = tool.Classification.get_classification_reference_props()
attributes = bonsai.bim.helper.export_attributes(props.reference_attributes)
ifc_file = tool.Ifc.get()
ifcopenshell.api.classification.edit_reference(
@@ -370,7 +365,7 @@ class AddClassificationReference(bpy.types.Operator, tool.Ifc.Operator):
objects = [context.active_object.name]
else:
objects = [self.obj]
props = context.scene.BIMClassificationProperties
props = tool.Classification.get_classification_props()
classification = None
classification_name = IfcStore.classification_file.by_id(int(props.available_classifications)).Name
for element in tool.Ifc.get().by_type("IfcClassification"):
@@ -384,8 +379,7 @@ class AddClassificationReference(bpy.types.Operator, tool.Ifc.Operator):
if (ifc_definition_id := tool.Blender.get_obj_ifc_definition_id(obj, self.obj_type, context))
]
if products:
ifcopenshell.api.run(
"classification.add_reference",
ifcopenshell.api.classification.add_reference(
tool.Ifc.get(),
reference=IfcStore.classification_file.by_id(self.reference),
products=products,
@@ -412,33 +406,31 @@ class AddClassificationReferenceFromBSDD(bpy.types.Operator, tool.Ifc.Operator):
objects = [context.active_object.name]
else:
objects = [self.obj]
props = context.scene.BIMClassificationProperties
bprops = context.scene.BIMBSDDProperties
bprops = tool.Bsdd.get_bsdd_props()
use_only_ifc_properties: bool = bprops.use_only_ifc_properties
bsdd_classification = bprops.classifications[bprops.active_classification_index]
classification = None
for element in tool.Ifc.get().by_type("IfcClassification"):
if element.Name == bsdd_classification.domain_name or (
tool.Classification.get_location(element) == bsdd_classification.domain_namespace_uri
if element.Name == bsdd_classification.dictionary_name or (
tool.Classification.get_location(element) == bsdd_classification.dictionary_namespace_uri
):
classification = element
break
if not classification:
classification = ifcopenshell.api.run(
"classification.add_classification", tool.Ifc.get(), classification=bsdd_classification.domain_name
classification = ifcopenshell.api.classification.add_classification(
tool.Ifc.get(), classification=bsdd_classification.dictionary_name
)
tool.Classification.set_location(classification, bsdd_classification.domain_namespace_uri)
tool.Classification.set_location(classification, bsdd_classification.dictionary_namespace_uri)
for obj in objects:
ifc_definition_id = tool.Blender.get_obj_ifc_definition_id(obj, self.obj_type, context)
if not ifc_definition_id:
continue
element = tool.Ifc.get().by_id(ifc_definition_id)
reference = ifcopenshell.api.run(
"classification.add_reference",
reference = ifcopenshell.api.classification.add_reference(
tool.Ifc.get(),
products=[element],
classification=classification,
@@ -476,18 +468,16 @@ class AddClassificationReferenceFromBSDD(bpy.types.Operator, tool.Ifc.Operator):
if pset:
pset = tool.Ifc.get().by_id(pset["id"])
elif is_pset:
pset = ifcopenshell.api.run(
"pset.add_pset", tool.Ifc.get(), product=element, name=classification_pset.name
pset = ifcopenshell.api.pset.add_pset(
tool.Ifc.get(), product=element, name=classification_pset.name
)
else:
pset = ifcopenshell.api.run(
"pset.add_qto", tool.Ifc.get(), product=element, name=classification_pset.name
)
pset = ifcopenshell.api.pset.add_qto(tool.Ifc.get(), product=element, name=classification_pset.name)
if is_pset:
ifcopenshell.api.run("pset.edit_pset", tool.Ifc.get(), pset=pset, properties=properties)
ifcopenshell.api.pset.edit_pset(tool.Ifc.get(), pset=pset, properties=properties)
else:
ifcopenshell.api.run("pset.edit_qto", tool.Ifc.get(), qto=pset, properties=properties)
ifcopenshell.api.pset.edit_qto(tool.Ifc.get(), qto=pset, properties=properties)
class ChangeClassificationLevel(bpy.types.Operator):
@@ -497,7 +487,7 @@ class ChangeClassificationLevel(bpy.types.Operator):
parent_id: bpy.props.IntProperty()
def execute(self, context):
props = context.scene.BIMClassificationProperties
props = tool.Classification.get_classification_props()
props.available_library_references.clear()
for reference in IfcStore.classification_file.by_id(self.parent_id).HasReferences:
new = props.available_library_references.add()
@@ -519,6 +509,6 @@ class DisableEditingClassificationReferences(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
props = context.scene.BIMClassificationProperties
props = tool.Classification.get_classification_props()
props.available_library_references.clear()
return {"FINISHED"}
@@ -17,6 +17,7 @@
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
import bpy
import bonsai.tool as tool
from bonsai.bim.prop import Attribute
from bonsai.bim.module.classification.data import ClassificationsData, ClassificationReferencesData
from bpy.types import PropertyGroup
@@ -30,9 +31,12 @@ from bpy.props import (
FloatVectorProperty,
CollectionProperty,
)
from typing import TYPE_CHECKING
def get_available_classifications(self, context):
def get_available_classifications(
self: "BIMClassificationProperties", context: bpy.types.Context
) -> tool.Blender.BLENDER_ENUM_ITEMS:
if not ClassificationsData.is_loaded:
ClassificationsData.load()
return ClassificationsData.data["available_classifications"]
@@ -44,25 +48,30 @@ def get_classifications(self, context):
return ClassificationReferencesData.data["classifications"]
def get_classification_source(
self: "BIMClassificationProperties", context: bpy.types.Context
) -> tool.Blender.BLENDER_ENUM_ITEMS:
if not ClassificationsData.is_loaded:
ClassificationsData.load()
return ClassificationsData.data["classification_source"]
class ClassificationReference(PropertyGroup):
name: StringProperty(name="Name")
identification: StringProperty(name="Identification")
ifc_definition_id: IntProperty(name="IFC Definition ID")
has_references: BoolProperty(name="Has References")
referenced_source: IntProperty(name="IFC Definition ID")
if TYPE_CHECKING:
identification: str
ifc_definition_id: int
has_references: bool
referenced_source: int
class BIMClassificationProperties(PropertyGroup):
is_adding: BoolProperty(name="Is Adding", default=False)
classification_source: EnumProperty(
items=[
("FILE", "IFC File", ""),
("BSDD", "buildingSMART Data Dictionary", ""),
("MANUAL", "Manual Entry", ""),
],
name="Classification Source",
default="FILE",
)
classification_source: EnumProperty(items=get_classification_source, name="Classification Source")
available_classifications: EnumProperty(items=get_available_classifications, name="Available Classifications")
classification_attributes: CollectionProperty(name="Classification Attributes", type=Attribute)
active_classification_id: IntProperty(name="Active Classification Id")
@@ -70,9 +79,25 @@ class BIMClassificationProperties(PropertyGroup):
active_library_referenced_source: IntProperty(name="Active Library Referenced Source")
active_library_reference_index: IntProperty(name="Active Library Reference Index")
if TYPE_CHECKING:
is_adding: bool
classification_source: str
available_classifications: str
classification_attributes: bpy.types.bpy_prop_collection_idprop[Attribute]
active_classification_id: int
available_library_references: bpy.types.bpy_prop_collection_idprop[ClassificationReference]
active_library_referenced_source: int
active_library_reference_index: int
class BIMClassificationReferenceProperties(PropertyGroup):
is_adding: BoolProperty(name="Is Adding", default=False)
classifications: EnumProperty(items=get_classifications, name="Classifications")
reference_attributes: CollectionProperty(name="Reference Attributes", type=Attribute)
active_reference_id: IntProperty(name="Active Reference Id")
if TYPE_CHECKING:
is_adding: bool
classifications: str
reference_attributes: bpy.types.bpy_prop_collection_idprop
active_reference_id: int
@@ -16,6 +16,7 @@
# You should have received a copy of the GNU General Public License
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
from __future__ import annotations
import bpy
import bonsai.bim.helper
import bonsai.tool as tool
@@ -27,6 +28,10 @@ from bonsai.bim.module.classification.data import (
MaterialClassificationsData,
CostClassificationsData,
)
from typing import TYPE_CHECKING, Any, Union
if TYPE_CHECKING:
from bonsai.bim.module.classification.prop import BIMClassificationProperties, ClassificationReference
class BIM_PT_classifications(Panel):
@@ -46,7 +51,8 @@ class BIM_PT_classifications(Panel):
if not ClassificationsData.is_loaded:
ClassificationsData.load()
self.props = context.scene.BIMClassificationProperties
self.props = tool.Classification.get_classification_props()
assert self.layout
row = self.layout.row(align=True)
row.label(text="Source", icon="OUTLINER")
@@ -54,10 +60,10 @@ class BIM_PT_classifications(Panel):
if self.props.classification_source == "FILE":
self.draw_add_file_ui(context)
elif self.props.classification_source == "BSDD":
self.draw_add_bsdd_ui(context)
elif self.props.classification_source == "MANUAL":
self.draw_add_manual_ui(context)
else:
self.draw_add_bsdd_ui(context)
for classification in ClassificationsData.data["classifications"]:
if self.props.active_classification_id == classification["id"]:
@@ -76,16 +82,6 @@ class BIM_PT_classifications(Panel):
row.operator("bim.enable_adding_manual_classification", text="Add Classification", icon="ADD")
def draw_add_bsdd_ui(self, context):
self.bprops = context.scene.BIMBSDDProperties
if not self.bprops.active_domain:
row = self.layout.row()
row.label(text="No Active bSDD Domain", icon="ERROR")
return
row = self.layout.row()
row.label(text="Active: " + self.bprops.active_domain, icon="URL")
row = self.layout.row()
row.operator("bim.add_classification_from_bsdd", icon="ADD")
@@ -117,12 +113,18 @@ class BIM_PT_classifications(Panel):
class ReferenceUI:
layout: bpy.types.UILayout
data: type[
Union[
ClassificationReferencesData,
MaterialClassificationsData,
CostClassificationsData,
]
]
def draw_ui(self, context):
obj = context.active_object
self.sprops = context.scene.BIMClassificationProperties
self.bprops = context.scene.BIMBSDDProperties
self.props = context.scene.BIMClassificationReferenceProperties
def draw_ui(self, context) -> None:
self.sprops = tool.Classification.get_classification_props()
self.bprops = tool.Bsdd.get_bsdd_props()
self.props = tool.Classification.get_classification_reference_props()
self.file = tool.Ifc.get()
self.draw_add_ui(context)
@@ -137,19 +139,19 @@ class ReferenceUI:
else:
self.draw_reference_ui(reference)
def draw_add_ui(self, context):
def draw_add_ui(self, context) -> None:
row = self.layout.row(align=True)
row.label(text="Source", icon="OUTLINER")
row.prop(self.sprops, "classification_source", text="")
if self.sprops.classification_source == "FILE":
self.draw_add_file_ui(context)
elif self.sprops.classification_source == "BSDD":
self.draw_add_bsdd_ui(context)
elif self.sprops.classification_source == "MANUAL":
self.draw_add_manual_ui(context)
else:
self.draw_add_bsdd_ui(context)
def draw_add_manual_ui(self, context):
def draw_add_manual_ui(self, context) -> None:
row = self.layout.row()
row.prop(self.props, "classifications", text="")
if self.props.is_adding:
@@ -162,23 +164,12 @@ class ReferenceUI:
row = self.layout.row()
row.operator("bim.enable_adding_manual_classification_reference", text="Add Reference", icon="ADD")
def draw_add_bsdd_ui(self, context):
if not self.bprops.active_domain:
row = self.layout.row()
row.label(text="No Active bSDD Domain", icon="ERROR")
return
row = self.layout.row()
row.label(text="Active: " + self.bprops.active_domain, icon="URL")
def draw_add_bsdd_ui(self, context) -> None:
row = self.layout.row(align=True)
row.prop(self.bprops, "keyword", text="")
row.prop(self.bprops, "should_filter_ifc_class", text="", icon="FILTER")
row.operator("bim.search_bsdd_classifications", text="", icon="VIEWZOOM")
row = self.layout.row()
row.prop(self.bprops, "should_filter_ifc_class")
row.prop(self.bprops, "use_only_ifc_properties")
if len(self.bprops.classifications):
self.layout.template_list(
"BIM_UL_bsdd_classifications",
@@ -199,23 +190,8 @@ class ReferenceUI:
)
op.obj = self.obj
op.obj_type = self.obj_type
row.operator("bim.get_bsdd_classification_properties", text="", icon="COPY_ID")
if len(self.bprops.classification_psets):
use_only_ifc_properties = self.bprops.use_only_ifc_properties
for pset in self.bprops.classification_psets:
properties = pset.properties
if use_only_ifc_properties:
properties = [p for p in properties if p.metadata == "IFC"]
if not properties:
continue
box = self.layout.box()
row = box.row()
row.label(text=pset.name, icon="COPY_ID")
bonsai.bim.helper.draw_attributes(properties, box)
def draw_add_file_ui(self, context):
def draw_add_file_ui(self, context) -> None:
if not self.data.data["active_classification_library"]:
row = self.layout.row(align=True)
row.label(text="No Active Classification Library", icon="ERROR")
@@ -249,13 +225,13 @@ class ReferenceUI:
"active_library_reference_index",
)
def draw_editable_ui(self):
def draw_editable_ui(self) -> None:
row = self.layout.row(align=True)
row.operator("bim.edit_classification_reference", text="Save changes", icon="CHECKMARK")
row.operator("bim.disable_editing_classification_reference", text="", icon="CANCEL")
bonsai.bim.helper.draw_attributes(self.props.reference_attributes, self.layout)
def draw_reference_ui(self, reference):
def draw_reference_ui(self, reference: dict[str, Any]) -> None:
row = self.layout.row(align=True)
if self.file.schema == "IFC2X3":
name = reference["ItemReference"] or "No Identification"
@@ -351,7 +327,16 @@ class BIM_PT_cost_classifications(Panel, ReferenceUI):
class BIM_UL_classifications(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
def draw_item(
self,
context,
layout: bpy.types.UILayout,
data: BIMClassificationProperties,
item: ClassificationReference,
icon,
active_data,
active_propname,
):
if item:
if item.has_references:
op = layout.operator("bim.change_classification_level", text="", icon="DISCLOSURE_TRI_RIGHT")
@@ -17,12 +17,27 @@
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
import bpy
import json
import ifcopenshell.api
import ifcopenshell.api.constraint
import ifcopenshell.util.attribute
import bonsai.bim.helper
import bonsai.tool as tool
from typing import TYPE_CHECKING
def get_active_object(context: bpy.types.Context, obj_name: str) -> bpy.types.Object:
if obj_name:
obj = bpy.data.objects[obj_name]
else:
assert (obj := context.active_object)
return obj
def get_selected_objects(context: bpy.types.Context, obj_name: str) -> list[bpy.types.Object]:
if obj_name:
objs = [bpy.data.objects[obj_name]]
else:
objs = context.selected_objects
return objs
class LoadObjectives(bpy.types.Operator):
@@ -31,7 +46,7 @@ class LoadObjectives(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
props = context.scene.BIMConstraintProperties
props = tool.Blender.get_constraint_props()
props.constraints.clear()
for constraint in tool.Ifc.get().by_type("IfcObjective"):
new = props.constraints.add()
@@ -48,7 +63,8 @@ class DisableConstraintEditingUI(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
context.scene.BIMConstraintProperties.is_editing = ""
props = tool.Blender.get_constraint_props()
props.is_editing = ""
bpy.ops.bim.disable_editing_constraint()
return {"FINISHED"}
@@ -59,10 +75,13 @@ class EnableEditingConstraint(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
constraint: bpy.props.IntProperty()
if TYPE_CHECKING:
constraint: int
def execute(self, context):
props = context.scene.BIMConstraintProperties
props = tool.Blender.get_constraint_props()
props.constraint_attributes.clear()
bonsai.bim.helper.import_attributes2(tool.Ifc.get().by_id(self.constraint), props.constraint_attributes)
bonsai.bim.helper.import_attributes(tool.Ifc.get().by_id(self.constraint), props.constraint_attributes)
props.active_constraint_id = self.constraint
return {"FINISHED"}
@@ -73,7 +92,8 @@ class DisableEditingConstraint(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
context.scene.BIMConstraintProperties.active_constraint_id = 0
props = tool.Blender.get_constraint_props()
props.active_constraint_id = 0
return {"FINISHED"}
@@ -83,7 +103,7 @@ class AddObjective(bpy.types.Operator, tool.Ifc.Operator):
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
result = ifcopenshell.api.run("constraint.add_objective", tool.Ifc.get())
result = ifcopenshell.api.constraint.add_objective(tool.Ifc.get())
bpy.ops.bim.load_objectives()
bpy.ops.bim.enable_editing_constraint(constraint=result.id())
return {"FINISHED"}
@@ -95,7 +115,7 @@ class EditObjective(bpy.types.Operator, tool.Ifc.Operator):
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
props = context.scene.BIMConstraintProperties
props = tool.Blender.get_constraint_props()
attributes = bonsai.bim.helper.export_attributes(props.constraint_attributes)
ifc_file = tool.Ifc.get()
ifcopenshell.api.constraint.edit_objective(
@@ -113,12 +133,13 @@ class RemoveConstraint(bpy.types.Operator, tool.Ifc.Operator):
bl_options = {"REGISTER", "UNDO"}
constraint: bpy.props.IntProperty()
if TYPE_CHECKING:
constraint: int
def _execute(self, context):
props = context.scene.BIMConstraintProperties
props = tool.Blender.get_constraint_props()
self.file = tool.Ifc.get()
ifcopenshell.api.run(
"constraint.remove_constraint", self.file, **{"constraint": self.file.by_id(self.constraint)}
)
ifcopenshell.api.constraint.remove_constraint(self.file, constraint=self.file.by_id(self.constraint))
if props.is_editing == "IfcObjective":
bpy.ops.bim.load_objectives()
return {"FINISHED"}
@@ -130,9 +151,12 @@ class EnableAssigningConstraint(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
if TYPE_CHECKING:
obj: str
def execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
props = obj.BIMObjectConstraintProperties
obj = get_active_object(context, self.obj)
props = tool.Blender.get_object_constraint_props(obj)
if props.available_constraint_types == "IfcObjective":
bpy.ops.bim.load_objectives()
props.is_adding = props.available_constraint_types
@@ -145,9 +169,12 @@ class DisableAssigningConstraint(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
if TYPE_CHECKING:
obj: str
def execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
props = obj.BIMObjectConstraintProperties
obj = get_active_object(context, self.obj)
props = tool.Blender.get_object_constraint_props(obj)
props.is_adding = ""
return {"FINISHED"}
@@ -159,8 +186,13 @@ class AssignConstraint(bpy.types.Operator, tool.Ifc.Operator):
obj: bpy.props.StringProperty()
constraint: bpy.props.IntProperty()
if TYPE_CHECKING:
obj: str
constraint: int
def _execute(self, context):
self.file = tool.Ifc.get()
objs = get_selected_objects(context, self.obj)
objs = [bpy.data.objects[self.obj]] if self.obj else context.selected_objects
products = [
self.file.by_id(obj_id)
@@ -168,13 +200,10 @@ class AssignConstraint(bpy.types.Operator, tool.Ifc.Operator):
if (obj_id := tool.Blender.get_object_bim_props(obj).ifc_definition_id)
]
if products:
ifcopenshell.api.run(
"constraint.assign_constraint",
ifcopenshell.api.constraint.assign_constraint(
self.file,
**{
"products": products,
"constraint": self.file.by_id(self.constraint),
},
products=products,
constraint=self.file.by_id(self.constraint),
)
return {"FINISHED"}
@@ -186,21 +215,22 @@ class UnassignConstraint(bpy.types.Operator, tool.Ifc.Operator):
obj: bpy.props.StringProperty()
constraint: bpy.props.IntProperty()
if TYPE_CHECKING:
obj: str
constraint: int
def _execute(self, context):
self.file = tool.Ifc.get()
objs = [bpy.data.objects[self.obj]] if self.obj else context.selected_objects
objs = get_selected_objects(context, self.obj)
products = [
self.file.by_id(obj_id)
for obj in objs
if (obj_id := tool.Blender.get_object_bim_props(obj).ifc_definition_id)
]
if products:
ifcopenshell.api.run(
"constraint.unassign_constraint",
ifcopenshell.api.constraint.unassign_constraint(
self.file,
**{
"products": products,
"constraint": self.file.by_id(self.constraint),
},
products=products,
constraint=self.file.by_id(self.constraint),
)
return {"FINISHED"}
@@ -32,6 +32,7 @@ from bpy.props import (
FloatVectorProperty,
CollectionProperty,
)
from typing import TYPE_CHECKING, Literal
def get_available_constraint_types(self, context):
@@ -44,6 +45,10 @@ class Constraint(PropertyGroup):
name: StringProperty(name="Name")
ifc_definition_id: IntProperty(name="IFC Definition ID")
if TYPE_CHECKING:
name: str
ifc_definition_id: int
class BIMConstraintProperties(PropertyGroup):
constraint_attributes: CollectionProperty(name="Constraint Attributes", type=Attribute)
@@ -52,7 +57,18 @@ class BIMConstraintProperties(PropertyGroup):
active_constraint_index: IntProperty(name="Active Constraint Index")
is_editing: StringProperty(name="Is Editing")
if TYPE_CHECKING:
constraint_attributes: bpy.types.bpy_prop_collection_idprop[Attribute]
active_constraint_id: int
constraints: bpy.types.bpy_prop_collection_idprop[Constraint]
active_constraint_index: int
is_editing: str
class BIMObjectConstraintProperties(PropertyGroup):
is_adding: StringProperty(name="Is Adding")
available_constraint_types: EnumProperty(items=get_available_constraint_types, name="Available Constraint Types")
if TYPE_CHECKING:
is_adding: str
available_constraint_types: Literal["IfcObjective"]
+31 -8
View File
@@ -16,10 +16,15 @@
# You should have received a copy of the GNU General Public License
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
from __future__ import annotations
import bonsai.tool as tool
from bpy.types import Panel, UIList
from bonsai.bim.helper import draw_attributes
from bonsai.bim.module.constraint.data import ConstraintsData, ObjectConstraintsData
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from bonsai.bim.module.constraint.prop import BIMConstraintProperties, BIMObjectConstraintProperties, Constraint
class BIM_PT_constraints(Panel):
@@ -39,7 +44,7 @@ class BIM_PT_constraints(Panel):
if not ConstraintsData.is_loaded:
ConstraintsData.load()
self.props = context.scene.BIMConstraintProperties
self.props = tool.Blender.get_constraint_props()
if not self.props.is_editing or self.props.is_editing == "IfcObjective":
row = self.layout.row(align=True)
@@ -91,8 +96,8 @@ class BIM_PT_object_constraints(Panel):
obj = context.active_object
assert obj
self.oprops = tool.Blender.get_object_bim_props(obj)
self.sprops = context.scene.BIMConstraintProperties
self.props = obj.BIMObjectConstraintProperties
self.sprops = tool.Blender.get_constraint_props()
self.props = tool.Blender.get_object_constraint_props(obj)
self.file = tool.Ifc.get()
self.draw_add_ui()
@@ -131,15 +136,24 @@ class BIM_PT_object_constraints(Panel):
class BIM_UL_constraints(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
def draw_item(
self,
context,
layout,
data: BIMConstraintProperties,
item: Constraint,
icon,
active_data,
active_propname,
):
if item:
row = layout.row(align=True)
row.label(text=item.name)
if context.scene.BIMConstraintProperties.active_constraint_id == item.ifc_definition_id:
if context.scene.BIMConstraintProperties.is_editing == "IfcObjective":
if data.active_constraint_id == item.ifc_definition_id:
if data.is_editing == "IfcObjective":
row.operator("bim.edit_objective", text="", icon="CHECKMARK")
row.operator("bim.disable_editing_constraint", text="", icon="CANCEL")
elif context.scene.BIMConstraintProperties.active_constraint_id:
elif data.active_constraint_id:
row.operator("bim.remove_constraint", text="", icon="X").constraint = item.ifc_definition_id
else:
op = row.operator("bim.enable_editing_constraint", text="", icon="GREASEPENCIL")
@@ -148,7 +162,16 @@ class BIM_UL_constraints(UIList):
class BIM_UL_object_constraints(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
def draw_item(
self,
context,
layout,
data: BIMObjectConstraintProperties,
item: Constraint,
icon,
active_data,
active_propname,
):
if item:
row = layout.row(align=True)
row.label(text=item.name)

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