Compare commits

...

693 Commits

Author SHA1 Message Date
Andrej730 1fca38625f A last bit of cleaning up temp files after tests 2023-03-02 10:45:00 +05:00
Sigma Dimensions cf2ad43ed7 refactor pset operator 2023-03-01 15:37:19 +01:00
Andrej730 fb3023bb79 Moved blenderbim tests temporary files to separate directory
Moved temporary test files to separate directory and added it to gitignore

Still have test.exp.cache.dat spawning from `test_feature`...
2023-03-01 18:46:13 +05:00
Andrej730 52f18fea85 Viewport preview for batting annotation
Currently it's always set to the same thickness and doesn't update from pset value.

It's currently using zigzag pattern to indicate batting because some techincal limitation of the shader (Error: C6033: Hardware limitation reached, can only emit 256 vertices of this size)

Also added batting to the annotation tool panel.
2023-03-01 17:37:17 +05:00
Dion Moult d5ecb9b8ea See #2820. Fix issue where some hatches weren't merged when an object had multiple SVG paths. 2023-03-01 22:25:12 +11:00
Dion Moult 67d33001d5 See #2820. Ensure only closed polygons are merged in drawings. 2023-03-01 21:57:33 +11:00
Dion Moult 4f571fa2fc Attempt to fix pytest bundling. 2023-03-01 21:39:15 +11:00
Dion Moult c5bd902dc6 See #2820. See #2822. Fix to accommodate empty element groups in drawings. 2023-03-01 21:08:34 +11:00
Dion Moult 68282c7a1e Fix #2821. 2023-03-01 20:54:07 +11:00
Sigma Dimensions d40dcae212 fix 5d csv imports with funky characters and numbers, fix 5d export 2023-03-01 10:51:52 +01:00
Dion Moult 12151c40ff Fix #2799. Drawing polygon merging now is purely material based. 2023-03-01 17:52:57 +11:00
Dion Moult 9c8b15fa1a Fix #2645. No longer override built-in operators for critical features. 2023-03-01 14:30:56 +11:00
Dion Moult 3d49cc9d13 By default, the site is now cut with a nice thick line on drawings 2023-03-01 11:28:45 +11:00
Dion Moult 955c0d19a6 See #2799. Cut walls with the same material in drawings are now merged by default. 2023-02-28 22:59:45 +11:00
Andrej730 3cf7a9e925 Renamed batting pset 2023-02-28 11:24:47 +05:00
Andrej730 8cbf719772 Replaced tool method from 17add48 with existing one 2023-02-28 10:04:18 +05:00
Dion Moult 2f614330c9 Fix #2781. Usability bug where it wasn't clear references were only loaded from the active external classification library. 2023-02-28 12:39:15 +11:00
Dion Moult 5a40519114 Minor fix 2023-02-28 11:43:36 +11:00
Dion Moult 96271e8bd8 Users can no longer create orphaned opening elements, which are invalid. 2023-02-28 10:05:22 +11:00
Dion Moult 9b75a415d4 See #2812. Resolve issue where obsolete link_to_scene argument was used. 2023-02-28 10:03:56 +11:00
Dion Moult 5f71abb5fa Minor fix to insulation scale 2023-02-28 09:23:28 +11:00
Dion Moult affb066b7b Fix #2813. Bug where the user could try to load thumbnails even though there were no types. 2023-02-28 08:44:36 +11:00
Andrej730 17add48254 Minor refactor for modifiers 2023-02-27 19:18:39 +05:00
Andrej730 f5b6fdcb5b Batting Annotation pattern
Added batting pattern to line annotations.
The way to use it is:
1) Add "Line" Annotation;
2) Change object type to "BATTING"
3) Add object property set "Pset_BBIM_Bating" (also added in this commit) to specify the batting thickness.

Currently there is no preview for batting in viewport.

Other changes:
- EPset_Annotation.ifc -> Psets_BBIM_Annotation.ifc (more general psets ifc library for the future use)
2023-02-27 19:18:39 +05:00
Dion Moult 68bdbe4c4f Bundle bpypolyskel so that users can start playing with the hipped roof generator 2023-02-27 21:49:04 +11:00
Dion Moult 363ddc2a9b Minor fix 2023-02-27 20:49:45 +11:00
Dion Moult 20d44378d9 You can now generate type-based spaces and regenerate spaces when boundaries change. 2023-02-27 20:49:38 +11:00
Dion Moult 4fe321275a Fix bug where space types were not loaded. 2023-02-27 20:47:20 +11:00
Dion Moult 60b1192703 Fix critical bug where a user might override and break existing objects if they reused an object name 2023-02-27 20:46:53 +11:00
Dion Moult b25233a748 Fix critical bug where duplicate orphaned collections were created in the background. 2023-02-27 20:12:39 +11:00
Dion Moult d9b54c923c Fix bug where you couldn't manage spatial types in the type manager 2023-02-27 20:10:27 +11:00
Dion Moult 131ee8270b Minor fix 2023-02-27 10:57:28 +11:00
Massimo Fabbro e71bace6a8 update makefile with shapely 2.0.1 2023-02-27 09:10:46 +11:00
Dion Moult 2c4a0669ed Fix bug where copying to container might get the wrong coordinates if containers weren't synced. 2023-02-26 22:31:21 +11:00
Dion Moult ee451b6b29 Experimental operator to generate hipped roofs from a footprint. 2023-02-26 21:23:35 +11:00
Dion Moult 7b858fffb2 All generated drawing text now has classes so you can customise their style. 2023-02-26 21:22:43 +11:00
Dion Moult 6ce0deeb63 Fix #2808. Bug where you couldn't assign element types in IFC4X3. 2023-02-26 11:26:13 +11:00
Dion Moult 96b2eaeae4 Experimental operator to generate spaces bounded by walls. 2023-02-25 21:40:02 +11:00
Andrej730 8fe8175a87 Monolithic stair variant generator 2023-02-25 10:40:27 +05:00
Andrej730 ee2bedfb64 Update openings position based on fillings position at wall regenarete
So basically now when you move windows or doors attached to the wall, their openings positions will also be updated if you hit "regenerate" for the wall.
2023-02-24 17:12:41 +05:00
Andrej730 aa8da74ed8 ifc2x3 pset library name fix + copyrights 2023-02-24 11:07:33 +05:00
Dion Moult f2c03a508f Fix #2600. Link back to exact docs folder in repo. 2023-02-24 16:21:21 +11:00
Dion Moult aa172488b0 Fix #2594. Document potential lag in PyPI releases of C++ core. 2023-02-24 13:35:46 +11:00
Dion Moult ae09adf854 Fix #2424. Bug where indirect assets (e.g. importing a type using a material) with relationships had their relationships duplicated 2023-02-24 13:01:26 +11:00
Andrej730 5b5dd73684 get_inverse_attributes - added more correct recursive unpacking for aggregates and selections
Not affected the result schemas though
2023-02-23 18:56:24 +05:00
Andrej730 3849a87adc Added simple tests for door and window modifier
This thing used to result in errors before we fixed `replace_object_ifc_representation`
2023-02-23 18:38:20 +05:00
Andrej730 708dc6aca2 IFC4X3/IFC2X3 pset templates library
Generated from xmls from the ISO release of IFC4X3.
Based on https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/annex/annex-a/general-usage/IFC4_ADD2.ifc

Also generated pset templates library for IFC2X3 (also based on similar xmls) but IFC2X3 ifc pset library is using... IFC4X3 schema because IfcPropertySetTemplate and IfcRelDeclares are not available in IFC2X3. Which is okay for our purposes for now.

Will need to update IFC4X3 library if one of the issues will be resolved:
https://github.com/buildingSMART/IFC4.3.x-development/issues/587
https://github.com/buildingSMART/IFC4.3.x-development/issues/586
2023-02-23 18:17:14 +05:00
Dion Moult 2fe2c349fd Fix #2743. obj2ifc scripts updated to support multiple OBJ files. 2023-02-23 22:17:34 +11:00
Dion Moult d396d32a8f You can now extend beams to walls. 2023-02-23 18:33:31 +11:00
Dion Moult 1fd02755f8 Fix BCF module bugs where certain BCF entities were assumed to exist. 2023-02-23 18:32:44 +11:00
Dion Moult 79d92b511b Fix #2442. Fix bug where viewboxes were not clipped when compiling drawing sheets. 2023-02-23 13:16:33 +11:00
Andrej730 ff7d93e66c Undo tests - proof of concept
Created some simple undo tests.

This "flip wall and ctrl-z" test actually is crashing blender if I do it manually - so I guess undo in background mode is still kind of different.
2023-02-22 20:24:43 +05:00
Dion Moult 68a8c7a235 Add basic integration tests for modeling slabs and beams 2023-02-22 21:50:12 +11:00
Dion Moult 5e744ff7d1 Add integration tests for basic wall modeling features 2023-02-22 20:38:02 +11:00
Dion Moult 921a185ffa Fix bug where natively loaded faceted breps didn't respect false origins. 2023-02-22 16:32:32 +11:00
Dion Moult 855751f026 Fix #2767. Removing representations now cleans up after orphaned Blender mesh data. 2023-02-22 16:24:24 +11:00
Sigma Dimensions 255c2efe98 Fix sequence data loading for unnamed items, ref #2783 2023-02-21 22:55:44 +01:00
Sigma Dimensions 892b282054 Fix loading task properties when calendar doesn't have a name, ref issue #2783 2023-02-21 20:23:07 +01:00
Dion Moult 26efc6b4e7 Fix bug where BIM tool hotkeys could activate operations without being in a correct state. 2023-02-21 21:54:53 +11:00
Dion Moult a0618c5827 Fix #2769. Bug where parametric UI was hardcoded to only work for walls. Now works on any generic parametric vertical layers. 2023-02-21 21:41:52 +11:00
Andrej730 d4bbb6e2c7 Added Model/Profile/Elevation_VIEW context as default for new projects #2784
Default Model/Profile/Elevation_VIEW context could be helpful to make openings created with BIM tool more correct.
2023-02-21 11:46:05 +05:00
Andrej730 66b7692227 Fixed windows/openings bugs
1) Window bug (#2792) - lining_offset wasn't converted from ifc units during window properties editing
2) Openings bug - OveallWidth/OverallHeight based openings was converted to ifc units even though their dimensions were already in ifc units.
2023-02-21 11:20:36 +05:00
Dion Moult 2429f507ad Fix #2785. Fix bug where invalid models from Revit that reused material usages could create invalid models. 2023-02-21 13:48:24 +11:00
Dion Moult 03a66d2806 Fix #2787. Fix creating invalid orphaned property and group relationships when removing systems / groups / elements. 2023-02-21 10:59:47 +11:00
Dion Moult 9d8551cb48 Demo library now has profile names to follow native IFC best practices 2023-02-21 10:14:06 +11:00
Dion Moult 2ca0b52c6a Fix failing test when editing a material profile with an unnamed profile 2023-02-21 09:51:30 +11:00
Massimo Fabbro 9cc754488b calculate all quantities smoke test 2023-02-21 09:12:30 +11:00
Massimo Fabbro f81ceb53cf add qto feature test 2023-02-21 09:12:30 +11:00
Dion Moult d1d0b5724d Revert "Solving test fail - keeping unnamed profiles when updating profiles in MaterialsData"
This reverts commit 0e1689c2d9.
2023-02-21 08:51:44 +11:00
Sigma Dimensions 40560b71c2 BBIM: fix material errors, and add smoke tests 2023-02-20 17:49:54 +01:00
Andrej730 378a25bbaa Added some .feature convenience debug fixtures 2023-02-20 19:57:05 +05:00
Andrej730 1dfabe2591 Fixed issue with float vectors assertion during tests
`sequence.feature` - Added `[:]` to make errors more readable values in errors (for example, (0.20000000298023224, 0.20000000298023224, 0.20000000298023224, 1.0) instead of `<bpy_float[4], Object.color>`

`test_feature.py` - fixed `FAILED test/bim/test_feature.py::test_animate_the_consumption_of_a_wall - AssertionError: Value is <bpy_float[4], Object.color>`
Test was failing because of float garbage in object.color (the comparison was (0.20000000298023224, 0.20000000298023224, 0.20000000298023224, 1.0) <> [0.2, 0.2, 0.2, 1] )

After that fix all tests now are passed 🥲
2023-02-20 19:53:16 +05:00
Andrej730 0e1689c2d9 Solving test fail - keeping unnamed profiles when updating profiles in MaterialsData
Solves test_enable_editing_material_set_item test error
```
FAILED test/bim/test_feature.py::test_enable_editing_material_set_item - RuntimeError: Error: Python: Traceback (most recent call last).

RuntimeError: Error: Python: Traceback (most recent call last):
  File "\blenderbim\bim\module\material\operator.py", line 587, in execute
    self.mprops.profiles = str(material_set_item.Profile.id())
TypeError: bpy_struct: item.attr = val: enum "107" not found in ('79', '98')
Location: C:\software\Steam\steamapps\common\Blender\3.4\scripts\modules\bpy\ops.py:113
```

The problem was `bim.enable_editing_material_set_item"` was trying to set `MaterialsData` profiles and wasn't able to set unnamed profile because `MaterialsData.profiles()` was discarding unnamed profiles (which is seems contradictory because `profiles()` is also handling unnamed profiles anyway).
2023-02-20 18:00:48 +05:00
Sigma Dimensions bd071cc472 Add smoke test for editing material profiles 2023-02-20 11:08:15 +01:00
Dion Moult dde847cb5a IfcTester can now output reports in BCF 2023-02-20 17:50:36 +11:00
Dion Moult 3c17718f70 Fix bug where adding a BCF viewpoint didn't take into account project units 2023-02-20 17:50:36 +11:00
Andrej730 0445119e77 Shifting dimensions defined openings by min_x and min_z to prevent #2784 2023-02-20 11:22:02 +05:00
Dion Moult e82cb5c149 IfcTester can now create reports in ODS format 2023-02-20 17:05:07 +11:00
Dion Moult 984548f315 IfcTester can now output reports in plain text 2023-02-20 17:04:38 +11:00
Dion Moult a0be4f58e6 Fix #2780. Fix bug where validation couldn't be run. pytest is now bundled. 2023-02-20 11:26:09 +11:00
Dion Moult 147602e107 Fix bug where ifcopenshell express rule validator required a logger with set_state. 2023-02-20 11:26:09 +11:00
Andrea Ghensi ac81169290 build(bcf): remove conda dependency 2023-02-20 07:26:34 +11:00
Dion Moult 1d78e05370 Fix #2774. Bump IfcOpenShell. 2023-02-19 21:53:49 +11:00
Andrej730 4b15b02ac8 Fixed Door modifier initial opening problem #2784
- add check of OverallHeight and OverallWidth for creating openings for doors and windows;
- OverallHeight, OverallWidth update for all the occurences of the type for doors and windows;
- new tool.Ifc method - get_all_element_occurences
2023-02-18 17:11:04 +05:00
Thomas Krijnen 57d18a0644 Update IfcIShapeProfileDef.cpp 2023-02-18 13:00:45 +01:00
Dion Moult 2f73af7e6a Fix bug where BlenderBIM Add-on BCF module was completely broken. Now works with latest BCF library for BCF-XML v2. 2023-02-18 21:12:36 +11:00
Dion Moult e712ef344d New feature in BCF to allow extracting specific files like header files, snippets, or bitmaps. 2023-02-18 21:11:05 +11:00
Dion Moult f91e9a3119 Fix BCF bug where viewpoints were not created with a .bcfv suffix which is not compliant. 2023-02-18 21:10:27 +11:00
Dion Moult 9cf36f1d91 Fix BCF bug where BCFv2 files were unable to be loaded 2023-02-18 21:09:32 +11:00
Andrej730 ab9a4856ab Update entity-types mapping for ifc4
Added IfcWindowStyle and IfcDoorStyle for suitable StandartCases since StandartCases inherit from IfcDoor and IfcWindow.
2023-02-17 17:57:17 +05:00
Andrej730 d52618277e Ifc4x3 - entity-types mapping
- added mapping entity-types mapping based on iso release of ifc4x3

- added some manual corrections for `generate_util_type_json.py` if someone would regenerate the map for ifc4.

- manual correction in ifc4x3 mapping for IfcCivilElement. It could be removed in the future releases of IFC if https://github.com/buildingSMART/IFC4.3.x-development/issues/583 is resolved

If curious to look what were the changes for mapping between ifc4 and ifc4x3:
https://github.com/IfcOpenShell/IfcOpenShell/commit/da51253fb0a9d08aa93f5fe3c074d3592b3aa997#diff-98f525c91bb47b9820e603fba61cc015d3e6ae7261593d727c51114e3ff73aa7
2023-02-17 17:57:16 +05:00
CyrilWaechter 743bd2c48f Add buttons to selected related boundaries
Related by a common:
- BuildingElement
- BuildingElementType
- Space
- SpaceType
2023-02-17 13:21:24 +01:00
CyrilWaechter 11894de455 Add button space to select its boundaries 2023-02-17 10:43:48 +01:00
Dion Moult 0c8122afc4 See #2762. Final final attempt. 2023-02-17 13:28:02 +11:00
Dion Moult f893503d0c See #2762. Final attempt. 2023-02-17 13:12:09 +11:00
Dion Moult 504782f156 See #2762. Attempt 3. 2023-02-17 12:53:55 +11:00
Dion Moult 5d1f32bfc0 See #2762. 2023-02-17 12:28:19 +11:00
Dion Moult 5bc315be92 See #2762. 2023-02-17 12:27:31 +11:00
Dion Moult e8f21c79b1 See #2762. 2023-02-17 12:03:25 +11:00
Sigma Dimensions ba24f9b491 - Improve ODS export
- Fix XLSX export bug
- Improve active cost item UI active cost item
- Fix missing tool cost function
2023-02-16 20:22:45 +01:00
Andrej730 937fa21cce Fix to prevent pyrna_enum_to_py warnings using BIM Tool
Prevents couple `pyrna_enum_to_py` warnings that occured using BIM Tool

```
WARN (bpy.rna): C:\Users\blender\git\blender-v340\blender.git\source\blender\python\intern\bpy_rna.c:1341 pyrna_enum_to_py: current value '0' matches no enum in 'BIMModelProperties', '', 'ifc_class_browser'
WARN (bpy.rna): C:\Users\blender\git\blender-v340\blender.git\source\blender\python\intern\bpy_rna.c:1341 pyrna_enum_to_py: current value '0' matches no enum in 'BIMModelProperties', '', 'relating_type_id'
WARN (bpy.rna): C:\Users\blender\git\blender-v340\blender.git\source\blender\python\intern\bpy_rna.c:1341 pyrna_enum_to_py: current value '0' matches no enum in 'BIMModelProperties', '', 'ifc_class'
```
2023-02-16 17:43:34 +05:00
Andrej730 16cbab2de7 Commented how to regenerate BIM Tool keymap in case of errors 2023-02-16 16:40:07 +05:00
Andrej730 a85f81d8ca Refactor - moved some helpers functions to tool 2023-02-16 15:16:42 +05:00
Andrej730 1617ede010 Simplified addings hotkeys descriptions in BIM Tool
Now it's more universal and doesn't require some single operator to be attached to hotkey description.
Also added description for some hotkeys
2023-02-16 14:36:24 +05:00
Dion Moult d9f1d501aa See #2762. Try again. 2023-02-16 15:01:19 +11:00
Sigma Dimensions 8ee60db0df Re-assigning a product type class should also reassign the class of its typed instances 2023-02-15 23:11:30 +01:00
Sigma Dimensions d2e02c8ab3 complete cost schedule exports commit 9dc69f3 2023-02-15 23:09:15 +01:00
Sigma Dimensions 2d4e4d2445 forgotten commit for duplicating tasks 2023-02-15 23:00:43 +01:00
Sigma Dimensions 9dc69f3aa7 You can now export Cost Schedules as CSV, ODS, XSLX with BlenderBIM or using the cli app 2023-02-15 22:50:39 +01:00
Andrej730 476ab506d0 Added ifc4x3 json schema and it's parser
API for docs works the same way as it worked for ifc2x3 and ifc4.

Besides changing `doc.py` and adding `.json` schemas added `ifc4x3dev_scrape_data_for_docs.py` (it's kind of hacky now and relies on `server.py` code from the further mentioned repo) that's used to parse some entities descriptions from https://github.com/buildingSMART/IFC4.3.x-development for later use in `doc.py`.
2023-02-15 18:38:04 +05:00
CyrilWaechter a6fa6201f7 Fix bugs introduced in cf87852 2023-02-15 10:34:25 +01:00
Dion Moult 26f7db0504 Fix #2698. IfcClash results are now aware of multiple linked IFC models. 2023-02-15 20:32:50 +11:00
Dion Moult 77a0da2a17 See #2762. Another attempt at fixing PyPI build. 2023-02-15 20:06:07 +11:00
Thomas Krijnen 785d2e601c Fix after 31cf1127a6 2023-02-15 09:52:21 +01:00
Massimo Fabbro 3687e4ed91 finish mapping Qto_FootingBaseQuantities 2023-02-15 09:35:29 +11:00
Thomas Krijnen 31cf1127a6 Validation output enhancements 2023-02-14 20:55:14 +01:00
Thomas Krijnen 97385efbca entity_instance.get_info(scalar_only=) 2023-02-14 20:11:24 +01:00
Andrej730 6a87b68c3b Window Modifier - added 2d representation
+ fixed lining offset for door 2d representation
2023-02-14 16:54:43 +05:00
Andrej730 be06bd6369 Added note about core.geometry representations functions 2023-02-14 16:54:19 +05:00
Andrea Ghensi a5b4ac863b fix: ifc clash operator fixes
fixes #2698
2023-02-14 21:55:41 +11:00
Thomas Krijnen e535f911d1 Update extrusion-dir cases for undeterminate handling 2023-02-14 10:04:46 +01:00
Thomas Krijnen 18670bcbd6 Recompile rules 2023-02-14 10:04:46 +01:00
Thomas Krijnen 27511d362e Use inverse unpacking in rule execution 2023-02-14 10:04:45 +01:00
Thomas Krijnen c634bffa10 Update reverse compilation to eliminate getattr() calls 2023-02-14 10:04:45 +01:00
Thomas Krijnen dffa90bd5d Better conformance wrt indeterminate with AST rewriting and dedicated type 2023-02-14 10:04:45 +01:00
Thomas Krijnen 6a47d33974 Add zone IsGroupedBy where rule test case 2023-02-14 10:04:45 +01:00
Thomas Krijnen e87524ba11 ifcopenshell.settings.unpack_non_aggregate_inverses 2023-02-14 10:04:44 +01:00
CyrilWaechter b1154ae377 Adapt to code changes introduced in ea91f43 2023-02-14 09:22:20 +01:00
Andrej730 cbc24c31a2 aud dependency fork fix
Solves #2759
2023-02-14 00:59:17 +05:00
Dion Moult 0a8a6e5825 Fix #2383. Don't default text alignment via CSS, let attributes take precedence. 2023-02-13 22:28:53 +11:00
Andrej730 e088a10aa8 New openings size wasn't converted using unit_scale #2765 2023-02-13 15:29:32 +05:00
Dion Moult 007d984baf Fix #2274. You can now purge unused types. 2023-02-13 15:20:56 +11:00
Dion Moult 7d979e5a04 Fix #2575. We are incompatible with add-ons using dictionary overrides for delete/duplicate macros. 2023-02-13 11:22:57 +11:00
CyrilWaechter 4134c709a8 Add fallback solution for invalid geometries
When an inner boundary cut an outer boundary in an IfcCurveBoundedPlane
which is invalid in IFC schema then ifcopenshell fails to create
geometry.
See #635
2023-02-12 18:29:24 +01:00
Thomas Krijnen ff5e4991ab Test files 2023-02-12 15:28:14 +01:00
Thomas Krijnen 0b4b45b7c8 Validate bounds of non-aggregate inverses 2023-02-12 15:28:14 +01:00
Massimo Fabbro 145ac1a0f4 Fix #2716 issue (#2761) 2023-02-12 14:09:33 +01:00
Dion Moult 74de7436b7 See #2762. Fix pip build. 2023-02-12 20:32:30 +11:00
Dion Moult 60fa05a913 Fix #2521. Fill areas have limited dissolve applied before rendering. 2023-02-12 18:58:49 +11:00
Dion Moult 303de61417 See #2760. Fix bug where editing pset with null'ed enumeration fails. 2023-02-12 15:12:31 +11:00
Dion Moult a9a4c6049f Fix bug where you couldn't duplicate text annotations. 2023-02-12 15:02:41 +11:00
Dion Moult 82b201a14e Fix #2352. Text styling is now possible with custom classes. 2023-02-12 15:01:47 +11:00
Dion Moult c9bd7adf8a Fix bug where you couldn't create a drawing with no linework. 2023-02-12 13:31:49 +11:00
Dion Moult 0655d77210 See #2760. Fix bug where groups UI had undefined variable. 2023-02-12 13:31:49 +11:00
Sigma Dimensions 515ec28fb3 Place text leader annotations horizontally 2023-02-12 01:07:41 +01:00
Sigma Dimensions c28c2dd077 BIM tool hotkey to update base quantities 2023-02-12 00:47:54 +01:00
Sigma Dimensions b04383bb64 Smoke test for duplicating tasks 2023-02-12 00:38:22 +01:00
Sigma Dimensions f4b93ef243 You can now duplicate tasks 2023-02-12 00:37:37 +01:00
Sigma Dimensions 05cf00bef6 Add smoke test for task product selection & minor fix 2023-02-11 23:24:52 +01:00
Andrej730 e81346c339 Door openings updated on door parameters update
Based on 6d1e9f73a
2023-02-11 19:01:56 +05:00
Sigma Dimensions ac911cefd6 minor fix for d4eb56e 2023-02-11 14:24:53 +01:00
Li1506 c4cea20849 add IfcConvert testing on sample files step to ci.yml 2023-02-11 13:25:50 +01:00
Sigma Dimensions e30992b905 minor fix for commit d3b176e 2023-02-11 13:23:23 +01:00
Sigma Dimensions 5beea8640e BBIM: rewrite Selector search filter to use core and tool functions 2023-02-11 13:19:08 +01:00
Sigma Dimensions d3b176e5eb complete costing module refactoring 2023-02-11 12:34:54 +01:00
Andrej730 0d0691a012 Door modifier - double doors type support 2023-02-11 16:09:13 +05:00
Sigma Dimensions d4eb56e360 New Search and Spatial selection Tools
+ refactor sequence modules
2023-02-11 11:23:07 +01:00
Andrej730 5cfc122b79 Door modifier - double swing doors
still no dashed lines in 2d representation
2023-02-11 14:20:04 +05:00
Sigma Dimensions fa0f484922 Inlcude audio library for BlenderBIM costing chaching sound 2023-02-11 10:05:54 +01:00
Dion Moult ceaa930aad Fix #2756. Bug where reassigning classes with a enum didn't work. 2023-02-11 11:03:35 +11:00
Dion Moult bbd2b67041 Add link to dev fund in add-on about section. 2023-02-11 09:54:43 +11:00
Sigma Dimensions 6c82e6f889 Fix assign cost value usecase 2023-02-10 21:28:49 +01:00
Thomas Krijnen 531e07767f #2754 latebound schema handling 2023-02-10 21:08:20 +01:00
Thomas Krijnen da39c903f3 Latebound schema handling: Improve error messages 2023-02-10 21:07:25 +01:00
Andrej730 f458aa9932 Door fixes
1) do not create new ifc representation if editing was canceled (similar to 25c650f4c)
2) plan and profile representations now  are optional (similar to 50d71d5ea)
2023-02-10 18:00:43 +05:00
Andrej730 9e383959c8 door black formatting 2023-02-10 16:58:36 +05:00
Andrej730 3654e49f87 Door handles 2023-02-10 16:55:13 +05:00
Dion Moult 13f5767921 Minor fix 2023-02-10 19:01:54 +11:00
Dion Moult 6d1e9f73a6 Windows now regenerate openings when window parameters updated 2023-02-10 19:01:20 +11:00
Andrej730 d9ddf67afb small fix for 50d71d5ea
The error was:
```
Error: Python: Traceback (most recent call last):
  File "\blenderbim\bim\module\model\window.py", line 337, in execute
    element = blenderbim.core.root.assign_class(
NameError: name 'blenderbim' is not defined
```
2023-02-10 12:49:33 +05:00
Dion Moult 47e6352350 Don't create temporary objs when updating filling openings. 2023-02-10 18:45:53 +11:00
Dion Moult ae564d1b61 Fix #1040. New utility for geometric shape analysis only requiring numpy. 2023-02-10 16:03:16 +11:00
Vukas Pajic d9cee04769 Fix #2755 2023-02-09 11:00:25 +01:00
Dion Moult 50d71d5ea1 Black code and make profile representation optional for windows. 2023-02-09 20:13:14 +11:00
Dion Moult 25c650f4cd Don't edit IFC when simply disabling window editing. 2023-02-09 19:33:08 +11:00
Andrej730 f0502c123e Window modifier - fixed bugs related to ae5ea3857
1) now replacing actual representation instead of the map
2) removed `should_sync_changes_first` as unnecessary representation update
2023-02-08 18:17:47 +05:00
Sigma Dimensions 693960a4ab start refactoring costing operators into core & tool 2023-02-08 13:39:46 +01:00
Vukas Pajic 5df0cf39a0 minor fix 2023-02-08 12:06:16 +01:00
Vukas Pajic 578220a1ab (IfcPatch) allow user to specify output directory for "SplitByBuildingStorey" 2023-02-08 12:04:58 +01:00
Andrej730 ae5ea3857e Fixing bug with updating window/door modifier with element types
The bug occured when you created a window/door type and some instance of it and then would try to update modifier.

There were two problems:
1) Because `remove_representation` removes all the meshes of the representation, representation has to be switched before removing the old one.

2) `switch_representation` parameter `is_global` was set to `False` and it wasn't affecting all instances of the type which led to errors during representation switch later on.
2023-02-08 15:56:57 +05:00
Dion Moult 93d0ffd459 Fix #2520. You can now duplicate drawings with or without annotations. 2023-02-08 21:16:54 +11:00
Vukas Pajic 1b85928810 allow user to specify output directory for "SplitByBuildingStorey" 2023-02-08 10:42:05 +01:00
Dion Moult f118c50d1d Fix bug where removing a drawing didn't remove its references. 2023-02-08 19:08:45 +11:00
Sigma Dimensions ac5ec72acf BBIM: improve work schedule visualisation workflow & refactor visualise_work_schedule_date operator 2023-02-08 08:25:11 +01:00
Sigma Dimensions 7fe2634d32 Improve guess schedule date range ( including tasks with input assignements) 2023-02-08 08:18:08 +01:00
Dion Moult 1279b9848f Fix regression with editing material psets 2023-02-08 17:43:50 +11:00
Dion Moult ea91f4302c Drawings now load on-demand instead of at project load. See #2520. 2023-02-08 16:53:19 +11:00
Dion Moult 88bddb9531 See #2520. You can now duplicate drawings. 2023-02-08 12:29:49 +11:00
Sigma Dimensions 4e308a6e31 fix api docstring description for nest.unassign_object usecase 2023-02-07 15:35:34 +01:00
Andrej730 4bd7fc5304 Simplified door ifc representation 2023-02-07 16:48:37 +05:00
Andrej730 0122fe4382 Fix #2742 2023-02-07 14:57:37 +05:00
Dion Moult 1730f25a5a Fix #2584. You can now edit any layered or profile material agnostic of class. 2023-02-07 19:13:22 +11:00
Dion Moult 3f8314bcf3 Minor fix. 2023-02-07 12:37:10 +11:00
Dion Moult 5879d14803 See #2737. Fix bug where convert length unit patch did not work outside Blender session. 2023-02-07 09:05:20 +11:00
Andrej730 4c1a821922 Pset_StairFlightCommon update
Added Pset_StairFlightCommon update to stair modifier
2023-02-06 18:54:25 +05:00
Andrej730 e1456533f2 Doors - clean up + black format 2023-02-06 18:53:48 +05:00
Andrej730 7522a3a2ab renamed "data" parameter in model/data.py
renamed "data" parameter in model/data.py to make it a bit more distinctive from neighbour "Data" parameter with json string
2023-02-06 18:45:48 +05:00
Andrej730 144d01a0ef Added modifiers folding to prevent clutter 2023-02-06 18:45:48 +05:00
Andrej730 f6cb6c8b21 Accepting predefined type for windows/stairs/doors from type manager
Also assigned layout name property for windows/doors
2023-02-06 18:45:48 +05:00
Andrej730 0f5264af99 Door - added to type manager
Also added predefined type for door and bit of refactoring
2023-02-06 18:44:09 +05:00
Andrej730 e57ff19e16 Door - added 2d representation 2023-02-06 18:44:09 +05:00
Andrej730 b2bdbdd8c3 Door modifier - basic setup
Also:
- refactored window modifier code - part of it is now reused building a door and can be used by other module in the future
- fixed bug with window lining intersecting frame when lining_depth < panel_depth
- simplified the way lining is built in ifc - previously it was using 4-6 extrusions, now it's 1-2 which is also better for 2D drawings
2023-02-06 18:44:09 +05:00
Andrej730 a8400ec963 geonodes prototype copyright 2023-02-06 18:44:09 +05:00
Sigma Dimensions d028b52d77 fix visualise_work_schedule_date - issue #2740 2023-02-06 12:35:42 +01:00
Sigma Dimensions 65990255e8 Fix issue #2740 2023-02-06 12:29:06 +01:00
Sigma Dimensions 3a71b03a9a Fix sequence module test bugs 2023-02-06 12:04:03 +01:00
Dion Moult 99274c2d7a Fix #1806. Changing filling types now also updates the void representation. 2023-02-06 20:28:50 +11:00
Dion Moult e40bbf2525 See #1806. Typed fillings now share the same representation for their openings. 2023-02-06 17:01:29 +11:00
Dion Moult 38c6378315 See #1806. New fillings inherit their voids from existing occurrences if possible. 2023-02-06 14:40:59 +11:00
Dion Moult c050ca1037 Fix #2736. Increase tolerance for HLR rendering to prevent "see through" objects in drawings. 2023-02-06 13:42:37 +11:00
Thomas Krijnen 9813dda778 Express rules: test case for instance comparison 2023-02-05 13:08:55 +01:00
Thomas Krijnen 6b7aeb90c7 Express rules: allow comparison of instances to instances and values 2023-02-05 13:08:55 +01:00
Dion Moult 3cb29e1344 Fix unintuitive UI where objects had to be visible for the operation to run. 2023-02-05 22:27:56 +11:00
Dion Moult 4d4961a490 Close #2622. IfcCSV now supports ODS and XLSX formats for export. 2023-02-05 18:53:16 +11:00
Dion Moult ccd7db06f8 Fix #2611. Address multiple situations in the UI where the users could create invalid material data without an indication that it is invalid. 2023-02-05 17:10:25 +11:00
Dion Moult 77234706b0 Assigning a profile set auto creates profiles if none exist to prevent invalid data. 2023-02-05 15:29:54 +11:00
Einar Raknes 9f399b34fb Rename panel from IFC Construction Type to IFC Type
As discussed here: https://community.osarch.org/discussion/1332/blenderbim-ifc-construction-type-ifcbuiltelementtype-confusion
2023-02-05 12:18:17 +11:00
Dion Moult dc77a798b5 Assigning a layer set for the first time prefills with a layer to prevent invalid data. 2023-02-04 22:14:33 +11:00
Dion Moult 207093fe99 Fix regression where changing assigned types didn't update the materials. 2023-02-04 22:12:28 +11:00
Dion Moult 42c4a68108 Fix bug where unnamed profiles clogged up the material profile settings. 2023-02-04 16:20:52 +11:00
Dion Moult a7cc58fb50 Update libraries to use Plan/Body/PLAN_VIEW instead of annotations. 2023-02-04 14:09:35 +11:00
Dion Moult 6bab10d2ac Minor fix. 2023-02-04 12:51:01 +11:00
Thomas Krijnen c4b77c2a07 Recompile rules 2023-02-03 22:22:40 +01:00
Thomas Krijnen 66d0627035 Property names test case 2023-02-03 21:18:06 +01:00
Thomas Krijnen 3f4417c9f2 Window lining test case 2023-02-03 21:17:48 +01:00
Thomas Krijnen 5526bed4c3 Express rules: allow indexing in sets 2023-02-03 21:17:08 +01:00
Thomas Krijnen 078bf8646a Express rules: enclose rhs of not in parens when needed 2023-02-03 21:10:34 +01:00
Dion Moult 6adbcdd8e1 Fix #2472. BREAKING CHANGE. Annotations are now drawn in addition to body views, not in lieu of. 2023-02-03 21:31:15 +11:00
Dion Moult b4d633a34d Fix #2735. 2023-02-03 21:28:36 +11:00
Dion Moult e1e4380fc7 Fix 3 bugs related to representations:
* Bug when switching representations when active object not selected.
 * Bug when switching to a non MODEL_VIEW body representation would load the MODEL_VIEW one instead all the time.
 * Add support for plan body representations.
2023-02-03 21:27:25 +11:00
Dion Moult 1ed7b21ab0 Fix #1496. Mirrored elements have their placements correctly synced. 2023-02-03 15:53:21 +11:00
Dion Moult 158a4c5e75 Fix bug where IFC2X3 fallback styles couldn't be assigned. 2023-02-03 13:21:23 +11:00
Dion Moult f72d48aab5 Fix bug where calendar caching meant invalid duration calculations if you edited your calendar after a date calculation. 2023-02-03 10:06:55 +11:00
Dion Moult be26a77ecb Support times specified as ISO strings too in date utils, not just datetimes. 2023-02-02 23:01:49 +11:00
Dion Moult 7c275a99a2 New utility functions for automatic local to global elevation conversions and easier unit conversions 2023-02-02 23:01:12 +11:00
Dion Moult f77d2f449d Close #2451. Implement georeferenced elevations in drawings. 2023-02-02 22:55:45 +11:00
Dion Moult 18ba805a15 Fix silly bug. 2023-02-02 21:36:13 +11:00
Dion Moult a5adf0021f Fix #2599. Unassigning materials now also removes the Blender material. 2023-02-02 18:41:07 +11:00
Dion Moult 7d944255d0 Fix bug where you could unassign a material usage and create an invalid parametric occurrence 2023-02-02 18:26:14 +11:00
Dion Moult b984a77862 IfcFM parser and writer is now easier to override for custom FM requirements 2023-02-02 14:29:48 +11:00
Dion Moult d69aa20802 Fix #2620. You can now run patches from models in memory. 2023-02-01 19:29:35 +11:00
Dion Moult 7e16cb797f See #1848. Some straggling refactoring for resource data. 2023-02-01 17:37:59 +11:00
Dion Moult b3c7971ea9 Purge all data classes from API. 2023-02-01 15:44:08 +11:00
Dion Moult a778b98fdb Fix #1848. Purge remaining references to API data classes. 2023-02-01 15:41:21 +11:00
Dion Moult 4efb80efe4 See #1848. Refactor structural data class. 2023-02-01 14:54:42 +11:00
Sigma Dimensions cfaacf8650 Ifcopenshell api usecase to unassign object nesting 2023-01-31 18:59:33 +01:00
Sigma Dimensions 60da4cb87b BBIM: Refactor activate drawing view 2023-01-31 18:42:18 +01:00
Andrej730 db6207db6c prevent flickering in profile manager thumbnails 2023-01-31 14:48:26 +05:00
Dion Moult 9b4568c881 Minor fix 2023-01-31 20:40:32 +11:00
Dion Moult cd132d4fe9 See #1848. Refactor sequence data class. 2023-01-31 20:38:03 +11:00
Andrej730 f01900bdb1 Profile manager thumnails (#2568) 2023-01-31 13:55:50 +05:00
Dion Moult 0fddd243c2 See #1848. Refactor resource data class. 2023-01-31 18:30:26 +11:00
Dion Moult 1066e5e0a7 See #1848. Refactor layer data class. 2023-01-31 17:35:54 +11:00
Dion Moult 7e0b1b9cbd See #1848. Refactor group data class. Add support for multiple subgroup relationships. 2023-01-31 15:35:14 +11:00
Dion Moult 95e924a5bc Fix #2206. Bug where removing a cost item with active data created an error. 2023-01-31 14:33:29 +11:00
Dion Moult f9cfd17303 Fix #2251. Fix #2603. Bug where annotations don't show because redeclaration of built-in glsl variables is not allowed. 2023-01-31 11:04:23 +11:00
Dion Moult 9f37f73e52 Fix #2585. Bug where geometry could randomly get corrupted. 2023-01-31 10:54:07 +11:00
Andrej730 9acd2043d7 Window/Stair types added as presets in type manager 2023-01-30 15:31:50 +05:00
Dion Moult ab6159f173 See #1848. Refactor cost data class. 2023-01-30 20:53:17 +11:00
Andrej730 f9bff64f0d ifc geonodes modifier prototype 2023-01-30 13:45:52 +05:00
Andrej730 978f362073 More consistent default and current units for stair and window modifiers 2023-01-30 12:44:11 +05:00
Dion Moult 7a185e7f7c See #1848. Refactor constraint data class. 2023-01-30 11:47:02 +11:00
Dion Moult 044cd6e97b See #1848. Refactor boundary data class. 2023-01-30 11:27:38 +11:00
Dion Moult b939370f2b See #1848. Remove reliance on data class for material module. 2023-01-30 10:20:55 +11:00
Sigma Dimensions 4af0c1c98c BBIM: Fix editing slab representation bug 2023-01-29 21:20:33 +01:00
Dion Moult 181531ada8 Fix bug where circle profiles didn't follow project units. 2023-01-29 21:30:11 +11:00
Thomas Krijnen e92d908063 Delete ifcopenshell_wrapper.py 2023-01-29 10:26:05 +01:00
Dion Moult 686f4a023d Fix #2356. Handle cases where the annotation types get changed to an incompatible object type. 2023-01-29 20:10:23 +11:00
Thomas Krijnen c0c6c23fca Update schema.py 2023-01-29 09:47:46 +01:00
Dion Moult 396fd5520f Fix #2349. Be more defensive on invalid smart annotations. 2023-01-29 15:15:00 +11:00
Dion Moult efc3a692cb Fix #2726. 2023-01-29 12:27:50 +11:00
Ryan Schultz 2902d96690 #2692 - bulk assign and unassign ifcmaterials 2023-01-29 10:05:03 +11:00
Massimo Fabbro 70f35095ff Writing qto tests (#2728)
* Update qto tool tests and fix some bugs

* complete test_run qto tests
2023-01-29 10:03:07 +11:00
Ryan Schultz 87c67abac9 #2652 Removes all IFC data, and converts the file to a simple Blender file 2023-01-29 10:00:43 +11:00
Thomas Krijnen 7df9640807 #2704 properly propagate iterator destructor 2023-01-28 14:21:52 +01:00
Thomas Krijnen 25edc91976 #2704 retain kernel pool to free cache 2023-01-28 14:21:52 +01:00
Thomas Krijnen 2174fda88d #2704 keep track of simple types by iden to free later 2023-01-28 14:21:52 +01:00
Thomas Krijnen 2fc5c6c110 #2704 newobject after decl does not work (duh) 2023-01-28 14:21:52 +01:00
Dion Moult 6a2307308c Fix bug where copying objects with profiles should only share profiles if the profile is named. 2023-01-28 21:51:00 +11:00
Dion Moult 1a05fae826 Fix #2426. bug where unrelated objects would get drawing properties by accident. 2023-01-28 14:27:47 +11:00
Andrej730 cce27d73fd Fixed ifc window bugs
1) The bug occured if you would try to remove ifc window modifier and add it back again. Now adding new window checks if window was previously added
2) Second bug occured if you import .ifc project with window and some props were not used before (for example window was without transom), then newly added props would have incorrect units.
2023-01-27 17:44:52 +05:00
Andrej730 87515f3839 Added creating window to Shift+A menu 2023-01-27 16:30:35 +05:00
Dion Moult 24f3918769 Fix #2705. Annotation draw error when splines are outside the zoom level of the screen. 2023-01-27 14:32:41 +11:00
Dion Moult f7af38c863 Fix #2712. 2023-01-27 12:09:17 +11:00
Dion Moult 6c2a4711da Fix bug where annotations were assumed to only be part of one drawing group 2023-01-27 08:38:00 +11:00
Dion Moult 41fdd77bfa Fix #2579. Bug where measurements were considered to be greasepencil strokes when drawing walls. 2023-01-27 08:32:25 +11:00
Dion Moult 9185a51234 Fix #2363. Bug when adding unbuilt schedule to sheet. 2023-01-27 08:22:10 +11:00
Ryan Schultz c5ae86d10b fix bug on Extend IfcBeam: https://community.osarch.org/discussion/1327/blenderbim-bug-on-extend-ifcbeam 2023-01-26 08:31:04 -06:00
Andrej730 74477d0051 Window modifier - removed hardcoded mm units
Related to #2724
2023-01-26 18:29:58 +05:00
Andrej730 cee0ff2074 BIM tool UI black format 2023-01-26 17:14:42 +05:00
Andrej730 85bba9d5f8 Added default blender selection behaviour to BIM Tool
Copied keymap templates from blender source and mentioned them

Related to #2568
2023-01-26 17:14:42 +05:00
Andrej730 8e696cbf02 Added description to some operators with hotkeys 2023-01-26 17:14:42 +05:00
Andrej730 c7d1f5efc0 Changed split hotkey to Shift-K (#2568)
to preserve useful default shift-s hotkey
2023-01-26 17:14:42 +05:00
Andrej730 68bbb4d7a1 Solving interface issues from #2568
1) Added type manager to N-panel. Actually made side-panel and header layout logic more similar.
2) Added interface from sidebar to properties too
2023-01-26 17:14:42 +05:00
Andrej730 b1dd20fef7 Fixed window units in bmesh (#2724) 2023-01-26 14:42:35 +05:00
Thomas Krijnen a701624d7b #2720 add gltf to CD yml 2023-01-26 09:07:53 +01:00
Thomas Krijnen f912be7875 #2720 add gltf to CD yml 2023-01-26 08:59:38 +01:00
Andrej730 04bdb12e79 Window Modifier - added mullions/transoms, removed relative panel dimensions 2023-01-26 12:30:34 +05:00
Dion Moult d69f6c02b6 See #1636. Fix drawing caching when new voids are added or parametric objects updated. 2023-01-26 12:14:45 +11:00
Dion Moult 95c3a57a4d Fix #2722. 2023-01-26 11:11:04 +11:00
Dion Moult 0b6d321d83 Fix #2721. 2023-01-26 11:09:51 +11:00
Dion Moult e0fd3fd90e See #1636. Fix bug where moving an opening didn't update the drawing cache for it's element. 2023-01-26 09:18:08 +11:00
Dion Moult 7686e35858 Fix #2697. 2023-01-25 20:52:43 +11:00
Dion Moult 8eeb8804d4 Handle corrupt geometry caches (e.g. due to crash) by creating a fresh cache. 2023-01-25 17:56:05 +11:00
Dion Moult cb9a7cf5be Fix #2712. Handle deletion of rel services buildings. 2023-01-25 17:42:39 +11:00
Andrej730 54cec30a60 Window modifier - fixed bug in bmesh 2023-01-25 11:15:01 +05:00
Andrej730 9f0ad9be3c Black formatting window modifier 2023-01-25 11:13:41 +05:00
Andrej730 a6271d0936 IFC Window modifier
Added IFC Window in object modifiers tab (works similar way to Ifc Stair)
2023-01-25 11:13:41 +05:00
Andrej730 cf6acd5dd6 ShapeBuilder - added 3d rectangles and polylines support 2023-01-25 11:10:20 +05:00
Andrej730 f8468e66e6 Fixed omitted ifc operators returns 2023-01-25 11:10:20 +05:00
Andrej730 d0cf5f9d0c Fixed ShapeBuilder bug for mirroring IfcExtrudedAreaSolid
Also fixed typo in shape_builder_examples
2023-01-25 11:10:19 +05:00
Dion Moult 2c123fdbfb Bump IfcOpenShell - mainly to fix geometry caching bug 2023-01-25 16:55:05 +11:00
Dion Moult 49b5bbd585 Fix PyPI IfcOpenShell action 2023-01-25 14:08:59 +11:00
Dion Moult bf3063ec00 You can now convert ArchiCAD spaces across multiple stories to Revit rooms 2023-01-25 13:55:38 +11:00
Sigma Dimensions 1e2ac1fe24 Fix IFC Array bug 2023-01-23 22:51:41 +01:00
Sigma Dimensions 9d7145746f Drawing feature tests 2023-01-23 22:06:29 +01:00
Sigma Dimensions 6e7fee5bc8 BBIM: Fix drawing elevation view bug 2023-01-21 12:00:07 +01:00
Sigma Dimensions 9638ae3e77 BBIM: Fix bug error when trying to open a drawing rather than a sheet/schedule 2023-01-21 01:26:26 +01:00
Sigma Dimensions 69cee56b7c BBIM: fix profile joiner for beams & other products 2023-01-21 01:18:06 +01:00
Sigma Dimensions 2257942676 BBIM: When adding doors to a wall, maintain the wall as the active object.
This allows adding multiple doors smoothly.
2023-01-21 00:51:46 +01:00
Sigma Dimensions a5ca7c540f remove useless lines and run quality check black 2023-01-20 21:39:24 +01:00
Sigma Dimensions c9b0a7c3de Attempt to fix selector bug where retrieving material values gets caught in exception 2023-01-20 21:23:40 +01:00
Sigma Dimensions 358d19c883 BBIM: fix bug where task inputs could also be "USERDEFINED" or "NOTDEFINED" 2023-01-20 13:51:08 +01:00
Sigma Dimensions e624d56516 BBIM: improve task bar visual creation 2023-01-20 13:48:14 +01:00
Sigma Dimensions 78a6532615 ifcopenshell: Improve purging for remove_task usecase 2023-01-20 12:58:11 +01:00
Sigma Dimensions 44bb4ac74d BBIM: enable adding task bars visual when creating an animation 2023-01-20 11:34:38 +01:00
Sigma Dimensions f782d784af BBIM: improve task object selection 2023-01-20 11:31:06 +01:00
Sigma Dimensions caf9034839 BBIM: refactor gantt generation operator 2023-01-20 11:26:32 +01:00
Sigma Dimensions 893fd50b85 BBIM: Attempt to fix 3D viewport decoration issue 2023-01-20 10:12:42 +01:00
Sigma Dimensions 7a3e53a9f8 update qto core and qto tools:
- when calculating quantities of an object, assign base qto to object if none exists.
- optimise core and tool functions so it's faster for many selected objects
2023-01-19 21:05:47 +01:00
Dion Moult 07ee62eb91 Add support for editing material set item psets. 2023-01-18 16:40:15 +11:00
Thomas Krijnen b41878e960 #2697 take only a single inverse at a time 2023-01-16 21:29:59 +01:00
Vukas Pajic d9f1cb5525 reassign_class now preserves original STEP ID and GLOBALID - #2701 2023-01-16 13:34:57 +01:00
Vukas Pajic 8110951987 Now support custom attributes in IfcSelector #2649 2023-01-16 08:58:14 +01:00
Vukas Pajic 6b63b5aae7 selector now correctly applies comparison operator. #2643 2023-01-16 08:08:26 +01:00
Martyna Jakubowska e814736e68 Update README.md 2023-01-16 06:15:38 +11:00
Thomas Krijnen 802e3a0ea1 #2688 fix non-planar face triangulation 2023-01-15 14:42:24 +01:00
Massimo Fabbro 4e8a962f60 Minor fix 2023-01-15 11:34:54 +11:00
Massimo Fabbro e20d4c93b7 Delete unuseful qto tool function 2023-01-15 11:34:54 +11:00
Massimo Fabbro 1be76c8aaa Fix little bug 2023-01-15 11:34:18 +11:00
Andrea Ghensi a4566797fe fix: ifcclash bcfxml export 2023-01-15 07:23:42 +11:00
Kristoffer Andersen 4ee3458ff9 Run linux/osx builds with fewer cpus 2023-01-14 17:23:44 +01:00
Sigma Dimensions f86de533f8 BBIM: Feature to select an annotation's assigned object 2023-01-13 16:13:57 +01:00
Sigma Dimensions 693831dca3 BBIM: replace "guess_all_quantities" operator in the UI 2023-01-13 15:00:17 +01:00
Kristoffer Andersen 7500919b5d Set to fixed daily release
Remove association to every completed CI workflow. High frequency of releases makes it better to keep this at a fixed interval given it's so compute intensive
2023-01-13 14:59:06 +01:00
Sigma Dimensions e37b6fa8a2 BBIM: Replace "GuessAllQuantities" with "CalculateQuantities in QTO subpanel, and also re-calculate for all selected objects 2023-01-13 14:57:36 +01:00
Thomas Krijnen 9de9645229 #2665 increase halfspace tolerance comparison 2023-01-13 13:56:38 +01:00
Thomas Krijnen 373d664251 #2361 2023-01-13 12:50:18 +01:00
Dion Moult 6ab912a049 Fix invalid IFC context dimensionality 2023-01-13 22:35:23 +11:00
Dion Moult 9b9c900340 You can now manage material set psets from the UI 2023-01-13 22:35:23 +11:00
Thomas Krijnen a1ac820050 #2685 Recompile rules 2023-01-13 12:12:32 +01:00
Thomas Krijnen e812a886dc #2685 also implement __mul__ on express_set 2023-01-13 12:12:32 +01:00
Thomas Krijnen b759e22875 #2685 assoc material test cases 2023-01-13 12:12:32 +01:00
Thomas Krijnen 93e0babe5a #1785 disable hdf5 brep softlinking 2023-01-13 12:10:57 +01:00
Kristoffer Andersen 7d021fb7a4 reduce number of parallel tasks 2023-01-13 08:03:16 +01:00
Kristoffer Andersen b2cb8ffd8f Update ci-ifcopenshell-conda-daily.yml 2023-01-13 07:56:24 +01:00
Kristoffer Andersen 25a5651e50 Update ci-ifcopenshell-conda-daily.yml 2023-01-13 07:53:53 +01:00
Thomas Krijnen b301eb6520 Update boolean_utils.cpp 2023-01-12 13:07:40 +01:00
Thomas Krijnen 9f90d37401 Delete .travis.yml 2023-01-12 12:41:28 +01:00
Dion Moult ba7741fef5 Fix #2285. You can now get all the elements in a presentation layer. 2023-01-12 12:28:09 +11:00
Thomas Krijnen d1a40d129f #2685 Recompile rules 2023-01-11 13:38:12 +01:00
Thomas Krijnen 791076df2d invalid escape sequence 2023-01-11 13:20:44 +01:00
Thomas Krijnen 608952a4dd #2685 WR test cases for local placement / indeterminate 2023-01-11 12:50:30 +01:00
Thomas Krijnen e64047c511 WR test cases for rigid op / typeof() of simple types 2023-01-11 12:49:47 +01:00
Thomas Krijnen fc3c6599c7 WR test cases for MLS / usedin() 2023-01-11 12:48:53 +01:00
Thomas Krijnen 2336f299f7 Implement typeof() on simple types 2023-01-11 12:47:40 +01:00
Thomas Krijnen 4a71ab9631 #2685 Don't raise issues on WR evaluating to indeterminate 2023-01-11 12:47:13 +01:00
Dion Moult ac3c4cac89 Flange thickness is thickness at edge. 2023-01-11 09:33:20 +01:00
Dion Moult 2be1baab32 I-shape angle fix, fix incorrect flange thickness, and clean up ifdef. 2023-01-11 09:33:20 +01:00
Dion Moult 3e42fdb883 Minor fix 2023-01-11 09:33:20 +01:00
Dion Moult ed37d11002 Fix #2631. Add support for flange edge radius and flange slope for I-shapes. 2023-01-11 09:33:20 +01:00
Dion Moult 4cf896f564 Fix bug where half space booleans didn't show in the right spot due to units 2023-01-11 17:05:02 +11:00
Dion Moult 7e151799b0 Fix #2650. IFC selector only operates on visible objects. 2023-01-11 15:20:58 +11:00
Dion Moult 597b2bb0a8 Fix #2596. Bug with IFC4X3 UI errors as docs couldn't be fetched. 2023-01-10 12:14:10 +11:00
Dion Moult ddc0f7e652 Theme improvements to IfcOpenShell docs 2023-01-10 11:43:20 +11:00
Jesusbill 4845d191f4 Minor fixes to docs 2023-01-10 01:06:49 +01:00
Dion Moult da84cc5925 Add documentation for IfcPatch and all recipes 2023-01-10 10:18:13 +11:00
Dion Moult c8ba96cde8 Make syntax highlighting explicit in docs 2023-01-10 10:16:28 +11:00
Dion Moult 66e067d1ce Remove IfcMax from documentation as it's no longer relevant 2023-01-10 06:38:53 +11:00
Dion Moult 9d39760f6f Add IfcClash to docs 2023-01-10 06:38:53 +11:00
Sigma Dimensions 8aa4934d92 BBIM: Move out Search/Selection sub-panels from "IFC Collaboration" to "IFC Selection" 2023-01-09 14:49:59 +01:00
Dion Moult f848340124 Fix #2681. Fix 90 degree rotation on walls. 2023-01-09 21:55:22 +11:00
Dion Moult b699f55d9c Deleting a spatial or aggregated element now also removes its collection 2023-01-09 16:46:51 +11:00
Dion Moult 745709c56a Handle orphaned spatial elements when loading projects 2023-01-09 16:46:23 +11:00
Dion Moult 0c4c8ad9bc Fix #1012. Half complete example documentation for the IFC Project panel.
Incredibly incomplete, but serves as an example of how reference user-based documentation should be structured.
2023-01-09 15:24:50 +11:00
Ryan Schultz 3ce010de77 added IfcCivilElement to entity_to_type_map_4.json 2023-01-09 12:14:11 +11:00
Dion Moult dd600dd20f Fix #2024. Boolean diagram included in IfcOpenShell docs. 2023-01-09 11:50:49 +11:00
Dion Moult 360b9956c0 Fix failing tests from refactor which I really should've done before the release 2023-01-09 11:00:46 +11:00
Andrej730 1888048d4d Moved ShapeBuilder to separate module #2679
Moved ShapeBuilder to separate module - now `mathutils` will be only required if you're actually using ShapeBuilder (related to #2679)
2023-01-08 19:47:18 +05:00
Andrej730 4ab6d648c9 sverchok_modifier - minor refactor 2023-01-08 19:47:18 +05:00
Andrej730 c29048ecfb Furniture library bug fixes 2023-01-08 19:47:18 +05:00
Dion Moult 0294c4e02c Fix #2678. Critical typo bug meant that units were fixed to metric millimeters. 2023-01-08 12:58:53 +11:00
Dion Moult 3cfcd847cf Fix #2677. Docs now generate in a reasonable time and exclude generated IFC modules. 2023-01-08 11:12:54 +11:00
Massimo Fabbro 694b21a0e1 Delete unuseful functions and improve speed (#2672)
* Delete unuseful functions and improve speed

* Delete active object function, cleaning around and improve performance

* Better functions names and prevent user defined qto use
2023-01-07 18:11:46 +01:00
Dion Moult 73d43649a5 Link to latest IfcSverchok release 2023-01-07 17:08:46 +11:00
Dion Moult 55036653df Update IfcSverchok loading based on latest Sverchok master 2023-01-07 13:04:30 +11:00
Dion Moult 66ecb92cc2 Minor UX improvements to library name and Sverchok UI 2023-01-07 12:00:22 +11:00
Andrej730 23c7bc3f56 Added ShapeBuilder class and library for non structural assets (
`ShapeBuilder` class is used to programmatically create 2d and 3d ifc shapes and representations. It's inspired by the way OpenSCAD language is designed - the goal was to create a readable and easy way to create new shapes. The class can be imported from `ifcopenshell.util.representation` module.

Examples of things created with `ShapeBuilder` can be found in `shape_builder_examples.py` or `generate_furniture_library.py` / `Non_structural_assets_library.ifc`.

Also added ifc library of non structual assets (furniture etc) - `Non_structural_assets_library.ifc`. All assets are using most common dimensions for them - it makes it good for prototyping. Related to https://community.osarch.org/discussion/1192
2023-01-06 16:35:55 +05:00
Andrej730 74fee24004 Update ifc geometry on sverchok update 2023-01-06 12:23:54 +05:00
Dion Moult 618ecccb7b Fix #2428. Document minimum system support. 2023-01-06 17:11:51 +11:00
Dion Moult f818fc659a Bundle ifcopenshell for Python 3.11 on PyPI. See #2551. 2023-01-06 16:37:50 +11:00
Dion Moult 4afada48a6 Fix #2440. Fix #2439. Describe how to access advanced mode when dealing with large models. 2023-01-06 14:01:42 +11:00
Dion Moult 59451cb90c Fix #2664. Package with latest IfcOpenShell build. See #2670. 2023-01-06 11:16:29 +11:00
Sigma Dimensions 53bf746227 BBIM: Fixes for:
- Task Time attributes
- Fix typo animation time calculation
2023-01-05 18:43:50 +01:00
Sigma Dimensions cd6f995471 BBIM: Disable "Create animation" buttons when no start dates/end dates are given 2023-01-05 18:36:22 +01:00
Dion Moult 839a00c2a8 Run black on validation, lazy-load _pytest.assertion so that it isn't a hard dependency for non-express rule validation. 2023-01-05 20:54:17 +11:00
Dion Moult 9208781b8f Bump IfcOpenShell version 2023-01-05 18:38:32 +11:00
Dion Moult 25af42f4cb Fix circular import of json_logger when doing import ifcopenshell.validate 2023-01-05 18:37:14 +11:00
Dion Moult d386a884aa You can now purge properties explicitly instead of setting them to None 2023-01-05 17:37:40 +11:00
Dion Moult cb92be2b08 No need to specify SI unit names as they can be derived from the unit type 2023-01-05 17:37:40 +11:00
Martyna Jakubowska 70b7f1424f Resolved Code Review 2023-01-05 17:37:40 +11:00
Martyna Jakubowska 56858540b8 Update README.md 2023-01-05 17:37:40 +11:00
Martyna Jakubowska 0331a21280 Update README.md 2023-01-05 17:37:40 +11:00
Martyna Jakubowska 9f53d724d1 Update README.md 2023-01-05 17:37:40 +11:00
martinaCodes c114f09f9a Added example model and a corresponding description 2023-01-05 17:37:40 +11:00
martinaCodes 1a2183ef71 Bug fixes in create_entity 2023-01-05 17:37:40 +11:00
martinaCodes 0ab0213dea Bug fixes and changed behaviour of create_entity, so it creates an entity pr. each representation 2023-01-05 17:37:40 +11:00
Martyna Jakubowska 421f861e46 Updated README 2023-01-05 17:37:38 +11:00
martinaCodes 6e5f2f3f8d Updated by_query and removed print statements 2023-01-05 17:36:33 +11:00
martinaCodes 05633cd657 Updated following nodes to work with the current workflow: add_pset, by_guid, get_attribute, get_property, read_entity. Added tooltips and other minor fixes 2023-01-05 17:36:33 +11:00
Martyna Jakubowska 41ae350f33 Create README.md 2023-01-05 17:36:33 +11:00
martinaCodes 8a26f30506 Important rework of handling multiple/nested items in bmesh_to_ifc, create_entity and sverchok_to_ifc nodes. Formatting and other minor fixes. 2023-01-05 17:36:33 +11:00
martinaCodes 4ae7643874 cleaned print statements etc 2023-01-05 17:36:33 +11:00
martinaCodes b09068e1f4 write_file added logic that ensures proper model hierarchy 2023-01-05 17:36:33 +11:00
martinaCodes 87dbaa39d8 bmesh_to_ifc now creates separate representations for each distinct bmesh geometry 2023-01-05 17:36:33 +11:00
martinaCodes d193e8a3eb major changes primarily to file creation/saving logic.
Bug fix in create entity.
Other..
this commit is way to big and unstructured.
2023-01-05 17:36:33 +11:00
martinaCodes f9e766138c new pick_ifc_class node + minor fixes. Add_spatial_element is WIP 2023-01-05 17:36:33 +11:00
martinaCodes 36899d362e added blender panel for saving file + minor fix in create_entity 2023-01-05 17:36:33 +11:00
martinaCodes fd789178c1 Add_spatial_element node updated 2023-01-05 17:36:33 +11:00
martinaCodes a0757851c8 Add_spacial_element node updated to work with multiple elements. V0.1 - still a bit buggy. 2023-01-05 17:36:33 +11:00
martinaCodes 039727cb19 added entity locations 2023-01-05 17:36:33 +11:00
martinaCodes b5133f1c41 write_file node updated 2023-01-05 17:36:33 +11:00
martinaCodes ab218b842a create_shape node updated + minor changes 2023-01-05 17:36:33 +11:00
martinaCodes 37aa17fcf3 sverchok_to_ifc node added + works for multiple geometries. Parent contexts also get deleted if not used. 2023-01-05 17:36:33 +11:00
martinaCodes 6542fc7db3 updated by_type to current workflow + added sv_to_ifc 2023-01-05 17:36:33 +11:00
martinaCodes c8c690afc2 changes
1) pasing ID references
2) bugs in create_entities, where number of representations couldn't be updated
3) by_id node updated to current workflow
... other small fixes
2023-01-05 17:36:33 +11:00
martinaCodes 5d569dea30 fixed create_entity and minor changes to bmesh_to_ifc 2023-01-05 17:36:33 +11:00
martinaCodes 38031a02bc new nodes, and changes to create_entity2, but not working yet 2023-01-05 17:36:33 +11:00
martinaCodes 25460661ab created new singleton class SvIfcStore, created new 'create_entity2' node 2023-01-05 17:36:33 +11:00
martinaCodes 3684be1936 fixed api_WIP, minor cleanup in other nodes 2023-01-05 17:36:33 +11:00
martinaCodes 0ada921ad8 fixed a typo-bug 2023-01-05 17:36:33 +11:00
martinaCodes 8fb285596a added new nodes to node index 2023-01-05 17:36:33 +11:00
martinaCodes ba4130d395 Added new nodes: api_WIP, bmesh_to_ifc, create_project and quick_project_setup
Modified nodes: api, create_entity
2023-01-05 17:36:33 +11:00
Sigma Dimensions f818446e7e BBIM fix: prevent "guess_all_quantities" operator from showing up on Property Sets 2023-01-04 13:59:00 +01:00
Dion Moult 82ac895bbb Write docs for void API module 2023-01-04 16:10:13 +11:00
Dion Moult fd8774718e Write docs for unit API module 2023-01-04 16:10:13 +11:00
Dion Moult 90d4d5afda Write docs for type API module 2023-01-04 16:10:12 +11:00
Dion Moult 92623b98e2 Write docs for system API module 2023-01-04 16:10:12 +11:00
Dion Moult b86818ead3 Write docs for style API module 2023-01-04 16:10:12 +11:00
Dion Moult 9e45435024 Documentation example fixes where I forgot to give things with representations a placement 2023-01-04 16:10:12 +11:00
Sigma Dimensions 981b6cd65f Various 4D improvements:
- refactor work schedule visualisation operator
- Fix text handler
- Improve durations UI for task times (proposal to replicate for all durations)
- Simplify durations ( P32H as P4D or P1D8H)
- Implement years/months in various datetime calculations
- Feature to customise animation colors per construction task type
- Feature to add Visual task bars
- Feature to customise Visual task bar material colors
- Move helper functions from blenderbim.bim.module.sequence to ifcopenshell.util.sequence
2023-01-04 02:08:52 +01:00
Dion Moult 0c77fb81ce Write documentation for structural API module 2023-01-03 17:32:08 +11:00
Dion Moult e53a9cd66c Write documentation for spatial API module 2023-01-03 17:31:56 +11:00
Dion Moult 2805109cef Write documentation for sequence API module 2023-01-03 17:31:48 +11:00
Dion Moult 6594445fa3 Add new code example for creating a work schedule 2023-01-03 17:31:23 +11:00
Massimo Fabbro bac6c62073 Finish mapping quantities (see #2533) (#2661)
* Finish Qto_SignBaseQuantities

* Finish Qto_RailBaseQuantities

* Finish Qto_PitorialSignQuantities

* Finish Qto_JunctionBoxBaseQuantities

* Finish Qto_ArealStratumBaseQuantities

* Finish Qto_SiteBaseQuantities

* Finish Qto_RoofBaseQuantities

* Finish Qto_RoofBaseQuantities

* Finish Qto_ChimneyBaseQuantities

* Finish Qto_EarthworksCutBaseQuantities

* Finish Qto_KerbBaseQuantities

* Finish Qto_PavementBaseQuantities

* Finish Qto_RampFlightBaseQuantities

* Finish Qto_SurfaceFeatureBaseQuantities

* Finish Qto_StairFlightBaseQuantities

* Finish Qto_ConstructionMaterialResourceBaseQuantities

* Finish Qto_DistributionChamberElementBaseQuantities

* Finish Qto_CourseBaseQuantities

* Finish Qto_RailingBaseQuantities

* Finish Qto_SleeperBaseQuantities

* Finish Qto_TankSurfaceQuantities

* Finish Qto_BuildingElementProxy

* Finish Qto_MemberBaseQuantities

* Finish Qto_BodyGeometryValidation

* Finish Qto_VolumetricStratumBaseQuantities

* Finish Qto_SpatialZoneBaseQuantities

* Finish Qto_ReinforcingElementBaseQuantities

* Finish update calculate all quantities button with new calculating functions
2022-12-30 21:33:18 +01:00
Thomas Krijnen 6180d73f75 Fix build-all collada compile on gcc 9.4.0 2022-12-28 13:33:16 +01:00
Sigma Dimensions 599c391407 code clean up for sequence module 2022-12-26 22:48:51 +01:00
Sigma Dimensions 1f18c5ab82 features to guess schedule date range and setup default task columns, and code clean up 2022-12-26 18:30:25 +01:00
Sigma Dimensions 2bb4e645b2 Fix: When counting working days, consider if calendar is applicable 2022-12-26 17:19:57 +01:00
Sigma Dimensions 5326e21fef 2 Fixes for 1 commit:
- Consider all tasks descendants when deriving task earliest or latest dates
2022-12-26 17:18:33 +01:00
Sigma Dimensions 891b49ff6f BBIM: feature to display total task Assignements 2022-12-26 13:38:54 +01:00
Thomas Krijnen 0f9c84c1ce Feature: express rules (#2662)
* temp reasonable state, missing rule_head contents

* better state, with init() and rule_id, but some missing parameters

* performance; embed root in dict; fix lookup

* Add ast_utils

* Fixes related to listnode and typo

* First steps with code gen

* Finalize first steps towards working code generation

* Backwards compat

* Rule execution, error formatting, test cases

* Where rule: support indexing, more expressions, test plane angle measure

* positive length measure test cases

* Venture into entity rules, test actor role

* Fixed for not unary op and address test cases

* Test group qualifier and air terminal type

* typeof() implementation and test annotation curve occ

* Flexible handling of expression, type set intersection, IfcAnnotationSurface test cases

* Functions, derived attrs, fix branch order, allow to terminate branches, apply -1 to index

* Implement express query(), test cases for 2x3 bspline curve

* Function args, if-else, repeat, entity instance construction, cshape test cases

* Multiple function call arguments and other small but influential changes. IfcDotProject() almost working except for variable case

* Make lowercase and fix assignment to qualified lhs

* Entity instance schema without file, aggregate assignment workaround, range fix, lowercase instance locals, extruded area direction test cases

* Enumeration item handling, query() robustness, arbitrary profile test cases

* Empty aggregate_initializer, xor and instance equality, nested expression fix in IfcCrossProduct, axis2-3d test cases

* Numeric stable sort with indices greater then 10, get ast node parent, redeclared derived support, nested expression robustness, define entity functions, case statements, xor, mod, bool literals, nvl, unknown, conversion based unit test cases

* Safeguard for schema name case norm, case norm query variable_id

* free instance comparison, supertype serialization fix, include inherited attributes in locals, proper elif-else in case stmt, loindex, shape rep test cases

* Union operator on set, retain general_aggr_types in parse tree, test cases for property set

* Escape stmt, rule locals and statements

* Return calculated values for redeclared derived attributes

* fix for rules without stmt, add derived and inverse attributes to locals, filter unused locals, wrap exists() arg with lambda to catch indexerror, support repeated aggr init element, typeof() none check, enum namespace uppercase

* Regen 2x3 rules

* Rule test filter only on basename

* Update fixtures for compliance with full body of rules

* Add rule support to ifcopenshell.validate

* Implement usedin() function, test cases for IfcWallSC MLS

* blength

* Run code generation on all schemas

* Try to eliminate runtime pyparsing dep

* Try to eliminate runtime pyparsing dep
2022-12-26 11:29:32 +01:00
Sigma Dimensions 2ccf25f7eb Display Scheduled Resource Usage as a property within rersource tree 2022-12-26 07:04:11 +01:00
Sigma Dimensions 082a326e67 Enable displaying number of resources used by a task 2022-12-26 06:16:19 +01:00
Sigma Dimensions d33a7244a3 Task Output selection improvements - allow recursive nesting and add checkbox to toggle between nested and non-nested 2022-12-26 05:17:56 +01:00
Thomas Krijnen bdb352ab22 Update ci-py-only.yml 2022-12-25 17:36:31 +01:00
Thomas Krijnen 04fb7cdf9c Update ci.yml 2022-12-25 17:36:18 +01:00
Andrej730 16de2f2e7e Added mention of sverchok's copyrights 2022-12-23 09:24:26 +05:00
Andrej730 0b6c8fe83e Added base for ifc sverchok modifier
It's located in "Modifiers" section of the properties (the same way as IFC Array or IFC Stair).
It requires Sverchok addon to be enabled (it's lazy importing it when it's needed, so there is no new constant dependency).

What you can now do with it:
1) Add sverchok graphs to this modifier and store simple data from sverchok in IFC ("Update data from sverchok). The "output" node of the sverchok graph has to be type of "Viewer Draw" and it's name should start with "ifcoutput" (no case-sensitive).
2) Import/export json of sverchok graphs.
2022-12-21 20:10:39 +05:00
Andrej730 d11959d303 Removed deprecated angle annotations operator
As it's currently completely replace with new more intuitive operator.
2022-12-19 20:13:59 +05:00
Andrej730 2bd5f3bbb3 Fixed annotations angle operator svg rendering bug
Fixed bug where the angle was drawn in svg only for the first two edges of the curve.
Now it takes into account all edges - first angle = edge0 + edge1, second angle = edge1 + edge2 etc.
2022-12-19 20:07:29 +05:00
CyrilWaechter cabc91dd5e Allow to write to unexisting directory
`ifcopenshell.write("/unexisting/path/model.ifc")` was failing without
throwing an error. It now works and a test ensure it works.
2022-12-19 01:44:40 +01:00
Sigma Dimensions 88c9f15320 fix library import for date utils 2022-12-18 04:34:20 +01:00
Sigma Dimensions b206e90097 Proposal to use parent productivity to calculate resource work. 2022-12-18 04:17:17 +01:00
Sigma Dimensions 73d3e9589c BBIM: forgotten commit for "ScheduleDuration" formatting 2022-12-18 03:56:10 +01:00
Sigma Dimensions 84a07c3fdf fix typo for resource_work calculation 2022-12-18 03:41:13 +01:00
Sigma Dimensions ea1c306733 fix offset date calculation when finish date lies outside of calendar range 2022-12-18 03:34:20 +01:00
Dion Moult 30dc13f1a7 Add support for loading IFCs with aggregated types 2022-12-17 20:48:05 +11:00
Dion Moult 9b3b06adf3 Write documentation for root API module 2022-12-16 15:29:50 +11:00
Dion Moult 436482532d Write documentation for resource API module 2022-12-16 15:07:02 +11:00
Dion Moult d2fee728f2 Write documentation for pset_template API module 2022-12-16 11:22:46 +11:00
Dion Moult 0ccea3cef8 Add documentation for pset API module 2022-12-15 16:02:27 +11:00
Dion Moult 241406f676 Consolidate standard "clean environment" instructions in docs 2022-12-14 23:13:01 +11:00
Dion Moult a89c061b58 Write docs for project API module 2022-12-14 17:28:34 +11:00
Dion Moult 1211c153d9 Revert detailed SPF comments in code example
Sorry I find these really hard to read an intimidating for new users.
2022-12-14 17:28:12 +11:00
Dion Moult a5bdcf488b Write docs for profile API module 2022-12-14 16:49:06 +11:00
Dion Moult a5a1ef2e8e Write docs for owner API module 2022-12-14 15:58:53 +11:00
Ryan Schultz 05cd11ae12 Added some troubleshooting measures to determine if another plugin is interfering with BB. 2022-12-13 08:12:21 -06:00
fbpyr 307f9a2322 bump python version
bumping python version, so it works with our ubuntu latest image and `actions/setup-python@v2`
2022-12-13 21:35:42 +11:00
CyrilWaechter 3d05dc234a Ignore mypy cache, swig and compiled files 2022-12-11 22:49:44 +01:00
CyrilWaechter 1f87165601 Remove bloat line 2022-12-11 22:46:05 +01:00
CyrilWaechter a513e1b56a Remove duplicate line 2022-12-10 19:45:17 +01:00
CyrilWaechter ec0c8f509e Move boundary origin to space origin
Ensure that boundary origin is same as space origin to assign correct
geometry into IFC.
2022-12-10 19:44:28 +01:00
Ryan Schultz a23bac6ecb where to look to see if a dependency has changed. 2022-12-09 10:31:57 -06:00
Kristoffer Andersen 569c19abca Merge pull request #2642 from CyrilWaechter/v0.7.0
Remove unecessary line which make build failing
2022-12-09 10:52:01 +01:00
CyrilWaechter dfeac601e4 Remove unecessary line which make build failing 2022-12-09 09:44:19 +01:00
Dion Moult 53cecb3546 Write documentation for the nest API module. 2022-12-07 18:56:44 +11:00
Dion Moult cf87852d9c Add API documentation for the material module 2022-12-07 17:30:08 +11:00
Dion Moult 7da3d9ee70 Add docs for library API module 2022-12-07 12:20:29 +11:00
Dion Moult 9650bbb5ee Add docs to layer API 2022-12-07 08:59:24 +11:00
Dion Moult 45953c6b49 Add API documentation for group module 2022-12-06 16:42:47 +11:00
Dion Moult b34caa1b5f Add documentation for grid API module 2022-12-05 17:10:22 +11:00
Ryan Schultz ea8433b187 Added more examples to the Code Examples page 2022-12-05 15:44:38 +11:00
Andrej730 390f23a30c Added ReloadSelectedIfcFile operator
Added ReloadSelectedIfcFile operator and added it to IFC Quality Control -> IFC Debug, it's useful when you're debugging and doing a lot of iterations on the same file.
2022-12-05 14:59:58 +11:00
Massimo Fabbro c8ba2b53c9 Mapping quantities (see #2533) (#2627)
* Finish mapping Qto_PileBaseQuantities

* Finish mapping IfcPipeSegmentBaseQuantities

* Finish mapping IfcVehicleBaseQuantities

* Finish mapping Qto_OpeningElementBaseQuantities

* Finish mapping Qto_PlateBaseQuantities

* Finish Qto_MarineFacilityBaseQuantities

* Finish Qto_FacilityPartBaseQuantities

* Finish Qto_ProjectionElementBaseQuantities

* Better syntax
2022-12-02 18:19:43 +01:00
Thomas Krijnen dbbbb52e9e #2624 2022-12-02 13:43:44 +01:00
Dion Moult 052d6e2254 Add API docs for georeferencing module 2022-11-28 10:30:51 +11:00
Massimo Fabbro b05c711fb9 Merge pull request #2619 from maxfb87/mapping_quantities
Finish mapping quantities Qto_SpaceBaseQuantities (see #2533)
2022-11-27 08:17:32 +01:00
CyrilWaechter eb0322759c Allow ifcpatch to work with .ifcZIP | .ifcXML
Closes #2350
2022-11-26 05:37:00 +01:00
CyrilWaechter 25a7a95284 Allow to write .ifcZIP | .ifcXML #2350
Transparently write any .ifc | .ifcXML | .ifcZIP formatted file
 with any extension :
- by guessing format from its extension
(eg. .zip | .ifczip | .ifcZIP | .ifcXML | .ifcxml | .IFC | .ifc)
zipped file can file can be of any supported format
- by specifying a format
- by specifying if it should be zipped
2022-11-26 05:19:01 +01:00
Ryan Schultz 8ec1b7bb3e misspellings with outer_boundary 2022-11-25 12:29:00 -06:00
CyrilWaechter 5f094f6a95 Fix IfcSystem assignment bug introduced in f0b1935
Fixing my own bug…
Note : always run all test before pushing not only the one which used to fail…
2022-11-25 19:01:07 +01:00
CyrilWaechter f0b1935861 Add system assignment check
@aothms Is there a way to check against IFC formal propositions ?
Example for [IfcZone](http://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/IfcZone.htm#5.4.3.82.4-Formal-propositions)
@Moult Should I move is_assignable to an util ?
2022-11-25 18:52:58 +01:00
Massimo Fabbro 2540ba3467 Finish mapping quantities Qto_SpaceBaseQuantities 2022-11-25 17:22:13 +01:00
CyrilWaechter 88093728a7 Add IfcSpatialElementType to assignable classes
Required for IFC4+ to assign IfcSpaceType and IfcSpatialZoneType
Solves #2615
2022-11-25 15:55:32 +01:00
Thomas Krijnen af6734d7b0 IfcOpenShell WASM module build script (#2617) 2022-11-25 13:16:36 +01:00
Thomas Krijnen 8a108f37c3 Moar schema indices 2022-11-24 09:36:02 +01:00
Andrej730 d88510b919 Added new angle annotations operator
New operator should be more intuitive to work with. I kept the old one too for now.
2022-11-24 12:05:54 +06:00
Thomas Krijnen afe9f7602f Test cases for #2586 2022-11-23 13:46:15 +01:00
Thomas Krijnen ff2b79a41f Distinguish between nil and derived in validate.py 2022-11-23 13:45:44 +01:00
Thomas Krijnen 631722303f https://raw.githubusercontent.com/buildingSMART/IFC4.3-html/sep-13-release/IFC/RELEASE/IFC4x3/HTML/IFC4X3_TC1.exp 2022-11-23 13:23:49 +01:00
Dion Moult 4b20a407e9 Add API docs for drawing module 2022-11-23 11:23:40 +09:00
Dion Moult b23f445946 Add API documentation for document module 2022-11-23 10:56:41 +09:00
CyrilWaechter 09c8268494 Fix path for ci (root at src/ifcopenshell-python) 2022-11-22 23:25:11 +01:00
CyrilWaechter 0bc3543914 Update test/input submodule to https://github.com/IfcOpenShell/files/commit/20a17fcf3b1be2bc48af0273f7604af5b7cc4068
#2350
2022-11-22 21:23:26 +01:00
CyrilWaechter 0412eb48a0 Fix type hints #2350 for python <3.10 2022-11-22 01:00:04 +01:00
CyrilWaechter e7d5add13d Add test suite for ifcopenshell.open #2350
Fix open function: format argument was unused
2022-11-22 00:40:27 +01:00
Thomas Krijnen 25948a608a Missing parse updates after 4e0a19e 2022-11-21 09:43:13 +01:00
Dion Moult 11f772b5a3 Add documentation for cost module 2022-11-21 12:05:58 +09:00
CyrilWaechter 412cf7c6cb Add stable AUR package 2022-11-21 01:42:26 +01:00
CyrilWaechter b2d5580c37 Allow to open .ifcZIP | .ifcXML #2350
Transparently open any .ifc | .ifcXML | .ifcZIP formatted file
 with any extension :
- by guessing format from its extension
(eg. .zip | .ifczip | .ifcZIP | .ifcXML | .ifcxml | .IFC | .ifc)
zipped file can file can be of any supported format
-  by specifying a format
2022-11-21 01:32:12 +01:00
CyrilWaechter 8386aa0e0a IfcZone: Allow to remove an object from an IfcZone 2022-11-20 17:48:30 +01:00
CyrilWaechter ae0f2a9de6 IfcZone : Allow basic selection/deletion/creation 2022-11-20 17:35:27 +01:00
Thomas Krijnen 4e0a19e013 https://raw.githubusercontent.com/buildingSMART/IFC4.3-html/v4.3.1.0/IFC/RELEASE/IFC4x3/HTML/IFC4X3_ADD1.exp 2022-11-20 16:52:46 +01:00
Thomas Krijnen 9a840174d2 Fix test case 2022-11-20 14:38:17 +01:00
Massimo Fabbro 83528ba80a Merge pull request #2612 from maxfb87/Mapping_quantities
Finish mapping Qto_CoveringBaseQuantities (see #2533)
2022-11-19 23:05:29 +01:00
Massimo Fabbro eb23372fd0 Finish mapping Qto_CoveringBaseQuantities 2022-11-19 21:44:51 +01:00
Massimo Fabbro 286e9dfda9 Merge pull request #2605 from maxfb87/mapping_quantities
Mapping quantities (see #2533)
2022-11-19 17:28:29 +01:00
Vukas Pajic f0b9c9ad7b #2602 get_properties no longer throws error when EnumerationValues = None 2022-11-18 14:35:57 +01:00
Kristoffer Andersen afd79ee16d bump conda daily build number 2022-11-18 13:52:35 +01:00
Ryan Schultz caa4445eb0 Added diffuse_color inside IFC Style panel 2022-11-18 12:36:10 +11:00
Andrej730 f850bd5741 documented create_simple_2dcurve() 2022-11-17 12:03:28 +06:00
Massimo Fabbro 8c5883ffaa Finish mapping Qto_DoorBaseQuantities and Qto_WindowBaseQuantities 2022-11-16 22:28:10 +01:00
Massimo Fabbro 8f8940de2a Minor fix 2022-11-16 21:56:29 +01:00
Massimo Fabbro bf59871db5 Fix get_gross_side_area for Qto_WallBaseQuantities 2022-11-16 21:50:52 +01:00
Dion Moult 22eb0bf2e4 Write API documentation for control module 2022-11-16 18:38:51 +09:00
Dion Moult 68838a37e5 Add API documentation for context module 2022-11-16 18:18:22 +09:00
Dion Moult 22b4bfe07d Add API docs for constraint module 2022-11-16 08:57:25 +09:00
Andrej730 ff2714d991 Added c-, z-profiles with lips to AU steel library
Added c- and z-profiles with lips to IFC AU Steel profile library (based on data from https://github.com/boltsparts/boltsparts/pull/15).

For z-profiles I've created simple 2D curve generation function that can be reused later - it requires list of points and list of points with fillets and the results are list of curve points, segments and IfcIndexedPolyCurve.

Example:

    points, segments, ifc_curve = create_simple_curve_from_coords(coords,
        fillets =     (0,   1,   4, 5, 6,   7,   10, 11),
        fillet_radius=(r+t, r+t, r, r, r+t, r+t, r, r),
        closed=True, ifc_file=ifc_file)
2022-11-15 13:35:44 +06:00
Dion Moult 308ee7f7a9 Add API documentation for classification module 2022-11-15 16:11:47 +09:00
Dion Moult c87924c4ba Add API docs for boundary module 2022-11-15 08:27:26 +09:00
Thomas Krijnen 8f33c2ca9d Missing file 2022-11-14 09:38:51 +01:00
Thomas Krijnen 9351038009 Refactoring 2022-11-14 09:23:59 +01:00
Dion Moult 66281ec1f1 Add docs for attribute API 2022-11-14 07:55:50 +11:00
Dion Moult ae843e6faf Add docs for aggregate api 2022-11-14 07:48:20 +11:00
Massimo Fabbro 5ca0a254ff Cleaning around in qto_calculator 2022-11-14 06:50:33 +11:00
Thomas Krijnen c18458b3db Refactoring 2022-11-13 11:21:58 +01:00
Thomas Krijnen 3736a8b4b4 Refactoring 2022-11-13 11:04:16 +01:00
Dion Moult 6558f68cd4 Minor fix 2022-11-13 15:32:24 +11:00
Dion Moult 4fd5806f70 Minor documentation tweaks 2022-11-13 15:30:35 +11:00
Thomas Krijnen b1c730c04d draw.include_projection setting 2022-11-12 15:49:29 +01:00
Dion Moult bdec443410 Minor fixes to IfcTester 2022-11-12 14:08:01 +11:00
Erik Abrahamsson d9afb0f8a2 Fix translation of days in Powerproject importer 2022-11-12 08:32:19 +11:00
Andrej730 9dfd7e03f7 IFC Stair - added "Wood / Steel" stair type
Added new stair type "Wood / Steel" to IFC Stair modifier. It's less sophisticated than "Concrete" stair type - it acts more like a "Dumb stair" operator we have.
2022-11-11 17:32:57 +06:00
Andrej730 31b633b8b6 IFC Stair - cosmetic adjustments 2022-11-11 16:22:55 +06:00
Massimo Fabbro 20f4737ffd Fix some errors and update notes 2022-11-11 12:00:08 +11:00
Massimo Fabbro 4d4ab63404 Finish mapping Qto_BeamBaseQuantities 2022-11-11 12:00:08 +11:00
Massimo Fabbro 056f7610c1 Fix some get_lateral_area function errors 2022-11-11 12:00:08 +11:00
Massimo Fabbro bfb2f06e44 Finish mapping Qto_BeamBaseQuantities 2022-11-11 12:00:08 +11:00
Dion Moult a7febaa9b0 Fix astapowerproject import to ifc which seemed mostly broken for some reason 2022-11-11 11:38:09 +11:00
Sigma Dimensions ff984f62af BBIM feature to load nested tasks outputs, and select all nested task outputs 2022-11-10 18:05:26 +01:00
Otso Alho d005524c4c catch missing instances one by one. do not throw away whole relation. 2022-11-10 14:18:50 +01:00
Dion Moult a2362668a3 The profile manager now only manages named profiles 2022-11-10 23:32:15 +11:00
Andrej730 277317e6fd Added "IFC Stair" to object modifiers
1) Added "IFC Stair" to object modifiers - it works in similar way as "IFC Array". It creates a stair from current object (if it's IfcStairFlight) and writes it's parameters to property set "BBIM_Stair" and also to IfcStairFlight parameters.
Then you can edit it - modifying stair parameters and immediately see the changes, then you can save those changes or discard them.
2) "Clever Stair" operator (in Shift+A menu) works a bit different now - it creates new IfcStairFlight with IFC Stair modifier activated.
2022-11-10 15:57:08 +06:00
Dion Moult b3f0b9b37b Get cross section area from profile directly rather than mesh parsing 2022-11-10 17:37:14 +11:00
Dion Moult 6de1e73e4f Distinguish between calculating gross and net surface area 2022-11-10 17:02:11 +11:00
Thomas Krijnen c6cdf4ab77 Be a bit more careful in throwing NoSuchObject in case of triangulated face handling https://github.com/IfcOpenShell/wasm-preview/issues/1 2022-11-09 20:32:53 +01:00
Dion Moult 21f8aca00f Elementary fixes to the BCF module just so that things load (but lots are still broken) 2022-11-09 23:35:47 +11:00
Thomas Krijnen 1d6c3b3fc2 Update build-all.py 2022-11-09 10:58:38 +01:00
Thomas Krijnen 4362163ea9 Update build-all.py 2022-11-09 10:57:42 +01:00
Dion Moult 67808b8384 Fix issue where sometimes world coordinate system would trigger the georeferencing distance threshold 2022-11-09 18:37:16 +11:00
Andrea Ghensi 2cbc7a10ab [BCF] New BCF XML handler with better performances (#2355)
BREAKING CHANGE: incompatible methods and objects with previous version.

closes #2321
2022-11-09 08:54:23 +11:00
Massimo Fabbro d97594df96 Finish Qto_SlabBaseQuantities quantities (#2567) 2022-11-08 22:21:00 +11:00
Dion Moult c7eca135de Document how you can get the matrix of elements from IfcOpenShell Python. 2022-11-08 20:18:08 +11:00
Dion Moult e63ec83325 Fix some bugs in IfcTester that prevented tests running on IFC2X3 2022-11-08 20:05:56 +11:00
Dion Moult 1ddae8a6f5 Fix #2577. Bug where system editing UI appears multiple times. 2022-11-08 20:04:59 +11:00
Andrej730 03781bdf9b Updated EU, AU steel profiles libraries
1) Updated AU profiles based on more correct data (https://github.com/boltsparts/boltsparts/pull/4) - also added welded i-beam profiles
2) Added inner and outer fillet radius to hollow profiles since this data was added to https://github.com/boltsparts/boltsparts
3) Added more data to ifc profiles that was available on bolts: FilletRadius for I-beams, C-beams; FilletRadius and EdgeRadius for I-beams. Sovles #2556
4) Set names IfcProfileDef names too. Solves #2556
2022-11-08 11:29:43 +06:00
Dion Moult 62e5c3684b Fix #2564. Bug where profiles were not reused across copies. 2022-11-07 22:37:53 +11:00
Dion Moult ff90728a22 New unassign container API function 2022-11-07 18:47:15 +11:00
Dion Moult dadf23681b Fix #2123. Bug where mesh to extrusion conversion didn't work for thin extrusions. 2022-11-07 17:45:05 +11:00
Dion Moult 516bd37518 Fix #2550. Bug where material styles weren't included when appending from a library 2022-11-07 16:20:23 +11:00
Dion Moult 521ec1b124 Support styles defined somewhere along a boolean result stack 2022-11-07 11:24:13 +11:00
Thomas Krijnen 4881e4c686 Error in type checking introduced in ca98e77b40 2022-11-06 14:45:00 +01:00
Thomas Krijnen a57cac5f9c #2558 make conditional 2022-11-06 14:39:01 +01:00
Thomas Krijnen ca98e77b40 #2471 Fix refcount when setting aggregates 2022-11-05 16:24:04 +01:00
Dion Moult cbd9c898c3 Fix failing tests 2022-11-04 17:08:41 +11:00
Massimo Fabbro f57731a10a Mapping quantities #2533 (#2565)
* Implement some Qto_WallBaseQuantities quantities

* finish Qto_WallBaseQuantities
2022-11-04 08:56:38 +11:00
Dion Moult 0142bface5 You can now edit parameterised profiles again in the material panel 2022-11-04 00:05:09 +11:00
Dion Moult 5acbf4658f Material UI now prompts to add a profile if none exist in your library 2022-11-03 23:44:47 +11:00
Dion Moult 32e00f6d49 Refactor material UI to not depend on IOS API data class 2022-11-03 23:36:16 +11:00
Gorgious56 4e3ce192cc Custom property set attributes now show their description when right clicking the field
+ Add explicit numerical constraint members to attribute class #2562
+ Fix error in numerical value callback #2562
+ Fix error in setting attribute values because of wrong type cast
2022-11-03 09:54:35 +01:00
Gorgious56 841bb59d18 Fix error when the user selects a class suggestion that is not a predefined type 2022-11-03 08:21:55 +01:00
Gorgious56 8dd8a23af9 The predefined type suggestions now fetch their values from the ifcopenshell.util.doc module instead of parsing the file data 2022-11-03 08:16:40 +01:00
Dion Moult adc59de2bf Minor fix to accommodate null descriptions and parents with no attributes
# Conflicts:
#	src/ifcopenshell-python/ifcopenshell/util/doc.py
2022-11-03 17:13:56 +11:00
Gorgious56 20198bccdb Numerical Attribute validation callback (#2562)
* Numerical attributes can now be clamped

* #2545 : IfcMaterialLayer Priority is now clamped between 0 and 100

* Run black
2022-11-03 14:41:47 +11:00
Andrej730 c0909dace0 black.py formatting 2022-11-02 17:35:51 +06:00
Andrej730 bd054d4baf Added more advanced stair generating operator 2022-11-02 17:31:27 +06:00
Dion Moult 35a70a4ae3 Fix #2559. Don't consider door and window styles in IFC4X3. 2022-11-02 17:31:10 +11:00
Dion Moult a1d30d157b Minor fix. 2022-11-02 17:27:18 +11:00
Kristoffer Andersen 621d89c80e Update ci-ifcopenshell-conda-daily.yml
Bump GA versions away from deprecated versions
2022-11-01 20:58:59 +01:00
Gorgious 2fa3793622 Selecting an ifc predefined type in addition to the ifc class in the object ifc class selector now correctly assigns it 2022-11-01 17:34:29 +01:00
Kristoffer Andersen 872c057c4f bump conda daily to python v 3.10 and 3.11 2022-11-01 16:16:28 +01:00
Kristoffer Andersen f6a530e5e3 remove cap on boost version 2022-11-01 15:17:32 +01:00
Dion Moult 2043a629c7 Material profile sets UI cleanup and now lets you choose from a profile in your library instead of creating new profiles on the fly all the time 2022-11-01 23:39:07 +11:00
Kristoffer Andersen a7f9d548cf fix: add conditional #include <windows.h> for certain files breaking … (#2558)
* fix: add conditional #include <windows.h> for certain files breaking with boost v1.78+

* Revert "fix: add conditional #include <windows.h> for certain files breaking with boost v1.78+"

This reverts commit 7cd5c6292b.

* fix: add only `BOOST_REGEX_NO_W32` to fix issues boost v1.78+
2022-11-01 13:30:07 +01:00
Dion Moult 1391dd38ec Update README.md 2022-11-01 13:29:20 +01:00
Dion Moult 38f6b7aca0 Clean up README
This new proposed readme makes it more obvious to readers that there is a large collection of tools available, a brief glance of what they do, and directs users to the dedicated websites and documentation for further instructions on installation and so on.
2022-11-01 13:29:20 +01:00
Dion Moult f0e4397ef9 Change PyPI package readme. See #2531. 2022-11-01 09:08:13 +11:00
Dion Moult 6b7ffc8ca6 Merge pull request #2544 from Gorgious56/Tooltips
Blenderbim : Implement tooltips and online docs in the interface
2022-11-01 08:46:51 +11:00
Gorgious 5458f4f9dd Slightly optimise the display of the description popup 2022-10-31 22:22:46 +01:00
Gorgious 0c3269d9e1 Revert "Squashed commit of the following:"
This reverts commit f07cf1d1ef.
2022-10-31 19:53:19 +01:00
Gorgious 84dde4c5c0 Property set templates attributes now show a tooltip on hover 2022-10-31 19:34:59 +01:00
Gorgious 7eefb3f62f Pset nominal values now show their description on right click 2022-10-31 19:09:58 +01:00
Gorgious 90369bf7f7 Directly use predefined type doc function instead of wrapper 2022-10-31 18:10:03 +01:00
Gorgious eff3318399 Directly use attribute doc functions instead of wrapper 2022-10-31 18:01:59 +01:00
Gorgious ee59d366dc Directly use entity doc functions instead of wrappers 2022-10-31 17:57:24 +01:00
Gorgious c0b91848a3 Refactor right click context menu 2022-10-31 17:30:33 +01:00
Gorgious d05d430642 Directly use pset doc functions instead of wrapped ones 2022-10-31 17:30:18 +01:00
Gorgious e8a73e008e Pset types and template types enums now show tooltips on hover 2022-10-31 17:15:58 +01:00
Thomas Krijnen 259bb4f36a Let's try #2551 2022-10-31 13:39:01 +01:00
Dion Moult b87191a358 Forgot to commit new profile import export in model tool 2022-10-31 22:34:48 +11:00
Gorgious a359230181 Actually fix #2553 : Add safeguard to unregister custom right click context menu 2022-10-31 11:29:04 +01:00
Gorgious a95e12c0b2 Pset enums now display a tooltip on hover 2022-10-31 11:09:31 +01:00
Gorgious 2ac27c6fdd Add additional line to the ifc description popup to display the attribute type 2022-10-31 10:43:50 +01:00
Gorgious 757578a74b Fix error fetching the description of a nameless attribute 2022-10-31 10:34:18 +01:00
Gorgious 3466a38a44 Removed hardcoded descriptions thanks to 937f39c 2022-10-31 10:32:49 +01:00
Gorgious f07cf1d1ef Squashed commit of the following:
commit 7fb4cdaa16
Author: Gorgious <nathan.hild@gmail.com>
Date:   Mon Oct 31 09:35:06 2022 +0100

    Fix #2546 : Editing a material now automatically ends the edition of its material set item if necessary

commit 427411e758
Author: Dion Moult <dion@thinkmoult.com>
Date:   Mon Oct 31 19:04:57 2022 +1100

    You can now edit arbitrary profiles in the profile manager

commit cc056d5d53
Author: Gorgious <nathan.hild@gmail.com>
Date:   Mon Oct 31 08:52:11 2022 +0100

    Fix #2553 : Right click documentation is now only available for Blender 3.3+
    Also fix an error in the console when right clicking a button

commit 31600033bf
Author: ArturTomczak <artomczak@gmail.com>
Date:   Sun Oct 30 23:16:08 2022 +0100

    add BCF reporter to IDS (#2552)

    * grammar

    * reason not always a list

    * extend readme with instruction

    * remove microseconds from date

    * add BCF reporter

commit 0338825175
Author: Dion Moult <dion@thinkmoult.com>
Date:   Sun Oct 30 23:26:59 2022 +1100

    You can now add new profile definitions in the profile manager

commit d9f8aa4e7b
Author: Andrej730 <azhilenkov@gmail.com>
Date:   Sun Oct 30 11:29:42 2022 +0600

    Added ifc types schema for Ifc2x3 and Ifc4

    Types schema located in separate json file, it's structure:
    { ifc_type_name: {"description": type_description, "spec_url": type_spec_url} }

    I've also added new API function to get type data - `get_type_doc(version, ifc_type)`

commit 6803788676
Author: Dion Moult <dion@thinkmoult.com>
Date:   Sun Oct 30 11:02:18 2022 +1100

    Load thumbnails in a paginated manner to allow to scale to larger models

commit d421cc0bbb
Author: Massimo Fabbro <79401028+maxfb87@users.noreply.github.com>
Date:   Sun Oct 30 00:07:56 2022 +0200

    No mapped quantities in qto calculator  has None value (#2549)

commit bf57c8b1bf
Author: Dion Moult <dion@thinkmoult.com>
Date:   Sat Oct 29 23:45:04 2022 +1100

    Refactor profile module to use data class

commit 9a25db6584
Author: Andrej730 <azhilenkov@gmail.com>
Date:   Sat Oct 29 18:31:40 2022 +0600

    get_attribute_doc get_entity_doc now use recursive method by default

commit 17ccd0adf7
Author: Andrej730 <azhilenkov@gmail.com>
Date:   Sat Oct 29 18:27:51 2022 +0600

    Simplified getting entity parent information for schema api

    Removed unnecssary parent information from json schema - now API is getting it from ifcopenshell schema.

commit 937f39c898
Author: Andrej730 <azhilenkov@gmail.com>
Date:   Sat Oct 29 17:35:43 2022 +0600

    Added information about parent entity to schema

    Added information about parent entity to schema (it's located in "parent_entity" entity parameter inside schema), both for Ifc2x3 and Ifc4.

    Modified get_entity_doc and get_attribute_doc - now you can use additional optional parameter `recursive=True` with those functions - then parent entities attributes will be included to the list of entity's attributes. For example IfcWindow will also have attributes from IfcBuildingElement, IfcElement, IfcProduct etc...

commit 0b5ccd88a5
Author: Andrej730 <azhilenkov@gmail.com>
Date:   Sat Oct 29 16:31:54 2022 +0600

    Added descriptions for psets and qsets to ifc2x3, ifc4 schema

    Example of getting pset description: `get_property_set_doc("IFC4", "Pset_ZoneCommon")['description']`

commit ebf3cd8909
Author: Andrej730 <azhilenkov@gmail.com>
Date:   Sat Oct 29 10:32:26 2022 +0600

    Added IFC libraries for AU and EU profiles

    Also added script that was used to generate those libraries (the script is using data from https://github.com/boltsparts/BOLTS).

    Types of steel profiles included:
    I profiles, Z profiles, C profiles (known as U profiles in IFC), L profiles (both equal and unequal), hollow profiles (circle, square and rectangular).

commit 08afec3461
Author: Dion Moult <dion@thinkmoult.com>
Date:   Fri Oct 28 22:32:15 2022 +1100

    Support material layer set usage negative direction sense for parametric walls

commit 4408a23873
Author: Dion Moult <dion@thinkmoult.com>
Date:   Fri Oct 28 22:30:43 2022 +1100

    Merge duplicate types IfcPatch recipe can now merge based on any attribute
2022-10-31 09:51:05 +01:00
Gorgious 7fb4cdaa16 Fix #2546 : Editing a material now automatically ends the edition of its material set item if necessary 2022-10-31 09:35:06 +01:00
Dion Moult 427411e758 You can now edit arbitrary profiles in the profile manager 2022-10-31 19:05:04 +11:00
Gorgious cc056d5d53 Fix #2553 : Right click documentation is now only available for Blender 3.3+
Also fix an error in the console when right clicking a button
2022-10-31 08:52:11 +01:00
ArturTomczak 31600033bf add BCF reporter to IDS (#2552)
* grammar

* reason not always a list

* extend readme with instruction

* remove microseconds from date

* add BCF reporter
2022-10-31 09:16:08 +11:00
Dion Moult 0338825175 You can now add new profile definitions in the profile manager 2022-10-30 23:27:11 +11:00
Andrej730 d9f8aa4e7b Added ifc types schema for Ifc2x3 and Ifc4
Types schema located in separate json file, it's structure:
{ ifc_type_name: {"description": type_description, "spec_url": type_spec_url} }

I've also added new API function to get type data - `get_type_doc(version, ifc_type)`
2022-10-30 11:33:20 +06:00
Dion Moult 6803788676 Load thumbnails in a paginated manner to allow to scale to larger models 2022-10-30 11:02:31 +11:00
Massimo Fabbro d421cc0bbb No mapped quantities in qto calculator has None value (#2549) 2022-10-30 09:07:56 +11:00
Dion Moult bf57c8b1bf Refactor profile module to use data class 2022-10-29 23:45:12 +11:00
Andrej730 9a25db6584 get_attribute_doc get_entity_doc now use recursive method by default 2022-10-29 18:31:40 +06:00
Andrej730 17ccd0adf7 Simplified getting entity parent information for schema api
Removed unnecssary parent information from json schema - now API is getting it from ifcopenshell schema.
2022-10-29 18:27:51 +06:00
Andrej730 937f39c898 Added information about parent entity to schema
Added information about parent entity to schema (it's located in "parent_entity" entity parameter inside schema), both for Ifc2x3 and Ifc4.

Modified get_entity_doc and get_attribute_doc - now you can use additional optional parameter `recursive=True` with those functions - then parent entities attributes will be included to the list of entity's attributes. For example IfcWindow will also have attributes from IfcBuildingElement, IfcElement, IfcProduct etc...
2022-10-29 17:35:43 +06:00
Andrej730 0b5ccd88a5 Added descriptions for psets and qsets to ifc2x3, ifc4 schema
Example of getting pset description: `get_property_set_doc("IFC4", "Pset_ZoneCommon")['description']`
2022-10-29 16:31:54 +06:00
Andrej730 ebf3cd8909 Added IFC libraries for AU and EU profiles
Also added script that was used to generate those libraries (the script is using data from https://github.com/boltsparts/BOLTS).

Types of steel profiles included:
I profiles, Z profiles, C profiles (known as U profiles in IFC), L profiles (both equal and unequal), hollow profiles (circle, square and rectangular).
2022-10-29 10:32:26 +06:00
Gorgious56 d9b1c26bf0 Fix bug when prop_with_search is used with a prop that is not an enum 2022-10-28 16:50:11 +02:00
Gorgious56 490da99792 Pset primary measure type should now display a tooltip on hover
Awaiting for ifc measure descriptions to be added to the docs
2022-10-28 16:32:17 +02:00
Gorgious56 528df00027 Organization and person now show a tooltip on hover 2022-10-28 16:30:04 +02:00
Gorgious56 bc10f934d6 Add tooltips to attributes to guide users to their description 2022-10-28 15:46:02 +02:00
Gorgious56 819db11db4 Simplify attribute description 2022-10-28 15:44:31 +02:00
Gorgious56 37a0854d40 Material set profile attributes now show a description on right click
+ added link to docs to Material set profile attributes and Material set item
2022-10-28 14:39:36 +02:00
Gorgious56 b984268b5d parameterized profile classes now shot a tooltip on hover 2022-10-28 14:34:57 +02:00
Gorgious56 e4d2ae185d Fix bug when an entity doesn't have any predefined type to fetch in the docs 2022-10-28 14:26:08 +02:00
Gorgious56 345098b4af Fix bug when attribute enum items descriptions are not filled in 2022-10-28 13:51:52 +02:00
Gorgious56 98b517954f Material layer set item attributes now show a description on right click 2022-10-28 13:47:42 +02:00
Gorgious56 dba8d2df9d Fix bug when right clicking an operator button in the interface 2022-10-28 13:43:41 +02:00
Gorgious56 6756ba7022 Ifc profile classes enum now shows a tooltip on hover 2022-10-28 13:42:51 +02:00
Dion Moult 08afec3461 Support material layer set usage negative direction sense for parametric walls 2022-10-28 22:32:15 +11:00
Dion Moult 4408a23873 Merge duplicate types IfcPatch recipe can now merge based on any attribute 2022-10-28 22:30:57 +11:00
Gorgious56 c33b5aa4b7 Extract attribute description fetching 2022-10-28 12:08:28 +02:00
Gorgious56 0ebbc62a06 Add popup window when clicking Ifc description in right click context menu 2022-10-28 11:54:07 +02:00
Gorgious56 0494373155 Store Ifc class in attribute
This shouldn't be necessary ? but it's easier for now
2022-10-28 11:53:23 +02:00
Gorgious56 c897cb3ea0 Show descriptions for attributes in right click context menu
This is hacky, looking for a solution to get tooltips on hover over field
2022-10-28 11:10:12 +02:00
Gorgious56 f38e8f26f5 You can now access property sets online documentation through the right click context menu 2022-10-28 09:07:33 +02:00
Gorgious56 7164103a80 Search button now only displays when an enum has more than 10 items
To reduce visual clutter for enums with only a few values
Also tweak the online docs ui function
2022-10-28 08:33:56 +02:00
Gorgious56 5fb7f33fb0 You can now access the ifc classes online docs from the right click context menu
+ Remove the ugly buttons in the interface
2022-10-28 08:19:35 +02:00
Gorgious56 e602f16ed3 Add description to more ifc entities enums
- Constraint type
- Material type
- ObjectMaterial type
- Actor class and type
- Structural load type and boundary condition type
- Style type
- System type
- Relating type
- Unit type
2022-10-27 17:19:24 +02:00
Gorgious56 17a872a8d8 Use new get_predefined_type_doc function #1982 2022-10-27 16:00:12 +02:00
Dion Moult 4533b74d65 Minor IfcTester fixes 2022-10-27 23:37:01 +11:00
Dion Moult 62229b032e You can now array IFC elements parametrically 2022-10-27 23:36:09 +11:00
Andrej730 f2c2fa8dce Added entities predefined types to schema
Both for ifc2x3 and ifc4 but there is still a problem with ifc2x3 predefined types description - it's empty since constants have no description in ifc2x3. For temporary solution I've used description available on ifc4 for ifc2x3 too.

I've also added API function to get entity's predefined type description: `get_predefined_type_doc(version, entity, predefined_type)`

Example for the same constant that has no description in ifc2x3 and has one in ifc4:
https://github.com/buildingSMART/IFC/blob/Ifc2.3.0.1/Constants/s/STEAMINJECTION_1b6zZZM99EevT2LVdQ7B%24V.xml
https://github.com/buildingSMART/IFC/blob/Ifc4.0.2.1/Constants/s/STEAMINJECTION_1b6zZZM99EevT2LVdQ7B$V.xml
2022-10-27 16:23:46 +06:00
Dion Moult ae170bbfac Fix #2541. Forgot that wall body must be based off the axis reference line. 2022-10-27 19:08:37 +11:00
Dion Moult 706b925f4c FM documents now use the identification as the key, and refer to the doc info instead of doc reference 2022-10-27 09:46:11 +11:00
Dion Moult 65288eac04 Minor fix. See #2533. 2022-10-27 09:45:28 +11:00
Massimo Fabbro bf5a9bdac6 Start quantity calculator implementation (related to #2533) (#2534)
* Start quantity calculator implementation

* Fix get qto_name
2022-10-26 23:51:52 +11:00
Dion Moult e22ebfbf94 Fix #2523. Extrusion depth now reflects perpendicular depth 2022-10-26 22:24:18 +11:00
Dion Moult fb2989c17f Fix #2528. Setting x angle on slabs now only affects the X euler angle 2022-10-26 18:11:54 +11:00
Dion Moult ffbf6483b5 You can now disconnect a wall from a roof clipping 2022-10-26 17:53:20 +11:00
Dion Moult 5c5488fa49 You can now clip walls by sloped slabs / roofs. 2022-10-25 23:13:44 +11:00
Dion Moult 7ff1b8a34d New API for connecting and disconnecting elements 2022-10-25 17:54:24 +11:00
Dion Moult fb786bb90c Batch reassignments now take advantage of inverse indices for a slight speedup. See #2408. 2022-10-25 16:27:13 +11:00
Dion Moult fad80eacaf Bump IOS 2022-10-25 14:03:25 +11:00
Gorgious56 84020c9832 Type Manager : Add search to ifc class and descriptions to class an predefined type enums 2022-10-24 15:20:52 +02:00
Dion Moult 59bce590cc Fix #2529. Bulk copying decomposed elements to multiple storeys now maintains decomposition. 2022-10-24 22:57:23 +11:00
Dion Moult a7ae78ec2a Fix crash when deleting opening. See #2529. 2022-10-24 21:49:44 +11:00
Dion Moult 523a97329e Fix door appearing on wrong RL when not on ground floor. See #2529. 2022-10-24 20:34:09 +11:00
1048 changed files with 482550 additions and 17760 deletions
+3 -1
View File
@@ -29,7 +29,7 @@ jobs:
sudo apt-get install --no-install-recommends \
git cmake gcc g++ libboost-all-dev python3-all-dev swig libpcre3-dev libxml2-dev \
libocct-foundation-dev libocct-modeling-algorithms-dev libocct-modeling-data-dev libocct-ocaf-dev libocct-visualization-dev libocct-data-exchange-dev \
libhdf5-dev libcgal-dev
libhdf5-dev libcgal-dev nlohmann-json3-dev
-
name: ccache
@@ -61,6 +61,8 @@ jobs:
-DGMP_LIBRARY_DIR=/usr/lib/x86_64-linux-gnu \
-DMPFR_LIBRARY_DIR=/usr/lib/x86_64-linux-gnu \
-DHDF5_INCLUDE_DIR=/usr/include/hdf5/serial \
-DGLTF_SUPPORT=On \
-DJSON_INCLUDE_DIR=/usr/include \
../cmake
make -j $(nproc)
make install
+5 -6
View File
@@ -19,20 +19,19 @@ jobs:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
with:
python-version: '3.x'
python-version: '3.10'
- name: Build package
run: |
cd src/bcf
pip install build
python -m build
- name: Test
run: |
cd src/bcf
make test
- name: Publish package
uses: pypa/gh-action-pypi-publish@release/v1
with:
user: __token__
password: ${{ secrets.PYPI_TOKEN }}
packages_dir: src/bcf/dist
+1 -1
View File
@@ -43,7 +43,7 @@ jobs:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2 # https://github.com/actions/setup-python
with:
python-version: '3.7.7' # Version range or exact version of a Python version to use, using SemVer's version range syntax
python-version: '3.10.9' # Version range or exact version of a Python version to use, using SemVer's version range syntax
architecture: 'x64' # optional x64 or x86. Defaults to x64 if not specified
- run: echo ${{ env.DATE }}
@@ -61,7 +61,6 @@ jobs:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2 # https://github.com/actions/setup-python
with:
python-version: '3.7.7' # Version range or exact version of a Python version to use, using SemVer's version range syntax
architecture: 'x64' # optional x64 or x86. Defaults to x64 if not specified
- run: echo ${{ env.DATE }}
- name: Get current date
@@ -1,10 +1,17 @@
name: ci-ifcopenshell-conda-daily
on:
workflow_run:
workflows: ["ci"]
types:
- completed
push:
paths:
- .github/workflows/ci-ifcopenshell-conda-daily.yml
schedule:
# ┌───────────── minute (0 - 59)
# │ ┌───────────── hour (0 - 23)
# │ │ ┌───────────── day of the month (1 - 31)
# │ │ │ ┌───────────── month (1 - 12 or JAN-DEC)
# │ │ │ │ ┌───────────── day of the week (0 - 6 or SUN-SAT)
# * * * * *
- cron: "49 23 * * *" # 11min before utc midnight every day
jobs:
activate:
@@ -26,19 +33,16 @@ jobs:
fail-fast: false
matrix:
pyver: [
{ name: py39, distver: '3.9' },
{ name: py310, distver: '3.10'}
{ name: py310, distver: '3.10'},
{ name: py311, distver: '3.11'}
]
platform: [
{ name: Windows, distver: windows-2022, upload: 'false' },
{ name: Windows, distver: windows-2019, upload: 'true' },
{ name: Linux, distver: ubuntu-20.04, upload: 'false' },
{ name: Linux, distver: ubuntu-18.04, upload: 'true' },
{ name: macOS, distver: macos-11, upload: 'true' },
{ name: macOS, distver: macos-10.15, upload: 'false' }
{ name: Windows, distver: windows-2022, upload: 'true' },
{ name: Linux, distver: ubuntu-22.04, upload: 'true' },
{ name: macOS, distver: macos-12, upload: 'true' }
]
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
with:
submodules: recursive
- name: Download MacOSX SDK
@@ -57,8 +61,10 @@ jobs:
- name: build, test and upload ifcopenshell
if: ${{ matrix.platform.upload == 'true' }}
run: |
conda-build . --python ${{ matrix.pyver.distver }} -c conda-forge --token ${{ secrets.ANACONDA_TOKEN }} --user ifcopenshell --no-remove-work-dir
conda-build . --python ${{ matrix.pyver.distver }} -c conda-forge --token ${{ secrets.ANACONDA_TOKEN }} --user ifcopenshell
working-directory: ./conda
- name: build & test ifcopenshell
if: ${{ matrix.platform.upload == 'false' }}
run: |
conda-build . --python ${{ matrix.pyver.distver }} -c conda-forge --no-remove-work-dir
conda-build . --python ${{ matrix.pyver.distver }} -c conda-forge
working-directory: ./conda
+7 -3
View File
@@ -8,7 +8,11 @@ on:
# │ │ │ ┌───────────── month (1 - 12 or JAN-DEC)
# │ │ │ │ ┌───────────── day of the week (0 - 6 or SUN-SAT)
# * * * * *
- cron: "49 23 1 * *" # 11min before utc midnight
- cron: "0 0 18 * *"
push:
paths:
- '.github/workflows/ci-ifcopenshell-pypi.yml'
env:
major: 0
@@ -31,7 +35,7 @@ jobs:
strategy:
fail-fast: false
matrix:
pyver: [py36, py37, py38, py39, py310]
pyver: [py36, py37, py38, py39, py310, py311]
config:
- {
name: "Windows Build",
@@ -53,7 +57,7 @@ jobs:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2 # https://github.com/actions/setup-python
with:
python-version: '3.7.7' # Version range or exact version of a Python version to use, using SemVer's version range syntax
python-version: '3.10' # Version range or exact version of a Python version to use, using SemVer's version range syntax
architecture: 'x64' # optional x64 or x86. Defaults to x64 if not specified
- run: echo ${{ env.DATE }}
- name: Get current date
+1
View File
@@ -85,6 +85,7 @@ jobs:
sudo /usr/bin/python -m pip install isodate
sudo /usr/bin/python -m pip install lark
sudo /usr/bin/python -m pip install networkx
sudo /usr/bin/python -m pip install tabulate
- name: Test
run: |
+16
View File
@@ -86,6 +86,20 @@ jobs:
sudo make -j $(nproc)
sudo make install
- name: Run IfcConvert on Sample files
run: |
(find test/input src/blenderbim/test/files -name '*.ifc' | while read i; do \
echo $i | tee -a log; \
timeout 1m "$(which IfcConvert)" -yv "$i" "$i.obj" --validate >> log 2>&1; \
echo $i $? >> statuses; \
done) || true
echo Failed
grep -v 0$ statuses
grep -v 0$ statuses | wc -l
echo Succeeded
grep 0$ statuses
grep 0$ statuses | wc -l
- name: Install Python dependencies
run: |
sudo /usr/bin/python -m pip install -U pip
@@ -95,6 +109,8 @@ jobs:
sudo /usr/bin/python -m pip install isodate
sudo /usr/bin/python -m pip install lark
sudo /usr/bin/python -m pip install networkx
sudo /usr/bin/python -m pip install https://github.com/Andrej730/aud/archive/refs/heads/master-reduced-size.zip
sudo /usr/bin/python -m pip install tabulate
- name: Test
run: |
+17
View File
@@ -73,3 +73,20 @@ _build/
# IDS Docs
src/ifcopenshell-python/test/build
# tox cache
.tox/
*.egg-info/
# mypy cache
.mypy_cache
# blenderbim libs
src/blenderbim/blenderbim/libs
# blenderbim test temp files
src/blenderbim/test/files/temp
# ifcopenshell swig and compiled files
src/ifcopenshell-python/ifcopenshell/_ifcopenshell_wrapper.so
src/ifcopenshell-python/ifcopenshell/ifcopenshell_wrapper.py
-81
View File
@@ -1,81 +0,0 @@
language: cpp
compiler: gcc
cache: ccache
os: linux
dist: focal
addons:
apt:
update: true
packages:
- libboost-date-time-dev
- libboost-filesystem-dev
- libboost-iostreams-dev
- libboost-program-options-dev
- libboost-regex-dev
- libboost-system-dev
- libboost-thread-dev
- libocct-data-exchange-dev
- libocct-foundation-dev
- libocct-modeling-algorithms-dev
- libocct-modeling-data-dev
- libxml2-dev
- nlohmann-json3-dev
- opencollada-dev
- python3-all-dev
- python3-pip
- swig
- libhdf5-dev
before_script:
- if [ $TRAVIS_OS_NAME == "linux" ]; then ccache -z; fi
install:
# for IDS
- python3 -m pip install xmlschema
script:
- pwd
- mkdir build && cd build
- |
cmake \
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
-DOCC_INCLUDE_DIR=/usr/include/opencascade \
-DOCC_LIBRARY_DIR=/usr/lib/x86_64-linux-gnu \
-DPYTHON_EXECUTABLE:FILEPATH=/usr/bin/python3 \
-DPYTHON_INCLUDE_DIR:PATH=/usr/include/python3.8 \
-DPYTHON_LIBRARY:FILEPATH=/usr/lib/x86_64-linux-gnu/libpython3.8.so \
"-DSCHEMA_VERSIONS=2x3;4" \
-DGLTF_SUPPORT=On \
-DJSON_INCLUDE_DIR=/usr/include \
-DHDF5_INCLUDE_DIR=/usr/include/hdf5/serial \
../cmake
# TODO: Drop sudo
- sudo make -j $(nproc) install
- cd ../test
- /usr/bin/python tests.py
- /usr/bin/python ../src/ifcopenshell-python/ifcopenshell/test_ids.py
- cd input
- /usr/local/bin/IfcConvert -yv acad2010_walls.ifc acad2010_walls.glb
- /usr/bin/python -c "from io import open; import ifcopenshell; f = ifcopenshell.open('encoding.ifc'); assert list(map(ord, f[1][0])) == [39, 97, 39, 32, 49, 109, 179, 32, 8804, 32, 53, 109, 179, 32, 8805, 32, 49, 48, 109, 179]"
- |
(for i in *.ifc; do \
echo $i | tee -a log; \
timeout 1m /usr/local/bin/IfcConvert -yv "$i" "$i.dae" --validate >> log 2>&1; \
echo $i $? >> statuses; \
done) || true
- echo Failed
- grep -v 0$ statuses
- grep -v 0$ statuses | wc -l
- echo Succeeded
- grep 0$ statuses
- grep 0$ statuses | wc -l
after_script:
- if [ $TRAVIS_OS_NAME == "linux" ]; then ccache -s; fi
+55 -306
View File
@@ -1,325 +1,74 @@
IfcOpenShell
============
IfcOpenShell is an open source ([LGPL]) software library for working with the Industry Foundation Classes ([IFC])
file format. Extensive geometric support is implemented for the IFC releases [IFC2x3 TC1] and [IFC4 Add2 TC1].
Support for parsing is provided for IFC4x1, IFC4x2, and the IFC4x3 release candidates. Extending with support for
arbitrary IFC schemas is possible at compile-time when using C++ and at run-time when using Python.
For more information, see
* [http://ifcopenshell.org](http://ifcopenshell.org)
* [http://academy.ifcopenshell.org](http://academy.ifcopenshell.org)
IfcOpenShell is an open source ([LGPL]) software library for working with Industry Foundation Classes ([IFC]). Complete
parsing support is provided for [IFC2x3 TC1], [IFC4 Add2 TC1], IFC4x1, IFC4x3, and IFC4x3. Extensive geometric support
is implemented for the IFC releases [IFC2x3 TC1] and [IFC4 Add2 TC1]. Extending with support for arbitrary IFC schemas
is possible at compile-time when using C++ and at run-time when using Python.
In addition to a C++ and Python API, IfcOpenShell comes with an ecosystem of tools, notably including IfcConvert (an application to convert IFC models to
other formats), the BlenderBIM Add-on (an add-on to Blender providing a graphical IFC authoring platform), and many
other libraries, CLI apps, and more. Support is also provided for auxiliary standards such as BCF and IDS.
For more information, see:
* [IfcOpenShell Website](http://ifcopenshell.org)
* [IfcOpenShell Documentation](http://blenderbim.org/docs-python)
* [IfcOpenShell C++ Installation](https://blenderbim.org/docs-python/ifcopenshell/installation.html)
* [IfcOpenShell Python Installation](https://blenderbim.org/docs-python/ifcopenshell-python/installation.html)
* [IfcOpenShell Python Hello World Tutorial](https://blenderbim.org/docs-python/ifcopenshell-python/hello_world.html)
* [BlenderBIM Add-on Website](https://blenderbim.org)
* [BlenderBIM Add-on Documentation](http://blenderbim.org/docs)
* [Add-on Installation](https://blenderbim.org/docs/users/installation.html)
* [Exploring an IFC model](https://blenderbim.org/docs/users/exploring_an_ifc_model.html)
| Service | Status |
| ----------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| Anaconda Daily Build | [![Anaconda-Server Badge](https://img.shields.io/conda/vn/ifcopenshell/ifcopenshell)](https://anaconda.org/ifcopenshell/ifcopenshell) |
| Anaconda v0.7.0 Stable | [![Anaconda-Server Badge](https://img.shields.io/conda/vn/conda-forge/ifcopenshell)](https://anaconda.org/conda-forge/ifcopenshell) |
| PyPi Daily Build | [![PyPi Badge](https://img.shields.io/pypi/v/ifcopenshell)](https://pypi.org/project/ifcopenshell/) |
| ArchLinux AUR Package | [![AUR Badge](https://img.shields.io/aur/version/ifcopenshell-git)](https://aur.archlinux.org/packages/ifcopenshell-git) |
| ArchLinux AUR Package Stable | [![AUR Badge](https://img.shields.io/aur/version/ifcopenshell)](https://aur.archlinux.org/packages/ifcopenshell) |
| ArchLinux AUR Package git | [![AUR Badge](https://img.shields.io/aur/version/ifcopenshell-git)](https://aur.archlinux.org/packages/ifcopenshell-git) |
| BlenderBIM Add-on Chocolatey (under moderation) | [![Chocolatey Badge](https://img.shields.io/chocolatey/v/blenderbim-nightly)](https://community.chocolatey.org/packages/blenderbim-nightly/) |
| Sponsor development on OpenCollective | [![Financial Contributors](https://opencollective.com/opensourcebim/tiers/badge.svg)](https://opencollective.com/opensourcebim/) |
| Docker hub | [![Docker Pulls](https://img.shields.io/docker/pulls/aecgeeks/ifcopenshell)](https://hub.docker.com/r/aecgeeks/ifcopenshell) |
Prerequisites
-------------
* Git
* CMake (3.1.3 or newer)
* Windows: [Visual Studio] 2008 to 2019 (2022 not yet supported by dependency CMake) with C++ toolset (or [Visual C++ Build Tools]) or [MSYS2] + MinGW
* *nix: GCC 4.7 or newer, or Clang (any version)
Dependencies
-------------
* [Boost](http://www.boost.org/)
* [Open Cascade](https://dev.opencascade.org/) - *optional*, but required for building IfcGeom
([official](https://dev.opencascade.org/release), "OCCT", or [community edition](https://github.com/tpaviot/oce), "OCE")
For converting IFC representation items into BRep solids and tessellated meshes
* [OpenCOLLADA](https://github.com/khronosGroup/OpenCOLLADA/) - *optional*
For IfcConvert to be able to write tessellated Collada (.dae) files
* [SWIG](http://www.swig.org/) and [Python](https://www.python.org/) - *optional*
For building the IfcOpenShell Python interface and the Blender add-on
* [3ds Max SDK](http://www.autodesk.com/products/3ds-max/free-trial) - *optional*
For building the 3ds Max plug-in.
All recent versions of 3ds Max (2014 and newer) are 64-bit only, so a 64-bit installation is assumed.
Building IfcOpenShell
---------------------
Note 1: The path where the source code is cloned to can contain spaces but non-ASCII characters are very likely to cause problems with the build.
Note 2: If you had not used `git clone --recursive https://github.com/IfcOpenShell/IfcOpenshell.git`, update the submodules by running `git submodule init & git submodule update`.
Note 3: Be careful with special characters is the path when using the nix or win build scripts, because the OpenCASCADE build will fail on paths containing ++ and likely other situations.
### Compiling on Windows
The preferred way to fetch and build this project's dependencies is to use the build scripts
in win/ folder. **See [win/readme.md] for more information**.
#### Using Visual Studio
Instructions in a nutshell (**assuming Visual Studio 2015 x64 environment variables set**):
> cd IfcOpenShell\win
> build-deps.cmd
> run-cmake.bat
NB: `build-deps.cmd` need to be ran from the directory containing it, i.e. the `./win` folder.
You can now open and build the solution file in Visual Studio:
> ..\build-vs2015-x64\IfcOpenShell.sln
As the scripts default to using the `RelWithDebInfo` configuration, and a freshly created solution by CMake defaults
to `Debug`, make sure to switch the used build configuration. Build the `INSTALL` project (right-click -> Project
Only) to deploy the headers and binaries into a single location if wanted/needed.
Alternatively, one can use the utility batch file(s) to build and install the project easily from the command-line
(installing a project will build it also, if required):
> install-ifcopenshell.bat
#### Using MSYS2 + MinGW
Start the MSYS2 Shell and then:
$ cd IfcOpenShell/win
$ ./build-deps.sh
$ ./run-cmake.sh
$ ./install-ifcopenshell.sh
#### Using Bash on Ubuntu on Windows
Start Bash on Ubuntu on Windows and follow the instructions below. Compiling on Ubuntu 14.04.4 LTS using GCC 4.8.4
or Clang 3.5 has been confirmed to work.
### Compiling on *nix
The following instructions are for Ubuntu, modify as required for other operating systems. [nix/build-all.py] script
can be experimented with and studied for pointers for other operating systems, but note that this script is not currently
meant to be used for a typical IfcOpenShell workspace setup.
Note 1: It is recommended to use OCCT for IfcOpenShell. You could use OCE as well, but sometimes it may lag behind OCCT and
therefore not compile with the latest IfcOpenShell.
Note 2: where `make -j` is written, add a number roughly equal to the amount of CPU cores + 1.
**1)** Install most of the prerequisites and dependencies:
$ sudo apt-get install git cmake gcc g++ libboost-all-dev libcgal-dev
**2a)** Either use an OCCT package from your operating system's software repository
$ sudo apt-get install libocct-data-exchange-dev libocct-draw-dev libocct-foundation-dev libocct-modeling-algorithms-dev libocct-modeling-data-dev libocct-ocaf-dev libocct-visualization-dev
**2b)** or, if OCCT is not available or the latest code is wanted, obtain and compile OCCT from https://dev.opencascade.org/release
**2c)** or, if you'd like to try using OCE, use the package from your operating system's software repository
$ sudo apt-get install liboce-foundation-dev liboce-modeling-dev liboce-ocaf-dev liboce-visualization-dev liboce-ocaf-lite-dev
**2d)** or if OCE is not available, or the latest code is wanted, compile OCE yourself (note that the build takes a long time)
$ sudo apt-get install libftgl-dev libtbb2 libtbb-dev libgl1-mesa-dev libfreetype6-dev
$ git clone https://github.com/tpaviot/oce.git
$ cd oce
$ mkdir build && cd build
$ cmake ..
$ make -j
$ sudo make install
**3)** For building IfcConvert with COLLADA (.dae) support (on by default), OpenCOLLADA is needed:
$ sudo apt-get install libpcre3-dev libxml2-dev
$ git clone https://github.com/KhronosGroup/OpenCOLLADA.git
$ cd OpenCOLLADA
Using a known good revision, but HEAD should work too:
$ git checkout 064a60b65c2c31b94f013820856bc84fb1937cc6
$ mkdir build && cd build
$ cmake ..
$ make -j
$ sudo make install
**4)** For building the IfcPython wrapper (on by default), SWIG and Python development are needed, if not already available:
$ sudo apt-get install python-all-dev swig
**5)** To build IfcOpenShell please take the following steps. Alternatively use environment variables for setting the
dependencies' paths. `OCC_INCLUDE_DIR` might be needed to set also. `OPENCOLLADA_INCLUDE_DIR` and `OPENCOLLADA_LIBRARY_DIR`
(and potentially `PCRE_LIBRARY_DIR`) are needed if building with COLLADA support. (`-DCOLLADA_SUPPORT=0` disables it).
$ cd /path/to/IfcOpenShell
$ mkdir build && cd build
$ cmake ../cmake -DOCC_LIBRARY_DIR=/usr/lib/x86_64-linux-gnu/ \
-DOPENCOLLADA_INCLUDE_DIR="/usr/local/include/opencollada" \
-DOPENCOLLADA_LIBRARY_DIR="/usr/local/lib/opencollada" \
-DPCRE_LIBRARY_DIR=/usr/lib/x86_64-linux-gnu/ \
-DCGAL_INCLUDE_DIR=/usr/include \
-DGMP_INCLUDE_DIR=/usr/include \
-DMPFR_INCLUDE_DIR=/usr/include \
-DGMP_LIBRARY_DIR=/usr/lib/x86_64-linux-gnu \
-DMPFR_LIBRARY_DIR=/usr/lib/x86_64-linux-gnu \
-DHDF5_SUPPORT=Off
$ make -j
If all worked out correctly you can now use IfcOpenShell. See the examples below.
**6)** Install the project if wanted:
$ sudo make install
### Installing on MacOS Using Homebrew
**1)** Install all dependencies using [Homebrew](https://brew.sh/)
```{shell}
$ brew install boost cmake python3 cgal ftgl gmp libaec opencascade swig hdf5 zlib
# homebrew automatically links most libraries, except some keg-only ones
$ brew link zlib --force
```
**2)** Clone the git repo and its submodules
```{shell}
$ git clone --recurse-submodules https://github.com/IfcOpenShell/IfcOpenShell.git
```
**3)** Build IfcOpenShell with flags for Homebrew dependencies: (``/usr/local/``) for Intel machines with x84_64 architecture,
(``/opt/homebrew/``) for Apple Silicon processors with arm64 architecture.
```{shell}
$ cd /path/to/IfcOpenShell
$ mkdir build && cd build
# set library flags
$ export LDFLAGS="$LDFLAGS -Wl,-flat_namespace,-undefined,suppress"
$ cmake ../cmake
-DPYTHON_EXECUTABLE=/opt/homebrew/bin/python3.10 \
-DPYTHON_LIBRARY=/opt/homebrew/opt/python@3.10/Frameworks/Python.framework/Versions/3.10/lib/libpython3.10.dylib \
-DPYTHON_INCLUDE_DIR=/opt/homebrew/opt/python@3.10/Frameworks/Python.framework/Versions/3.10/include/python3.10/ \
-DOCC_LIBRARY_DIR=/opt/homebrew/lib/ \
-DOCC_INCLUDE_DIR=/opt/homebrew/include/opencascade/ \
-DCGAL_INCLUDE_DIR=/opt/homebrew/include/ \
-DGMP_LIBRARY_DIR=/opt/homebrew/lib/ \
-DMPFR_LIBRARY_DIR=/opt/homebrew/lib/ \
-DHDF5_LIBRARY_DIR=/opt/homebrew/lib/ \
-DHDF5_INCLUDE_DIR=/opt/homebrew/include/ \
-DCOLLADA_SUPPORT=0
# `sysctl -n hw.ncpu` returns the number of cpu cores on macOS
$ make -j$(sysctl -n hw.ncpu)
```
Note: Make sure to compile using the XCode provided Apple Clang compiler, installed with `xcode-select --install
`, rather than a ```brew``` installed C/C++ compiler.
Installing IfcOpenShell with Conda
----------------------------------
Another option for building and installing IfcOpenShell is to use the popular
[Anaconda Python Distribution](https://www.anaconda.com/download).
The requirements are spread across a number of channels.
You can add these channels to your configuration, or specify them all on the command line:
$ conda install -c conda-forge -c oce -c dlr-sc -c ifcopenshell ifcopenshell
Usage examples
--------------
**Invoking IfcConvert from the command line**
$ wget -O duplex.zip https://portal.nibs.org/files/wl/\?id=4DsTgHFQAcOXzFetxbpRCECPbbfUqpgo
$ unzip duplex.zip
$ ./IfcConvert Duplex_A_20110907_optimized.ifc
$ less Duplex_A_20110907_optimized.obj
**Using the IfcOpenShell Python interface**
$ wget -O duplex.zip https://portal.nibs.org/files/wl/\?id=4DsTgHFQAcOXzFetxbpRCECPbbfUqpgo
$ unzip duplex.zip
$ python
>>> import ifcopenshell
>>> f = ifcopenshell.open("Duplex_A_20110907_optimized.ifc")
>>>
>>> # Accessing entity instances by type:
>>> f.by_type("ifcwall")[:2]
[#91=IfcWallStandardCase('2O2Fr$t4X7Zf8NOew3FL9r',#1,'Basic Wall:Interior - Partition (92mm Stud):144586',$,'Basic Wall:Interior - Partition (92mm Stud):128360',#5198,#18806,'144586'), #92=IfcWallStandardCase('2O2Fr$t4X7Zf8NOew3FLIE',#1,'Basic Wall:Interior - Partition (92mm Stud):143921',$,'Basic Wall:Interior - Partition (92mm Stud):128360',#5206,#18805,'143921')]
>>> wall = _[0]
>>> len(wall) # number of EXPRESS attributes
8
>>>
>>> # Accessing EXPRESS attributes by name:
>>> wall.GlobalId
'2O2Fr$t4X7Zf8NOew3FL9r'
>>> wall.Name = "My wall"
>>> wall.NonExistingAttr
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File ".\ifcopenshell.py", line 14, in __getattr__
except: raise AttributeError("entity instance of type '%s' has no attribute'%s'"%(self.wrapped_data.is_a(), name)) from None
AttributeError: entity instance of type 'IfcWallStandardCase' has no attribute 'NonExistingAttr'
>>> wall.GlobalId = 3
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File ".\ifcopenshell.py", line 26, in __setattr__
self[self.wrapped_data.get_argument_index(key)] = value
File ".\ifcopenshell.py", line 30, in __setitem__
self.wrapped_data.set_argument(idx, entity_instance.map_value(value))
File ".\ifc_wrapper.py", line 118, in <lambda>
set_argument = lambda self,x,y: self._set_argument(x) if y is None else self
._set_argument(x,y)
File ".\ifc_wrapper.py", line 114, in _set_argument
def _set_argument(self, *args): return _ifc_wrapper.entity_instance__set_argument(self, *args)
RuntimeError: INT is not a valid type for 'GlobalId'
>>> # Creating new entity instances
>>> f.createIfcCartesianPoint(Coordinates=(1.0,1.5,2.0))
#27530=IfcCartesianPoint((1.,1.5,2.))
>>>
>>> # Working with GlobalId attributes:
>>> import uuid
>>> ifcopenshell.guid.compress(uuid.uuid1().hex)
'3x4C8Q_6qHuv$P$FYkANRX'
>>> new_guid = _
>>> owner_hist = f.by_type("IfcOwnerHistory")[0]
>>> new_wall = f.createIfcWallStandardCase(new_guid, owner_hist, None, None, Tag='my_tag')
>>> new_wall.ObjectType = ''
>>> new_wall.ObjectPlacement = new_wall.Representation = None
>>>
>>> # Accessing entity instances by instance id or GlobalId:
>>> f[92]
#92=IfcWallStandardCase('2O2Fr$t4X7Zf8NOew3FLIE',#1,'Basic Wall:Interior - Partition (92mm Stud):143921',$,'Basic Wall:Interior - Partition (92mm Stud):128360',#5206,#18805,'143921')
>>> f['2O2Fr$t4X7Zf8NOew3FLIE']
#92=IfcWallStandardCase('2O2Fr$t4X7Zf8NOew3FLIE',#1,'Basic Wall:Interior - Partition (92mm Stud):143921',$,'Basic Wall:Interior - Partition (92mm Stud):128360',#5206,#18805,'143921')
>>>
>>> # Writing IFC-SPF files to disk:
>>> f.write("out.ifc")
Extra tools
-----------
Also available are a series of utilities that are based on or related to IfcOpenShell.
Contents
--------
Those marked with an asterisk are part of IfcOpenShell.
| Name | License |
| ------------------------- | ------------------- |
| bcf | LGPL-3.0-or-later |
| blenderbim | GPL-3.0-or-later |
| bsdd | LGPL-3.0-or-later |
| ifc2ca | LGPL-3.0-or-later |
| ifc4d | LGPL-3.0-or-later |
| ifc5d | LGPL-3.0-or-later |
| ifcbimtester | LGPL-3.0-or-later |
| ifcblender | LGPL-3.0-or-later\* |
| ifccityjson | LGPL-3.0-or-later |
| ifcclash | LGPL-3.0-or-later |
| ifccobie | LGPL-3.0-or-later |
| ifcconvert | LGPL-3.0-or-later\* |
| ifccsv | LGPL-3.0-or-later |
| ifcdiff | LGPL-3.0-or-later |
| ifcfm | LGPL-3.0-or-later |
| ifcgeom | LGPL-3.0-or-later\* |
| ifcgeom\_schema\_agnostic | LGPL-3.0-or-later\* |
| ifcgeomserver | LGPL-3.0-or-later\* |
| ifcjni | LGPL-3.0-or-later\* |
| ifcmax | LGPL-3.0-or-later\* |
| ifcopenshell-python | LGPL-3.0-or-later\* |
| ifcparse | LGPL-3.0-or-later\* |
| ifcpatch | LGPL-3.0-or-later |
| ifcsverchok | GPL-3.0-or-later |
| ifcwrap | LGPL-3.0-or-later\* |
| qtviewer | LGPL-3.0-or-later\* |
| serializers | LGPL-3.0-or-later\* |
| Name | Description | License |
| ------------------------- | ----------------------------------------------------------------------| ------------------- |
| bcf | Library to read and write BCF-XML and query OpenCDE BCF-API modules | LGPL-3.0-or-later |
| blenderbim | Add-on to Blender providing a graphical native IFC authoring platform | GPL-3.0-or-later |
| bsdd | Library to query the bSDD API | LGPL-3.0-or-later |
| ifc2ca | Utility to convert IFC structural analysis models to Code_Aster | LGPL-3.0-or-later |
| ifc4d | Convert to and from IFC and project management software | LGPL-3.0-or-later |
| ifc5d | Report and optimise cost information from IFC | LGPL-3.0-or-later |
| ifcbimtester | Wrapper for Gherkin based unit testing for IFC models | LGPL-3.0-or-later |
| ifcblender | Historic Blender IFC import add-on | LGPL-3.0-or-later\* |
| ifccityjson | Convert CityJSON to IFC | LGPL-3.0-or-later |
| ifcclash | Clash detection library and CLI app | LGPL-3.0-or-later |
| ifccobie | Extract IFC data for COBie handover requirements | LGPL-3.0-or-later |
| ifcconvert | CLI app to convert IFC to many other formats | LGPL-3.0-or-later\* |
| ifccsv | Library and CLI app to export and import schedules from IFC | LGPL-3.0-or-later |
| ifcdiff | Compare changes between IFC models | LGPL-3.0-or-later |
| ifcfm | Extract IFC data for FM handover requirements | LGPL-3.0-or-later |
| ifcgeom | Internal library for IfcOpenShell | LGPL-3.0-or-later\* |
| ifcgeom\_schema\_agnostic | Internal library for IfcOpenShell | LGPL-3.0-or-later\* |
| ifcgeomserver | Internal library for IfcOpenShell | LGPL-3.0-or-later\* |
| ifcjni | Internal library for IfcOpenShell | LGPL-3.0-or-later\* |
| ifcmax | Historic extension for IFC support in 3DS Max | LGPL-3.0-or-later\* |
| ifcopenshell-python | Python library for IFC manipulation | LGPL-3.0-or-later\* |
| ifcparse | Internal library for IfcOpenShell | LGPL-3.0-or-later\* |
| ifcpatch | Utility to run pre-packaged scripts to manipulate IFCs | LGPL-3.0-or-later |
| ifcsverchok | Blender Add-on for visual node programming with IFC | GPL-3.0-or-later |
| ifctester | Library, CLI and webapp for IDS model auditing | LGPL-3.0-or-later |
| ifcwrap | Internal library for IfcOpenShell | LGPL-3.0-or-later\* |
| qtviewer | Internal library for IfcOpenShell | LGPL-3.0-or-later\* |
| serializers | Internal library for IfcOpenShell | LGPL-3.0-or-later\* |
[LGPL]: https://github.com/IfcOpenShell/IfcOpenShell/tree/master/COPYING.LESSER "LGPL-3.0-or-later"
[IFC]: https://technical.buildingsmart.org/standards/ifc/ "IFC"
+64 -14
View File
@@ -54,6 +54,7 @@ OPTION(USE_VLD "Use Visual Leak Detector for debugging memory leaks, MSVC-only."
OPTION(USE_MMAP "Adds a command line options to parse IFC files from memory mapped files using Boost.Iostreams" OFF)
OPTION(BUILD_SHARED_LIBS "Build IfcParse and IfcGeom as shared libs (SO/DLL)." OFF)
OPTION(MSVC_PARALLEL_BUILD "Multi-threaded compilation in Microsoft Visual Studio (/MP)" OFF)
OPTION(WASM_BUILD OFF)
if (${HAS_MAX})
OPTION(BUILD_IFCMAX "Build IfcMax, a 3ds Max plug-in, Windows-only." OFF)
endif()
@@ -149,6 +150,14 @@ UNIFY_ENVVARS_AND_CACHE(MPFR_INCLUDE_DIR)
UNIFY_ENVVARS_AND_CACHE(MPFR_LIBRARY_DIR)
endif()
if(WASM_BUILD)
# when using the nix/build-all.py build script we should not
# look into the sysroot for most of the dependencies but rather
# in the designated build/ folder created by the script.
set(CMAKE_FIND_ROOT_PATH_BACKUP "${CMAKE_FIND_ROOT_PATH}")
set(CMAKE_FIND_ROOT_PATH "")
endif()
if (NOT MINIMAL_BUILD AND GLTF_SUPPORT AND BUILD_CONVERT)
UNIFY_ENVVARS_AND_CACHE(JSON_INCLUDE_DIR)
FIND_FILE(json_hpp "json.hpp" ${JSON_INCLUDE_DIR}/nlohmann)
@@ -199,7 +208,14 @@ ELSE()
ENDIF()
ENDIF()
set(BOOST_COMPONENTS system program_options regex thread date_time)
if (WASM_BUILD)
set(BOOST_COMPONENTS)
else()
# @todo review this, shouldn't this be all possible header-only now?
# ... or rewritten using C++17 features?
set(BOOST_COMPONENTS system program_options regex thread date_time)
endif()
if(USE_MMAP)
if(MSVC)
# filesystem is necessary for the utf-16 wpath
@@ -210,6 +226,10 @@ if(USE_MMAP)
add_definitions(-DUSE_MMAP)
endif()
FIND_PACKAGE(Boost REQUIRED COMPONENTS ${BOOST_COMPONENTS})
MESSAGE(STATUS "Boost include files found in ${Boost_INCLUDE_DIRS}")
MESSAGE(STATUS "Boost libraries found in ${Boost_LIBRARY_DIRS}")
if (NOT MINIMAL_BUILD)
# libxml2 is required for IFCXML (optional) and SVGFILL (mandatory)
find_package(LibXml2 REQUIRED)
@@ -220,11 +240,6 @@ if (NOT MINIMAL_BUILD AND IFCXML_SUPPORT)
set(SWIG_DEFINES ${SWIG_DEFINES} -DWITH_IFCXML)
endif()
FIND_PACKAGE(Boost REQUIRED COMPONENTS ${BOOST_COMPONENTS})
MESSAGE(STATUS "Boost include files found in ${Boost_INCLUDE_DIRS}")
MESSAGE(STATUS "Boost libraries found in ${Boost_LIBRARY_DIRS}")
# Usage:
# set(SOME_LIRARIES foo bar)
# add_debug_variants(SOME_LIRARIES "${SOME_LIRARIES}" d)
@@ -343,10 +358,21 @@ if("${libTKernelExt}" STREQUAL ".a")
set(OCCT_STATIC ON)
endif()
if(WASM_BUILD)
set(CMAKE_FIND_ROOT_PATH "${CMAKE_FIND_ROOT_PATH_BACKUP}")
endif()
if(OCCT_STATIC)
find_package(Threads)
# OPENCASCADE_LIBRARIES repeated three times below in order to fix cyclic dependencies - use --start-group ... --end-group instead?
set(OPENCASCADE_LIBRARIES ${OPENCASCADE_LIBRARIES} ${OPENCASCADE_LIBRARIES} ${OPENCASCADE_LIBRARIES} ${OPENCASCADE_LIBRARIES} ${OPENCASCADE_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT})
if(WASM_BUILD)
set(OPENCASCADE_LIBRARIES ${OPENCASCADE_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT})
else()
# OPENCASCADE_LIBRARIES repeated N times below in order to fix cyclic dependencies - use --start-group ... --end-group instead?
# tfk: --start-group ... --end-group didn't work on the apple linker when last tested
set(OPENCASCADE_LIBRARIES ${OPENCASCADE_LIBRARIES} ${OPENCASCADE_LIBRARIES} ${OPENCASCADE_LIBRARIES} ${OPENCASCADE_LIBRARIES} ${OPENCASCADE_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT})
endif()
if (NOT APPLE AND NOT WIN32)
set(OPENCASCADE_LIBRARIES ${OPENCASCADE_LIBRARIES} "rt")
endif()
@@ -620,7 +646,12 @@ function(files_for_ifc_version IFC_VERSION RESULT_NAME)
endfunction()
if(NOT SCHEMA_VERSIONS)
set(SCHEMA_VERSIONS "2x3" "4" "4x1" "4x2" "4x3_rc1" "4x3_rc2" "4x3_rc3" "4x3_rc4" "4x3")
if(WASM_BUILD)
# super arbitrarily try to keep size down at least a little bit
set(SCHEMA_VERSIONS "2x3" "4")
else()
set(SCHEMA_VERSIONS "2x3" "4" "4x1" "4x2" "4x3_rc1" "4x3_rc2" "4x3_rc3" "4x3_rc4" "4x3" "4x3_tc1" "4x3_add1")
endif()
endif()
foreach(s ${SCHEMA_VERSIONS})
@@ -697,7 +728,11 @@ if (BUILD_IFCGEOM)
foreach(s ${SCHEMA_VERSIONS})
set(IFCGEOM_SCHEMA_LIBRARIES ${IFCGEOM_SCHEMA_LIBRARIES} IfcGeom_ifc${s})
endforeach()
set(IFCOPENSHELL_LIBRARIES ${IFCOPENSHELL_LIBRARIES} IfcGeom ${IFCGEOM_SCHEMA_LIBRARIES} IfcGeom ${IFCGEOM_SCHEMA_LIBRARIES})
if (WASM_BUILD)
set(IFCOPENSHELL_LIBRARIES ${IFCOPENSHELL_LIBRARIES} IfcGeom ${IFCGEOM_SCHEMA_LIBRARIES})
else()
set(IFCOPENSHELL_LIBRARIES ${IFCOPENSHELL_LIBRARIES} IfcGeom ${IFCGEOM_SCHEMA_LIBRARIES} IfcGeom ${IFCGEOM_SCHEMA_LIBRARIES})
endif()
endif()
if (BUILD_CONVERT OR BUILD_IFCPYTHON)
foreach(s ${SCHEMA_VERSIONS})
@@ -741,7 +776,11 @@ set(IFCPARSE_FILES ${IFCPARSE_CPP_FILES} ${IFCPARSE_H_FILES})
add_library(IfcParse ${IFCPARSE_FILES})
set_target_properties(IfcParse PROPERTIES COMPILE_FLAGS -DIFC_PARSE_EXPORTS VERSION "0.6.0" SOVERSION "0.6")
TARGET_LINK_LIBRARIES(IfcParse ${Boost_LIBRARIES} ${BCRYPT_LIBRARIES} ${LIBXML2_LIBRARIES})
if (WASM_BUILD)
TARGET_LINK_LIBRARIES(IfcParse ${BCRYPT_LIBRARIES} ${LIBXML2_LIBRARIES})
else()
TARGET_LINK_LIBRARIES(IfcParse ${Boost_LIBRARIES} ${BCRYPT_LIBRARIES} ${LIBXML2_LIBRARIES})
endif()
if (BUILD_IFCGEOM)
@@ -753,7 +792,9 @@ set(IFCGEOM_FILES ${IFCGEOM_CPP_FILES} ${IFCGEOM_H_FILES})
foreach(s ${SCHEMA_VERSIONS})
add_library(IfcGeom_ifc${s} STATIC ${IFCGEOM_FILES})
set_target_properties(IfcGeom_ifc${s} PROPERTIES COMPILE_FLAGS "-DIFC_GEOM_EXPORTS -DIfcSchema=Ifc${s}")
TARGET_LINK_LIBRARIES(IfcGeom_ifc${s} IfcParse ${OPENCASCADE_LIBRARIES})
if (NOT WASM_BUILD)
TARGET_LINK_LIBRARIES(IfcGeom_ifc${s} IfcParse ${OPENCASCADE_LIBRARIES})
endif()
endforeach()
# IfcGeom (schema agnostic)
@@ -768,7 +809,11 @@ if (UNIX)
find_package(Threads)
endif()
TARGET_LINK_LIBRARIES(IfcGeom IfcParse ${IFCGEOM_SCHEMA_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT})
if (WASM_BUILD)
TARGET_LINK_LIBRARIES(IfcGeom ${IFCGEOM_SCHEMA_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT})
else()
TARGET_LINK_LIBRARIES(IfcGeom IfcParse ${IFCGEOM_SCHEMA_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT})
endif()
endif(BUILD_IFCGEOM)
@@ -785,7 +830,12 @@ set(SERIALIZERS_S_FILES ${SERIALIZERS_S_H_FILES} ${SERIALIZERS_S_CPP_FILES})
foreach(s ${SCHEMA_VERSIONS})
add_library(Serializers_ifc${s} STATIC ${SERIALIZERS_S_FILES})
set_target_properties(Serializers_ifc${s} PROPERTIES COMPILE_FLAGS "-DIFC_GEOM_EXPORTS -DIfcSchema=Ifc${s} ${CONVERT_PRECISION}")
TARGET_LINK_LIBRARIES(Serializers_ifc${s} IfcGeom ${OPENCASCADE_LIBRARIES} ${HDF5_LIBRARIES})
if (WASM_BUILD)
TARGET_LINK_LIBRARIES(Serializers_ifc${s} ${HDF5_LIBRARIES})
else()
TARGET_LINK_LIBRARIES(Serializers_ifc${s} IfcGeom ${OPENCASCADE_LIBRARIES} ${HDF5_LIBRARIES})
endif()
endforeach()
add_library(Serializers ${SERIALIZERS_FILES})
+1 -1
View File
@@ -41,4 +41,4 @@ cmake -G Ninja \
ninja
ninja install
ninja install -j 2
+3 -2
View File
@@ -13,6 +13,7 @@ source:
build:
binary_relocation: false [osx]
number: 1
requirements:
build:
@@ -24,7 +25,7 @@ requirements:
host:
- python
- boost-cpp <=1.74.0
- boost-cpp
- occt
- libxml2
- cgal-cpp
@@ -36,7 +37,7 @@ requirements:
run:
- python
- boost-cpp <=1.74.0
- boost-cpp
- occt
- libxml2
- cgal-cpp
+174 -70
View File
@@ -26,7 +26,7 @@
# * cmake * git * bzip2 * tar * c(++) compilers * autoconf #
# #
# if building with USE_OCCT additionally: #
# * freetype * glx.h #
# * glx.h #
# #
# if building with OCCT 7.4.0 additionally: #
# * libfontconfig1-dev #
@@ -39,14 +39,14 @@
# #
# on debian 7.8 these can be obtained with: #
# $ apt-get install git gcc g++ autoconf bison bzip2 cmake #
# libfreetype6-dev mesa-common-dev libffi-dev libfontconfig1-dev #
# mesa-common-dev libffi-dev libfontconfig1-dev #
# #
# on ubuntu 14.04: #
# $ apt-get install git gcc g++ autoconf bison make cmake #
# libfreetype6-dev mesa-common-dev libffi-dev libfontconfig1-dev #
# mesa-common-dev libffi-dev libfontconfig1-dev #
# #
# on OS X El Capitan with homebrew: #
# $ brew install git bison autoconf automake freetype libffi cmake #
# $ brew install git bison autoconf automake libffi cmake #
# #
###############################################################################
import logging
@@ -72,11 +72,11 @@ PROJECT_NAME = "IfcOpenShell"
USE_CURRENT_PYTHON_VERSION = os.getenv("USE_CURRENT_PYTHON_VERSION")
ADD_COMMIT_SHA = os.getenv("ADD_COMMIT_SHA")
PYTHON_VERSIONS = ["3.6.14", "3.7.13", "3.8.13", "3.9.11", "3.10.3"]
PYTHON_VERSIONS = ["3.6.14", "3.7.13", "3.8.13", "3.9.11", "3.10.3", "3.11.0"]
JSON_VERSION = "v3.6.1"
OCE_VERSION = "0.18.3"
OCCT_VERSION = "7.5.3"
BOOST_VERSION = "1.71.0"
BOOST_VERSION = "1.80.0"
PCRE_VERSION = "8.41"
LIBXML2_VERSION = "2.9.11"
SWIG_VERSION = "4.0.2"
@@ -103,6 +103,9 @@ curl = "curl"
wget = "wget"
strip = "strip"
explicit_targets = [s for s in sys.argv[1:] if not s.startswith("-")]
flags = set(s.lstrip('-') for s in sys.argv[1:] if s.startswith("-"))
# Helper function for coloured printing
NO_COLOR = "\033[0m" # <ref>http://stackoverflow.com/questions/5947742/how-to-change-the-output-color-of-echo-in-linux</ref>
@@ -135,9 +138,11 @@ if platform.system() == "Darwin":
IFCOS_NUM_BUILD_PROCS = os.getenv("IFCOS_NUM_BUILD_PROCS", multiprocessing.cpu_count() + 1)
CMAKE_DIR = os.path.realpath(os.path.join("..", "cmake"))
CMAKE_DIR = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", "cmake"))
path = ["..", "build", platform.system(), platform.machine()]
build_dir = os.environ.get("BUILD_DIR", os.path.join(os.path.dirname(__file__), "..", "build"))
path = [build_dir, platform.system(), "wasm" if "wasm" in flags else platform.machine()]
if TOOLSET:
path.append(TOOLSET)
DEFAULT_DEPS_DIR = os.path.realpath(os.path.join(*path))
@@ -186,11 +191,12 @@ dependency_tree = {
'boost': (),
'libxml2': (),
'python': (),
'occ': (),
'occ': ('freetype',),
'pcre': (),
'json': (),
'hdf5': (),
'cgal': ()
'cgal': (),
'freetype': (),
}
def v(dep):
@@ -199,10 +205,12 @@ def v(dep):
for x in v(d):
yield x
tgts = [s for s in sys.argv[1:] if not s.startswith("-")]
flags = set(s for s in sys.argv[1:] if s.startswith("-"))
if "v" in flags:
logger.setLevel(logging.DEBUG)
else:
logger.setLevel(logging.INFO)
BUILD_STATIC = not "-shared" in flags
BUILD_STATIC = "shared" not in flags
ENABLE_FLAG = "--enable-static" if BUILD_STATIC else "--enable-shared"
DISABLE_FLAG = "--disable-shared" if BUILD_STATIC else "--disable-static"
LINK_TYPE = "static" if BUILD_STATIC else "shared"
@@ -210,10 +218,15 @@ LINK_TYPE_UCFIRST = LINK_TYPE[0].upper() + LINK_TYPE[1:]
LIBRARY_EXT = "a" if BUILD_STATIC else "so"
PIC = "-fPIC" if BUILD_STATIC else ""
if len(tgts):
targets = set(sum((list(v(target)) for target in tgts), []))
if any(f.startswith("py-") for f in flags):
PYTHON_VERSIONS = [pyv for pyv in PYTHON_VERSIONS if "py-%s" % "".join(pyv.split('.')[0:2]) in flags]
if len(explicit_targets):
targets = set(sum((list(v(target)) for target in explicit_targets), []))
else:
targets = set(dependency_tree.keys())
targets = set(t for t in targets if 'without-%s' % t.lower() not in flags)
print("Building:", *sorted(targets, key=lambda t: len(list(v(t)))))
@@ -237,7 +250,7 @@ if not os.path.exists(LOG_FILE):
open(LOG_FILE, "w").close()
logger.info(f"using command log file '{LOG_FILE}'")
def run(cmds, cwd=None):
def run(cmds, cwd=None, can_fail=False):
"""
Wraps `subprocess.Popen.communicate()` and logs the command being executed,
@@ -254,7 +267,7 @@ def run(cmds, cwd=None):
log_file_handle.close()
logger.debug(f"command returned {proc.returncode}")
if proc.returncode != 0:
if proc.returncode != 0 and not can_fail:
print("-" * 70)
print(stderr)
print("-" * 70)
@@ -266,6 +279,9 @@ if platform.system() == "Darwin":
if run(["sw_vers", "-productVersion"]) >= "11.":
# Apparently not supported
PYTHON_VERSIONS = [pv for pv in PYTHON_VERSIONS if tuple(map(int, pv.split("."))) >= (3, 7)]
if run(["sw_vers", "-productVersion"]) < "10.16":
# Apparently not supported
PYTHON_VERSIONS = [pv for pv in PYTHON_VERSIONS if tuple(map(int, pv.split("."))) < (3, 11)]
BOOST_VERSION_UNDERSCORE = BOOST_VERSION.replace(".", "_")
@@ -281,7 +297,12 @@ def run_autoconf(arg1, configure_args, cwd):
run([bash, "./autogen.sh"], cwd=os.path.realpath(os.path.join(cwd, ".."))) # only run autogen.sh in the directory it is located and use cwd to achieve that in order to not mess up things
# Using `sh` over `bash` fixes issues with building swig
prefix = os.path.realpath(f"{DEPS_DIR}/install/{arg1}")
run(["/bin/sh", "../configure"] + configure_args + [f"--prefix={prefix}"], cwd=cwd)
wasm = []
if "wasm" in flags:
wasm.append("emconfigure")
run([*wasm, "/bin/sh", "../configure"] + configure_args + [f"--prefix={prefix}"], cwd=cwd)
def run_cmake(arg1, cmake_args, cmake_dir=None, cwd=None):
@@ -289,7 +310,12 @@ def run_cmake(arg1, cmake_args, cmake_dir=None, cwd=None):
P = ".."
else:
P = cmake_dir
run(["cmake", P] + cmake_args + [f"-DCMAKE_BUILD_TYPE={BUILD_CFG}"], cwd=cwd)
wasm = []
if "wasm" in flags:
wasm.append("emcmake")
run([*wasm, "cmake", P, *cmake_args, f"-DCMAKE_BUILD_TYPE={BUILD_CFG}"], cwd=cwd)
def git_clone_or_pull_repository(clone_url, target_dir, revision=None):
@@ -371,12 +397,15 @@ def build_dependency(name, mode, build_tool_args, download_url, download_name, d
urlretrieve(url, os.path.join(extract_dir, path))
if patch is not None:
patch_abs = os.path.abspath(os.path.join(os.path.dirname(__file__), patch))
if os.path.exists(patch_abs):
try: run(["patch", "-p1", "--batch", "--forward", "-i", patch_abs], cwd=extract_dir)
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 isinstance(patch, str):
patch = [patch]
for p in patch:
patch_abs = os.path.abspath(os.path.join(os.path.dirname(__file__), p))
if os.path.exists(patch_abs):
try: run(["patch", "-p1", "--batch", "--forward", "-i", patch_abs], cwd=extract_dir)
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 mode == "ctest":
run(["ctest", "-S", "HDF5config.cmake,BUILD_GENERATOR=Unix", "-C", BUILD_CFG, "-V", "-O", "hdf5.log"], cwd=extract_dir)
@@ -407,7 +436,7 @@ def build_dependency(name, mode, build_tool_args, download_url, download_name, d
logger.info(f"\rConfiguring {name}...")
run([bash, "./bootstrap.sh"], cwd=extract_dir)
logger.info(f"\rBuilding {name}... ")
run(["./b2", f"-j{IFCOS_NUM_BUILD_PROCS}"] + build_tool_args, cwd=extract_dir)
run(["./b2", f"-j{IFCOS_NUM_BUILD_PROCS}"] + build_tool_args, cwd=extract_dir, can_fail="wasm" in flags)
logger.info(f"\rInstalling {name}... ")
shutil.copytree(os.path.join(extract_dir, "boost"), os.path.join(DEPS_DIR, "install", f"boost-{BOOST_VERSION}", "boost"))
logger.info(f"\rInstalled {name} \n")
@@ -418,11 +447,13 @@ cecho("Collecting dependencies:", GREEN)
# TODO: This is untested
ADDITIONAL_ARGS = []
BOOST_ADDRESS_MODEL = []
if platform.system() == "Darwin":
ADDITIONAL_ARGS = [f"-mmacosx-version-min={TOOLSET}"] + ADDITIONAL_ARGS
if "wasm" in flags:
ADDITIONAL_ARGS.extend(("-sWASM_BIGINT", "-fexceptions"))
# If the linker supports GC sections, set it up to reduce binary file size
# -fPIC is required for the shared libraries to work
@@ -431,7 +462,7 @@ CFLAGS = os.environ.get("CFLAGS", "")
LDFLAGS = os.environ.get("LDFLAGS", "")
ADDITIONAL_ARGS_STR = " ".join(ADDITIONAL_ARGS)
if sp.call([bash, "-c", "ld --gc-sections 2>&1 | grep -- --gc-sections &> /dev/null"]) != 0:
if "wasm" not in flags and 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:
@@ -501,8 +532,27 @@ if "swig" in targets:
download_tool=download_tool_git,
revision=f"rel-{SWIG_VERSION}"
)
if "freetype" in targets:
build_dependency(
name=f"freetype",
mode="cmake",
build_tool_args=[
f"-DCMAKE_INSTALL_PREFIX={DEPS_DIR}/install/freetype"
],
download_url = "https://github.com/freetype/freetype",
download_name = "freetype2",
download_tool=download_tool_git,
)
if USE_OCCT and "occ" in targets:
patches = []
if OCCT_VERSION < "7.4":
patches.append("./patches/occt/enable-exception-handling.patch")
if "wasm" in flags:
patches.append("./patches/occt/no_em_js.patch")
build_dependency(
name=f"occt-{OCCT_VERSION}",
mode="cmake",
@@ -510,12 +560,13 @@ if USE_OCCT and "occ" in targets:
f"-DINSTALL_DIR={DEPS_DIR}/install/occt-{OCCT_VERSION}",
f"-DBUILD_LIBRARY_TYPE={LINK_TYPE_UCFIRST}",
"-DBUILD_MODULE_Draw=0",
"-DBUILD_RELEASE_DISABLE_EXCEPTIONS=Off"
"-DBUILD_RELEASE_DISABLE_EXCEPTIONS=Off",
f"-D3RDPARTY_FREETYPE_DIR={DEPS_DIR}/install/freetype"
],
download_url = "https://github.com/Open-Cascade-SAS/OCCT",
download_name = "occt",
download_tool=download_tool_git,
patch=None if OCCT_VERSION >= "7.4" else "./patches/occt/enable-exception-handling.patch",
patch=patches,
revision="V" + OCCT_VERSION.replace('.', '_')
)
elif "occ" in targets:
@@ -552,6 +603,15 @@ if "libxml2" in targets:
)
if "OpenCOLLADA" in targets:
patches = ["./patches/opencollada/pr622_and_disable_subdirs.patch"]
if "wasm" in flags:
# This is necessary for the WASM build, because recent versions of
# clang don't have the tr1:: namespace anymore. However, it breaks
# some versions of gcc (9.4.0 at least) due to specializing std::hash
# outside of the std:: namespace.
patches.append("./patches/opencollada/remove_tr1.patch")
build_dependency(
"OpenCOLLADA",
"cmake",
@@ -566,11 +626,11 @@ if "OpenCOLLADA" in targets:
download_url="https://github.com/KhronosGroup/OpenCOLLADA.git",
download_name="OpenCOLLADA",
download_tool=download_tool_git,
patch="./patches/opencollada/pr622_and_disable_subdirs.patch",
patch=patches,
revision=OPENCOLLADA_VERSION
)
if "python" in targets and not USE_CURRENT_PYTHON_VERSION:
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_CXX_FLAGS = os.environ["CXXFLAGS"]
OLD_C_FLAGS = os.environ["CFLAGS"]
@@ -609,6 +669,9 @@ if "python" in targets and not USE_CURRENT_PYTHON_VERSION:
if "boost" in targets:
str_concat = lambda prefix: lambda postfix: "" if postfix.strip() == "" else "=".join((prefix, postfix.strip()))
toolset = []
if "wasm" in flags:
toolset.append("toolset=emscripten")
build_dependency(
f"boost-{BOOST_VERSION}",
mode="bjam",
@@ -620,21 +683,30 @@ if "boost" in targets:
"--with-thread",
"--with-date_time",
"--with-iostreams",
f"link={LINK_TYPE}"
] + \
BOOST_ADDRESS_MODEL + \
list(map(str_concat("cxxflags"), CXXFLAGS.strip().split(' '))) + \
list(map(str_concat("linkflags"), LDFLAGS.strip().split(' '))) + \
["stage", "-s", "NO_BZIP2=1"],
f"link={LINK_TYPE}",
*toolset,
*map(str_concat("cxxflags"), CXXFLAGS.strip().split(' ')),
*map(str_concat("linkflags"), LDFLAGS.strip().split(' ')),
"stage", "-s", "NO_BZIP2=1"],
download_url=BOOST_LOCATION,
patch="./patches/boost/boostorg_regex_62.patch",
download_name=f"boost_{BOOST_VERSION_UNDERSCORE}.tar.bz2"
)
if "wasm" in flags:
# only supported on nix for now
run(("find", ".", "-name", "*.bc", "-exec", "bash", "-c", "emar q ${1%.bc}.a $1", "bash", "{}", ";"), cwd=f"{DEPS_DIR}/install/boost-{BOOST_VERSION}/lib")
if "cgal" in targets:
gmp_args = []
mpfr_args = []
if "wasm" in flags:
gmp_args.extend(("--disable-assembly", "--host", "none", "--enable-cxx"))
mpfr_args.extend(("--host", "none"))
build_dependency(
name=f"gmp-{GMP_VERSION}",
mode="autoconf",
build_tool_args=[ENABLE_FLAG, DISABLE_FLAG, "--with-pic"],
build_tool_args=[ENABLE_FLAG, DISABLE_FLAG, "--with-pic", *gmp_args],
download_url="https://ftp.gnu.org/gnu/gmp/",
download_name=f"gmp-{GMP_VERSION}.tar.bz2"
)
@@ -642,7 +714,7 @@ if "cgal" in targets:
build_dependency(
name=f"mpfr-{MPFR_VERSION}",
mode="autoconf",
build_tool_args=[ENABLE_FLAG, DISABLE_FLAG, f"--with-gmp={DEPS_DIR}/install/gmp-{GMP_VERSION}"],
build_tool_args=[ENABLE_FLAG, DISABLE_FLAG, *mpfr_args, f"--with-gmp={DEPS_DIR}/install/gmp-{GMP_VERSION}"],
download_url=f"http://www.mpfr.org/mpfr-{MPFR_VERSION}/",
download_name=f"mpfr-{MPFR_VERSION}.tar.bz2"
)
@@ -676,18 +748,8 @@ os.makedirs(IFCOS_DIR, exist_ok=True)
executables_dir = os.path.join(IFCOS_DIR, "executables")
os.makedirs(executables_dir, exist_ok=True)
logger.info("\rConfiguring executables...")
OFF_ON = ["OFF", "ON"]
exec_args = [
"-DBUILD_IFCGEOM=" +OFF_ON["IfcGeom" in targets],
"-DBUILD_GEOMSERVER=" +OFF_ON["IfcGeomServer" in targets],
"-DBUILD_CONVERT=" +OFF_ON["IfcConvert" in targets],
"-DBUILD_IFCPYTHON=" "OFF",
"-DCMAKE_INSTALL_PREFIX=" f"{DEPS_DIR}/install/ifcopenshell",
]
cmake_args = [
"-DUSE_MMAP=" "OFF",
"-DBUILD_EXAMPLES=" "OFF",
@@ -699,6 +761,11 @@ cmake_args = [
"-DADD_COMMIT_SHA=" +("On" if ADD_COMMIT_SHA else "Off")
]
if "wasm" in flags:
# Boost is built by the build script so should not be found
# inside of the sysroot set by the emscriptem toolchain
cmake_args.append("-DWASM_BUILD=On")
if "cgal" in targets:
cmake_args.extend([
"-DCGAL_INCLUDE_DIR=" f"{DEPS_DIR}/install/cgal-{CGAL_VERSION}/include",
@@ -750,20 +817,36 @@ if "hdf5" in targets:
"-DHDF5_INCLUDE_DIR=" f"{DEPS_DIR}/install/hdf5-{HDF5_VERSION}/include",
"-DHDF5_LIBRARY_DIR=" f"{DEPS_DIR}/install/hdf5-{HDF5_VERSION}/lib"
])
else:
cmake_args.append("-DHDF5_SUPPORT=Off")
run_cmake("", exec_args + cmake_args, cmake_dir=CMAKE_DIR, cwd=executables_dir)
if not explicit_targets or {"IfcGeom", "IfcConvert", "IfcGeomServer"} & set(explicit_targets):
logger.info("\rConfiguring executables...")
logger.info("\rBuilding executables... ")
exec_args = [
"-DBUILD_IFCGEOM=" +OFF_ON["IfcGeom" in targets],
"-DBUILD_GEOMSERVER=" +OFF_ON["IfcGeomServer" in targets],
"-DBUILD_CONVERT=" +OFF_ON["IfcConvert" in targets],
"-DBUILD_IFCPYTHON=" "OFF",
"-DCMAKE_INSTALL_PREFIX=" f"{DEPS_DIR}/install/ifcopenshell",
]
run_cmake("", exec_args + cmake_args, cmake_dir=CMAKE_DIR, cwd=executables_dir)
run([make, f"-j{IFCOS_NUM_BUILD_PROCS}"], cwd=executables_dir)
run([make, "install/strip" if BUILD_CFG == "Release" else "install"], cwd=executables_dir)
logger.info("\rBuilding executables... ")
run([make, f"-j{IFCOS_NUM_BUILD_PROCS}"], cwd=executables_dir)
run([make, "install/strip" if BUILD_CFG == "Release" else "install"], cwd=executables_dir)
if "IfcOpenShell-Python" in targets:
# On OSX the actual Python library is not linked against.
ADDITIONAL_ARGS = ""
if platform.system() == "Darwin":
ADDITIONAL_ARGS = "-Wl,-flat_namespace,-undefined,suppress"
if "wasm" in flags:
ADDITIONAL_ARGS = f"-Wl,-undefined,suppress -sSIDE_MODULE=2 -sEXPORTED_FUNCTIONS=_PyInit__ifcopenshell_wrapper"
os.environ["CXXFLAGS"] = f"{CXXFLAGS_MINIMAL} {ADDITIONAL_ARGS}"
os.environ["CFLAGS"] = f"{CFLAGS_MINIMAL} {ADDITIONAL_ARGS}"
os.environ["LDFLAGS"] = f"{LDFLAGS} {ADDITIONAL_ARGS}"
@@ -780,36 +863,57 @@ if "IfcOpenShell-Python" in targets:
os.environ["PYTHON_LIBRARY_BASENAME"] = os.path.basename(python_library)
swig_when_built = []
if "swig" in targets:
swig_when_built.append(f"-DSWIG_EXECUTABLE={DEPS_DIR}/install/swig/bin/swig")
run_cmake("",
cmake_args + [
"-DPYTHON_LIBRARY=" +python_library,
"-DPYTHON_EXECUTABLE=" +python_executable,
*([f"-DPYTHON_EXECUTABLE={python_executable}"] if python_executable else []),
# *([f"-DPYTHON_MODULE_INSTALL_DIR={os.environ['PYTHONPATH']}/ifcopenshell"] if "wasm" in flags else []),
*(["-DPYTHON_MODULE_INSTALL_DIR="+os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "package"))] if "wasm" in flags else []),
"-DPYTHON_INCLUDE_DIR=" +python_include,
"-DSWIG_EXECUTABLE=" f"{DEPS_DIR}/install/swig/bin/swig",
"-DCMAKE_INSTALL_PREFIX=" f"{DEPS_DIR}/install/ifcopenshell/tmp",
"-DUSERSPACE_PYTHON_PREFIX=" +["Off", "On"][os.environ.get("PYTHON_USER_SITE", "").lower() in {"1", "on", "true"}]
], cmake_dir=CMAKE_DIR, cwd=python_dir)
"-DUSERSPACE_PYTHON_PREFIX=" +["Off", "On"][os.environ.get("PYTHON_USER_SITE", "").lower() in {"1", "on", "true"}],
*swig_when_built],
cmake_dir=CMAKE_DIR, cwd=python_dir)
logger.info(f"\rBuilding python {python_version} wrapper... ")
run([make, f"-j{IFCOS_NUM_BUILD_PROCS}", "_ifcopenshell_wrapper"], cwd=python_dir)
run([make, "install/local"], cwd=os.path.join(python_dir, "ifcwrap"))
module_dir = os.path.dirname(run([python_executable, "-c", "import inspect, ifcopenshell; print(inspect.getfile(ifcopenshell))"]))
if python_executable:
module_dir = os.path.dirname(run([python_executable, "-c", "import inspect, ifcopenshell; print(inspect.getfile(ifcopenshell))"]))
if platform.system() != "Darwin":
# TODO: This symbol name depends on the Python version?
run([strip, "-s", "-K", "PyInit__ifcopenshell_wrapper", "_ifcopenshell_wrapper.so"], cwd=module_dir)
return module_dir
if platform.system() != "Darwin":
# TODO: This symbol name depends on the Python version?
run([strip, "-s", "-K", "PyInit__ifcopenshell_wrapper", "_ifcopenshell_wrapper.so"], cwd=module_dir)
return module_dir
if USE_CURRENT_PYTHON_VERSION:
python_info = sysconfig.get_paths()
python_lib = os.path.join(
sysconfig.get_config_var('LIBDIR'),
sysconfig.get_config_var('multiarchsubdir').replace("/", ""),
sysconfig.get_config_var("INSTSONAME")
if "wasm" in flags:
compile_python_wrapper(
f"{os.environ['PYMAJOR']}.{os.environ['PYMINOR']}.{os.environ['PYMICRO']}",
f"{os.environ['TARGETINSTALLDIR']}/lib/libpython{os.environ['PYMAJOR']}.{os.environ['PYMINOR']}.a",
os.environ['PYTHONINCLUDE'],
None
)
elif USE_CURRENT_PYTHON_VERSION:
python_info = sysconfig.get_paths()
py_path_components = [
sysconfig.get_config_var('LIBDIR'),
sysconfig.get_config_var("INSTSONAME")
]
if sysconfig.get_config_var('multiarchsubdir'):
py_path_components.insert(1, sysconfig.get_config_var('multiarchsubdir').replace("/", ""))
python_lib = os.path.join(*py_path_components)
compile_python_wrapper(platform.python_version(), python_lib, python_info["include"], sys.executable)
else:
for python_version in PYTHON_VERSIONS:
+20
View File
@@ -0,0 +1,20 @@
--- a/tools/build/src/tools/emscripten.jam
+++ b/tools/build/src/tools/emscripten.jam
@@ -6,6 +6,7 @@
import feature ;
import os ;
import toolset ;
+import generators ;
import common ;
import gcc ;
import type ;
@@ -52,6 +53,8 @@
<debug-symbols>off <debug-symbols>on
<rtti>off <rtti>on
;
+generators.override builtin.lib-generator : emscripten.prebuilt ;
+generators.override emscripten.searched-lib-generator : searched-lib-generator ;
type.set-generated-target-suffix EXE : <toolset>emscripten : "js" ;
type.set-generated-target-suffix OBJ : <toolset>emscripten : "bc" ;
+99
View File
@@ -0,0 +1,99 @@
diff --git a/src/Message/Message_PrinterSystemLog.cxx b/src/Message/Message_PrinterSystemLog.cxx
index 0c82c2167..a2e9e6d68 100644
--- a/src/Message/Message_PrinterSystemLog.cxx
+++ b/src/Message/Message_PrinterSystemLog.cxx
@@ -55,27 +55,6 @@
return ANDROID_LOG_DEBUG;
}
#elif defined(__EMSCRIPTEN__)
- #include <emscripten/emscripten.h>
-
- //! Print message to console.debug().
- EM_JS(void, occJSConsoleDebug, (const char* theStr), {
- console.debug(UTF8ToString(theStr));
- });
-
- //! Print message to console.info().
- EM_JS(void, occJSConsoleInfo, (const char* theStr), {
- console.info(UTF8ToString(theStr));
- });
-
- //! Print message to console.warn().
- EM_JS(void, occJSConsoleWarn, (const char* theStr), {
- console.warn(UTF8ToString(theStr));
- });
-
- //! Print message to console.error().
- EM_JS(void, occJSConsoleError, (const char* theStr), {
- console.error(UTF8ToString(theStr));
- });
#else
#include <syslog.h>
@@ -169,16 +148,6 @@ void Message_PrinterSystemLog::send (const TCollection_AsciiString& theString,
#elif defined(__ANDROID__)
__android_log_write (getAndroidLogPriority (theGravity), myEventSourceName.ToCString(), theString.ToCString());
#elif defined(__EMSCRIPTEN__)
- // don't use bogus emscripten_log() corrupting UNICODE strings
- switch (theGravity)
- {
- case Message_Trace: occJSConsoleDebug(theString.ToCString()); return;
- case Message_Info: occJSConsoleInfo (theString.ToCString()); return;
- case Message_Warning: occJSConsoleWarn (theString.ToCString()); return;
- case Message_Alarm: occJSConsoleError(theString.ToCString()); return;
- case Message_Fail: occJSConsoleError(theString.ToCString()); return;
- }
- occJSConsoleWarn (theString.ToCString());
#else
syslog (getSysLogPriority (theGravity), "%s", theString.ToCString());
#endif
diff --git a/src/OSD/OSD_MemInfo.cxx b/src/OSD/OSD_MemInfo.cxx
index 08a939beb..7c1fc79b3 100644
--- a/src/OSD/OSD_MemInfo.cxx
+++ b/src/OSD/OSD_MemInfo.cxx
@@ -37,15 +37,6 @@
#include <OSD_MemInfo.hxx>
-#if defined(__EMSCRIPTEN__)
- #include <emscripten.h>
-
- //! Return WebAssembly heap size in bytes.
- EM_JS(size_t, OSD_MemInfo_getModuleHeapLength, (), {
- return Module.HEAP8.length;
- });
-#endif
-
// =======================================================================
// function : OSD_MemInfo
// purpose :
@@ -156,29 +147,6 @@ void OSD_MemInfo::Update()
}
#elif defined(__EMSCRIPTEN__)
- if (IsActive (MemHeapUsage)
- || IsActive (MemWorkingSet)
- || IsActive (MemWorkingSetPeak))
- {
- // /proc/%d/status is not emulated - get more info from mallinfo()
- const struct mallinfo aMI = mallinfo();
- if (IsActive (MemHeapUsage))
- {
- myCounters[MemHeapUsage] = aMI.uordblks;
- }
- if (IsActive (MemWorkingSet))
- {
- myCounters[MemWorkingSet] = aMI.uordblks;
- }
- if (IsActive (MemWorkingSetPeak))
- {
- myCounters[MemWorkingSetPeak] = aMI.usmblks;
- }
- }
- if (IsActive (MemVirtual))
- {
- myCounters[MemVirtual] = OSD_MemInfo_getModuleHeapLength();
- }
#elif (defined(__linux__) || defined(__linux))
if (IsActive (MemHeapUsage))
{
+81
View File
@@ -0,0 +1,81 @@
diff --git a/COLLADABaseUtils/include/COLLADABUhash_map.h b/COLLADABaseUtils/include/COLLADABUhash_map.h
index 8ab0fb9b..12503bfb 100644
--- a/COLLADABaseUtils/include/COLLADABUhash_map.h
+++ b/COLLADABaseUtils/include/COLLADABUhash_map.h
@@ -32,9 +32,9 @@
#include <unordered_map>
#include <unordered_set>
- #define COLLADABU_HASH_MAP std::tr1::unordered_map
- #define COLLADABU_HASH_MULTIMAP std::tr1::unordered_multimap
- #define COLLADABU_HASH_SET std::tr1::unordered_set
+ #define COLLADABU_HASH_MAP std::unordered_map
+ #define COLLADABU_HASH_MULTIMAP std::unordered_multimap
+ #define COLLADABU_HASH_SET std::unordered_set
#define COLLADABU_HASH_NAMESPACE_OPEN std { namespace tr1
#define COLLADABU_HASH_NAMESPACE_CLOSE }
#define COLLADABU_HASH_FUN hash
@@ -50,12 +50,12 @@
#define COLLADABU_HASH_FUN hash
#endif
#elif defined(__MINGW32__) || defined(__MINGW64__)
- #include <tr1/unordered_map>
- #include <tr1/unordered_set>
+ #include <unordered_map>
+ #include <unordered_set>
- #define COLLADABU_HASH_MAP std::tr1::unordered_map
- #define COLLADABU_HASH_MULTIMAP std::tr1::unordered_multimap
- #define COLLADABU_HASH_SET std::tr1::unordered_set
+ #define COLLADABU_HASH_MAP std::unordered_map
+ #define COLLADABU_HASH_MULTIMAP std::unordered_multimap
+ #define COLLADABU_HASH_SET std::unordered_set
#define COLLADABU_HASH_NAMESPACE_OPEN std { namespace tr1
#define COLLADABU_HASH_NAMESPACE_CLOSE }
#define COLLADABU_HASH_FUN hash
@@ -107,12 +107,12 @@
#define COLLADABU_HASH_NAMESPACE_CLOSE
#define COLLADABU_HASH_FUN hash
#else
- #include <tr1/unordered_map>
- #include <tr1/unordered_set>
+ #include <unordered_map>
+ #include <unordered_set>
- #define COLLADABU_HASH_MAP std::tr1::unordered_map
- #define COLLADABU_HASH_MULTIMAP std::tr1::unordered_multimap
- #define COLLADABU_HASH_SET std::tr1::unordered_set
+ #define COLLADABU_HASH_MAP std::unordered_map
+ #define COLLADABU_HASH_MULTIMAP std::unordered_multimap
+ #define COLLADABU_HASH_SET std::unordered_set
#define COLLADABU_HASH_NAMESPACE_OPEN std { namespace tr1
#define COLLADABU_HASH_NAMESPACE_CLOSE }
#define COLLADABU_HASH_FUN hash
diff --git a/common/libBuffer/include/CommonFWriteBufferFlusher.h b/common/libBuffer/include/CommonFWriteBufferFlusher.h
index c7af45b2..fac4f133 100644
--- a/common/libBuffer/include/CommonFWriteBufferFlusher.h
+++ b/common/libBuffer/include/CommonFWriteBufferFlusher.h
@@ -15,12 +15,12 @@
#if (defined(WIN64) || defined(_WIN64) || defined(__WIN64__)) || (defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) || defined(__APPLE__))
#if defined(__GNUC__) && !defined(_LIBCPP_VERSION)
-# include <tr1/unordered_map>
+# include <unordered_map>
#else
# include <unordered_map>
#endif
#else
-# include <tr1/unordered_map>
+# include <unordered_map>
#endif
#ifdef _LIBCPP_VERSION
@@ -58,7 +58,7 @@ namespace Common
#else
typedef __int64 FilePosType;
#endif
- typedef std::tr1::unordered_map<MarkId, FilePosType > MarkIdToFilePos;
+ typedef std::unordered_map<MarkId, FilePosType > MarkIdToFilePos;
public:
static const size_t DEFAUL_BUFFER_SIZE = 64*1024;
+18
View File
@@ -0,0 +1,18 @@
package:
name: ifcopenshell
version: 0.7.0
source:
path: IfcOpenShell
build:
script: |
python nix/build-all.py --without-hdf5 --without-opencollada --without-swig --without-pcre -v --wasm --py310 IfcOpenShell-Python
cp pyodide/setup.py .
about:
home: http://ifcopenshell.org
license: LGPL-3.0-or-later
summary: |
IfcOpenShell is an open source (LGPL) software library for
working with the Industry Foundation Classes (IFC) file format.
+11
View File
@@ -0,0 +1,11 @@
from setuptools import setup, find_packages
setup(name='IfcOpenShell',
version='0.7.0',
description='IfcOpenShell is an open source (LGPL) software library for working with the Industry Foundation Classes (IFC) file format.',
author='Thomas Krijnen',
author_email='thomas@aecgeeks.com',
url='http://ifcopenshell.org',
packages=find_packages(),
package_data={'': ['*.so']},
)
+1
View File
@@ -0,0 +1 @@
.tox
+17
View File
@@ -0,0 +1,17 @@
.PHONY: ci
ci:
tox
.PHONY: license
license:
copyright-header --license LGPL3 --copyright-holder "Andrea Ghensi <andrea.ghensi@gmail.com>" --copyright-year "2022" --copyright-software "IfcOpenShell" --copyright-software-description "BCF XML file handling" -a ./ -o ./
# TODO: make this based on xsd file presence
.PHONY: models
models:
cd src && xsdata generate -p bcf.v2.model --unnest-classes --kw-only --slots -ds Google bcf/v2/xsd
cd src && xsdata generate -p bcf.v3.model --unnest-classes --kw-only --slots -ds Google bcf/v3/xsd
# .PHONY
# api:
# openapi-python-client generate --url https://api.swaggerhub.com/apis/buildingSMART/BCF/3.0
+31 -48
View File
@@ -1,64 +1,54 @@
# bcf
A simple Python implementation of BCF. The data model is described in `data.py`.
A simple Python implementation of BCF.
Manipulation of BCF-XML is available via `bcfxml.py` and manipulation of BCF-API
is available via `bcfapi.py`.
- BCF-XML version 2.1: Fully supported
- BCF-API version 2.1: Not supported, will probably tackle this after BCF-API v3.0
- BCF-XML version 3.0: Almost fully supported, except for the documents module
- BCF-API version 3.0: Almost fully supported, except for two requests.
It tries to support BCF-XML version 2.1 and 3.0, and BCF-API 3.0.
## bcfxml
The `bcfxml` module lets you interact with the BCF-XML standard.
The `bcfxml.load` function lets you read a BCF-XML file.
It takes care of using the right version based on the "bcf.version" file contained in the BCF package.
The BCF files are extracted and parsed on-demand, and edits are stored in memory until you call the `save` method.
```python
from bcf import bcfxml
from bcf.bcfxml import load
# Load a project
bcfxml = bcfxml.load("/path/to/file.bcf")
with load("/path/to/file.bcf") as bcfxml:
project = bcfxml.project
print(project.name)
# To edit a project, just modify the object directly
bcfxml.project.name = "New name"
# The project is also stored in the module
# project == bcfxml.project
project=bcfxml.get_project()
print(project.name)
# Get a dictionary of topics
topics = bcfxml.topics
# To edit a project, just modify the object directly
bcfxml.project.name = "New name"
bcfxml.edit_project()
for topic_guid, topic_handler in bcfxml.topics.items():
topic = topic_handler.topic
print("Topic guid is", topic.guid)
print("Topic title is", topic.title)
# The BCF file is extracted to this temporary directory
print(bcfxml.filepath)
# Fetch extra data about a topic
header = topic_handler.header
comments = topic_handler.comments
viewpoints = topic_handler.viewpoints
# Get a dictionary of topics
topics = bcfxml.get_topics()
for comment in comments:
print(comment.guid)
print(comment.comment)
print(comment.author)
# Note: topics == bcfxml.topics
for guid, topic in bcfxml.topics.items():
print("Topic guid is", guid)
print("Topic guid is", topic.guid)
print("Topic title is", topic.title)
# Get a particular topic
topic = bcfxml.get_topic(guid)
# Fetch extra data about a topic
header = bcfxml.get_header(guid)
comments = bcfxml.get_comments(guid)
viewpoints = bcfxml.get_viewpoints(guid)
# Modify a topic
topic.title = "New title"
# Note: comments == topic.comments, and so on
for comment_guid, comment in comments.items():
print(comment_guid)
print(comment.comment)
print(comment.author)
# Get a particular topic
topic = bcfxml.get_topic(guid)
# Modify a topic
topic.title = "New title"
bcfxml.edit_topic(topic)
bcfxml.save()
```
## bcfapi
@@ -92,10 +82,3 @@ print(data)
data = bcf_client.get_extensions(project_id)
print(data)
```
## Todo List
The remaining work that needs to be completed in `bcfxml.py` and `bcfapi.py`.
- For `bcfxml.py` two xsds support is remaining namely 'documents.xsd`and`extensions.xsd`.
- For `bcfapi.py` two requests that are `get_topics` and `get_comments` are remaining.
+131 -2
View File
@@ -1,12 +1,141 @@
[build-system]
requires = [
"setuptools>=42",
"setuptools>=61",
"wheel"
]
build-backend = "setuptools.build_meta"
[project]
name = "bcf-client"
# author = "IfcOpenShell"
description = "BCF-XML file handler."
readme = "README.md"
requires-python = ">=3.8"
keywords = ["IFC", "BCF", "BIM", "eingineering"]
dependencies = [
"xsdata",
"numpy",
"ifcopenshell",
]
version = "0.0.1"
classifiers = [
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3",
"Topic :: Scientific/Engineering",
"Topic :: Utilities",
]
[project.urls]
Source = "https://github.com/IfcOpenShell/IfcOpenShell"
Issues = "https://github.com/IfcOpenShell/IfcOpenShell/issues"
[tool.black]
line-length = 120
extend-exclude = "model"
[tool.isort]
profile = "black"
profile = "black"
extend_skip_glob = ["src/bcf/*/model/*"]
[tool.coverage.paths]
source = ["src"]
[tool.coverage.run]
branch = true
source = ["bcf"]
omit = ["*/model/*"]
[tool.coverage.report]
show_missing = true
fail_under = 65
exclude_lines = [
"pragma: no cover",
"if TYPE_CHECKING",
"if __name__ == .__main__.:",
"Protocol",
]
[tool.tox]
legacy_tox_ini = """
[tox]
env_list = lint, type, py3{10,11}
skip_missing_interpreters = true
[testenv]
deps =
pytest
pytest-cov
coverage
commands = pytest --cov --cov-report=term tests
[testenv:lint]
description = run linters
skip_install = true
deps =
black
isort
pylint
commands =
black {posargs:.}
isort {posargs:.}
pylint {posargs:.} --output-format=colorized
[testenv:type]
description = run type checks
deps =
mypy>=0.991
commands =
- mypy {posargs:src}
"""
[tool.mypy]
check_untyped_defs = true
disallow_any_generics = true
disallow_incomplete_defs = true
disallow_subclassing_any = true
disallow_untyped_calls = true
disallow_untyped_defs = true
no_implicit_optional = true
#no_implicit_reexport = true
show_column_numbers = true
show_error_codes = true
show_error_context = true
strict_equality = true
strict_optional = true
warn_redundant_casts = true
#warn_return_any = true
warn_unreachable = true
warn_unused_configs = true
warn_unused_ignores = true
exclude= "src/bcf/v(2|3)/model"
plugins = "numpy.typing.mypy_plugin"
[[tool.mypy.overrides]]
module = "tests"
disallow_untyped_decorators = false
disallow_untyped_defs = false
[[tool.mypy.overrides]]
module = [
"pytest",
"pytest_mock",
"ifcopenshell",
"ifcopenshell.*",
]
ignore_missing_imports = true
[tool.pylint.main]
ignore = ["model"]
ignored-modules = ["bcf.v2.model", "bcf.v3.model", "xsdata"]
jobs = 0
disable="all"
enable="E" # B,B9,BLK,C,D,E,F,I,N,S,W
[tool.pylint.design]
max-args = 10
max-attributes = 10
[tool.pylint.format]
expected-line-ending-format = "LF"
max-line-length = 120
+6
View File
@@ -0,0 +1,6 @@
black
mypy
pylint
isort
xsdata
tox==3.27.1
-1
View File
@@ -1 +0,0 @@
xmlschema
-37
View File
@@ -1,37 +0,0 @@
[metadata]
name=bcf-client
version=0.0.1
author = Ifcopenshell
description = A simple Python implementation of BCF
url = https://github.com/IfcOpenShell/IfcOpenShell
project_urls =
Code=https://github.com/IfcOpenShell/IfcOpenShell
Issues=https://github.com/IfcOpenShell/IfcOpenShell/issues
long_description = file: README.md
long_description_content_type = text/markdown
classifiers =
License :: OSI Approved :: GNU General Public License v3 (GPLv3)
Operating System :: OS Independent
Programming Language :: Python :: 3
Topic :: Scientific/Engineering
Topic :: Utilities
keywords =
Python
file formats
engineering
[options]
package_dir =
= src
packages = find:
python_requires = >=3
install_requires=
xmlschema
include_package_data = True
[options.packages.find]
where = src
[flake8]
max-line-length = 120
ignore = E24, E121, E123, E126, E203, E226, E704, E741, W503, W504
+3
View File
@@ -0,0 +1,3 @@
from setuptools import setup
setup()
+62 -50
View File
@@ -1,61 +1,73 @@
# BCF - BCF Python library
# Copyright (C) 2021 Prabhat Singh <singh01prabhat@gmail.com>
#
# This file is part of BCF.
#
# BCF is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BCF 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with BCF. If not, see <http://www.gnu.org/licenses/>.
"""
BCF - BCF Python library
Copyright (C) 2021 Prabhat Singh <singh01prabhat@gmail.com>
Copyright (C) 2022 Andrea Ghensi <andrea.ghensi@gmail.com>
This file is part of BCF.
import os.path
BCF is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
BCF 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 Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with BCF. If not, see <http://www.gnu.org/licenses/>.
"""
import zipfile
import tempfile
from xml.dom import minidom
from pathlib import Path
from typing import Optional, Union
from bcf.v2.bcfxml import BcfXml as BcfXml2
from bcf.v2.model import Version as Version2
from bcf.v3.bcfxml import BcfXml as BcfXml3
from bcf.v3.model import Version as Version3
from bcf.xml_parser import AbstractXmlParserSerializer, XmlParserSerializer
def load(filepath):
filepath = extract_project(filepath)
if os.path.isfile(os.path.join(filepath, "bcf.version")):
version_path = os.path.join(filepath, "bcf.version")
version_id = get_version(version_path)
# TODO: we actually coded it for 2.1, let's check the difference between 2.0 and 2.1
if version_id == "2.1" or version_id == "2.0":
from bcf.v2.bcfxml import BcfXml
def load(
filepath: Path, xml_handler: Optional[AbstractXmlParserSerializer] = None
) -> Optional[Union[BcfXml2, BcfXml3]]:
"""
Load a BCF file.
bcfxml = BcfXml()
bcfxml.filepath = filepath
return bcfxml
elif version_id == "3.0":
from bcf.v3.bcfxml import BcfXml
Args:
filepath: The path to the BCF file.
bcfxml = BcfXml()
bcfxml.filepath = filepath
return bcfxml
else:
raise Exception(f"Version {version_id} not supported.")
Returns:
The loaded BCF file.
Raises:
ValueError: If the BCF version is not supported.
"""
xml_handler = xml_handler or XmlParserSerializer()
version_id = _get_version(filepath, xml_handler)
if version_id in {"2.1", "2.0"}:
return BcfXml2.load(filepath, xml_handler)
if version_id == "3.0":
return BcfXml3.load(filepath, xml_handler)
raise ValueError(f"Version {version_id} not supported.")
def get_version(version_path):
xmlparse = minidom.parse(version_path)
version_el = xmlparse.getElementsByTagName("Version")[0]
version = version_el.getAttribute("VersionId")
return version
def _get_version(filepath: Union[str, Path], xml_handler: Optional[AbstractXmlParserSerializer] = None) -> str:
"""
Returns the version of the BCF file.
Args:
filepath: The path to the BCF file.
xml_handler: The XML handler. If none is given, XmlParserSerializer is used.
def extract_project(filepath):
if not filepath:
return
zip_file = zipfile.ZipFile(filepath)
filepath = tempfile.mkdtemp()
zip_file.extractall(filepath)
return filepath
Returns:
The version of the BCF file.
"""
xml_handler = xml_handler or XmlParserSerializer()
with zipfile.ZipFile(filepath) as bcf_zip:
try:
version = xml_handler.parse(bcf_zip.read("bcf.version"), Version3)
except:
version = xml_handler.parse(bcf_zip.read("bcf.version"), Version2)
return version.version_id
+64
View File
@@ -0,0 +1,64 @@
from typing import Optional
import numpy as np
from numpy.typing import NDArray
def camera_vectors_from_element_placement(
elem_placement: NDArray[np.float_],
) -> tuple[NDArray[np.float_], NDArray[np.float_], NDArray[np.float_]]:
"""
Calculate the vectors of a camera pointing to an element.
Args:
elem_placement: Placement matrix of an element.
Returns:
Camera position, direction and up vectors
"""
target_position = elem_placement[:3, 3]
return camera_vectors_from_target_position(target_position)
def camera_vectors_from_target_position(
target_position: NDArray[np.float_], offset: Optional[NDArray[np.float_]] = None
) -> tuple[NDArray[np.float_], NDArray[np.float_], NDArray[np.float_]]:
"""
Calculate the vectors of a camera pointing to a target point.
Args:
target_position: point the camera is pointing to.
camera_offset: offset of the camera from the target point.
Returns:
Camera position, direction and up vectors
"""
camera_offset = np.array((5, 5, 5)) if offset is None else offset
camera_position = target_position + camera_offset
camera_direction = unit_vector(-camera_offset) # pylint: disable=invalid-unary-operand-type
camera_right = unit_vector(np.cross(np.array([0.0, 0.0, 1.0]), camera_direction))
camera_up = unit_vector(np.cross(camera_direction, camera_right))
return camera_position, camera_direction, camera_up
# rotation_transform = np.eye(4)
# rotation_transform[0, :3] = camera_right
# rotation_transform[1, :3] = camera_up
# rotation_transform[2, :3] = camera_direction
# translation_transform = np.eye(4)
# translation_transform[:3, -1] = -camera_position
# look_at_transform = np.matmul(rotation_transform, translation_transform)
# mat = np.linalg.inv(look_at_transform)
# return camera_position, -mat[:3, 2], mat[:3, 1]
def unit_vector(v: NDArray[np.float_]) -> NDArray[np.float_]:
"""
Return the unit vector of a vector.
Args:
v: vector
Returns:
unit vector.
"""
norm = np.linalg.norm(v)
return v if norm == 0 else v / norm
+55
View File
@@ -0,0 +1,55 @@
"""
In Memory Zip File management, taken from ruamel.std.zipfile
Copyright (c) 2017-2020 Anthon van der Neut, Ruamel bvba
original idea from https://stackoverflow.com/a/19722365/1307905
"""
import zipfile
from io import BytesIO
from os import PathLike
from pathlib import Path
from typing import Any, Optional, Protocol
class ZipFileInterface(Protocol):
def writestr(self, filename_in_zip: str | zipfile.ZipInfo, file_contents: bytes | str) -> None:
...
class InMemoryZipFile:
def __init__(
self, file_name: Optional[str | Path] = None, compression: int = zipfile.ZIP_DEFLATED, debug: int = 0
) -> None:
# Create the in-memory file-like object
self._file_name: Optional[str | Path] = str(file_name) if hasattr(file_name, "_from_parts") else file_name
self.in_memory_data = BytesIO()
# Create the in-memory zipfile
self.in_memory_zip = zipfile.ZipFile(self.in_memory_data, "w", compression, False)
self.in_memory_zip.debug = debug
def writestr(self, filename_in_zip: str | zipfile.ZipInfo, file_contents: bytes | str) -> None:
"""Appends a file with name filename_in_zip and contents of
file_contents to the in-memory zip."""
self.in_memory_zip.writestr(filename_in_zip, file_contents)
def write_to_file(self, filename: str | bytes | PathLike[str] | PathLike[bytes] | int) -> None:
"""Writes the in-memory zip to a file."""
# Mark the files as having been created on Windows so that
# Unix permissions are not inferred as 0000
for zfile in self.in_memory_zip.filelist:
zfile.create_system = 0
self.in_memory_zip.close()
with open(filename, "wb") as f:
f.write(self.data)
@property
def data(self) -> bytes:
return self.in_memory_data.getvalue()
def __enter__(self) -> "InMemoryZipFile":
return self
def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> None:
if self._file_name:
self.write_to_file(self._file_name)
View File
-2
View File
@@ -1,4 +1,3 @@
# BCF - BCF Python library
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
#
@@ -16,4 +15,3 @@
#
# You should have received a copy of the GNU Lesser General Public License
# along with BCF. If not, see <http://www.gnu.org/licenses/>.
File diff suppressed because it is too large Load Diff
-198
View File
@@ -1,198 +0,0 @@
# BCF - BCF Python library
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BCF.
#
# BCF is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BCF 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with BCF. If not, see <http://www.gnu.org/licenses/>.
class Project:
def __init__(self):
self.project_id = ""
self.name = ""
self.extension_schema = ""
class BimSnippet:
def __init__(self):
self.snippet_type = None
self.is_external = False
self.reference = None
self.reference_schema = None
class DocumentReference:
def __init__(self):
self.referenced_document = None
self.description = None
self.guid = None
self.is_external = False
class RelatedTopic:
def __init__(self):
self.guid = None
class HeaderFile:
def __init__(self):
self.filename = None
self.date = None
self.reference = None
self.ifc_project = None
self.ifc_spatial_structure_element = None
self.is_external = True
class Header:
def __init__(self):
self.files = []
class Topic:
def __init__(self):
self.reference_links = []
self.title = ""
self.priority = None
self.index = None # Deprecated, stored, but ignored
self.labels = []
self.creation_date = None
self.creation_author = None
self.modified_date = None
self.modified_author = None
self.due_date = None
self.assigned_to = None
self.stage = None
self.description = None
self.bim_snippet = None
self.document_references = []
self.related_topics = []
self.topic_status = None
self.topic_type = None
self.guid = None
self.header = None
self.comments = {}
self.viewpoints = {}
class Comment:
def __init__(self):
self.guid = None
self.date = None
self.author = None
self.comment = None
self.viewpoint = None
self.modified_date = None
self.modified_author = None
self.topic_guid = None # Part of BCF-API
class ViewSetupHints:
def __init__(self):
self.spaces_visible = False
self.space_boundaries_visible = False
self.openings_visible = False
class Component:
def __init__(self):
self.originating_system = None
self.authoring_tool_id = None
self.ifc_guid = None
class ComponentVisibility:
def __init__(self):
self.exceptions = []
self.default_visibility = False
class Color:
def __init__(self):
self.color = None
self.components = []
class Components:
def __init__(self):
self.view_setup_hints = None
self.selection = []
self.visibility = None
self.coloring = []
class Point:
def __init__(self):
self.x = 0
self.y = 0
self.z = 0
class Direction(Point):
pass
class OrthogonalCamera:
def __init__(self):
self.camera_view_point = Point()
self.camera_direction = Direction()
self.camera_up_vector = Direction()
self.view_to_world_scale = 1.0
class PerspectiveCamera:
def __init__(self):
self.camera_view_point = Point()
self.camera_direction = Direction()
self.camera_up_vector = Direction()
self.field_of_view = 60.0
class Line:
def __init__(self):
self.start_point = Point()
self.end_point = Point()
class ClippingPlane:
def __init__(self):
self.location = Point()
self.direction = Direction()
class Bitmap:
def __init__(self):
self.reference = "" # Only in BCF-XML
self.bitmap_data = None # Only in BCF-API
self.bitmap_format = "PNG" # Enum of png or jpg
self.location = Point()
self.normal = Direction()
self.up = Direction()
self.height = 1.0
class Viewpoint:
def __init__(self):
self.guid = None
self.viewpoint = None
self.snapshot = None
self.index = None
self.components = None # It's not a list, despite the plural name
self.orthogonal_camera = None
self.perspective_camera = None
self.lines = []
self.clipping_planes = []
self.bitmaps = []
+70
View File
@@ -0,0 +1,70 @@
from bcf.v2.model.markup import (
BimSnippet,
Comment,
CommentViewpoint,
Header,
HeaderFile,
Markup,
Topic,
TopicDocumentReference,
TopicRelatedTopic,
ViewPoint,
)
from bcf.v2.model.project import Project, ProjectExtension
from bcf.v2.model.version import Version
from bcf.v2.model.visinfo import (
BitmapFormat,
ClippingPlane,
Component,
ComponentColoring,
ComponentColoringColor,
Components,
ComponentSelection,
ComponentVisibility,
ComponentVisibilityExceptions,
Direction,
Line,
OrthogonalCamera,
PerspectiveCamera,
Point,
ViewSetupHints,
VisualizationInfo,
VisualizationInfoBitmap,
VisualizationInfoClippingPlanes,
VisualizationInfoLines,
)
__all__ = [
"BimSnippet",
"Comment",
"CommentViewpoint",
"Header",
"HeaderFile",
"Markup",
"Topic",
"TopicDocumentReference",
"TopicRelatedTopic",
"ViewPoint",
"Project",
"ProjectExtension",
"Version",
"BitmapFormat",
"ClippingPlane",
"Component",
"ComponentColoring",
"ComponentColoringColor",
"ComponentSelection",
"ComponentVisibility",
"ComponentVisibilityExceptions",
"Components",
"Direction",
"Line",
"OrthogonalCamera",
"PerspectiveCamera",
"Point",
"ViewSetupHints",
"VisualizationInfo",
"VisualizationInfoBitmap",
"VisualizationInfoClippingPlanes",
"VisualizationInfoLines",
]
+461
View File
@@ -0,0 +1,461 @@
from dataclasses import dataclass, field
from typing import List, Optional
from xsdata.models.datatype import XmlDateTime
@dataclass(slots=True, kw_only=True)
class BimSnippet:
reference: str = field(
metadata={
"name": "Reference",
"type": "Element",
"namespace": "",
"required": True,
}
)
reference_schema: str = field(
metadata={
"name": "ReferenceSchema",
"type": "Element",
"namespace": "",
"required": True,
}
)
snippet_type: str = field(
metadata={
"name": "SnippetType",
"type": "Attribute",
"required": True,
}
)
is_external: bool = field(
default=False,
metadata={
"name": "isExternal",
"type": "Attribute",
}
)
@dataclass(slots=True, kw_only=True)
class CommentViewpoint:
class Meta:
global_type = False
guid: str = field(
metadata={
"name": "Guid",
"type": "Attribute",
"required": True,
"pattern": r"[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}",
}
)
@dataclass(slots=True, kw_only=True)
class HeaderFile:
class Meta:
global_type = False
filename: Optional[str] = field(
default=None,
metadata={
"name": "Filename",
"type": "Element",
"namespace": "",
}
)
date: Optional[XmlDateTime] = field(
default=None,
metadata={
"name": "Date",
"type": "Element",
"namespace": "",
}
)
reference: Optional[str] = field(
default=None,
metadata={
"name": "Reference",
"type": "Element",
"namespace": "",
}
)
ifc_project: Optional[str] = field(
default=None,
metadata={
"name": "IfcProject",
"type": "Attribute",
"length": 22,
"pattern": r"[0-9,A-Z,a-z,_$]*",
}
)
ifc_spatial_structure_element: Optional[str] = field(
default=None,
metadata={
"name": "IfcSpatialStructureElement",
"type": "Attribute",
"length": 22,
"pattern": r"[0-9,A-Z,a-z,_$]*",
}
)
is_external: bool = field(
default=True,
metadata={
"name": "isExternal",
"type": "Attribute",
}
)
@dataclass(slots=True, kw_only=True)
class TopicDocumentReference:
class Meta:
global_type = False
referenced_document: Optional[str] = field(
default=None,
metadata={
"name": "ReferencedDocument",
"type": "Element",
"namespace": "",
}
)
description: Optional[str] = field(
default=None,
metadata={
"name": "Description",
"type": "Element",
"namespace": "",
}
)
guid: Optional[str] = field(
default=None,
metadata={
"name": "Guid",
"type": "Attribute",
"pattern": r"[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}",
}
)
is_external: bool = field(
default=False,
metadata={
"name": "isExternal",
"type": "Attribute",
}
)
@dataclass(slots=True, kw_only=True)
class TopicRelatedTopic:
class Meta:
global_type = False
guid: str = field(
metadata={
"name": "Guid",
"type": "Attribute",
"required": True,
"pattern": r"[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}",
}
)
@dataclass(slots=True, kw_only=True)
class ViewPoint:
viewpoint: Optional[str] = field(
default=None,
metadata={
"name": "Viewpoint",
"type": "Element",
"namespace": "",
}
)
snapshot: Optional[str] = field(
default=None,
metadata={
"name": "Snapshot",
"type": "Element",
"namespace": "",
}
)
index: Optional[int] = field(
default=None,
metadata={
"name": "Index",
"type": "Element",
"namespace": "",
}
)
guid: str = field(
metadata={
"name": "Guid",
"type": "Attribute",
"required": True,
"pattern": r"[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}",
}
)
@dataclass(slots=True, kw_only=True)
class Comment:
date: XmlDateTime = field(
metadata={
"name": "Date",
"type": "Element",
"namespace": "",
"required": True,
}
)
author: str = field(
metadata={
"name": "Author",
"type": "Element",
"namespace": "",
"required": True,
}
)
comment: str = field(
metadata={
"name": "Comment",
"type": "Element",
"namespace": "",
"required": True,
}
)
viewpoint: Optional[CommentViewpoint] = field(
default=None,
metadata={
"name": "Viewpoint",
"type": "Element",
"namespace": "",
}
)
modified_date: Optional[XmlDateTime] = field(
default=None,
metadata={
"name": "ModifiedDate",
"type": "Element",
"namespace": "",
}
)
modified_author: Optional[str] = field(
default=None,
metadata={
"name": "ModifiedAuthor",
"type": "Element",
"namespace": "",
}
)
guid: str = field(
metadata={
"name": "Guid",
"type": "Attribute",
"required": True,
"pattern": r"[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}",
}
)
@dataclass(slots=True, kw_only=True)
class Header:
file: List[HeaderFile] = field(
default_factory=list,
metadata={
"name": "File",
"type": "Element",
"namespace": "",
"min_occurs": 1,
}
)
@dataclass(slots=True, kw_only=True)
class Topic:
reference_link: List[str] = field(
default_factory=list,
metadata={
"name": "ReferenceLink",
"type": "Element",
"namespace": "",
}
)
title: str = field(
metadata={
"name": "Title",
"type": "Element",
"namespace": "",
"required": True,
}
)
priority: Optional[str] = field(
default=None,
metadata={
"name": "Priority",
"type": "Element",
"namespace": "",
}
)
index: Optional[int] = field(
default=None,
metadata={
"name": "Index",
"type": "Element",
"namespace": "",
}
)
labels: List[str] = field(
default_factory=list,
metadata={
"name": "Labels",
"type": "Element",
"namespace": "",
}
)
creation_date: XmlDateTime = field(
metadata={
"name": "CreationDate",
"type": "Element",
"namespace": "",
"required": True,
}
)
creation_author: str = field(
metadata={
"name": "CreationAuthor",
"type": "Element",
"namespace": "",
"required": True,
}
)
modified_date: Optional[XmlDateTime] = field(
default=None,
metadata={
"name": "ModifiedDate",
"type": "Element",
"namespace": "",
}
)
modified_author: Optional[str] = field(
default=None,
metadata={
"name": "ModifiedAuthor",
"type": "Element",
"namespace": "",
}
)
due_date: Optional[XmlDateTime] = field(
default=None,
metadata={
"name": "DueDate",
"type": "Element",
"namespace": "",
}
)
assigned_to: Optional[str] = field(
default=None,
metadata={
"name": "AssignedTo",
"type": "Element",
"namespace": "",
}
)
stage: Optional[str] = field(
default=None,
metadata={
"name": "Stage",
"type": "Element",
"namespace": "",
}
)
description: Optional[str] = field(
default=None,
metadata={
"name": "Description",
"type": "Element",
"namespace": "",
}
)
bim_snippet: Optional[BimSnippet] = field(
default=None,
metadata={
"name": "BimSnippet",
"type": "Element",
"namespace": "",
}
)
document_reference: List[TopicDocumentReference] = field(
default_factory=list,
metadata={
"name": "DocumentReference",
"type": "Element",
"namespace": "",
}
)
related_topic: List[TopicRelatedTopic] = field(
default_factory=list,
metadata={
"name": "RelatedTopic",
"type": "Element",
"namespace": "",
}
)
guid: str = field(
metadata={
"name": "Guid",
"type": "Attribute",
"required": True,
"pattern": r"[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}",
}
)
topic_type: Optional[str] = field(
default=None,
metadata={
"name": "TopicType",
"type": "Attribute",
}
)
topic_status: Optional[str] = field(
default=None,
metadata={
"name": "TopicStatus",
"type": "Attribute",
}
)
@dataclass(slots=True, kw_only=True)
class Markup:
header: Optional[Header] = field(
default=None,
metadata={
"name": "Header",
"type": "Element",
"namespace": "",
}
)
topic: Topic = field(
metadata={
"name": "Topic",
"type": "Element",
"namespace": "",
"required": True,
}
)
comment: List[Comment] = field(
default_factory=list,
metadata={
"name": "Comment",
"type": "Element",
"namespace": "",
}
)
viewpoints: List[ViewPoint] = field(
default_factory=list,
metadata={
"name": "Viewpoints",
"type": "Element",
"namespace": "",
}
)
+41
View File
@@ -0,0 +1,41 @@
from dataclasses import dataclass, field
from typing import Optional
@dataclass(slots=True, kw_only=True)
class Project:
name: Optional[str] = field(
default=None,
metadata={
"name": "Name",
"type": "Element",
"namespace": "",
}
)
project_id: str = field(
metadata={
"name": "ProjectId",
"type": "Attribute",
"required": True,
}
)
@dataclass(slots=True, kw_only=True)
class ProjectExtension:
project: Optional[Project] = field(
default=None,
metadata={
"name": "Project",
"type": "Element",
"namespace": "",
}
)
extension_schema: str = field(
metadata={
"name": "ExtensionSchema",
"type": "Element",
"namespace": "",
"required": True,
}
)
+21
View File
@@ -0,0 +1,21 @@
from dataclasses import dataclass, field
from typing import Optional
@dataclass(slots=True, kw_only=True)
class Version:
detailed_version: Optional[str] = field(
default=None,
metadata={
"name": "DetailedVersion",
"type": "Element",
"namespace": "",
}
)
version_id: Optional[str] = field(
default=None,
metadata={
"name": "VersionId",
"type": "Attribute",
}
)
+476
View File
@@ -0,0 +1,476 @@
from dataclasses import dataclass, field
from enum import Enum
from typing import List, Optional
class BitmapFormat(Enum):
PNG = "PNG"
JPG = "JPG"
@dataclass(slots=True, kw_only=True)
class Component:
originating_system: Optional[str] = field(
default=None,
metadata={
"name": "OriginatingSystem",
"type": "Element",
}
)
authoring_tool_id: Optional[str] = field(
default=None,
metadata={
"name": "AuthoringToolId",
"type": "Element",
}
)
ifc_guid: Optional[str] = field(
default=None,
metadata={
"name": "IfcGuid",
"type": "Attribute",
"length": 22,
"pattern": r"[0-9,A-Z,a-z,_$]*",
}
)
@dataclass(slots=True, kw_only=True)
class Direction:
x: float = field(
metadata={
"name": "X",
"type": "Element",
"required": True,
}
)
y: float = field(
metadata={
"name": "Y",
"type": "Element",
"required": True,
}
)
z: float = field(
metadata={
"name": "Z",
"type": "Element",
"required": True,
}
)
@dataclass(slots=True, kw_only=True)
class Point:
x: float = field(
metadata={
"name": "X",
"type": "Element",
"required": True,
}
)
y: float = field(
metadata={
"name": "Y",
"type": "Element",
"required": True,
}
)
z: float = field(
metadata={
"name": "Z",
"type": "Element",
"required": True,
}
)
@dataclass(slots=True, kw_only=True)
class ViewSetupHints:
spaces_visible: Optional[bool] = field(
default=None,
metadata={
"name": "SpacesVisible",
"type": "Attribute",
}
)
space_boundaries_visible: Optional[bool] = field(
default=None,
metadata={
"name": "SpaceBoundariesVisible",
"type": "Attribute",
}
)
openings_visible: Optional[bool] = field(
default=None,
metadata={
"name": "OpeningsVisible",
"type": "Attribute",
}
)
@dataclass(slots=True, kw_only=True)
class ClippingPlane:
location: Point = field(
metadata={
"name": "Location",
"type": "Element",
"required": True,
}
)
direction: Direction = field(
metadata={
"name": "Direction",
"type": "Element",
"required": True,
}
)
@dataclass(slots=True, kw_only=True)
class ComponentColoringColor:
class Meta:
global_type = False
component: List[Component] = field(
default_factory=list,
metadata={
"name": "Component",
"type": "Element",
"min_occurs": 1,
}
)
color: Optional[str] = field(
default=None,
metadata={
"name": "Color",
"type": "Attribute",
"pattern": r"[0-9,a-f,A-F]{6}([0-9,a-f,A-F]{2})?",
}
)
@dataclass(slots=True, kw_only=True)
class ComponentSelection:
component: List[Component] = field(
default_factory=list,
metadata={
"name": "Component",
"type": "Element",
"min_occurs": 1,
}
)
@dataclass(slots=True, kw_only=True)
class ComponentVisibilityExceptions:
class Meta:
global_type = False
component: List[Component] = field(
default_factory=list,
metadata={
"name": "Component",
"type": "Element",
"min_occurs": 1,
}
)
@dataclass(slots=True, kw_only=True)
class Line:
start_point: Point = field(
metadata={
"name": "StartPoint",
"type": "Element",
"required": True,
}
)
end_point: Point = field(
metadata={
"name": "EndPoint",
"type": "Element",
"required": True,
}
)
@dataclass(slots=True, kw_only=True)
class OrthogonalCamera:
"""
Attributes
camera_view_point:
camera_direction:
camera_up_vector:
view_to_world_scale: view's visible size in meters
"""
camera_view_point: Point = field(
metadata={
"name": "CameraViewPoint",
"type": "Element",
"required": True,
}
)
camera_direction: Direction = field(
metadata={
"name": "CameraDirection",
"type": "Element",
"required": True,
}
)
camera_up_vector: Direction = field(
metadata={
"name": "CameraUpVector",
"type": "Element",
"required": True,
}
)
view_to_world_scale: float = field(
metadata={
"name": "ViewToWorldScale",
"type": "Element",
"required": True,
}
)
@dataclass(slots=True, kw_only=True)
class PerspectiveCamera:
"""
Attributes
camera_view_point:
camera_direction:
camera_up_vector:
field_of_view: It is currently limited to a value between 45 and
60 degrees. This limitation will be dropped in the next
release and viewers should be expect values outside this
range in current implementations.
"""
camera_view_point: Point = field(
metadata={
"name": "CameraViewPoint",
"type": "Element",
"required": True,
}
)
camera_direction: Direction = field(
metadata={
"name": "CameraDirection",
"type": "Element",
"required": True,
}
)
camera_up_vector: Direction = field(
metadata={
"name": "CameraUpVector",
"type": "Element",
"required": True,
}
)
field_of_view: float = field(
metadata={
"name": "FieldOfView",
"type": "Element",
"required": True,
"min_inclusive": 1.0,
"max_inclusive": 170.0,
}
)
@dataclass(slots=True, kw_only=True)
class VisualizationInfoBitmap:
class Meta:
global_type = False
bitmap: BitmapFormat = field(
metadata={
"name": "Bitmap",
"type": "Element",
"required": True,
}
)
reference: str = field(
metadata={
"name": "Reference",
"type": "Element",
"required": True,
}
)
location: Point = field(
metadata={
"name": "Location",
"type": "Element",
"required": True,
}
)
normal: Direction = field(
metadata={
"name": "Normal",
"type": "Element",
"required": True,
}
)
up: Direction = field(
metadata={
"name": "Up",
"type": "Element",
"required": True,
}
)
height: float = field(
metadata={
"name": "Height",
"type": "Element",
"required": True,
}
)
@dataclass(slots=True, kw_only=True)
class ComponentColoring:
color: List[ComponentColoringColor] = field(
default_factory=list,
metadata={
"name": "Color",
"type": "Element",
"min_occurs": 1,
}
)
@dataclass(slots=True, kw_only=True)
class ComponentVisibility:
exceptions: Optional[ComponentVisibilityExceptions] = field(
default=None,
metadata={
"name": "Exceptions",
"type": "Element",
}
)
default_visibility: Optional[bool] = field(
default=None,
metadata={
"name": "DefaultVisibility",
"type": "Attribute",
}
)
@dataclass(slots=True, kw_only=True)
class VisualizationInfoClippingPlanes:
class Meta:
global_type = False
clipping_plane: List[ClippingPlane] = field(
default_factory=list,
metadata={
"name": "ClippingPlane",
"type": "Element",
}
)
@dataclass(slots=True, kw_only=True)
class VisualizationInfoLines:
class Meta:
global_type = False
line: List[Line] = field(
default_factory=list,
metadata={
"name": "Line",
"type": "Element",
"min_occurs": 1,
}
)
@dataclass(slots=True, kw_only=True)
class Components:
view_setup_hints: Optional[ViewSetupHints] = field(
default=None,
metadata={
"name": "ViewSetupHints",
"type": "Element",
}
)
selection: Optional[ComponentSelection] = field(
default=None,
metadata={
"name": "Selection",
"type": "Element",
}
)
visibility: ComponentVisibility = field(
metadata={
"name": "Visibility",
"type": "Element",
"required": True,
}
)
coloring: Optional[ComponentColoring] = field(
default=None,
metadata={
"name": "Coloring",
"type": "Element",
}
)
@dataclass(slots=True, kw_only=True)
class VisualizationInfo:
"""
VisualizationInfo documentation.
"""
components: Optional[Components] = field(
default=None,
metadata={
"name": "Components",
"type": "Element",
}
)
orthogonal_camera: Optional[OrthogonalCamera] = field(
default=None,
metadata={
"name": "OrthogonalCamera",
"type": "Element",
}
)
perspective_camera: Optional[PerspectiveCamera] = field(
default=None,
metadata={
"name": "PerspectiveCamera",
"type": "Element",
}
)
lines: Optional[VisualizationInfoLines] = field(
default=None,
metadata={
"name": "Lines",
"type": "Element",
}
)
clipping_planes: Optional[VisualizationInfoClippingPlanes] = field(
default=None,
metadata={
"name": "ClippingPlanes",
"type": "Element",
}
)
bitmap: List[VisualizationInfoBitmap] = field(
default_factory=list,
metadata={
"name": "Bitmap",
"type": "Element",
}
)
guid: str = field(
metadata={
"name": "Guid",
"type": "Attribute",
"required": True,
"pattern": r"[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}",
}
)
+304
View File
@@ -0,0 +1,304 @@
"""BCF XML V2 Topic handler."""
import datetime
import tempfile
import uuid
import zipfile
from pathlib import Path
from typing import Any, NoReturn, Optional
import numpy as np
from ifcopenshell import entity_instance
from numpy.typing import NDArray
from xsdata.models.datatype import XmlDateTime
import bcf.v2.model as mdl
from bcf.inmemory_zipfile import ZipFileInterface
from bcf.v2.visinfo import VisualizationInfoHandler
from bcf.xml_parser import AbstractXmlParserSerializer, XmlParserSerializer
class TopicHandler:
"""BCF Topic and related objects handler."""
def __init__(
self,
topic_dir: Optional[zipfile.Path] = None,
xml_handler: Optional[AbstractXmlParserSerializer] = None,
) -> None:
self._markup: Optional[mdl.Markup] = None
self._viewpoints: dict[str, VisualizationInfoHandler] = {}
self._reference_files: dict[str, bytes] = {}
self._document_references: dict[str, bytes] = {}
self._bim_snippet: Optional[bytes] = None
self._xml_handler = xml_handler or XmlParserSerializer()
self._topic_dir = topic_dir
@property
def markup(self) -> Optional[mdl.Markup]:
if not self._markup:
markup_path = self._topic_dir.joinpath("markup.bcf")
if markup_path.exists():
self._markup = self._xml_handler.parse(markup_path.read_bytes(), mdl.Markup)
return self._markup
@markup.setter
def markup(self, value: mdl.Markup) -> None:
self._markup = value
@property
def topic(self) -> mdl.Topic:
"""Return the Topic object."""
return self.markup.topic
@property
def guid(self) -> str:
"""Return the GUID of the topic."""
if self._markup:
return self.topic.guid
return self._topic_dir.name if self._topic_dir else ""
@property
def header(self) -> Optional[mdl.Header]:
"""Return the header of the topic."""
return self.markup.header if self.markup else None
@property
def comments(self) -> list[mdl.Comment]:
"""Return the comments of the topic."""
return self.markup.comment if self.markup else []
@property
def bim_snippet(self) -> Optional[bytes]:
if not self._bim_snippet and self._topic_dir:
self._bim_snippet = self._load_bim_snippet()
return self._bim_snippet
@bim_snippet.setter
def bim_snippet(self, value: bytes) -> None:
self._bim_snippet = value
@property
def viewpoints(self) -> dict[str, VisualizationInfoHandler]:
if not self._viewpoints and self._topic_dir:
self._viewpoints = self._load_viewpoints()
return self._viewpoints
@property
def reference_files(self) -> dict[str, bytes]:
if self._reference_files or not self.header:
return self._reference_files
for ref in self.header.file:
if ref.is_external:
continue
real_path = self._topic_dir
for path_part in ref.reference.split("/"):
real_path = real_path.parent if path_part == ".." else real_path.joinpath(path_part)
self._reference_files[ref.reference] = real_path.read_bytes()
return self._reference_files
@property
def document_references(self) -> dict[str, bytes]:
if self._document_references or not self.topic:
return self._document_references
for doc in self.topic.document_reference:
if doc.is_external or not doc.referenced_document:
continue
real_path = self._topic_dir
for path_part in doc.referenced_document.split("/"):
real_path = real_path.parent if path_part == ".." else real_path.joinpath(path_part)
self._document_references[doc.referenced_document] = real_path.read_bytes()
return self._document_references
def _load_bim_snippet(self) -> Optional[bytes]:
bim_snippet_obj = self.topic.bim_snippet
if bim_snippet_obj and not bim_snippet_obj.is_external:
bim_snippet_path = self._topic_dir.joinpath(bim_snippet_obj.reference)
if bim_snippet_path.exists():
return bim_snippet_path.read_bytes()
return None
def _load_viewpoints(self) -> dict[str, VisualizationInfoHandler]:
if self.markup and (viewpoints := self.markup.viewpoints):
return VisualizationInfoHandler.from_topic_viewpoints(self._topic_dir, viewpoints)
return {}
@classmethod
def create_new(
cls,
title: str,
description: str,
author: str,
topic_type: str = "",
topic_status: str = "",
xml_handler: Optional[AbstractXmlParserSerializer] = None,
) -> "TopicHandler":
"""
Create a new BCF topic.
Args:
title: The title of the topic.
description: The description of the topic.
author: The author of the topic.
topic_type: The type of the topic.
topic_status: The status of the topic.
xml_handler: The XML parser/serializer to use.
Returns:
The BCF topic definition.
"""
creation_date = XmlDateTime.from_datetime(datetime.datetime.now())
guid = str(uuid.uuid4())
topic = mdl.Topic(
title=title,
description=description,
creation_author=author,
creation_date=creation_date,
guid=guid,
topic_type=topic_type,
topic_status=topic_status,
)
markup = mdl.Markup(topic=topic)
obj = cls(topic_dir=Path(guid), xml_handler=xml_handler or XmlParserSerializer())
obj.markup = markup
return obj
def save(self, destination_zip: ZipFileInterface) -> None:
"""
Save the topic to a BCF zip file.
Args:
bcf_zip: The BCF zip file to save to.
"""
topic_dir = self.guid
self._save_xml(destination_zip, self._markup, "markup.bcf")
self._save_viewpoints(destination_zip, topic_dir)
self._save_bim_snippet(destination_zip)
self._save_reference_files(destination_zip)
self._save_document_references(destination_zip)
def _save_viewpoints(self, destination_zip: ZipFileInterface, topic_dir: str) -> None:
if not self.markup or not (viewpoints := self.markup.viewpoints):
return
for vpt in viewpoints:
if vpt.viewpoint:
self.viewpoints[vpt.viewpoint].save(destination_zip, topic_dir, vpt)
def _save_xml(self, destination_zip: ZipFileInterface, item: Any, target: str) -> None:
if self._topic_dir is None:
return
to_write = self._xml_handler.serialize(item) if item else self._topic_dir.joinpath(target).read_bytes()
destination_zip.writestr(f"{self._topic_dir.name}/{target}", to_write)
def _save_bim_snippet(self, destination_zip: ZipFileInterface) -> None:
snippet = self.topic.bim_snippet
if not snippet or snippet.is_external:
return
ref_filename = Path(snippet.reference).name
if self.bim_snippet:
destination_zip.writestr(f"{self.topic.guid}/{ref_filename}", self.bim_snippet)
def _save_reference_files(self, destination_zip: ZipFileInterface) -> None:
if not self.header:
return
for ref in self.header.file:
if ref.is_external or not ref.reference:
continue
real_path = self._topic_dir
for path_part in ref.reference.split("/"):
real_path = real_path.parent if path_part == ".." else real_path.joinpath(path_part)
destination_zip.writestr(real_path.at, self.reference_files[ref.reference])
def _save_document_references(self, destination_zip: ZipFileInterface) -> None:
if not self.topic:
return
for doc in self.topic.document_reference:
if doc.is_external or not doc.referenced_document:
continue
real_path = self._topic_dir
for path_part in doc.referenced_document.split("/"):
real_path = real_path.parent if path_part == ".." else real_path.joinpath(path_part)
destination_zip.writestr(real_path.at, self.document_references[doc.referenced_document])
def extract_file(self, entity, outfile: Optional[Path] = None) -> Path:
"""Extracts an element with a file into a temporary directory
These include header files, bim snippets, document references, and
viewpoint bitmaps. External reference are not downloaded. Instead, the
URI reference is returned.
:param entity: The entity with a file reference to extract
:type entity: bcf.v2.model.HeaderFile,bcf.v2.model.BimSnippet,bcf.v2.model.TopicDocumentReference
:param outfile: If provided, save the header file to that location.
Otherwise, a temporary directory is created and the filename is
derived from the header's original filename.
:type outfile: pathlib.Path,optional
:return: The filepath of the extracted file. It may be a URL if the
header file is external.
:rtype: Path
"""
if hasattr(entity, "reference"):
reference = entity.reference
else:
reference = entity.referenced_document
if not reference:
return
if getattr(entity, "is_external", False):
return entity.reference
resolved_reference = self._topic_dir
for part in Path(reference).parts:
if part == "..":
resolved_reference = resolved_reference.parent
else:
resolved_reference = resolved_reference.joinpath(part)
if not outfile:
if getattr(entity, "filename", None):
filename = entity.filename
else:
filename = resolved_reference.name
outfile = Path(tempfile.mkdtemp()) / filename
with open(outfile, "wb") as f:
f.write(resolved_reference.read_bytes())
return outfile
def add_viewpoint(self, element: entity_instance) -> None:
"""Add a viewpoint pointed at the placement of an IFC element to the topic.
Args:
element: The IFC element.
"""
new_viewpoint = VisualizationInfoHandler.create_new(element, self._xml_handler)
self.add_visinfo_handler(new_viewpoint)
def add_viewpoint_from_point_and_guids(self, position: NDArray[np.float_], *guids: str) -> None:
"""Add a viewpoint pointing at an XYZ point in space
Args:
position: the XYZ point in space
guids: one or more element GlobalIds.
"""
vi_handler = VisualizationInfoHandler.create_from_point_and_guids(
position, *guids, xml_handler=self._xml_handler
)
self.add_visinfo_handler(vi_handler)
def add_visinfo_handler(self, new_viewpoint: VisualizationInfoHandler) -> None:
self.viewpoints[new_viewpoint.guid + ".bcfv"] = new_viewpoint
self.markup.viewpoints.append(mdl.ViewPoint(viewpoint=new_viewpoint.guid + ".bcfv", guid=new_viewpoint.guid))
def __eq__(self, other: object) -> bool | NoReturn:
return (
(
self.markup == other.markup
and self.viewpoints == other.viewpoints
and self.bim_snippet == other.bim_snippet
)
if isinstance(other, TopicHandler)
else NotImplemented
)
+299
View File
@@ -0,0 +1,299 @@
import uuid
import zipfile
from functools import lru_cache
from typing import Any, Iterable, Optional
import numpy as np
from ifcopenshell import entity_instance
import ifcopenshell.util.unit
import ifcopenshell.util.placement
from numpy.typing import NDArray
import bcf.v2.model as mdl
from bcf.geometry import (
camera_vectors_from_element_placement,
camera_vectors_from_target_position,
)
from bcf.inmemory_zipfile import ZipFileInterface
from bcf.xml_parser import AbstractXmlParserSerializer, XmlParserSerializer
class VisualizationInfoHandler:
"""Handle the VisualizationInfo and related objects."""
def __init__(
self,
visualization_info: mdl.VisualizationInfo,
snapshot: Optional[bytes] = None,
bitmaps: Optional[dict[str, bytes]] = None,
xml_handler: Optional[AbstractXmlParserSerializer] = None,
) -> None:
self.visualization_info = visualization_info
self.snapshot = snapshot
self.bitmaps = bitmaps or {}
self._xml_handler = xml_handler or XmlParserSerializer()
@property
def guid(self) -> str:
"""Return the GUID of the visualization info."""
return self.visualization_info.guid
@classmethod
def from_topic_viewpoints(
cls,
topic_dir: zipfile.Path,
vps: Iterable[mdl.ViewPoint],
xml_handler: Optional[AbstractXmlParserSerializer] = None,
) -> dict[str, "VisualizationInfoHandler"]:
"""Create VisualizationInfoHandler objects of a Topic's ViewPoints."""
viewpoints = {}
for vpt in vps:
visinfo = cls.load(topic_dir, vpt, xml_handler)
if visinfo and vpt.viewpoint:
viewpoints[vpt.viewpoint] = visinfo
return viewpoints
@classmethod
def load(
cls,
topic_dir: zipfile.Path,
vpt: mdl.ViewPoint,
xml_handler: Optional[AbstractXmlParserSerializer] = None,
) -> Optional["VisualizationInfoHandler"]:
"""
Load the VisualizationInfo and related objects from a BCF zip file.
Args:
topic_dir: The directory in the BCF zip file to load from.
vpt: The ViewPoint to load.
xml_handler: The XML handler to use to parse the VisualizationInfo.
Returns:
The VisualizationInfoHandler object.
"""
visinfo = cls._load_visinfo(topic_dir, vpt.viewpoint, xml_handler)
if not visinfo:
return None
snapshot = cls._load_snapshot(topic_dir, vpt.snapshot)
bitmaps = cls._load_bitmaps(topic_dir, visinfo)
return cls(visinfo, snapshot, bitmaps, xml_handler)
@staticmethod
def _load_visinfo(
topic_dir: zipfile.Path,
vp_name: Optional[str],
xml_handler: Optional[AbstractXmlParserSerializer] = None,
) -> Optional[mdl.VisualizationInfo]:
if not vp_name:
return None
vp_path = topic_dir.joinpath(vp_name)
if vp_path.exists():
xml_handler = xml_handler or XmlParserSerializer()
return xml_handler.parse(vp_path.read_bytes(), mdl.VisualizationInfo)
return None
@staticmethod
def _load_snapshot(topic_dir: zipfile.Path, vp_snapshot: Optional[str]) -> Optional[bytes]:
if vp_snapshot:
snapshot_path = topic_dir.joinpath(vp_snapshot)
if snapshot_path.exists():
return snapshot_path.read_bytes()
return None
@staticmethod
def _load_bitmaps(topic_dir: zipfile.Path, visinfo: Optional[mdl.VisualizationInfo]) -> dict[str, bytes]:
if not visinfo or not (bitmaps := visinfo.bitmap):
return {}
bitmaps_dict = {}
for bitmap in bitmaps:
if not bitmap.reference:
continue
bitmap_path = topic_dir.joinpath(bitmap.reference)
if bitmap_path.exists():
bitmaps_dict[bitmap.reference] = bitmap_path.read_bytes()
return bitmaps_dict
def save(
self,
bcf_zip: ZipFileInterface,
topic_dir: str,
vpt: mdl.ViewPoint,
) -> None:
"""
Save the VisualizationInfo and related objects to a BCF zip file.
Args:
bcf_zip: The BCF zip file to save to.
topic_dir: The directory in the BCF zip file to save to.
vpt: The ViewPoint to save.
"""
if not (vp_name := vpt.viewpoint):
return
self._save_visinfo(bcf_zip, topic_dir, vp_name)
self._save_snapshot(bcf_zip, topic_dir, vpt.snapshot)
self._save_bitmaps(bcf_zip, topic_dir)
def _save_snapshot(self, bcf_zip: ZipFileInterface, topic_dir: str, filename: Optional[str]) -> None:
if self.snapshot and filename:
bcf_zip.writestr(f"{topic_dir}/{filename}", self.snapshot)
def _save_visinfo(self, bcf_zip: ZipFileInterface, topic_dir: str, vp_name: str) -> None:
bcf_zip.writestr(
f"{topic_dir}/{vp_name}",
self._xml_handler.serialize(self.visualization_info),
)
def _save_bitmaps(self, bcf_zip: ZipFileInterface, topic_dir: str) -> None:
if not self.bitmaps:
return
if not (bitmaps_defs := self.visualization_info.bitmap):
return
for bitmap_def in bitmaps_defs:
if not (bitmap_name := bitmap_def.reference):
continue
if bitmap_name in self.bitmaps:
bcf_zip.writestr(f"{topic_dir}/{bitmap_name}", self.bitmaps[bitmap_name])
@classmethod
def create_new(
cls,
element: entity_instance,
xml_handler: Optional[AbstractXmlParserSerializer] = None,
) -> "VisualizationInfoHandler":
"""
Create a new VisualizationInfoHandler object from an IFC element.
Args:
element: The IFC element to point at.
xml_handler: The XML handler to use.
Returns:
The VisualizationInfoHandler object.
"""
xml_handler = xml_handler or XmlParserSerializer()
return cls(visualization_info=build_viewpoint(element), xml_handler=xml_handler)
@classmethod
def create_from_point_and_guids(
cls,
position: NDArray[np.float_],
*guids: str,
xml_handler: Optional[AbstractXmlParserSerializer] = None,
) -> "VisualizationInfoHandler":
"""
Create a new VisualizationInfoHandler object from an IFC element.
Args:
position: target point coordinates.
*guids: One or more IFC element GUID.
xml_handler: The XML handler to use.
Returns:
The VisualizationInfoHandler object.
"""
xml_handler = xml_handler or XmlParserSerializer()
return cls(
visualization_info=build_viewpoint_from_position_and_guids(position, *guids), xml_handler=xml_handler
)
@lru_cache(maxsize=None)
def build_viewpoint(element: entity_instance) -> mdl.VisualizationInfo:
"""
Return a BCF viewpoint of an IFC element.
This function is cached to speedudp the creation of multiple BCF topics regarding the same element.
Args:
element: The IFC element to point at.
Returns:
The BCF viewpoint definition.
"""
ifc_file = element.wrapped_data.file
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(ifc_file)
elem_placement = ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement)
elem_placement[0][3] *= unit_scale
elem_placement[1][3] *= unit_scale
elem_placement[2][3] *= unit_scale
return mdl.VisualizationInfo(
guid=str(uuid.uuid4()),
components=build_components(element.GlobalId),
perspective_camera=build_camera(elem_placement),
)
def build_viewpoint_from_position_and_guids(position: NDArray[np.float_], *guids: str) -> mdl.VisualizationInfo:
"""
Return a BCF viewpoint of an IFC element.
This function is cached to speedudp the creation of multiple BCF topics regarding the same element.
Args:
position: target point coordinates.
*guids: One or more IFC element GUID.
Returns:
The BCF viewpoint definition.
"""
return mdl.VisualizationInfo(
guid=str(uuid.uuid4()),
components=build_components(*guids),
perspective_camera=build_camera_from_vectors(*camera_vectors_from_target_position(position)),
)
def build_components(*guids: str) -> mdl.Components:
"""
Return the BCF components from an IFC element GUID.
Args:
*guids: One or more IFC element GUID.
Returns:
The BCF components definition.
"""
components = [mdl.Component(ifc_guid=guid) for guid in guids]
return mdl.Components(
selection=mdl.ComponentSelection(component=components),
visibility=mdl.ComponentVisibility(default_visibility=True),
)
def build_camera(elem_placement: NDArray[np.float_]) -> mdl.PerspectiveCamera:
"""
Return a BCF camera for an IFC element placement matrix.
Args:
elem_placement: The IFC element placement as a rototranslation matrix.
Returns:
The BCF camera definition.
"""
return build_camera_from_vectors(*camera_vectors_from_element_placement(elem_placement))
def build_camera_from_vectors(
camera_position: NDArray[np.float_], camera_dir: NDArray[np.float_], camera_up: NDArray[np.float_]
) -> mdl.PerspectiveCamera:
"""
Return a BCF camera for an IFC element placement matrix.
Args:
camera_position: camera position array
camera_dir: camera direction versor
camera_up_vector: camera up versor
Returns:
The BCF camera definition.
"""
camera_viewpoint = mdl.Point(x=camera_position[0], y=camera_position[1], z=camera_position[2])
camera_direction = mdl.Direction(x=camera_dir[0], y=camera_dir[1], z=camera_dir[2])
camera_up_vector = mdl.Direction(x=camera_up[0], y=camera_up[1], z=camera_up[2])
return mdl.PerspectiveCamera(
camera_view_point=camera_viewpoint,
camera_direction=camera_direction,
camera_up_vector=camera_up_vector,
field_of_view=60.0,
)
+1 -19
View File
@@ -1,19 +1 @@
# BCF - BCF Python library
# Copyright (C) 2021 Prabhat Singh <singh01prabhat@gmail.com>
#
# This file is part of BCF.
#
# BCF is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BCF 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with BCF. If not, see <http://www.gnu.org/licenses/>.
"""BCF XML v3 handler."""
+172 -184
View File
@@ -1,4 +1,3 @@
# BCF - BCF Python library
# Copyright (C) 2021 Prabhat Singh <singh01prabhat@gmail.com>
#
@@ -17,25 +16,27 @@
# You should have received a copy of the GNU Lesser General Public License
# along with BCF. If not, see <http://www.gnu.org/licenses/>.
import uuid
import time
import json
import urllib
import requests
import webbrowser
import http.server
import base64
import tempfile
import http.server
import os
import tempfile
import time
import urllib
import uuid
import webbrowser
from re import A
from typing import Any, Optional, Tuple
import requests
client_id, client_secret = "", ""
class OAuthReceiver(http.server.BaseHTTPRequestHandler):
def do_GET(self):
def do_GET(self) -> None:
query = urllib.parse.parse_qs(urllib.parse.urlparse(self.path).query)
self.server.auth_code = query.get("code", [""])[0]
self.server.auth_state = query.get("state", [""])[0]
self.server.auth_code = query.get("code", [""])[0] # type:ignore
self.server.auth_state = query.get("state", [""])[0] # type:ignore
self.send_response(200)
self.send_header("Content-type", "text/plain")
self.end_headers()
@@ -43,20 +44,21 @@ class OAuthReceiver(http.server.BaseHTTPRequestHandler):
class FoundationClient:
def __init__(self, client_id, client_secret, base_url=None, redirect_subdir=None):
def __init__(
self, client_id: str, client_secret: str, base_url: Optional[str] = None, redirect_subdir: Optional[str] = None
) -> None:
self.baseurl = base_url
self.access_token = ""
self.refresh_token = ""
self.access_token_expires_on = time.time()
self.refresh_token_expires_on = float("inf")
self.auth_endpoint = None
self.token_endpoint = None
self.token_endpoint = ""
self.client_id = client_id
self.client_secret = client_secret
self.auth_method = None
self.auth_method: Optional[str] = None
self.redirect_subdir = redirect_subdir
def get_access_token(self):
def get_access_token(self) -> str:
if self.access_token and self.access_token_expires_on > time.time():
return self.access_token
elif self.refresh_token and self.refresh_token_expires_on > time.time():
@@ -65,18 +67,18 @@ class FoundationClient:
self.login()
return self.access_token
def get_auth_methods(self):
def get_auth_methods(self) -> list[Any]:
resp = requests.get(f"{self.baseurl}foundation/1.0/auth")
return resp.json()["supported_oauth2_flows"]
def get_versions(self):
def get_versions(self) -> list[Any]:
resp = requests.get(f"{self.baseurl}foundation/versions")
return resp.json()["versions"]
def login(self):
def login(self) -> None:
resp = requests.get(f"{self.baseurl}foundation/1.0/auth")
values = resp.json()
self.auth_endpoint = values["oauth2_auth_url"]
auth_endpoint = values["oauth2_auth_url"]
self.token_endpoint = values["oauth2_token_url"]
with http.server.HTTPServer(("", 8080), OAuthReceiver) as server:
@@ -89,25 +91,22 @@ class FoundationClient:
"redirect_uri": f"http://localhost:{server.server_address[1]}/{self.redirect_subdir}",
}
)
if "?" in self.auth_endpoint:
webbrowser.open(f"{self.auth_endpoint}&{query}")
if "?" in auth_endpoint:
webbrowser.open(f"{auth_endpoint}&{query}")
else:
webbrowser.open(f"{self.auth_endpoint}?{query}")
webbrowser.open(f"{auth_endpoint}?{query}")
server.timeout = 100
server.state = state
server.handle_request()
if server.auth_code and server.auth_state == state:
if server.auth_code and server.auth_state == state: # pylint: disable=E1101
data = {
"grant_type": "authorization_code",
"code": server.auth_code,
"code": server.auth_code, # pylint: disable=E1101 type:ignore
"redirect_uri": f"http://localhost:{server.server_address[1]}/{self.redirect_subdir}",
}
auth_string = f"{self.client_id}:{self.client_secret}"
header_string = base64.b64encode(auth_string.encode("utf-8")).decode("utf-8")
headers = {"Authorization": f"Basic {header_string}"}
headers = self._get_access_token_headers()
self.set_tokens_from_response(requests.post(self.token_endpoint, data=data, headers=headers))
def get_refresh_token(self):
def get_refresh_token(self) -> None:
self.set_tokens_from_response(
requests.post(
self.token_endpoint,
@@ -118,10 +117,8 @@ class FoundationClient:
).json()
)
def get_new_access_token(self):
auth_string = f"{self.client_id}:{self.client_secret}"
header_string = base64.b64encode(auth_string.encode("utf-8")).decode("utf-8")
headers = {"Authorization": f"Basic {header_string}"}
def get_new_access_token(self) -> None:
headers = self._get_access_token_headers()
self.set_tokens_from_response(
requests.post(
self.token_endpoint,
@@ -133,35 +130,41 @@ class FoundationClient:
).json()
)
def set_auth_method(self, method="authorization_code_grant"):
def _get_access_token_headers(self) -> dict[str, str]:
auth_string = f"{self.client_id}:{self.client_secret}"
header_string = base64.b64encode(auth_string.encode("utf-8")).decode("utf-8")
return {"Authorization": f"Basic {header_string}"}
def set_auth_method(self, method: str = "authorization_code_grant") -> None:
if method != "authorization_code_grant":
raise NotImplementedError(f"{method} not supported")
else:
self.auth_method = method
def set_tokens_from_response(self, response):
response = response.json()
self.access_token = response["access_token"]
self.refresh_token = response["refresh_token"]
self.access_token_expires_on = time.time() + response["expires_in"]
if "refresh_token_expires_in" in response:
self.refresh_token_expires_on = time.time() + response["refresh_token_expires_in"]
def set_tokens_from_response(self, response: requests.Response) -> None:
response_dict = response.json()
self.access_token = response_dict["access_token"]
self.refresh_token = response_dict["refresh_token"]
self.access_token_expires_on = time.time() + response_dict["expires_in"]
if "refresh_token_expires_in" in response_dict:
self.refresh_token_expires_on = time.time() + response_dict["refresh_token_expires_in"]
class BcfClient:
def __init__(self, foundation_client):
def __init__(self, foundation_client: FoundationClient) -> None:
self.foundation_client = foundation_client
self.version_id = None
self.baseurl = None
self.version_id: Optional[str] = None
self.baseurl: Optional[str] = None
self.filepath = tempfile.mkdtemp()
def set_version(self, version):
def set_version(self, version: dict[str, str]) -> None:
self.version_id = version["version_id"]
self.baseurl = version["api_base_url"]
def get(self, endpoint, params=None, is_auth_required=False):
def get(self, endpoint: str, params: Any = None, is_auth_required: bool = False) -> Any:
# TODO: handle error http status codes and raise exception. Follow error.json standard.
headers = {"Authorization": "Bearer " + self.foundation_client.get_access_token()}
headers = {"Authorization": f"Bearer {self.foundation_client.get_access_token()}"}
response = requests.get(f"{self.baseurl}{endpoint}", headers=headers, params=params or None)
try:
response = requests.get(f"{self.baseurl}{endpoint}", headers=headers, params=params or None)
@@ -171,68 +174,62 @@ class BcfClient:
except requests.exceptions.HTTPError as e:
print(f"message: {response.reason}' '{response.status_code}' '{ e }")
def post(self, endpoint, data=None, params=None):
def post(self, endpoint: str, data: Any = None, params: Any = None) -> Tuple[int, str]:
headers = {
"Authorization": "Bearer " + self.foundation_client.get_access_token(),
"Authorization": f"Bearer {self.foundation_client.get_access_token()}",
"Content-type": "application/json",
}
try:
response = requests.post(
f"{self.baseurl}{endpoint}",
headers=headers,
params=params or None,
data=data or None,
f"{self.baseurl}{endpoint}", headers=headers, params=params or None, data=data or None
)
if response.status_code == 201:
return response.status_code, response.text
response.raise_for_status()
if response.status_code != 201:
response.raise_for_status()
return response.status_code, response.text
except requests.exceptions.HTTPError as errh:
print(f"message: {response.reason}' '{response.status_code}, {errh}")
return response.status_code, response.reason
def put(self, endpoint, data=None, params=None):
def put(self, endpoint: str, data: Any = None, params: Any = None) -> Tuple[int, str]:
headers = {
"Authorization": "Bearer " + self.foundation_client.get_access_token(),
"Authorization": f"Bearer {self.foundation_client.get_access_token()}",
"Content-type": "application/json",
}
try:
response = requests.put(
f"{self.baseurl}{endpoint}",
headers=headers,
params=params or None,
data=data or None,
f"{self.baseurl}{endpoint}", headers=headers, params=params or None, data=data or None
)
if response.status_code == 200:
return response.status_code, response.text
response.raise_for_status()
if response.status_code != 200:
response.raise_for_status()
return response.status_code, response.text
except requests.exceptions.HTTPError as errh:
print(f"message: {response.reason}' '{response.status_code}, {errh}")
return response.status_code, response.reason
def delete(self, endpoint, params=None):
def delete(self, endpoint: str, params: Any = None) -> Tuple[int, str]:
headers = {
"Authorization": "Bearer " + self.foundation_client.get_access_token(),
"Authorization": f"Bearer {self.foundation_client.get_access_token()}",
"Content-type": "application/json",
}
try:
response = requests.delete(
f"{self.baseurl}{endpoint}",
headers=headers,
params=params or None,
)
if response.status_code == 200:
return response.status_code, response.text
response.raise_for_status()
response = requests.delete(f"{self.baseurl}{endpoint}", headers=headers, params=params or None)
if response.status_code != 200:
response.raise_for_status()
return response.status_code, response.text
except requests.exceptions.HTTPError as errh:
print(f"message: {response.reason}' '{response.status_code}, {errh}")
return response.status_code, response.reason
def get_projects(self) -> list:
return self.get(
f"/projects",
)
def get_projects(self) -> list[Any]:
return self.get("/projects")
def get_project(
self,
project_id="",
) -> dict:
def get_project(self, project_id: str = "") -> dict[str, Any]:
return self.get(
f"/projects/{project_id}",
{
@@ -240,16 +237,13 @@ class BcfClient:
},
)
def update_project(self, project_id="", data=None) -> dict:
def update_project(self, project_id: str = "", data: Any = None) -> Tuple[int, str]:
url = f"{self.baseurl}/projects/{project_id}"
headers = {"Authorization": "Bearer " + self.foundation_client.get_access_token()}
headers = {"Authorization": f"Bearer {self.foundation_client.get_access_token()}"}
resp = requests.put(url, headers=headers, data=data)
return resp.status_code, resp.text
def get_extensions(
self,
project_id="",
) -> dict:
def get_extensions(self, project_id: str = "") -> dict[str, Any]:
return self.get(
f"/projects/{project_id}/extensions",
{
@@ -259,10 +253,10 @@ class BcfClient:
def get_topics(
self,
project_id="",
topics="",
query_string=None,
) -> list:
project_id: str = "",
topics: str = "",
query_string: Optional[str] = None,
) -> list[Any]:
# return self.get(
# f"/projects/{project_id}/topics",
# {
@@ -273,7 +267,7 @@ class BcfClient:
# )
pass
def get_topic(self, project_id="", topic_id="") -> dict:
def get_topic(self, project_id: str = "", topic_id: str = "") -> dict[str, Any]:
return self.get(
f"/projects/{project_id}/topics/{topic_id}",
{
@@ -282,42 +276,40 @@ class BcfClient:
},
)
def create_topic(self, project_id="", data=None):
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="", topic_id="", data=None) -> dict:
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="", topic_id=""):
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="", topic_id="") -> str:
def get_snippet(self, project_id: str = "", topic_id: str = "") -> Tuple[int, str]:
headers = {
"Authorization": "Bearer " + self.foundation_client.get_access_token(),
"Authorization": f"Bearer {self.foundation_client.get_access_token()}",
"Content-type": "application/octet-stream",
}
response = requests.get(
f"{self.baseurl}/projects/{project_id}/topics/{topic_id}/snippet",
headers=headers,
)
# TODO: write to tmpdir
with open(os.path.join(self.filepath, f"{project_id}_{topic_id}_snippet.txt"), "wb") as f:
f.write(response.content.decode("utf-8"))
return response.status_code, response.content
def update_snippet(self, project_id="", topic_id="", files=None, data=None):
response = requests.get(f"{self.baseurl}/projects/{project_id}/topics/{topic_id}/snippet", headers=headers)
content = response.content.decode("utf-8")
with open(os.path.join(self.filepath, f"{project_id}_{topic_id}_snippet.txt"), "w") as f:
f.write(content)
return response.status_code, content
def update_snippet(self, project_id: str = "", topic_id: str = "", files: Any = None, data: Any = None) -> int:
headers = {
"Authorization": "Bearer " + self.foundation_client.get_access_token(),
"Authorization": f"Bearer {self.foundation_client.get_access_token()}",
"Content-type": "application/octet-stream",
}
response = requests.put(
f"{self.baseurl}/projects/{project_id}/topics/{topic_id}/snippet",
headers=headers,
files=files,
f"{self.baseurl}/projects/{project_id}/topics/{topic_id}/snippet", headers=headers, files=files
)
return response.status_code
def get_files_information(self, project_id="") -> list:
def get_files_information(self, project_id: str = "") -> list[Any]:
return self.get(
f"/projects/{project_id}/files_information",
{
@@ -325,7 +317,7 @@ class BcfClient:
},
)
def get_files(self, project_id="", topic_id="") -> list:
def get_files(self, project_id: str = "", topic_id: str = "") -> list[Any]:
return self.get(
f"/projects/{project_id}/topics/{topic_id}/files",
{
@@ -336,32 +328,32 @@ class BcfClient:
def update_files(
self,
project_id="",
topic_id="",
data=None,
params=None,
):
project_id: str = "",
topic_id: str = "",
data: Any = None,
params: Any = None,
) -> Tuple[int, str]:
return self.put(
f"/projects/{project_id}/topics/{topic_id}/files",
data=data,
)
def get_comments(self, project_id="", topic_id="") -> list:
def get_comments(self, project_id: str = "", topic_id: str = "") -> None:
pass
def create_comments(
self,
project_id="",
topic_id="",
data=None,
params=None,
):
project_id: str = "",
topic_id: str = "",
data: Any = None,
params: Any = None,
) -> Tuple[int, str]:
return self.post(
f"/projects/{project_id}/topics/{topic_id}/comments",
data=data,
)
def get_comment(self, project_id="", topic_id="", comment_id="") -> dict:
def get_comment(self, project_id: str = "", topic_id: str = "", comment_id: str = "") -> dict[str, Any]:
return self.get(
f"/projects/{project_id}/topics/{topic_id}/comments/{comment_id}",
{
@@ -371,22 +363,22 @@ class BcfClient:
},
)
def delete_comment(self, project_id="", topic_id="", comment_id=""):
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(
self,
project_id="",
topic_id="",
comment_id="",
data=None,
):
project_id: str = "",
topic_id: str = "",
comment_id: str = "",
data: Any = None,
) -> Tuple[int, str]:
return self.put(
f"/projects/{project_id}/topics/{topic_id}/comments/{comment_id}",
data=data,
)
def get_viewpoints(self, project_id="", topic_id="") -> list:
def get_viewpoints(self, project_id: str = "", topic_id: str = "") -> list[Any]:
return self.get(
f"/projects/{project_id}/topics/{topic_id}/viewpoints",
{
@@ -395,13 +387,13 @@ class BcfClient:
},
)
def create_viewpoints(self, project_id="", topic_id="", data=None):
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,
)
def get_viewpoint(self, project_id="", topic_id="", viewpoint_id="") -> dict:
def get_viewpoint(self, project_id: str = "", topic_id: str = "", viewpoint_id: str = "") -> dict[str, Any]:
return self.get(
f"/projects/{project_id}/topics/{topic_id}/viewpoints/{viewpoint_id}",
{
@@ -413,15 +405,15 @@ class BcfClient:
def delete_viewpoint(
self,
project_id="",
topic_id="",
viewpoint_id="",
):
project_id: str = "",
topic_id: str = "",
viewpoint_id: str = "",
) -> Tuple[int, str]:
return self.delete(
f"/projects/{project_id}/topics/{topic_id}/viewpoints/{viewpoint_id}",
)
def get_snapshot(self, project_id="", topic_id="", viewpoint_id="") -> str:
def get_snapshot(self, project_id: str = "", topic_id: str = "", viewpoint_id: str = "") -> str:
return self.get(
f"/projects/{project_id}/topics/{topic_id}/viewpoints/{viewpoint_id}/snapshot",
{
@@ -431,7 +423,7 @@ class BcfClient:
},
)
def get_bitmap(self, project_id="", topic_id="", viewpoint_id="", bitmap_id="") -> str:
def get_bitmap(self, project_id: str = "", topic_id: str = "", viewpoint_id: str = "", bitmap_id: str = "") -> str:
return self.get(
f"/projects/{project_id}/topics/{topic_id}/viewpoints/{viewpoint_id}/bitmaps/{bitmap_id}",
{
@@ -442,7 +434,7 @@ class BcfClient:
},
)
def get_selection(self, project_id="", topic_id="", viewpoint_id="") -> dict:
def get_selection(self, project_id: str = "", topic_id: str = "", viewpoint_id: str = "") -> dict[str, Any]:
return self.get(
f"/projects/{project_id}/topics/{topic_id}/viewpoints/{viewpoint_id}/selection",
{
@@ -452,7 +444,7 @@ class BcfClient:
},
)
def get_coloring(self, project_id="", topic_id="", viewpoint_id="") -> dict:
def get_coloring(self, project_id: str = "", topic_id: str = "", viewpoint_id: str = "") -> dict[str, Any]:
return self.get(
f"/projects/{project_id}/topics/{topic_id}/viewpoints/{viewpoint_id}/coloring",
{
@@ -462,7 +454,7 @@ class BcfClient:
},
)
def get_visibility(self, project_id="", topic_id="", viewpoint_id="") -> dict:
def get_visibility(self, project_id: str = "", topic_id: str = "", viewpoint_id: str = "") -> dict[str, Any]:
return self.get(
f"/projects/{project_id}/topics/{topic_id}/viewpoints/{viewpoint_id}/visibility",
{
@@ -472,7 +464,7 @@ class BcfClient:
},
)
def get_related_topics(self, project_id="", topic_id="") -> list:
def get_related_topics(self, project_id: str = "", topic_id: str = "") -> list[Any]:
return self.get(
f"/projects/{project_id}/topics/{topic_id}/related_topics",
{
@@ -483,16 +475,16 @@ class BcfClient:
def update_related_topics(
self,
project_id="",
topic_id="",
data=None,
):
project_id: str = "",
topic_id: str = "",
data: Any = None,
) -> Tuple[int, str]:
return self.put(
f"/projects/{project_id}/topics/{topic_id}/related_topics",
data=data,
)
def get_document_references(self, project_id="", topic_id="") -> list:
def get_document_references(self, project_id: str = "", topic_id: str = "") -> list[Any]:
return self.get(
f"/projects/{project_id}/topics/{topic_id}/document_references",
{
@@ -503,10 +495,10 @@ class BcfClient:
def create_document_reference(
self,
project_id="",
topic_id="",
data=None,
):
project_id: str = "",
topic_id: str = "",
data: Any = None,
) -> Tuple[int, str]:
return self.post(
f"/projects/{project_id}/topics/{topic_id}/document_references",
data=data,
@@ -514,17 +506,17 @@ class BcfClient:
def update_document_references(
self,
project_id="",
topic_id="",
document_reference_id="",
data=None,
):
project_id: str = "",
topic_id: str = "",
document_reference_id: str = "",
data: Any = None,
) -> Tuple[int, str]:
return self.put(
f"/projects/{project_id}/topics/{topic_id}/document_references/{document_reference_id}",
data=data,
)
def get_documents(self, project_id="", topic_id="") -> list:
def get_documents(self, project_id: str = "", topic_id: str = "") -> list[Any]:
return self.get(
f"/projects/{project_id}/topics/{topic_id}/documents",
{
@@ -534,40 +526,36 @@ class BcfClient:
)
def create_document(
self,
project_id="",
topic_id="",
guid=None,
files=None,
data=None,
):
self, project_id: str = "", topic_id: str = "", guid: Optional[str] = None, files: Any = None, data: Any = None
) -> int:
headers = {
"Authorization": "Bearer " + self.foundation_client.get_access_token(),
"Authorization": f"Bearer {self.foundation_client.get_access_token()}",
"Content-type": "application/octet-stream",
}
response = requests.post(
f"/projects/{project_id}/topics/{topic_id}/documents",
data=data,
params={guid},
params={"guid": guid},
files=files,
headers=headers,
)
return response.status_code
def get_document(self, project_id="", topic_id="", document_id="") -> str:
def get_document(self, project_id: str = "", topic_id: str = "", document_id: str = "") -> Tuple[int, str]:
headers = {
"Authorization": "Bearer " + self.foundation_client.get_access_token(),
"Authorization": f"Bearer {self.foundation_client.get_access_token()}",
"Content-type": "application/octet-stream",
}
response = requests.get(
f"{self.baseurl}/projects/{project_id}/topics/documents/{document_id}",
headers=headers,
)
with open(os.path.join(self.filepath, f"{project_id}_{topic_id}_{document_id}_document.txt"), "wb") as f:
f.write(response.content.decode("utf-8"))
return response.status_code, response.content
def get_topics_events(self, project_id="") -> list:
response = requests.get(f"{self.baseurl}/projects/{project_id}/topics/documents/{document_id}", headers=headers)
content = response.content.decode("utf-8")
with open(os.path.join(self.filepath, f"{project_id}_{topic_id}_{document_id}_document.txt"), "w") as f:
f.write(content)
return response.status_code, content
def get_topics_events(self, project_id: str = "") -> list[Any]:
return self.get(
f"/projects/{project_id}/topics/events",
{
@@ -575,7 +563,7 @@ class BcfClient:
},
)
def get_topic_events(self, project_id="", topic_id="") -> list:
def get_topic_events(self, project_id: str = "", topic_id: str = "") -> list[Any]:
return self.get(
f"/projects/{project_id}/topics/{topic_id}/events",
{
@@ -584,7 +572,7 @@ class BcfClient:
},
)
def get_comments_events(self, project_id="") -> list:
def get_comments_events(self, project_id: str = "") -> list[Any]:
return self.get(
f"/projects/{project_id}/topics/comments/events",
{
@@ -592,7 +580,7 @@ class BcfClient:
},
)
def get_comment_events(self, project_id="", topic_id="", comment_id="") -> list:
def get_comment_events(self, project_id: str = "", topic_id: str = "", comment_id: str = "") -> list[Any]:
return self.get(
f"/projects/{project_id}/topics/{topic_id}/comments/{comment_id}/events",
{
File diff suppressed because it is too large Load Diff
-200
View File
@@ -1,200 +0,0 @@
# BCF - BCF Python library
# Copyright (C) 2021 Prabhat Singh <singh01prabhat@gmail.com>
#
# This file is part of BCF.
#
# BCF is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BCF 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with BCF. If not, see <http://www.gnu.org/licenses/>.
class Project:
def __init__(self):
self.project_id = ""
self.name = ""
class BimSnippet:
def __init__(self):
self.snippet_type = None
self.is_external = False
self.reference = None
self.reference_schema = None
class DocumentReference:
def __init__(self):
self.description = None
self.document_guid = None
self.url = None
self.guid = None
class RelatedTopic:
def __init__(self):
self.guid = None
class HeaderFile:
def __init__(self):
self.filename = ""
self.date = None
self.reference = ""
self.ifc_project = None
self.ifc_spatial_structure_element = None
self.is_external = True
class Header:
def __init__(self):
self.files = []
class Topic:
def __init__(self):
self.reference_links = []
self.title = ""
self.priority = None
self.index = None # Deprecated, stored, but ignored
self.labels = []
self.creation_date = None
self.creation_author = None
self.modified_date = None
self.modified_author = None
self.due_date = None
self.assigned_to = None
self.stage = None
self.description = None
self.bim_snippet = None
self.document_references = []
self.related_topics = []
self.topic_status = None
self.topic_type = None
self.guid = None
self.header = None
self.comments = {}
self.viewpoints = {}
self.server_assigned_id = ""
class Comment:
def __init__(self):
self.guid = None
self.date = None
self.author = ""
self.comment = ""
self.viewpoint = None
self.modified_date = None
self.modified_author = ""
class ViewSetupHints:
def __init__(self):
self.spaces_visible = False
self.space_boundaries_visible = False
self.openings_visible = False
class Component:
def __init__(self):
self.originating_system = None
self.authoring_tool_id = None
self.ifc_guid = None
class ComponentVisibility:
def __init__(self):
self.exceptions = []
self.default_visibility = False
self.view_setup_hints = None
class Color:
def __init__(self):
self.color = None
self.components = []
class Components:
def __init__(self):
self.selection = []
self.visibility = None
self.coloring = []
class Point:
def __init__(self):
self.x = 0
self.y = 0
self.z = 0
class Direction(Point):
pass
class OrthogonalCamera:
def __init__(self):
self.camera_view_point = Point()
self.camera_direction = Direction()
self.camera_up_vector = Direction()
self.view_to_world_scale = 1.0
self.aspect_ratio = 1.0
class PerspectiveCamera:
def __init__(self):
self.camera_view_point = Point()
self.camera_direction = Direction()
self.camera_up_vector = Direction()
self.field_of_view = 60.0
self.aspect_ratio = 1.0
class Line:
def __init__(self):
self.start_point = Point()
self.end_point = Point()
class ClippingPlane:
def __init__(self):
self.location = Point()
self.direction = Direction()
class Bitmap:
def __init__(self):
self.reference = "" # Only in BCF-XML
self.bitmap_data = None # Only in BCF-API
self.bitmap_format = "PNG" # Enum of png or jpg
self.location = Point()
self.normal = Direction()
self.up = Direction()
self.height = 1.0
class Viewpoint:
def __init__(self):
self.guid = None
self.viewpoint = None
self.snapshot = None
self.index = None
self.components = None # It's not a list, despite the plural name
self.orthogonal_camera = None
self.perspective_camera = None
self.lines = []
self.clipping_planes = []
self.bitmaps = []
+61
View File
@@ -0,0 +1,61 @@
"""BCF XML V3 Documents handler."""
import zipfile
from typing import Any, Optional
import bcf.v3.model as mdl
from bcf.inmemory_zipfile import ZipFileInterface
from bcf.xml_parser import AbstractXmlParserSerializer, XmlParserSerializer
class DocumentsHandler:
"""BCF documents handler."""
def __init__(
self,
definition: mdl.DocumentInfo,
documents: Optional[dict[str, bytes]] = None,
xml_handler: Optional[AbstractXmlParserSerializer] = None,
) -> None:
self.definition = definition
self.documents = documents or {}
self._xml_handler = xml_handler or XmlParserSerializer()
@classmethod
def load(
cls,
zip_file: zipfile.ZipFile,
xml_handler: Optional[AbstractXmlParserSerializer] = None,
) -> Optional["DocumentsHandler"]:
"""
Loads the documents from the given zip file directory.
Args:
zip_path: The directory path inside the zip file.
xml_handler: The xml parser/serializer to use.
Returns:
The documents handler.
"""
xml_handler = xml_handler or XmlParserSerializer()
file_to_open = zipfile.Path(zip_file, "documents.xml")
if not file_to_open.exists():
return None
definition = xml_handler.parse(file_to_open.read_bytes(), mdl.DocumentInfo)
documents = {}
if def_docs := definition.documents:
for document in def_docs.document:
document_path = zipfile.Path(zip_file, f"documents/{document.guid}")
if document_path.exists():
documents[document.filename] = document_path.read_bytes()
return cls(definition, documents=documents)
def save(self, bcf_zip: ZipFileInterface) -> None:
"""Save the documents to the zip file."""
bcf_zip.writestr("documents.xml", self._xml_handler.serialize(self.definition))
if documents := self.definition.documents:
for doc in documents.document:
if doc.filename in self.documents:
bcf_zip.writestr(
f"documents/{doc.guid}",
self.documents[doc.filename],
)
+110
View File
@@ -0,0 +1,110 @@
from bcf.v3.model.documents import Document, DocumentInfo, DocumentInfoDocuments
from bcf.v3.model.extensions import (
Extensions,
ExtensionsPriorities,
ExtensionsSnippetTypes,
ExtensionsStages,
ExtensionsTopicLabels,
ExtensionsTopicStatuses,
ExtensionsTopicTypes,
ExtensionsUsers,
)
from bcf.v3.model.markup import (
BimSnippet,
Comment,
CommentViewpoint,
DocumentReference,
File,
Header,
HeaderFiles,
Markup,
Topic,
TopicComments,
TopicDocumentReferences,
TopicLabels,
TopicReferenceLinks,
TopicRelatedTopics,
TopicRelatedTopicsRelatedTopic,
TopicViewpoints,
ViewPoint,
)
from bcf.v3.model.project import Project, ProjectInfo
from bcf.v3.model.version import Version
from bcf.v3.model.visinfo import (
Bitmap,
BitmapFormat,
ClippingPlane,
Component,
ComponentColoring,
ComponentColoringColor,
ComponentColoringColorComponents,
Components,
ComponentSelection,
ComponentVisibility,
ComponentVisibilityExceptions,
Direction,
Line,
OrthogonalCamera,
PerspectiveCamera,
Point,
ViewSetupHints,
VisualizationInfo,
VisualizationInfoBitmaps,
VisualizationInfoClippingPlanes,
VisualizationInfoLines,
)
__all__ = [
"Document",
"DocumentInfo",
"DocumentInfoDocuments",
"Extensions",
"ExtensionsPriorities",
"ExtensionsSnippetTypes",
"ExtensionsStages",
"ExtensionsTopicLabels",
"ExtensionsTopicStatuses",
"ExtensionsTopicTypes",
"ExtensionsUsers",
"BimSnippet",
"Comment",
"CommentViewpoint",
"DocumentReference",
"File",
"Header",
"HeaderFiles",
"Markup",
"Topic",
"TopicComments",
"TopicDocumentReferences",
"TopicLabels",
"TopicReferenceLinks",
"TopicRelatedTopics",
"TopicRelatedTopicsRelatedTopic",
"TopicViewpoints",
"ViewPoint",
"Project",
"ProjectInfo",
"Version",
"Bitmap",
"BitmapFormat",
"ClippingPlane",
"Component",
"ComponentColoring",
"ComponentColoringColor",
"ComponentColoringColorComponents",
"ComponentSelection",
"ComponentVisibility",
"ComponentVisibilityExceptions",
"Components",
"Direction",
"Line",
"OrthogonalCamera",
"PerspectiveCamera",
"Point",
"ViewSetupHints",
"VisualizationInfo",
"VisualizationInfoBitmaps",
"VisualizationInfoClippingPlanes",
"VisualizationInfoLines",
]
+61
View File
@@ -0,0 +1,61 @@
from dataclasses import dataclass, field
from typing import List, Optional
@dataclass(slots=True, kw_only=True)
class Document:
filename: str = field(
metadata={
"name": "Filename",
"type": "Element",
"namespace": "",
"required": True,
"min_length": 1,
"white_space": "collapse",
}
)
description: Optional[str] = field(
default=None,
metadata={
"name": "Description",
"type": "Element",
"namespace": "",
"min_length": 1,
"white_space": "collapse",
}
)
guid: str = field(
metadata={
"name": "Guid",
"type": "Attribute",
"required": True,
"pattern": r"[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}",
}
)
@dataclass(slots=True, kw_only=True)
class DocumentInfoDocuments:
class Meta:
global_type = False
document: List[Document] = field(
default_factory=list,
metadata={
"name": "Document",
"type": "Element",
"namespace": "",
}
)
@dataclass(slots=True, kw_only=True)
class DocumentInfo:
documents: Optional[DocumentInfoDocuments] = field(
default=None,
metadata={
"name": "Documents",
"type": "Element",
"namespace": "",
}
)
+181
View File
@@ -0,0 +1,181 @@
from dataclasses import dataclass, field
from typing import List, Optional
@dataclass(slots=True, kw_only=True)
class ExtensionsPriorities:
class Meta:
global_type = False
priority: List[str] = field(
default_factory=list,
metadata={
"name": "Priority",
"type": "Element",
"namespace": "",
"min_length": 1,
"white_space": "collapse",
}
)
@dataclass(slots=True, kw_only=True)
class ExtensionsSnippetTypes:
class Meta:
global_type = False
snippet_type: List[str] = field(
default_factory=list,
metadata={
"name": "SnippetType",
"type": "Element",
"namespace": "",
"min_length": 1,
"white_space": "collapse",
}
)
@dataclass(slots=True, kw_only=True)
class ExtensionsStages:
class Meta:
global_type = False
stage: List[str] = field(
default_factory=list,
metadata={
"name": "Stage",
"type": "Element",
"namespace": "",
"min_length": 1,
"white_space": "collapse",
}
)
@dataclass(slots=True, kw_only=True)
class ExtensionsTopicLabels:
class Meta:
global_type = False
topic_label: List[str] = field(
default_factory=list,
metadata={
"name": "TopicLabel",
"type": "Element",
"namespace": "",
"min_length": 1,
"white_space": "collapse",
}
)
@dataclass(slots=True, kw_only=True)
class ExtensionsTopicStatuses:
class Meta:
global_type = False
topic_status: List[str] = field(
default_factory=list,
metadata={
"name": "TopicStatus",
"type": "Element",
"namespace": "",
"min_length": 1,
"white_space": "collapse",
}
)
@dataclass(slots=True, kw_only=True)
class ExtensionsTopicTypes:
class Meta:
global_type = False
topic_type: List[str] = field(
default_factory=list,
metadata={
"name": "TopicType",
"type": "Element",
"namespace": "",
"min_length": 1,
"white_space": "collapse",
}
)
@dataclass(slots=True, kw_only=True)
class ExtensionsUsers:
class Meta:
global_type = False
user: List[str] = field(
default_factory=list,
metadata={
"name": "User",
"type": "Element",
"namespace": "",
"min_length": 1,
"white_space": "collapse",
}
)
@dataclass(slots=True, kw_only=True)
class Extensions:
topic_types: Optional[ExtensionsTopicTypes] = field(
default=None,
metadata={
"name": "TopicTypes",
"type": "Element",
"namespace": "",
}
)
topic_statuses: Optional[ExtensionsTopicStatuses] = field(
default=None,
metadata={
"name": "TopicStatuses",
"type": "Element",
"namespace": "",
}
)
priorities: Optional[ExtensionsPriorities] = field(
default=None,
metadata={
"name": "Priorities",
"type": "Element",
"namespace": "",
}
)
topic_labels: Optional[ExtensionsTopicLabels] = field(
default=None,
metadata={
"name": "TopicLabels",
"type": "Element",
"namespace": "",
}
)
users: Optional[ExtensionsUsers] = field(
default=None,
metadata={
"name": "Users",
"type": "Element",
"namespace": "",
}
)
snippet_types: Optional[ExtensionsSnippetTypes] = field(
default=None,
metadata={
"name": "SnippetTypes",
"type": "Element",
"namespace": "",
}
)
stages: Optional[ExtensionsStages] = field(
default=None,
metadata={
"name": "Stages",
"type": "Element",
"namespace": "",
}
)
+616
View File
@@ -0,0 +1,616 @@
from dataclasses import dataclass, field
from typing import List, Optional
from xsdata.models.datatype import XmlDateTime
@dataclass(slots=True, kw_only=True)
class BimSnippet:
reference: str = field(
metadata={
"name": "Reference",
"type": "Element",
"namespace": "",
"required": True,
"min_length": 1,
"white_space": "collapse",
}
)
reference_schema: str = field(
metadata={
"name": "ReferenceSchema",
"type": "Element",
"namespace": "",
"required": True,
"min_length": 1,
"white_space": "collapse",
}
)
snippet_type: str = field(
metadata={
"name": "SnippetType",
"type": "Attribute",
"required": True,
"min_length": 1,
"white_space": "collapse",
}
)
is_external: bool = field(
default=False,
metadata={
"name": "IsExternal",
"type": "Attribute",
}
)
@dataclass(slots=True, kw_only=True)
class CommentViewpoint:
class Meta:
global_type = False
guid: str = field(
metadata={
"name": "Guid",
"type": "Attribute",
"required": True,
"pattern": r"[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}",
}
)
@dataclass(slots=True, kw_only=True)
class DocumentReference:
document_guid: Optional[str] = field(
default=None,
metadata={
"name": "DocumentGuid",
"type": "Element",
"namespace": "",
"pattern": r"[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}",
}
)
url: Optional[str] = field(
default=None,
metadata={
"name": "Url",
"type": "Element",
"namespace": "",
"min_length": 1,
"white_space": "collapse",
}
)
description: Optional[str] = field(
default=None,
metadata={
"name": "Description",
"type": "Element",
"namespace": "",
"min_length": 1,
"white_space": "collapse",
}
)
guid: str = field(
metadata={
"name": "Guid",
"type": "Attribute",
"required": True,
"pattern": r"[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}",
}
)
@dataclass(slots=True, kw_only=True)
class File:
filename: Optional[str] = field(
default=None,
metadata={
"name": "Filename",
"type": "Element",
"namespace": "",
"min_length": 1,
"white_space": "collapse",
}
)
date: Optional[XmlDateTime] = field(
default=None,
metadata={
"name": "Date",
"type": "Element",
"namespace": "",
}
)
reference: Optional[str] = field(
default=None,
metadata={
"name": "Reference",
"type": "Element",
"namespace": "",
"min_length": 1,
"white_space": "collapse",
}
)
ifc_project: Optional[str] = field(
default=None,
metadata={
"name": "IfcProject",
"type": "Attribute",
"length": 22,
"pattern": r"[0-9A-Za-z_$]*",
}
)
ifc_spatial_structure_element: Optional[str] = field(
default=None,
metadata={
"name": "IfcSpatialStructureElement",
"type": "Attribute",
"length": 22,
"pattern": r"[0-9A-Za-z_$]*",
}
)
is_external: bool = field(
default=True,
metadata={
"name": "IsExternal",
"type": "Attribute",
}
)
@dataclass(slots=True, kw_only=True)
class TopicLabels:
class Meta:
global_type = False
label: List[str] = field(
default_factory=list,
metadata={
"name": "Label",
"type": "Element",
"namespace": "",
"min_length": 1,
"white_space": "collapse",
}
)
@dataclass(slots=True, kw_only=True)
class TopicReferenceLinks:
class Meta:
global_type = False
reference_link: List[str] = field(
default_factory=list,
metadata={
"name": "ReferenceLink",
"type": "Element",
"namespace": "",
"min_length": 1,
"white_space": "collapse",
}
)
@dataclass(slots=True, kw_only=True)
class TopicRelatedTopicsRelatedTopic:
class Meta:
global_type = False
guid: str = field(
metadata={
"name": "Guid",
"type": "Attribute",
"required": True,
"pattern": r"[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}",
}
)
@dataclass(slots=True, kw_only=True)
class ViewPoint:
viewpoint: Optional[str] = field(
default=None,
metadata={
"name": "Viewpoint",
"type": "Element",
"namespace": "",
"min_length": 1,
"white_space": "collapse",
}
)
snapshot: Optional[str] = field(
default=None,
metadata={
"name": "Snapshot",
"type": "Element",
"namespace": "",
"min_length": 1,
"white_space": "collapse",
}
)
index: Optional[int] = field(
default=None,
metadata={
"name": "Index",
"type": "Element",
"namespace": "",
}
)
guid: str = field(
metadata={
"name": "Guid",
"type": "Attribute",
"required": True,
"pattern": r"[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}",
}
)
@dataclass(slots=True, kw_only=True)
class Comment:
date: XmlDateTime = field(
metadata={
"name": "Date",
"type": "Element",
"namespace": "",
"required": True,
}
)
author: str = field(
metadata={
"name": "Author",
"type": "Element",
"namespace": "",
"required": True,
"min_length": 1,
"white_space": "collapse",
}
)
comment: Optional[str] = field(
default=None,
metadata={
"name": "Comment",
"type": "Element",
"namespace": "",
"min_length": 1,
"white_space": "collapse",
}
)
viewpoint: Optional[CommentViewpoint] = field(
default=None,
metadata={
"name": "Viewpoint",
"type": "Element",
"namespace": "",
}
)
modified_date: Optional[XmlDateTime] = field(
default=None,
metadata={
"name": "ModifiedDate",
"type": "Element",
"namespace": "",
}
)
modified_author: Optional[str] = field(
default=None,
metadata={
"name": "ModifiedAuthor",
"type": "Element",
"namespace": "",
"min_length": 1,
"white_space": "collapse",
}
)
guid: str = field(
metadata={
"name": "Guid",
"type": "Attribute",
"required": True,
"pattern": r"[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}",
}
)
@dataclass(slots=True, kw_only=True)
class HeaderFiles:
class Meta:
global_type = False
file: List[File] = field(
default_factory=list,
metadata={
"name": "File",
"type": "Element",
"namespace": "",
}
)
@dataclass(slots=True, kw_only=True)
class TopicDocumentReferences:
class Meta:
global_type = False
document_reference: List[DocumentReference] = field(
default_factory=list,
metadata={
"name": "DocumentReference",
"type": "Element",
"namespace": "",
}
)
@dataclass(slots=True, kw_only=True)
class TopicRelatedTopics:
class Meta:
global_type = False
related_topic: List[TopicRelatedTopicsRelatedTopic] = field(
default_factory=list,
metadata={
"name": "RelatedTopic",
"type": "Element",
"namespace": "",
}
)
@dataclass(slots=True, kw_only=True)
class TopicViewpoints:
class Meta:
global_type = False
view_point: List[ViewPoint] = field(
default_factory=list,
metadata={
"name": "ViewPoint",
"type": "Element",
"namespace": "",
}
)
@dataclass(slots=True, kw_only=True)
class Header:
files: Optional[HeaderFiles] = field(
default=None,
metadata={
"name": "Files",
"type": "Element",
"namespace": "",
}
)
@dataclass(slots=True, kw_only=True)
class TopicComments:
class Meta:
global_type = False
comment: List[Comment] = field(
default_factory=list,
metadata={
"name": "Comment",
"type": "Element",
"namespace": "",
}
)
@dataclass(slots=True, kw_only=True)
class Topic:
reference_links: Optional[TopicReferenceLinks] = field(
default=None,
metadata={
"name": "ReferenceLinks",
"type": "Element",
"namespace": "",
}
)
title: str = field(
metadata={
"name": "Title",
"type": "Element",
"namespace": "",
"required": True,
"min_length": 1,
"white_space": "collapse",
}
)
priority: Optional[str] = field(
default=None,
metadata={
"name": "Priority",
"type": "Element",
"namespace": "",
"min_length": 1,
"white_space": "collapse",
}
)
index: Optional[int] = field(
default=None,
metadata={
"name": "Index",
"type": "Element",
"namespace": "",
}
)
labels: Optional[TopicLabels] = field(
default=None,
metadata={
"name": "Labels",
"type": "Element",
"namespace": "",
}
)
creation_date: XmlDateTime = field(
metadata={
"name": "CreationDate",
"type": "Element",
"namespace": "",
"required": True,
}
)
creation_author: str = field(
metadata={
"name": "CreationAuthor",
"type": "Element",
"namespace": "",
"required": True,
"min_length": 1,
"white_space": "collapse",
}
)
modified_date: Optional[XmlDateTime] = field(
default=None,
metadata={
"name": "ModifiedDate",
"type": "Element",
"namespace": "",
}
)
modified_author: Optional[str] = field(
default=None,
metadata={
"name": "ModifiedAuthor",
"type": "Element",
"namespace": "",
"min_length": 1,
"white_space": "collapse",
}
)
due_date: Optional[XmlDateTime] = field(
default=None,
metadata={
"name": "DueDate",
"type": "Element",
"namespace": "",
}
)
assigned_to: Optional[str] = field(
default=None,
metadata={
"name": "AssignedTo",
"type": "Element",
"namespace": "",
"min_length": 1,
"white_space": "collapse",
}
)
stage: Optional[str] = field(
default=None,
metadata={
"name": "Stage",
"type": "Element",
"namespace": "",
"min_length": 1,
"white_space": "collapse",
}
)
description: Optional[str] = field(
default=None,
metadata={
"name": "Description",
"type": "Element",
"namespace": "",
"min_length": 1,
"white_space": "collapse",
}
)
bim_snippet: Optional[BimSnippet] = field(
default=None,
metadata={
"name": "BimSnippet",
"type": "Element",
"namespace": "",
}
)
document_references: Optional[TopicDocumentReferences] = field(
default=None,
metadata={
"name": "DocumentReferences",
"type": "Element",
"namespace": "",
}
)
related_topics: Optional[TopicRelatedTopics] = field(
default=None,
metadata={
"name": "RelatedTopics",
"type": "Element",
"namespace": "",
}
)
comments: Optional[TopicComments] = field(
default=None,
metadata={
"name": "Comments",
"type": "Element",
"namespace": "",
}
)
viewpoints: Optional[TopicViewpoints] = field(
default=None,
metadata={
"name": "Viewpoints",
"type": "Element",
"namespace": "",
}
)
guid: str = field(
metadata={
"name": "Guid",
"type": "Attribute",
"required": True,
"pattern": r"[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}",
}
)
server_assigned_id: Optional[str] = field(
default=None,
metadata={
"name": "ServerAssignedId",
"type": "Attribute",
"min_length": 1,
"white_space": "collapse",
}
)
topic_type: str = field(
metadata={
"name": "TopicType",
"type": "Attribute",
"required": True,
"min_length": 1,
"white_space": "collapse",
}
)
topic_status: str = field(
metadata={
"name": "TopicStatus",
"type": "Attribute",
"required": True,
"min_length": 1,
"white_space": "collapse",
}
)
@dataclass(slots=True, kw_only=True)
class Markup:
header: Optional[Header] = field(
default=None,
metadata={
"name": "Header",
"type": "Element",
"namespace": "",
}
)
topic: Topic = field(
metadata={
"name": "Topic",
"type": "Element",
"namespace": "",
"required": True,
}
)
+37
View File
@@ -0,0 +1,37 @@
from dataclasses import dataclass, field
from typing import Optional
@dataclass(slots=True, kw_only=True)
class Project:
name: Optional[str] = field(
default=None,
metadata={
"name": "Name",
"type": "Element",
"namespace": "",
"min_length": 1,
"white_space": "collapse",
}
)
project_id: str = field(
metadata={
"name": "ProjectId",
"type": "Attribute",
"required": True,
"min_length": 1,
"white_space": "collapse",
}
)
@dataclass(slots=True, kw_only=True)
class ProjectInfo:
project: Project = field(
metadata={
"name": "Project",
"type": "Element",
"namespace": "",
"required": True,
}
)
+12
View File
@@ -0,0 +1,12 @@
from dataclasses import dataclass, field
@dataclass(slots=True, kw_only=True)
class Version:
version_id: str = field(
metadata={
"name": "VersionId",
"type": "Attribute",
"required": True,
}
)
+524
View File
@@ -0,0 +1,524 @@
from dataclasses import dataclass, field
from enum import Enum
from typing import List, Optional
class BitmapFormat(Enum):
PNG = "png"
JPG = "jpg"
@dataclass(slots=True, kw_only=True)
class Component:
originating_system: Optional[str] = field(
default=None,
metadata={
"name": "OriginatingSystem",
"type": "Element",
"min_length": 1,
"white_space": "collapse",
}
)
authoring_tool_id: Optional[str] = field(
default=None,
metadata={
"name": "AuthoringToolId",
"type": "Element",
"min_length": 1,
"white_space": "collapse",
}
)
ifc_guid: Optional[str] = field(
default=None,
metadata={
"name": "IfcGuid",
"type": "Attribute",
"length": 22,
"pattern": r"[0-9A-Za-z_$]*",
}
)
@dataclass(slots=True, kw_only=True)
class Direction:
x: float = field(
metadata={
"name": "X",
"type": "Element",
"required": True,
}
)
y: float = field(
metadata={
"name": "Y",
"type": "Element",
"required": True,
}
)
z: float = field(
metadata={
"name": "Z",
"type": "Element",
"required": True,
}
)
@dataclass(slots=True, kw_only=True)
class Point:
x: float = field(
metadata={
"name": "X",
"type": "Element",
"required": True,
}
)
y: float = field(
metadata={
"name": "Y",
"type": "Element",
"required": True,
}
)
z: float = field(
metadata={
"name": "Z",
"type": "Element",
"required": True,
}
)
@dataclass(slots=True, kw_only=True)
class ViewSetupHints:
spaces_visible: bool = field(
default=False,
metadata={
"name": "SpacesVisible",
"type": "Attribute",
}
)
space_boundaries_visible: bool = field(
default=False,
metadata={
"name": "SpaceBoundariesVisible",
"type": "Attribute",
}
)
openings_visible: bool = field(
default=False,
metadata={
"name": "OpeningsVisible",
"type": "Attribute",
}
)
@dataclass(slots=True, kw_only=True)
class Bitmap:
format: BitmapFormat = field(
metadata={
"name": "Format",
"type": "Element",
"required": True,
}
)
reference: str = field(
metadata={
"name": "Reference",
"type": "Element",
"required": True,
"min_length": 1,
"white_space": "collapse",
}
)
location: Point = field(
metadata={
"name": "Location",
"type": "Element",
"required": True,
}
)
normal: Direction = field(
metadata={
"name": "Normal",
"type": "Element",
"required": True,
}
)
up: Direction = field(
metadata={
"name": "Up",
"type": "Element",
"required": True,
}
)
height: float = field(
metadata={
"name": "Height",
"type": "Element",
"required": True,
}
)
@dataclass(slots=True, kw_only=True)
class ClippingPlane:
location: Point = field(
metadata={
"name": "Location",
"type": "Element",
"required": True,
}
)
direction: Direction = field(
metadata={
"name": "Direction",
"type": "Element",
"required": True,
}
)
@dataclass(slots=True, kw_only=True)
class ComponentColoringColorComponents:
class Meta:
global_type = False
component: List[Component] = field(
default_factory=list,
metadata={
"name": "Component",
"type": "Element",
"min_occurs": 1,
}
)
@dataclass(slots=True, kw_only=True)
class ComponentSelection:
component: List[Component] = field(
default_factory=list,
metadata={
"name": "Component",
"type": "Element",
}
)
@dataclass(slots=True, kw_only=True)
class ComponentVisibilityExceptions:
class Meta:
global_type = False
component: List[Component] = field(
default_factory=list,
metadata={
"name": "Component",
"type": "Element",
}
)
@dataclass(slots=True, kw_only=True)
class Line:
start_point: Point = field(
metadata={
"name": "StartPoint",
"type": "Element",
"required": True,
}
)
end_point: Point = field(
metadata={
"name": "EndPoint",
"type": "Element",
"required": True,
}
)
@dataclass(slots=True, kw_only=True)
class OrthogonalCamera:
"""
Attributes
camera_view_point:
camera_direction:
camera_up_vector:
view_to_world_scale: view's visible vertical size in meters
aspect_ratio: Proportional relationship between the width and
the height of the view (w/h).
"""
camera_view_point: Point = field(
metadata={
"name": "CameraViewPoint",
"type": "Element",
"required": True,
}
)
camera_direction: Direction = field(
metadata={
"name": "CameraDirection",
"type": "Element",
"required": True,
}
)
camera_up_vector: Direction = field(
metadata={
"name": "CameraUpVector",
"type": "Element",
"required": True,
}
)
view_to_world_scale: float = field(
metadata={
"name": "ViewToWorldScale",
"type": "Element",
"required": True,
}
)
aspect_ratio: float = field(
metadata={
"name": "AspectRatio",
"type": "Element",
"required": True,
"min_exclusive": 0.0,
}
)
@dataclass(slots=True, kw_only=True)
class PerspectiveCamera:
"""
Attributes
camera_view_point:
camera_direction:
camera_up_vector:
field_of_view: Vertical field of view, in degrees. It is
currently limited to a value between 45 and 60 degrees. This
limitation will be dropped in the next release and viewers
should be expect values outside this range in current
implementations.
aspect_ratio: Proportional relationship between the width and
the height of the view (w/h).
"""
camera_view_point: Point = field(
metadata={
"name": "CameraViewPoint",
"type": "Element",
"required": True,
}
)
camera_direction: Direction = field(
metadata={
"name": "CameraDirection",
"type": "Element",
"required": True,
}
)
camera_up_vector: Direction = field(
metadata={
"name": "CameraUpVector",
"type": "Element",
"required": True,
}
)
field_of_view: float = field(
metadata={
"name": "FieldOfView",
"type": "Element",
"required": True,
"min_exclusive": 0.0,
"max_exclusive": 180.0,
}
)
aspect_ratio: float = field(
metadata={
"name": "AspectRatio",
"type": "Element",
"required": True,
"min_exclusive": 0.0,
}
)
@dataclass(slots=True, kw_only=True)
class ComponentColoringColor:
class Meta:
global_type = False
components: ComponentColoringColorComponents = field(
metadata={
"name": "Components",
"type": "Element",
"required": True,
}
)
color: str = field(
metadata={
"name": "Color",
"type": "Attribute",
"required": True,
"pattern": r"[0-9A-Fa-f]{6}([0-9A-Fa-f]{2})?",
}
)
@dataclass(slots=True, kw_only=True)
class ComponentVisibility:
view_setup_hints: Optional[ViewSetupHints] = field(
default=None,
metadata={
"name": "ViewSetupHints",
"type": "Element",
}
)
exceptions: Optional[ComponentVisibilityExceptions] = field(
default=None,
metadata={
"name": "Exceptions",
"type": "Element",
}
)
default_visibility: bool = field(
default=False,
metadata={
"name": "DefaultVisibility",
"type": "Attribute",
}
)
@dataclass(slots=True, kw_only=True)
class VisualizationInfoBitmaps:
class Meta:
global_type = False
bitmap: List[Bitmap] = field(
default_factory=list,
metadata={
"name": "Bitmap",
"type": "Element",
}
)
@dataclass(slots=True, kw_only=True)
class VisualizationInfoClippingPlanes:
class Meta:
global_type = False
clipping_plane: List[ClippingPlane] = field(
default_factory=list,
metadata={
"name": "ClippingPlane",
"type": "Element",
}
)
@dataclass(slots=True, kw_only=True)
class VisualizationInfoLines:
class Meta:
global_type = False
line: List[Line] = field(
default_factory=list,
metadata={
"name": "Line",
"type": "Element",
}
)
@dataclass(slots=True, kw_only=True)
class ComponentColoring:
color: List[ComponentColoringColor] = field(
default_factory=list,
metadata={
"name": "Color",
"type": "Element",
}
)
@dataclass(slots=True, kw_only=True)
class Components:
selection: Optional[ComponentSelection] = field(
default=None,
metadata={
"name": "Selection",
"type": "Element",
}
)
visibility: Optional[ComponentVisibility] = field(
default=None,
metadata={
"name": "Visibility",
"type": "Element",
}
)
coloring: Optional[ComponentColoring] = field(
default=None,
metadata={
"name": "Coloring",
"type": "Element",
}
)
@dataclass(slots=True, kw_only=True)
class VisualizationInfo:
"""
VisualizationInfo documentation.
"""
components: Optional[Components] = field(
default=None,
metadata={
"name": "Components",
"type": "Element",
}
)
orthogonal_camera: Optional[OrthogonalCamera] = field(
default=None,
metadata={
"name": "OrthogonalCamera",
"type": "Element",
}
)
perspective_camera: Optional[PerspectiveCamera] = field(
default=None,
metadata={
"name": "PerspectiveCamera",
"type": "Element",
}
)
lines: Optional[VisualizationInfoLines] = field(
default=None,
metadata={
"name": "Lines",
"type": "Element",
}
)
clipping_planes: Optional[VisualizationInfoClippingPlanes] = field(
default=None,
metadata={
"name": "ClippingPlanes",
"type": "Element",
}
)
bitmaps: Optional[VisualizationInfoBitmaps] = field(
default=None,
metadata={
"name": "Bitmaps",
"type": "Element",
}
)
guid: str = field(
metadata={
"name": "Guid",
"type": "Attribute",
"required": True,
"pattern": r"[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}",
}
)
+206
View File
@@ -0,0 +1,206 @@
"""BCF XML V3 Topic handler."""
import datetime
import uuid
import zipfile
from pathlib import Path
from typing import Any, NoReturn, Optional
import numpy as np
from ifcopenshell import entity_instance
from numpy.typing import NDArray
from xsdata.models.datatype import XmlDateTime
import bcf.v3.model as mdl
from bcf.inmemory_zipfile import ZipFileInterface
from bcf.v3.visinfo import VisualizationInfoHandler
from bcf.xml_parser import AbstractXmlParserSerializer, XmlParserSerializer
class TopicHandler:
"""BCF Topic and related objects handler."""
def __init__(
self,
topic_dir: Optional[zipfile.Path] = None,
xml_handler: Optional[AbstractXmlParserSerializer] = None,
) -> None:
self._markup: Optional[mdl.Markup] = None
self._viewpoints: dict[str, VisualizationInfoHandler] = {}
self._bim_snippet: Optional[bytes] = None
self._xml_handler = xml_handler or XmlParserSerializer()
self._topic_dir = topic_dir
@property
def markup(self) -> Optional[mdl.Markup]:
if not self._markup and self._topic_dir:
markup_path = self._topic_dir.joinpath("markup.bcf")
if markup_path.exists():
self._markup = self._xml_handler.parse(markup_path.read_bytes(), mdl.Markup)
return self._markup
@markup.setter
def markup(self, value: mdl.Markup) -> None:
self._markup = value
@property
def topic(self) -> mdl.Topic:
"""Return the Topic object."""
return self.markup.topic
@property
def guid(self) -> Optional[str]:
"""Return the GUID of the topic."""
if self._markup:
return self.topic.guid
return self._topic_dir.name if self._topic_dir else None
@property
def header(self) -> Optional[mdl.Header]:
"""Return the header of the topic."""
return self.markup.header
@property
def comments(self) -> list[mdl.Comment]:
"""Return the comments of the topic."""
return self.topic.comments.comment if self.topic.comments else []
@property
def bim_snippet(self) -> Optional[bytes]:
if not self._bim_snippet and self._topic_dir:
self._bim_snippet = self._load_bim_snippet()
return self._bim_snippet
@bim_snippet.setter
def bim_snippet(self, value: bytes) -> None:
self._bim_snippet = value
@property
def viewpoints(self) -> dict[str, "VisualizationInfoHandler"]:
if (
not self._viewpoints
and self._topic_dir
and self.topic.viewpoints
and (viewpoints := self.topic.viewpoints.view_point)
):
self._viewpoints = VisualizationInfoHandler.from_topic_viewpoints(self._topic_dir, viewpoints)
return self._viewpoints
def _load_bim_snippet(self) -> Optional[bytes]:
bim_snippet_obj = self.topic.bim_snippet
if bim_snippet_obj and not bim_snippet_obj.is_external and self._topic_dir:
bim_snippet_path = self._topic_dir.joinpath(bim_snippet_obj.reference)
if bim_snippet_path.exists():
return bim_snippet_path.read_bytes()
return None
@classmethod
def create_new(
cls,
title: str,
description: str,
author: str,
topic_type: str = "",
topic_status: str = "",
xml_handler: Optional[AbstractXmlParserSerializer] = None,
) -> "TopicHandler":
"""
Create a new BCF topic.
Args:
title: The title of the topic.
description: The description of the topic.
author: The author of the topic.
topic_type: The type of the topic.
topic_status: The status of the topic.
xml_handler: The XML parser/serializer to use.
Returns:
The BCF topic definition.
"""
creation_date = XmlDateTime.from_datetime(datetime.datetime.now())
guid = str(uuid.uuid4())
topic = mdl.Topic(
title=title,
description=description,
creation_author=author,
creation_date=creation_date,
guid=guid,
topic_type=topic_type,
topic_status=topic_status,
)
markup = mdl.Markup(topic=topic)
obj = cls(topic_dir=Path(guid), xml_handler=xml_handler or XmlParserSerializer())
obj.markup = markup
return obj
def save(self, destination_zip: ZipFileInterface) -> None:
"""
Save the topic to a BCF zip file.
Args:
bcf_zip: The BCF zip file to save to.
"""
topic_dir = self.guid
self._save_xml(destination_zip, self._markup, "markup.bcf")
self._save_viewpoints(destination_zip, topic_dir)
self._save_bim_snippet(destination_zip)
def _save_viewpoints(self, destination_zip: ZipFileInterface, topic_dir: str) -> None:
if not self.topic.viewpoints or not (viewpoints := self.topic.viewpoints.view_point):
return
for vpt in viewpoints:
if vpt.viewpoint:
self.viewpoints[vpt.viewpoint].save(destination_zip, topic_dir, vpt)
def _save_xml(self, destination_zip: ZipFileInterface, item: Any, target: str) -> None:
to_write = self._xml_handler.serialize(item) if item else self._topic_dir.joinpath(target).read_bytes()
destination_zip.writestr(f"{self._topic_dir.name}/{target}", to_write)
def _save_bim_snippet(self, destination_zip: ZipFileInterface) -> None:
snippet = self.topic.bim_snippet
if not snippet or snippet.is_external:
return
ref_filename = Path(snippet.reference).name
if self.bim_snippet:
destination_zip.writestr(f"{self.topic.guid}/{ref_filename}", self.bim_snippet)
def add_viewpoint(self, element: entity_instance) -> None:
"""
Add a viewpoint tergeting an IFC element to the topic.
Args:
element: The IFC element.
"""
new_viewpoint = VisualizationInfoHandler.create_new(element, self._xml_handler)
self.add_visinfo_handler(new_viewpoint)
def add_viewpoint_from_point_and_guids(self, position: NDArray[np.float_], *guids: str) -> None:
"""
Add a viewpoint tergeting an IFC element to the topic.
Args:
element: The IFC element.
"""
vi_handler = VisualizationInfoHandler.create_from_point_and_guids(
position, *guids, xml_handler=self._xml_handler
)
self.add_visinfo_handler(vi_handler)
def add_visinfo_handler(self, new_viewpoint: VisualizationInfoHandler) -> None:
self.viewpoints[new_viewpoint.guid + ".bcfv"] = new_viewpoint
if self.topic.viewpoints is None:
self.topic.viewpoints = mdl.TopicViewpoints()
self.topic.viewpoints.view_point.append(
mdl.ViewPoint(viewpoint=new_viewpoint.guid + ".bcfv", guid=new_viewpoint.guid)
)
def __eq__(self, other: object) -> bool | NoReturn:
return (
(
self.markup == other.markup
and self.viewpoints == other.viewpoints
and self.bim_snippet == other.bim_snippet
)
if isinstance(other, TopicHandler)
else NotImplemented
)
+294
View File
@@ -0,0 +1,294 @@
import uuid
import zipfile
from functools import lru_cache
from typing import Any, Iterable, Optional
import numpy as np
from ifcopenshell import entity_instance
from ifcopenshell.util import placement
from numpy.typing import NDArray
import bcf.v3.model as mdl
from bcf.geometry import (
camera_vectors_from_element_placement,
camera_vectors_from_target_position,
)
from bcf.inmemory_zipfile import ZipFileInterface
from bcf.xml_parser import AbstractXmlParserSerializer, XmlParserSerializer
class VisualizationInfoHandler:
"""Handle the VisualizationInfo and related objects."""
def __init__(
self,
visualization_info: mdl.VisualizationInfo,
snapshot: Optional[bytes] = None,
bitmaps: Optional[dict[str, bytes]] = None,
xml_handler: Optional[AbstractXmlParserSerializer] = None,
) -> None:
self.visualization_info = visualization_info
self.snapshot = snapshot
self.bitmaps = bitmaps or {}
self._xml_handler = xml_handler or XmlParserSerializer()
@property
def guid(self) -> str:
"""Return the GUID of the visualization info."""
return self.visualization_info.guid
@classmethod
def from_topic_viewpoints(
cls,
topic_dir: zipfile.Path,
vps: Iterable[mdl.ViewPoint],
xml_handler: Optional[AbstractXmlParserSerializer] = None,
) -> dict[str, "VisualizationInfoHandler"]:
"""Create VisualizationInfoHandler objects of a Topic's ViewPoints."""
viewpoints = {}
for vpt in vps:
visinfo = cls.load(topic_dir, vpt, xml_handler)
if visinfo and vpt.viewpoint:
viewpoints[vpt.viewpoint] = visinfo
return viewpoints
@classmethod
def load(
cls,
topic_dir: zipfile.Path,
vpt: mdl.ViewPoint,
xml_handler: Optional[AbstractXmlParserSerializer] = None,
) -> Optional["VisualizationInfoHandler"]:
"""
Load the VisualizationInfo and related objects from a BCF zip file.
Args:
topic_dir: The directory in the BCF zip file to load from.
vpt: The ViewPoint to load.
xml_handler: The XML handler to use to parse the VisualizationInfo.
Returns:
The VisualizationInfoHandler object.
"""
visinfo = cls._load_visinfo(topic_dir, vpt.viewpoint, xml_handler)
if not visinfo:
return None
snapshot = cls._load_snapshot(topic_dir, vpt.snapshot)
bitmaps = cls._load_bitmaps(topic_dir, visinfo)
return cls(visinfo, snapshot, bitmaps, xml_handler)
@staticmethod
def _load_visinfo(
topic_dir: zipfile.Path,
vp_name: Optional[str],
xml_handler: Optional[AbstractXmlParserSerializer] = None,
) -> Optional[mdl.VisualizationInfo]:
if not vp_name:
return None
vp_path = topic_dir.joinpath(vp_name)
if vp_path.exists():
xml_handler = xml_handler or XmlParserSerializer()
return xml_handler.parse(vp_path.read_bytes(), mdl.VisualizationInfo)
return None
@staticmethod
def _load_snapshot(topic_dir: zipfile.Path, vp_snapshot: Optional[str]) -> Optional[bytes]:
if vp_snapshot:
snapshot_path = topic_dir.joinpath(vp_snapshot)
if snapshot_path.exists():
return snapshot_path.read_bytes()
return None
@staticmethod
def _load_bitmaps(topic_dir: zipfile.Path, visinfo: Optional[mdl.VisualizationInfo]) -> dict[str, bytes]:
if not visinfo or not (bitmaps := visinfo.bitmaps):
return {}
bitmaps_dict = {}
for bitmap in bitmaps.bitmap:
if not bitmap.reference:
continue
bitmap_path = topic_dir.joinpath(bitmap.reference)
if bitmap_path.exists():
bitmaps_dict[bitmap.reference] = bitmap_path.read_bytes()
return bitmaps_dict
def save(
self,
bcf_zip: ZipFileInterface,
topic_dir: str,
vpt: mdl.ViewPoint,
) -> None:
"""
Save the VisualizationInfo and related objects to a BCF zip file.
Args:
bcf_zip: The BCF zip file to save to.
topic_dir: The directory in the BCF zip file to save to.
vpt: The ViewPoint to save.
"""
if not (vp_name := vpt.viewpoint):
return
self._save_visinfo(bcf_zip, topic_dir, vp_name)
self._save_snapshot(bcf_zip, topic_dir, vpt.snapshot)
self._save_bitmaps(bcf_zip, topic_dir)
def _save_snapshot(self, bcf_zip: ZipFileInterface, topic_dir: str, filename: Optional[str]) -> None:
if self.snapshot and filename:
bcf_zip.writestr(f"{topic_dir}/{filename}", self.snapshot)
def _save_visinfo(self, bcf_zip: ZipFileInterface, topic_dir: str, vp_name: str) -> None:
bcf_zip.writestr(
f"{topic_dir}/{vp_name}",
self._xml_handler.serialize(self.visualization_info),
)
def _save_bitmaps(self, bcf_zip: ZipFileInterface, topic_dir: str) -> None:
if not self.bitmaps:
return
if not (bitmaps_defs := self.visualization_info.bitmaps):
return
for bitmap_def in bitmaps_defs.bitmap:
if not (bitmap_name := bitmap_def.reference):
continue
if bitmap_name in self.bitmaps:
bcf_zip.writestr(f"{topic_dir}/{bitmap_name}", self.bitmaps[bitmap_name])
@classmethod
def create_new(
cls,
element: entity_instance,
xml_handler: Optional[AbstractXmlParserSerializer] = None,
) -> "VisualizationInfoHandler":
"""
Create a new VisualizationInfoHandler object from an IFC element.
Args:
element: The IFC element to point at.
xml_handler: The XML handler to use.
Returns:
The VisualizationInfoHandler object.
"""
xml_handler = xml_handler or XmlParserSerializer()
return cls(visualization_info=build_viewpoint(element), xml_handler=xml_handler)
@classmethod
def create_from_point_and_guids(
cls,
position: NDArray[np.float_],
*guids: str,
xml_handler: Optional[AbstractXmlParserSerializer] = None,
) -> "VisualizationInfoHandler":
"""
Create a new VisualizationInfoHandler object from an IFC element.
Args:
position: target point coordinates.
*guids: One or more IFC element GUID.
xml_handler: The XML handler to use.
Returns:
The VisualizationInfoHandler object.
"""
xml_handler = xml_handler or XmlParserSerializer()
return cls(
visualization_info=build_viewpoint_from_position_and_guids(position, *guids), xml_handler=xml_handler
)
@lru_cache(maxsize=None)
def build_viewpoint(element: entity_instance) -> mdl.VisualizationInfo:
"""
Return a BCF viewpoint of an IFC element.
This function is cached to speedudp the creation of multiple BCF topics regarding the same element.
Args:
element: The IFC element to point at.
Returns:
The BCF viewpoint definition.
"""
elem_placement = placement.get_local_placement(element.ObjectPlacement)
return mdl.VisualizationInfo(
guid=str(uuid.uuid4()),
components=build_components(element.GlobalId),
perspective_camera=build_camera(elem_placement),
)
def build_viewpoint_from_position_and_guids(position: NDArray[np.float_], *guids: str) -> mdl.VisualizationInfo:
"""
Return a BCF viewpoint of an IFC element.
This function is cached to speedudp the creation of multiple BCF topics regarding the same element.
Args:
position: target point coordinates.
*guids: One or more IFC element GUID.
Returns:
The BCF viewpoint definition.
"""
return mdl.VisualizationInfo(
guid=str(uuid.uuid4()),
components=build_components(*guids),
perspective_camera=build_camera_from_vectors(*camera_vectors_from_target_position(position)),
)
def build_components(*guids: str) -> mdl.Components:
"""
Return the BCF components from an IFC element GUID.
Args:
*guids: One or more IFC element GUID.
Returns:
The BCF components definition.
"""
components = [mdl.Component(ifc_guid=guid) for guid in guids]
return mdl.Components(
selection=mdl.ComponentSelection(component=components),
visibility=mdl.ComponentVisibility(default_visibility=True),
)
def build_camera(elem_placement: NDArray[np.float_]) -> mdl.PerspectiveCamera:
"""
Return a BCF camera for an IFC element placement matrix.
Args:
elem_placement: The IFC element placement as a rototranslation matrix.
Returns:
The BCF camera definition.
"""
return build_camera_from_vectors(*camera_vectors_from_element_placement(elem_placement))
def build_camera_from_vectors(
camera_position: NDArray[np.float_], camera_dir: NDArray[np.float_], camera_up: NDArray[np.float_]
) -> mdl.PerspectiveCamera:
"""
Return a BCF camera for an IFC element placement matrix.
Args:
camera_position: camera position array
camera_dir: camera direction versor
camera_up_vector: camera up versor
Returns:
The BCF camera definition.
"""
camera_viewpoint = mdl.Point(x=camera_position[0], y=camera_position[1], z=camera_position[2])
camera_direction = mdl.Direction(x=camera_dir[0], y=camera_dir[1], z=camera_dir[2])
camera_up_vector = mdl.Direction(x=camera_up[0], y=camera_up[1], z=camera_up[2])
return mdl.PerspectiveCamera(
camera_view_point=camera_viewpoint,
camera_direction=camera_direction,
camera_up_vector=camera_up_vector,
aspect_ratio=1.0,
field_of_view=60.0,
)
+83
View File
@@ -0,0 +1,83 @@
"""XML Parser and Serializer factories."""
from typing import Optional, Protocol, Type, TypeVar
from xsdata.formats.dataclass.context import XmlContext
from xsdata.formats.dataclass.parsers import XmlParser
from xsdata.formats.dataclass.serializers import XmlSerializer
from xsdata.formats.dataclass.serializers.config import SerializerConfig
def build_xml_parser(context: Optional[XmlContext] = None) -> XmlParser:
"""Return a parser for an XML file."""
parser = XmlParser(context=context or XmlContext())
parser.register_namespace("xs", "http://www.w3.org/2001/XMLSchema")
return parser
def build_serializer(context: Optional[XmlContext] = None) -> XmlSerializer:
"""Return a serializer for an XML file."""
return XmlSerializer(
config=SerializerConfig(pretty_print=True),
context=context or XmlContext(),
)
T = TypeVar("T")
class AbstractXmlParserSerializer(Protocol):
"""XML Parser and serializer wrapper."""
def parse(self, xml: bytes, clazz: Type[T]) -> T:
"""
Parse an XML file to an object.
Args:
xml: The XML file as bytes.
clazz: The class to parse to.
"""
def serialize(self, obj: T, ns_map: Optional[dict[str, str]] = None) -> str:
"""
Serialize an object to XML.
Args:
obj: The object to serialize.
ns_map: The namespace map to use.
Returns:
The XML as string.
"""
class XmlParserSerializer:
"""XML Parser and serializer wrapper."""
def __init__(self) -> None:
self.context = XmlContext()
self.parser = build_xml_parser(self.context)
self.serializer = build_serializer(self.context)
def parse(self, xml: bytes, clazz: Type[T]) -> T:
"""
Parse an XML file to an object.
Args:
xml: The XML file as bytes.
clazz: The class to parse to.
"""
return self.parser.from_bytes(xml, clazz)
def serialize(self, obj: T, ns_map: Optional[dict[str, str]] = None) -> str:
"""
Serialize an object to XML.
Args:
obj: The object to serialize.
ns_map: The namespace map to use.
Returns:
The XML as string.
"""
ns_map = ns_map or {"xs": "http://www.w3.org/2001/XMLSchema"}
return self.serializer.render(obj, ns_map)
+1
View File
@@ -0,0 +1 @@
"""BCF tests."""
+8
View File
@@ -0,0 +1,8 @@
import pytest
from bcf.xml_parser import XmlParserSerializer
@pytest.fixture(scope="session")
def xml_handler() -> XmlParserSerializer:
return XmlParserSerializer()
Binary file not shown.
View File
+126
View File
@@ -0,0 +1,126 @@
"""BCF XML tests."""
import uuid
from pathlib import Path
from tempfile import TemporaryDirectory
import pytest
import bcf.v2.model as mdl
from bcf.v2.bcfxml import BcfXml
from bcf.v2.topic import TopicHandler
from bcf.v2.visinfo import (
VisualizationInfoHandler,
build_camera_from_vectors,
build_components,
)
from bcf.xml_parser import XmlParserSerializer
@pytest.fixture()
def build_sample(xml_handler: XmlParserSerializer) -> tuple[BcfXml, TopicHandler]:
bcf = BcfXml.create_new("Test project", xml_handler=xml_handler)
orig_th = bcf.add_topic("Test topic", "Test message", "Test author", "Test type")
return bcf, orig_th
def test_bcf_roundtrip(xml_handler, build_sample) -> None:
"""Saving and loading a bcf xml project returns the same objects."""
bcf, orig_th = build_sample
with TemporaryDirectory() as tmp_dir:
file_path = Path(tmp_dir) / "test.bcf"
bcf.save(file_path)
with BcfXml.load(file_path, xml_handler=xml_handler) as parsed:
assert parsed == bcf
parsed_th = parsed.topics[orig_th.guid]
assert parsed_th == orig_th
def test_bcf_edit_saveas(xml_handler, build_sample) -> None:
"""Saving and loading a bcf xml project returns the same objects."""
bcf, orig_th = build_sample
with TemporaryDirectory() as tmp_dir:
file_path = Path(tmp_dir) / "test.bcf"
bcf.save(file_path)
with BcfXml.load(file_path, xml_handler=xml_handler) as parsed:
for th in parsed.topics.values():
th.topic.title = "New Topic Title"
modified_path = Path(tmp_dir) / "edited.bcf"
parsed.save(modified_path)
with BcfXml.load(modified_path, xml_handler=xml_handler) as modified_parsed:
_assert_modified_parsed(modified_parsed, bcf, orig_th, parsed)
def _assert_modified_parsed(modified_parsed, bcf, orig_th, parsed):
assert modified_parsed == bcf
parsed_th = modified_parsed.topics[orig_th.guid]
assert parsed_th.markup != orig_th.markup
assert parsed_th.markup == parsed.topics[orig_th.guid].markup
assert parsed_th.viewpoints == orig_th.viewpoints
assert parsed_th.bim_snippet == orig_th.bim_snippet
def test_bcf_edit(xml_handler, build_sample) -> None:
"""Saving and loading a bcf xml project returns the same objects."""
bcf, orig_th = build_sample
with TemporaryDirectory() as tmp_dir:
file_path = Path(tmp_dir) / "test.bcf"
bcf.save(file_path)
with BcfXml.load(file_path, xml_handler=xml_handler) as parsed:
for th in parsed.topics.values():
th.topic.title = "New Topic Title"
parsed.save()
with BcfXml.load(file_path, xml_handler=xml_handler) as modified_parsed:
_assert_modified_parsed(modified_parsed, bcf, orig_th, parsed)
assert len(modified_parsed.topics) == 1
def test_save_no_filename(build_sample) -> None:
bcf, _ = build_sample
with pytest.raises(ValueError):
bcf.save()
def test_load_no_filename() -> None:
with pytest.raises(ValueError):
BcfXml.load("")
def test_save_keep_open(build_sample) -> None:
bcf, _ = build_sample
with TemporaryDirectory() as tmp_dir:
file_path = Path(tmp_dir) / "test.bcf"
bcf.save(file_path, keep_open=True)
assert bcf._zip_file is not None
bcf._zip_file.close()
def test_massive_bcf(xml_handler) -> None:
bcf = BcfXml.create_new("Test project", xml_handler=xml_handler)
for i in range(100):
th = bcf.add_topic(f"Topic {i:04}", f"Message {i:04}", "Test author", "Test type")
vi = mdl.VisualizationInfo(
guid=str(uuid.uuid4()),
components=build_components(str(uuid.uuid4())),
perspective_camera=build_camera_from_vectors([i, 0, 0], [0, 1, 0], [0, 0, 1]),
)
vh = VisualizationInfoHandler(visualization_info=vi, xml_handler=xml_handler)
th.add_visinfo_handler(vh)
assert len(bcf.topics) == 100
with TemporaryDirectory() as tmp_dir:
file_path = Path(tmp_dir) / "test.bcf"
bcf.save(file_path)
def test_equality_with_wrong_object(build_sample) -> None:
assert build_sample[0] != "Wrong object"
def test_topic_equality_with_wrong_object(build_sample) -> None:
assert build_sample[1] != "Wrong object"
def test_bcf_get_set_version(build_sample) -> None:
bcf = build_sample[0]
assert bcf.version.version_id == "2.1"
bcf.version.version_id = "2.0"
assert bcf.version.version_id == "2.0"
+365
View File
@@ -0,0 +1,365 @@
import json
import os
import zipfile
from pathlib import Path
from xsdata.models.datatype import XmlDateTime
import bcf.v2.model as mdl
from bcf.v2.bcfxml import BcfXml
from bcf.v2.topic import TopicHandler
def test_maximum_information() -> None:
"""
All the info in a BCF is parsed correctly.
Uses sample file from https://github.com/buildingSMART/BCF-XML/
"""
bcf_path = Path(__file__).parent / "MaximumInformation.bcf"
with BcfXml.load(bcf_path) as bcf:
assert_everything_in_place(bcf)
def test_save_maximum_information() -> None:
base_dir = Path(__file__).parent
bcf_path = base_dir / "MaximumInformation.bcf"
target_path = base_dir / "MaximumInformationSaved.bcf"
with BcfXml.load(bcf_path) as bcf:
bcf.save(target_path)
assert target_path.exists()
with BcfXml.load(target_path) as bcf2:
assert_everything_in_place(bcf2)
expected_files = [
"bcf.version",
"project.bcfp",
"7ddc3ef0-0ab7-43f1-918a-45e38b42369c/markup.bcf",
"7ddc3ef0-0ab7-43f1-918a-45e38b42369c/bitmap.png",
"7ddc3ef0-0ab7-43f1-918a-45e38b42369c/tux.png",
"7ddc3ef0-0ab7-43f1-918a-45e38b42369c/JsonElement.json",
"7ddc3ef0-0ab7-43f1-918a-45e38b42369c/Viewpoint_4ab7514b-b216-4d56-98d2-45cf8500ff5a.bcfv",
"7ddc3ef0-0ab7-43f1-918a-45e38b42369c/Viewpoint_9a4a1878-ecbd-4916-83a8-dad82e560231.bcfv",
"7ddc3ef0-0ab7-43f1-918a-45e38b42369c/Viewpoint_fc4019d7-365e-47f3-b6d0-b39fc48f15fc.bcfv",
"7ddc3ef0-0ab7-43f1-918a-45e38b42369c/Snapshot_4ab7514b-b216-4d56-98d2-45cf8500ff5a.png",
"7ddc3ef0-0ab7-43f1-918a-45e38b42369c/Snapshot_9a4a1878-ecbd-4916-83a8-dad82e560231.png",
"7ddc3ef0-0ab7-43f1-918a-45e38b42369c/Snapshot_fc4019d7-365e-47f3-b6d0-b39fc48f15fc.png",
"d1068c81-af04-4546-b63c-348810f6c716/markup.bcf",
"extensions.xsd",
"IfcPile_01.ifc",
"markup.xsd",
]
assert_files_present(target_path, expected_files)
os.unlink(target_path)
def assert_everything_in_place(bcf: BcfXml):
assert bcf.version.version_id == "2.1"
assert bcf.project.name == "BCF API Implementation"
assert bcf.project_info.extension_schema == "extensions.xsd"
assert len(bcf.topics) == 2
assert_first_topic_handler(bcf.topics["7ddc3ef0-0ab7-43f1-918a-45e38b42369c"])
second_th = bcf.topics["d1068c81-af04-4546-b63c-348810f6c716"]
assert second_th.topic == mdl.Topic(
title="Referenced topic",
creation_date=XmlDateTime(2017, 5, 22, 7, 51, 0, 42987900),
creation_author="dangl@iabi.eu",
description="This is just an empty topic that acts as a referenced topic.",
guid="d1068c81-af04-4546-b63c-348810f6c716",
)
def assert_first_topic_handler(topic_handler: TopicHandler):
assert topic_handler.guid == "7ddc3ef0-0ab7-43f1-918a-45e38b42369c"
expected_bs1 = mdl.BimSnippet(
reference="JsonElement.json", reference_schema="http://json-schema.org", snippet_type="JSON"
)
expected_t1 = mdl.Topic(
reference_link=["https://bim--it.net"],
title="Maximum Content",
priority="High",
index=0,
labels=["Structural", "IT Development"],
creation_date=XmlDateTime(2015, 6, 21, 12, 0, 0),
creation_author="dangl@iabi.eu",
modified_date=XmlDateTime(2015, 6, 21, 14, 22, 47),
modified_author="dangl@iabi.eu",
due_date=XmlDateTime(2016, 10, 2, 14, 22, 47),
assigned_to="linhard@iabi.eu",
stage="Construction Start",
description="This is a topic with all informations present.",
bim_snippet=expected_bs1,
document_reference=[
mdl.TopicDocumentReference(
referenced_document="https://github.com/BuildingSMART/BCF-XML",
description="GitHub BCF Specification",
is_external=True,
),
mdl.TopicDocumentReference(
referenced_document="../markup.xsd",
description="Markup.xsd Schema",
is_external=False,
),
],
related_topic=[mdl.TopicRelatedTopic(guid="d1068c81-af04-4546-b63c-348810f6c716")],
guid="7ddc3ef0-0ab7-43f1-918a-45e38b42369c",
topic_type="Structural",
topic_status="Open",
)
assert topic_handler.topic == expected_t1
expected_h1 = mdl.Header(
file=[
mdl.HeaderFile(
filename="IfcPile_01.ifc",
date=XmlDateTime(2014, 10, 27, 16, 27, 27),
reference="../IfcPile_01.ifc",
ifc_project="0M6o7Znnv7hxsbWgeu7oQq",
ifc_spatial_structure_element="23B$bNeGHFQuMYJzvUX0FD",
is_external=False,
)
]
)
assert topic_handler.header == expected_h1
expected_th1_comments = [
mdl.Comment(
date=XmlDateTime(2015, 8, 31, 12, 40, 17),
author="dangl@iabi.eu",
comment="This is an unmodified topic at the uppermost hierarchical level.\nAll times in the XML are marked as UTC times.",
guid="07ccdba0-1736-47e1-807d-67dc6f3addaa",
),
mdl.Comment(
date=XmlDateTime(2015, 8, 31, 14, 0, 1),
author="dangl@iabi.eu",
comment="This comment was a reply to the first comment in BCF v2.0. This is a no longer supported functionality and therefore is to be treated as a regular comment in v2.1.",
guid="a12766c2-61bc-40b4-ab19-e9f45fd0b0bf",
),
mdl.Comment(
date=XmlDateTime(2015, 8, 31, 13, 7, 11),
author="dangl@iabi.eu",
comment="This comment again is in the highest hierarchy level.\nIt references a viewpoint.",
viewpoint=mdl.CommentViewpoint(guid="4ab7514b-b216-4d56-98d2-45cf8500ff5a"),
guid="c2bb5bb0-773d-45dd-bdaa-19a216439ed3",
),
mdl.Comment(
date=XmlDateTime(2015, 8, 31, 15, 42, 58),
author="dangl@iabi.eu",
comment="This comment contained some spllng errs.\nHopefully, the modifier did catch them all.",
modified_date=XmlDateTime(2015, 8, 31, 16, 7, 11),
modified_author="dangl@iabi.eu",
guid="0b843a5c-c3bf-41ef-be98-52a9f7bd9790",
),
]
assert topic_handler.comments == expected_th1_comments
expected_th1_viewpoints = [
mdl.ViewPoint(
viewpoint="Viewpoint_4ab7514b-b216-4d56-98d2-45cf8500ff5a.bcfv",
snapshot="Snapshot_4ab7514b-b216-4d56-98d2-45cf8500ff5a.png",
index=2,
guid="4ab7514b-b216-4d56-98d2-45cf8500ff5a",
),
mdl.ViewPoint(
viewpoint="Viewpoint_fc4019d7-365e-47f3-b6d0-b39fc48f15fc.bcfv",
snapshot="Snapshot_fc4019d7-365e-47f3-b6d0-b39fc48f15fc.png",
index=0,
guid="fc4019d7-365e-47f3-b6d0-b39fc48f15fc",
),
mdl.ViewPoint(
viewpoint="Viewpoint_9a4a1878-ecbd-4916-83a8-dad82e560231.bcfv",
snapshot="Snapshot_9a4a1878-ecbd-4916-83a8-dad82e560231.png",
index=1,
guid="9a4a1878-ecbd-4916-83a8-dad82e560231",
),
]
expected_m1 = mdl.Markup(
header=expected_h1,
topic=expected_t1,
comment=expected_th1_comments,
viewpoints=expected_th1_viewpoints,
)
assert topic_handler.markup == expected_m1
assert json.loads(topic_handler.bim_snippet) == {"Material": "Concrete", "Temperatures": ["Cold", "Hot", "Hotter"]}
assert topic_handler.reference_files["../IfcPile_01.ifc"] is not None
assert_viewpoints(topic_handler.viewpoints)
def assert_viewpoints(viewpoints):
assert len(viewpoints) == 3
expected_selection = mdl.ComponentSelection(
component=[
mdl.Component(ifc_guid="0cSRUx$EX1NRjqiKcYQ$a0"),
mdl.Component(ifc_guid="1jQQiGIAnFzxOUzrdmJYDS"),
mdl.Component(ifc_guid="0fdpeZZEX3FwJ7x0ox5kzF"),
mdl.Component(ifc_guid="23Zwlpd71EyvHlH6OZ77nK"),
mdl.Component(ifc_guid="1OpjQ1Nlv4sQuTxfUC_8zS"),
]
)
expected_exception = mdl.ComponentVisibilityExceptions(
component=[
mdl.Component(ifc_guid="0Gl71cVurFn8bxAOox6M4X"),
mdl.Component(ifc_guid="23Zwlpd71EyvHlH6OZ77nK"),
mdl.Component(ifc_guid="3DvyPxGIn8qR0KDwbL_9r1"),
mdl.Component(ifc_guid="0fdpeZZEX3FwJ7x0ox5kzF"),
mdl.Component(ifc_guid="1OpjQ1Nlv4sQuTxfUC_8zS"),
]
)
expected_coloring = mdl.ComponentColoring(
color=[
mdl.ComponentColoringColor(
component=[
mdl.Component(ifc_guid="0fdpeZZEX3FwJ7x0ox5kzF"),
mdl.Component(ifc_guid="23Zwlpd71EyvHlH6OZ77nK"),
mdl.Component(ifc_guid="1OpjQ1Nlv4sQuTxfUC_8zS"),
mdl.Component(ifc_guid="0cSRUx$EX1NRjqiKcYQ$a0"),
],
color="3498DB",
)
]
)
assert_first_viewpoint(
viewpoints["Viewpoint_4ab7514b-b216-4d56-98d2-45cf8500ff5a.bcfv"],
expected_selection,
expected_exception,
expected_coloring,
)
assert_second_viewpoint(
viewpoints["Viewpoint_fc4019d7-365e-47f3-b6d0-b39fc48f15fc.bcfv"],
expected_selection,
expected_exception,
expected_coloring,
)
assert_third_viewpoint(
viewpoints["Viewpoint_9a4a1878-ecbd-4916-83a8-dad82e560231.bcfv"],
expected_selection,
expected_exception,
expected_coloring,
)
def assert_first_viewpoint(viewpoint, expected_selection, expected_exception, expected_coloring) -> None:
expected_vp = mdl.VisualizationInfo(
components=mdl.Components(
view_setup_hints=mdl.ViewSetupHints(
spaces_visible=True,
space_boundaries_visible=True,
openings_visible=True,
),
selection=expected_selection,
visibility=mdl.ComponentVisibility(
exceptions=expected_exception,
default_visibility=True,
),
coloring=expected_coloring,
),
perspective_camera=mdl.PerspectiveCamera(
camera_view_point=mdl.Point(x=0.43079984188079834, y=69.52057647705078, z=10.666350364685059),
camera_direction=mdl.Direction(x=0.09159398823976517, y=-0.9375035166740417, z=-0.3357048034667969),
camera_up_vector=mdl.Direction(x=0.01938679628074169, y=-0.3353792130947113, z=0.9418837428092957),
field_of_view=60,
),
lines=mdl.VisualizationInfoLines(
line=[
mdl.Line(start_point=mdl.Point(x=0, y=0, z=0), end_point=mdl.Point(x=0, y=0, z=1)),
mdl.Line(start_point=mdl.Point(x=0, y=0, z=1), end_point=mdl.Point(x=0, y=1, z=1)),
mdl.Line(start_point=mdl.Point(x=0, y=1, z=1), end_point=mdl.Point(x=1, y=1, z=1)),
]
),
clipping_planes=mdl.VisualizationInfoClippingPlanes(
clipping_plane=[
mdl.ClippingPlane(location=mdl.Point(x=0, y=0, z=0), direction=mdl.Direction(x=0, y=0, z=1)),
mdl.ClippingPlane(location=mdl.Point(x=0, y=0, z=0), direction=mdl.Direction(x=0, y=1, z=0)),
]
),
bitmap=[
mdl.VisualizationInfoBitmap(
bitmap=mdl.BitmapFormat.PNG,
reference="bitmap.png",
location=mdl.Point(x=10, y=-10, z=7),
normal=mdl.Direction(x=0, y=1, z=0),
up=mdl.Direction(x=0, y=0, z=1),
height=5.0,
),
mdl.VisualizationInfoBitmap(
bitmap=mdl.BitmapFormat.PNG,
reference="tux.png",
location=mdl.Point(x=20, y=-10, z=7),
normal=mdl.Direction(x=0, y=1, z=0),
up=mdl.Direction(x=0, y=0, z=1),
height=5.0,
),
],
guid="8dc86298-9737-40b4-a448-98a9e953293a",
)
assert viewpoint.visualization_info == expected_vp
assert viewpoint.snapshot is not None
def assert_second_viewpoint(viewpoint, expected_selection, expected_exception, expected_coloring) -> None:
expected_vp = mdl.VisualizationInfo(
components=mdl.Components(
view_setup_hints=mdl.ViewSetupHints(
spaces_visible=False,
space_boundaries_visible=False,
openings_visible=False,
),
selection=expected_selection,
visibility=mdl.ComponentVisibility(
exceptions=expected_exception,
default_visibility=False,
),
coloring=expected_coloring,
),
perspective_camera=mdl.PerspectiveCamera(
camera_view_point=mdl.Point(x=-47.18794250488281, y=43.829200744628906, z=10.666350364685059),
camera_direction=mdl.Direction(x=0.6745243072509766, y=-0.6599355936050415, z=-0.33091068267822266),
camera_up_vector=mdl.Direction(x=0.2271970510482788, y=-0.24091780185699463, z=0.9435783624649048),
field_of_view=60,
),
guid="21dd4807-e9af-439e-a980-04d913a6b1ce",
)
assert viewpoint.visualization_info == expected_vp
assert viewpoint.snapshot is not None
def assert_third_viewpoint(viewpoint, expected_selection, expected_exception, expected_coloring) -> None:
expected_vp = mdl.VisualizationInfo(
components=mdl.Components(
view_setup_hints=mdl.ViewSetupHints(
spaces_visible=False,
space_boundaries_visible=False,
openings_visible=True,
),
selection=expected_selection,
visibility=mdl.ComponentVisibility(
exceptions=expected_exception,
default_visibility=True,
),
coloring=expected_coloring,
),
perspective_camera=mdl.PerspectiveCamera(
camera_view_point=mdl.Point(x=-48.974571228027344, y=-64.20051574707031, z=10.666350364685059),
camera_direction=mdl.Direction(x=0.7232745289802551, y=0.5967116951942444, z=-0.3475759029388428),
camera_up_vector=mdl.Direction(x=0.27662187814712524, y=0.21082592010498047, z=0.937567412853241),
field_of_view=60,
),
guid="81daa431-bf01-4a49-80a2-1ab07c177717",
)
assert viewpoint.visualization_info == expected_vp
assert viewpoint.snapshot is not None
def assert_files_present(saved_bcf_path, expected_files):
with zipfile.ZipFile(saved_bcf_path) as bcf_zip:
for file_path in expected_files:
assert zipfile.Path(bcf_zip, file_path).exists()
+36
View File
@@ -0,0 +1,36 @@
import numpy as np
import pytest
from bcf.v2.bcfxml import BcfXml
def test_create_clash_set_bcf() -> None:
bcfxml = BcfXml.create_new("Clash Test")
topic = bcfxml.add_topic("Test", "Test topic", "IfcClash")
topic.add_viewpoint_from_point_and_guids(
np.array([10, 10, 10]),
"firstId",
"secondId",
)
assert len(topic.viewpoints) == 1
guid, vi_handler = next((k, v) for k, v in topic.viewpoints.items())
v_info = vi_handler.visualization_info
assert f"{v_info.guid}.bcfv" == guid
components = v_info.components.selection.component
assert {c.ifc_guid for c in components} == {"firstId", "secondId"}
camera = v_info.perspective_camera
viewpoint = camera.camera_view_point
assert viewpoint.x == 15
assert viewpoint.y == 15
assert viewpoint.z == 15
# default direction is the unit vector of -1, -1, -1
direction = camera.camera_direction
assert direction.x == pytest.approx(-1 / 3**0.5)
assert direction.y == pytest.approx(-1 / 3**0.5)
assert direction.z == pytest.approx(-1 / 3**0.5)
# default
up_vector = camera.camera_up_vector
assert up_vector.x == pytest.approx(-1 / 6**0.5)
assert up_vector.y == pytest.approx(-1 / 6**0.5)
assert up_vector.z == pytest.approx(1 / 1.5**0.5)
assert camera.field_of_view == 60
Binary file not shown.
View File
+121
View File
@@ -0,0 +1,121 @@
"""BCF XML tests."""
import uuid
from pathlib import Path
from tempfile import TemporaryDirectory
import pytest
import bcf.v3.model as mdl
from bcf.v3.bcfxml import BcfXml
from bcf.v3.topic import TopicHandler
from bcf.v3.visinfo import (
VisualizationInfoHandler,
build_camera_from_vectors,
build_components,
)
from bcf.xml_parser import XmlParserSerializer
@pytest.fixture()
def build_sample(xml_handler: XmlParserSerializer) -> tuple[BcfXml, TopicHandler]:
ext = mdl.Extensions(topic_types=mdl.ExtensionsTopicTypes(topic_type=["Test type"]))
bcf = BcfXml.create_new("Test project", extensions=ext, xml_handler=xml_handler)
orig_th = bcf.add_topic("Test topic", "Test message", "Test author", "Test type")
return bcf, orig_th
def test_bcf_roundtrip(xml_handler, build_sample) -> None:
"""Saving and loading a bcf xml project returns the same objects."""
bcf, orig_th = build_sample
with TemporaryDirectory() as tmp_dir:
file_path = Path(tmp_dir) / "test.bcf"
bcf.save(file_path)
with BcfXml.load(file_path, xml_handler=xml_handler) as parsed:
assert parsed == bcf
parsed_th = parsed.topics[orig_th.guid]
assert parsed_th == orig_th
def test_bcf_edit_saveas(xml_handler, build_sample) -> None:
"""Saving and loading a bcf xml project returns the same objects."""
bcf, orig_th = build_sample
with TemporaryDirectory() as tmp_dir:
file_path = Path(tmp_dir) / "test.bcf"
bcf.save(file_path)
with BcfXml.load(file_path, xml_handler=xml_handler) as parsed:
for th in parsed.topics.values():
th.topic.title = "New Topic Title"
modified_path = Path(tmp_dir) / "edited.bcf"
parsed.save(modified_path)
with BcfXml.load(modified_path, xml_handler=xml_handler) as modified_parsed:
_assert_modified_parsed(modified_parsed, bcf, orig_th, parsed)
def _assert_modified_parsed(modified_parsed, bcf, orig_th, parsed):
assert modified_parsed == bcf
parsed_th = modified_parsed.topics[orig_th.guid]
assert parsed_th.markup != orig_th.markup
assert parsed_th.markup == parsed.topics[orig_th.guid].markup
assert parsed_th.viewpoints == orig_th.viewpoints
assert parsed_th.bim_snippet == orig_th.bim_snippet
def test_bcf_edit(xml_handler, build_sample) -> None:
"""Saving and loading a bcf xml project returns the same objects."""
bcf, orig_th = build_sample
with TemporaryDirectory() as tmp_dir:
file_path = Path(tmp_dir) / "test.bcf"
bcf.save(file_path)
with BcfXml.load(file_path, xml_handler=xml_handler) as parsed:
for th in parsed.topics.values():
th.topic.title = "New Topic Title"
parsed.save()
with BcfXml.load(file_path, xml_handler=xml_handler) as modified_parsed:
_assert_modified_parsed(modified_parsed, bcf, orig_th, parsed)
assert len(modified_parsed.topics) == 1
def test_save_no_filename(build_sample) -> None:
bcf, _ = build_sample
with pytest.raises(ValueError):
bcf.save()
def test_load_no_filename() -> None:
with pytest.raises(ValueError):
BcfXml.load("")
def test_save_keep_open(build_sample) -> None:
bcf, _ = build_sample
with TemporaryDirectory() as tmp_dir:
file_path = Path(tmp_dir) / "test.bcf"
bcf.save(file_path, keep_open=True)
assert bcf._zip_file is not None
bcf._zip_file.close()
def test_massive_bcf(xml_handler) -> None:
ext = mdl.Extensions(topic_types=mdl.ExtensionsTopicTypes(topic_type=["Test type"]))
bcf = BcfXml.create_new("Test project", extensions=ext, xml_handler=xml_handler)
for i in range(100):
th = bcf.add_topic(f"Topic {i:04}", f"Message {i:04}", "Test author", "Test type")
vi = mdl.VisualizationInfo(
guid=str(uuid.uuid4()),
components=build_components(str(uuid.uuid4())),
perspective_camera=build_camera_from_vectors([i, 0, 0], [0, 1, 0], [0, 0, 1]),
)
vh = VisualizationInfoHandler(visualization_info=vi, xml_handler=xml_handler)
th.add_visinfo_handler(vh)
assert len(bcf.topics) == 100
with TemporaryDirectory() as tmp_dir:
file_path = Path(tmp_dir) / "test.bcf"
bcf.save(file_path)
def test_equality_with_wrong_object(build_sample) -> None:
assert build_sample[0] != "Wrong object"
def test_topic_equality_with_wrong_object(build_sample) -> None:
assert build_sample[1] != "Wrong object"
+230
View File
@@ -0,0 +1,230 @@
import json
import os
import zipfile
from pathlib import Path
from xsdata.models.datatype import XmlDateTime
import bcf.v3.model as mdl
from bcf.v3.bcfxml import BcfXml
from bcf.v3.topic import TopicHandler
def test_doc_ref_internal() -> None:
"""
All the info in a BCF is parsed correctly.
Uses sample file from https://github.com/buildingSMART/BCF-XML/
"""
bcf_path = Path(__file__).parent / "Document reference internal.bcf"
with BcfXml.load(bcf_path) as bcf:
assert_everything_in_place(bcf)
def test_doc_ref_internal_save() -> None:
base_dir = Path(__file__).parent
bcf_path = base_dir / "Document reference internal.bcf"
target_path = base_dir / "Document reference internal Saved.bcf"
with BcfXml.load(bcf_path) as bcf:
bcf.save(target_path)
assert target_path.exists()
with BcfXml.load(target_path) as bcf2:
assert_everything_in_place(bcf2)
assert_files_present(target_path)
os.unlink(target_path)
def assert_everything_in_place(bcf: BcfXml):
assert bcf.version.version_id == "3.0"
assert bcf.project.name == "BCF 3.0 test cases"
assert bcf.project.project_id == "de894a86-3a08-4ea0-b2d1-6c222b5602d1"
assert len(bcf.topics) == 1
topic_handler = bcf.topics["8ac9822a-761a-4deb-9f39-f61286acbf6a"]
expected_h1 = mdl.Header(
files=mdl.HeaderFiles(
file=[
mdl.File(
filename="BCF-ARK",
date=XmlDateTime(2021, 1, 4, 9, 37, 45),
reference="https://bimsync.com/project/f5fbb3c695274d1890036bf64f77eb71/revisions/007afab57f264d2296aae0a452486ae1",
is_external=True,
),
mdl.File(
filename="BCF-MEP",
date=XmlDateTime(2017, 8, 7, 9, 51, 34),
reference="https://bimsync.com/project/f5fbb3c695274d1890036bf64f77eb71/revisions/a21ed391f9e046a2bb2bc879c48f1d48",
is_external=True,
),
]
)
)
assert topic_handler.header == expected_h1
expected_comments = [
mdl.Comment(
date=XmlDateTime(2021, 2, 17, 9, 16, 4, 160_000_000),
author="Architect@example.com",
comment="A comment",
viewpoint=mdl.CommentViewpoint(guid="20ad2ff7-ceac-4d4b-b288-ad92a0f65182"),
guid="c045ef04-324d-4c36-9ac8-831f2a15c6d6",
),
]
assert topic_handler.comments == expected_comments
expected_viewpoints = [
mdl.ViewPoint(
viewpoint="Viewpoint_20ad2ff7-ceac-4d4b-b288-ad92a0f65182.bcfv",
snapshot="Snapshot_20ad2ff7-ceac-4d4b-b288-ad92a0f65182.png",
guid="20ad2ff7-ceac-4d4b-b288-ad92a0f65182",
),
]
expected_topic = mdl.Topic(
reference_links=mdl.TopicReferenceLinks(),
title="Document Reference Internal",
labels=mdl.TopicLabels(),
creation_date=XmlDateTime(2021, 2, 17, 9, 16, 4, 160_000_000),
creation_author="Architect@example.com",
modified_date=XmlDateTime(2021, 2, 17, 9, 16, 4, 160_000_000),
assigned_to="OtherUser@doe.com",
document_references=mdl.TopicDocumentReferences(
document_reference=[
mdl.DocumentReference(
document_guid="b1d1b7f0-60b9-457d-ad12-16e0fb997bc5",
description="ThisIsADocument.txt",
guid="048e898f-555f-47c3-a273-6c664fb2ef69",
),
],
),
related_topics=mdl.TopicRelatedTopics(),
comments=mdl.TopicComments(comment=expected_comments),
viewpoints=mdl.TopicViewpoints(view_point=expected_viewpoints),
guid="8ac9822a-761a-4deb-9f39-f61286acbf6a",
server_assigned_id="6",
topic_type="ERROR",
topic_status="OPEN",
)
assert topic_handler.topic == expected_topic
expected_m1 = mdl.Markup(
header=expected_h1,
topic=expected_topic,
)
assert topic_handler.markup == expected_m1
assert topic_handler.bim_snippet is None
assert_viewpoints(topic_handler.viewpoints)
def assert_viewpoints(viewpoints):
assert len(viewpoints) == 1
expected_vp = mdl.VisualizationInfo(
components=mdl.Components(
selection=mdl.ComponentSelection(),
visibility=mdl.ComponentVisibility(
view_setup_hints=mdl.ViewSetupHints(
spaces_visible=False,
space_boundaries_visible=False,
openings_visible=False,
),
exceptions=mdl.ComponentVisibilityExceptions(
component=[
mdl.Component(ifc_guid="1m5wAJelDFdhn6qBdOGjos", originating_system="https://bimsync.com"),
mdl.Component(ifc_guid="1bbI761TbBCOoIa5Kt6PXt", originating_system="https://bimsync.com"),
]
),
default_visibility=True,
),
coloring=mdl.ComponentColoring(),
),
perspective_camera=mdl.PerspectiveCamera(
camera_view_point=mdl.Point(x=23.112083480037754, y=-25.45574897560043, z=18.52519828443092),
camera_direction=mdl.Direction(x=-0.6289237666876837, y=0.5933487270610425, z=-0.5023864884632315),
camera_up_vector=mdl.Direction(x=-0.36542566067664123, y=0.3447553774917179, z=0.8646431727652648),
field_of_view=60,
aspect_ratio=1,
),
lines=mdl.VisualizationInfoLines(),
clipping_planes=mdl.VisualizationInfoClippingPlanes(),
bitmaps=mdl.VisualizationInfoBitmaps(),
guid="20ad2ff7-ceac-4d4b-b288-ad92a0f65182",
)
viewpoint = viewpoints["Viewpoint_20ad2ff7-ceac-4d4b-b288-ad92a0f65182.bcfv"]
assert viewpoint.visualization_info == expected_vp
assert viewpoint.snapshot is not None
def assert_second_viewpoint(viewpoint, expected_selection, expected_exception, expected_coloring) -> None:
expected_vp = mdl.VisualizationInfo(
components=mdl.Components(
view_setup_hints=mdl.ViewSetupHints(
spaces_visible=False,
space_boundaries_visible=False,
openings_visible=False,
),
selection=expected_selection,
visibility=mdl.ComponentVisibility(
exceptions=expected_exception,
default_visibility=False,
),
coloring=expected_coloring,
),
perspective_camera=mdl.PerspectiveCamera(
camera_view_point=mdl.Point(x=-47.18794250488281, y=43.829200744628906, z=10.666350364685059),
camera_direction=mdl.Direction(x=0.6745243072509766, y=-0.6599355936050415, z=-0.33091068267822266),
camera_up_vector=mdl.Direction(x=0.2271970510482788, y=-0.24091780185699463, z=0.9435783624649048),
field_of_view=60,
),
guid="21dd4807-e9af-439e-a980-04d913a6b1ce",
)
assert viewpoint.visualization_info == expected_vp
assert viewpoint.snapshot is not None
def assert_third_viewpoint(viewpoint, expected_selection, expected_exception, expected_coloring) -> None:
expected_vp = mdl.VisualizationInfo(
components=mdl.Components(
view_setup_hints=mdl.ViewSetupHints(
spaces_visible=False,
space_boundaries_visible=False,
openings_visible=True,
),
selection=expected_selection,
visibility=mdl.ComponentVisibility(
exceptions=expected_exception,
default_visibility=True,
),
coloring=expected_coloring,
),
perspective_camera=mdl.PerspectiveCamera(
camera_view_point=mdl.Point(x=-48.974571228027344, y=-64.20051574707031, z=10.666350364685059),
camera_direction=mdl.Direction(x=0.7232745289802551, y=0.5967116951942444, z=-0.3475759029388428),
camera_up_vector=mdl.Direction(x=0.27662187814712524, y=0.21082592010498047, z=0.937567412853241),
field_of_view=60,
),
guid="81daa431-bf01-4a49-80a2-1ab07c177717",
)
assert viewpoint.visualization_info == expected_vp
assert viewpoint.snapshot is not None
def assert_files_present(saved_bcf_path):
expected_files = [
"bcf.version",
"project.bcfp",
"documents.xml",
"extensions.xml",
"documents/b1d1b7f0-60b9-457d-ad12-16e0fb997bc5",
"8ac9822a-761a-4deb-9f39-f61286acbf6a/markup.bcf",
"8ac9822a-761a-4deb-9f39-f61286acbf6a/Viewpoint_20ad2ff7-ceac-4d4b-b288-ad92a0f65182.bcfv",
"8ac9822a-761a-4deb-9f39-f61286acbf6a/Snapshot_20ad2ff7-ceac-4d4b-b288-ad92a0f65182.png",
]
with zipfile.ZipFile(saved_bcf_path) as bcf_zip:
for file_path in expected_files:
assert zipfile.Path(bcf_zip, file_path).exists()
+36
View File
@@ -0,0 +1,36 @@
import numpy as np
import pytest
from bcf.v3.bcfxml import BcfXml
def test_create_clash_set_bcf() -> None:
bcfxml = BcfXml.create_new("Clash Test")
topic = bcfxml.add_topic("Test", "Test topic", "IfcClash")
topic.add_viewpoint_from_point_and_guids(
np.array([10, 10, 10]),
"firstId",
"secondId",
)
assert len(topic.viewpoints) == 1
guid, vi_handler = next((k, v) for k, v in topic.viewpoints.items())
v_info = vi_handler.visualization_info
assert f"{v_info.guid}.bcfv" == guid
components = v_info.components.selection.component
assert {c.ifc_guid for c in components} == {"firstId", "secondId"}
camera = v_info.perspective_camera
viewpoint = camera.camera_view_point
assert viewpoint.x == 15
assert viewpoint.y == 15
assert viewpoint.z == 15
# default direction is the unit vector of -1, -1, -1
direction = camera.camera_direction
assert direction.x == pytest.approx(-1 / 3**0.5)
assert direction.y == pytest.approx(-1 / 3**0.5)
assert direction.z == pytest.approx(-1 / 3**0.5)
# default
up_vector = camera.camera_up_vector
assert up_vector.x == pytest.approx(-1 / 6**0.5)
assert up_vector.y == pytest.approx(-1 / 6**0.5)
assert up_vector.z == pytest.approx(1 / 1.5**0.5)
assert camera.field_of_view == 60
+50 -17
View File
@@ -47,7 +47,7 @@ PYNUMBER:=39
endif
ifeq ($(PYVERSION), py310)
PYLIBDIR:=python3.10
PYNUMBER:=31
PYNUMBER:=310
endif
ifeq ($(PLATFORM), linux)
@@ -56,7 +56,7 @@ HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.7.5/download/linux-64/hpp
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.5/download/linux-64/eigenpy-2.6.5-py37h95e2c48_0.tar.bz2
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/linux-64/boost-1.74.0-py37h0379df6_3.tar.bz2
LXML_URL:=https://files.pythonhosted.org/packages/30/c0/d0526314971fc661b083ab135747dc68446a3022686da8c16d25fcf6ef07/lxml-4.6.3-cp37-cp37m-manylinux2014_x86_64.whl
SHAPELY_URL:=https://files.pythonhosted.org/packages/a2/73/07f507b539527579c026ed5f411c35e287685ce794c4dfb7edf6675cdc24/Shapely-1.8.4-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl
SHAPELY_URL:=https://files.pythonhosted.org/packages/1d/a4/931d0780f31f3ea8c4f9ef6464a2825137c5241e6707a5fb03bef760a7eb/shapely-2.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
PILLOW_URL:=https://files.pythonhosted.org/packages/ed/d5/c2e84e1e36ab8ebea033921d5886a056c77e18bab5ab1051fcc22de2e8a2/Pillow-9.2.0-cp37-cp37m-manylinux_2_28_x86_64.whl
endif
ifeq ($(PYVERSION), py39)
@@ -64,7 +64,7 @@ HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.7.5/download/linux-64/hpp
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.5/download/linux-64/eigenpy-2.6.5-py39h5aed9d1_0.tar.bz2
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/linux-64/boost-1.74.0-py39h5472131_3.tar.bz2
LXML_URL:=https://files.pythonhosted.org/packages/19/d9/a69c6aff5673554df48120565a14a50eaa41d29ae03b02faa0b023666318/lxml-4.6.3-cp39-cp39-manylinux2014_x86_64.whl
SHAPELY_URL:=https://files.pythonhosted.org/packages/08/12/5af915b2bc0470f4a652ef9978557a0fa8b705b122ccaec6626029e1674c/Shapely-1.8.4-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl
SHAPELY_URL:=https://files.pythonhosted.org/packages/2d/f2/8ec281d357e8bb7d08dc8d727f0e4c8ef3dae7d3fa75c69c8e452bb82d50/shapely-2.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
PILLOW_URL:=https://files.pythonhosted.org/packages/01/61/3ff85fb4bb596ce3d223c8fcf93c8df5c12bc8899dfb4fb3cb1c5b20dd5f/Pillow-9.2.0-cp39-cp39-manylinux_2_28_x86_64.whl
endif
ifeq ($(PYVERSION), py310)
@@ -72,7 +72,7 @@ HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.8.0/download/linux-64/hpp
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.11/download/linux-64/eigenpy-2.6.11-py310hf3e5c9c_0.tar.bz2
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/linux-64/boost-1.74.0-py310h7c3ba0c_5.tar.bz2
LXML_URL:=https://files.pythonhosted.org/packages/25/1e/19b46d8e8881fe0df2e20945d51919eeb1817836d62a90efa8506530e45c/lxml-4.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl
SHAPELY_URL:=https://files.pythonhosted.org/packages/60/20/c6321e3da92a4ad9495727c9aa8c423e47295572ccd35ece82d1ac013e28/Shapely-1.8.4-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl
SHAPELY_URL:=https://files.pythonhosted.org/packages/a8/a5/403728b5614b28083f6424dfdefec5fcf58068495fb03bb08532671c642f/shapely-2.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
PILLOW_URL:=https://files.pythonhosted.org/packages/f6/51/320986ebd6d46a0e95c2240468ced73153b691ce07617078bcdf30c609ec/Pillow-9.2.0-cp310-cp310-manylinux_2_28_x86_64.whl
endif
ASSIMP_URL:=https://anaconda.org/conda-forge/assimp/5.0.1/download/linux-64/assimp-5.0.1-hedfc422_6.tar.bz2
@@ -86,7 +86,7 @@ HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.7.5/download/osx-64/hpp-f
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.5/download/osx-64/eigenpy-2.6.5-py37h0695097_0.tar.bz2
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/osx-64/boost-1.74.0-py37hd79e0ac_3.tar.bz2
LXML_URL:=https://files.pythonhosted.org/packages/1e/3e/f0abc15d5dac50939bccc589aae336d5ead4c72e7ad1039a2e0f3630ea92/lxml-4.6.3-cp37-cp37m-macosx_10_9_x86_64.whl
SHAPELY_URL:=https://files.pythonhosted.org/packages/47/a9/7c644e71638bff2bbaa87f05ee62fdf6b4552a55e74bbf731d6210b6d436/Shapely-1.8.4-cp37-cp37m-macosx_10_9_x86_64.whl
SHAPELY_URL:=https://files.pythonhosted.org/packages/e6/7d/4923f27c340339e1c896c77cafc8ed672c8d381a025bbab6c6ddcba27e8f/shapely-2.0.1-cp37-cp37m-macosx_10_9_x86_64.whl
PILLOW_URL:=https://files.pythonhosted.org/packages/88/49/c26fc3b5b0e82bdc9d8751d6b939da29327b0d98f7c3b95a575cbfed2743/Pillow-9.2.0-cp37-cp37m-macosx_10_10_x86_64.whl
endif
ifeq ($(PYVERSION), py39)
@@ -94,7 +94,7 @@ HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.7.5/download/osx-64/hpp-f
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.5/download/osx-64/eigenpy-2.6.5-py39h5405915_0.tar.bz2
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/osx-64/boost-1.74.0-py39ha641261_3.tar.bz2
LXML_URL:=https://files.pythonhosted.org/packages/b8/74/a71f7ad72e8db54ce899efab84507b801660750cbbfa6a39e6717557d36a/lxml-4.6.3-cp39-cp39-macosx_10_9_x86_64.whl
SHAPELY_URL:=https://files.pythonhosted.org/packages/f5/f9/bf6cc34b02b3c7d3777c5e47fa57d2f69387613bbd8decdf3577a9d83a83/Shapely-1.8.4-cp39-cp39-macosx_10_9_x86_64.whl
SHAPELY_URL:=https://files.pythonhosted.org/packages/36/a4/7e542a209f862f967d7cb8e939eff155f4294a27d17e16441fb8bdd51a2c/shapely-2.0.1-cp39-cp39-macosx_10_9_x86_64.whl
PILLOW_URL:=https://files.pythonhosted.org/packages/88/7a/ddfe28b485b623361457d4783007c1f9ba83a87f93e7fec32f64793efb6c/Pillow-9.2.0-cp39-cp39-macosx_10_10_x86_64.whl
endif
ifeq ($(PYVERSION), py310)
@@ -102,7 +102,7 @@ HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.8.0/download/osx-64/hpp-f
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.11/download/osx-64/eigenpy-2.6.11-py310hc03097c_0.tar.bz2
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/osx-64/boost-1.74.0-py310h509978a_5.tar.bz2
LXML_URL:=https://files.pythonhosted.org/packages/a1/44/17b7dac7a18807d30e2fe10c3328c152808f5464565e230bfd0e77f178c6/lxml-4.8.0-cp310-cp310-macosx_10_15_x86_64.whl
SHAPELY_URL:=https://files.pythonhosted.org/packages/f5/a8/02347c450b5507b9a7a2c15ad6a029613149468fc55ac2100ab215666159/Shapely-1.8.4-cp310-cp310-macosx_10_9_x86_64.whl
SHAPELY_URL:=https://files.pythonhosted.org/packages/1f/2a/dc3353c2431cf53e8d04bb8fba27e584410ca3435c9c85f76d71bf0c0e80/shapely-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl
PILLOW_URL:=https://files.pythonhosted.org/packages/d8/60/b13c00d403f34110e96c1b5c0afa73ce461efe3fe960c3a7e3e7fe190d82/Pillow-9.2.0-cp310-cp310-macosx_10_10_x86_64.whl
endif
ASSIMP_URL:=https://anaconda.org/conda-forge/assimp/5.0.1/download/osx-64/assimp-5.0.1-h1224e73_6.tar.bz2
@@ -117,7 +117,7 @@ HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.7.5/download/osx-64/hpp-f
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.5/download/osx-64/eigenpy-2.6.5-py37h0695097_0.tar.bz2
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/osx-64/boost-1.74.0-py37hd79e0ac_3.tar.bz2
LXML_URL:=https://anaconda.org/conda-forge/lxml/4.9.1/download/osx-64/lxml-4.9.1-py37h994c40b_0.tar.bz2
SHAPELY_URL:=https://files.pythonhosted.org/packages/47/a9/7c644e71638bff2bbaa87f05ee62fdf6b4552a55e74bbf731d6210b6d436/Shapely-1.8.4-cp37-cp37m-macosx_10_9_x86_64.whl
SHAPELY_URL:=https://files.pythonhosted.org/packages/e6/7d/4923f27c340339e1c896c77cafc8ed672c8d381a025bbab6c6ddcba27e8f/shapely-2.0.1-cp37-cp37m-macosx_10_9_x86_64.whl
PILLOW_URL:=https://files.pythonhosted.org/packages/aa/bc/21097cd891dd2fa02f2b3d767e02e883e026482e59d29975d1bc30024aa3/Pillow-9.2.0-cp39-cp39-macosx_11_0_arm64.whl
endif
ifeq ($(PYVERSION), py39)
@@ -125,7 +125,7 @@ HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.7.5/download/osx-arm64/hp
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.7/download/osx-arm64/eigenpy-2.6.7-py39ha69f3c1_0.tar.bz2
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/osx-arm64/boost-1.78.0-py39h2550fe3_0.tar.bz2
LXML_URL:=https://anaconda.org/conda-forge/lxml/4.9.1/download/osx-arm64/lxml-4.9.1-py39h9eb174b_0.tar.bz2
SHAPELY_URL:=https://files.pythonhosted.org/packages/8e/60/8186995a96c5a27c9b79c015bbdd89c6d9a6df9988f86f3f1027ecd4a56a/Shapely-1.8.4-cp39-cp39-macosx_11_0_arm64.whl
SHAPELY_URL:=https://files.pythonhosted.org/packages/ea/aa/45fbd031edf3149cb767d8b9f9db45d5faf0324d743c6b8fb0298cc022d0/shapely-2.0.1-cp39-cp39-macosx_11_0_arm64.whl
PILLOW_URL:=https://files.pythonhosted.org/packages/aa/bc/21097cd891dd2fa02f2b3d767e02e883e026482e59d29975d1bc30024aa3/Pillow-9.2.0-cp39-cp39-macosx_11_0_arm64.whl
endif
ifeq ($(PYVERSION), py310)
@@ -133,7 +133,7 @@ HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.8.0/download/osx-arm64/hp
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.9/download/osx-arm64/eigenpy-2.6.9-py310habfc766_1.tar.bz2
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/osx-arm64/boost-1.78.0-py310h4566fd1_0.tar.bz2
LXML_URL:=https://anaconda.org/conda-forge/lxml/4.9.1/download/osx-arm64/lxml-4.9.1-py310h02f21da_0.tar.bz2
SHAPELY_URL:=https://files.pythonhosted.org/packages/ba/d0/75cd36c81a1aa26dd6d37cf7ae1c79b0bc2d379e0f32c359bcb00b14e5c3/Shapely-1.8.4-cp310-cp310-macosx_11_0_arm64.whl
SHAPELY_URL:=https://files.pythonhosted.org/packages/ec/41/d59208743e737184e1b403e95a937aebb022b8459e99efbcd5208fc8be46/shapely-2.0.1-cp310-cp310-macosx_11_0_arm64.whl
PILLOW_URL:=https://files.pythonhosted.org/packages/0c/5f/117b653cad585f3aedfe0de996c292e67d4b020ed77f652e5a6c8c24f908/Pillow-9.2.0-cp310-cp310-macosx_11_0_arm64.whl
endif
ASSIMP_URL:=https://anaconda.org/conda-forge/assimp/5.0.1/download/osx-arm64/assimp-5.0.1-h0f81e16_7.tar.bz2
@@ -147,7 +147,7 @@ HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.7.5/download/win-64/hpp-f
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.5/download/win-64/eigenpy-2.6.5-py37h2c32e34_0.tar.bz2
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/win-64/boost-1.74.0-py37h3b38789_3.tar.bz2
LXML_URL:=https://files.pythonhosted.org/packages/9e/5e/171ee9d40a600f565fe691ec5bf7596247ec62cfb2edc00c91afe8ea837b/lxml-4.6.3-cp37-cp37m-win_amd64.whl
SHAPELY_URL:=https://files.pythonhosted.org/packages/ca/4d/85b912d7947261e6b3218b01946bcd7c2efb2bbafaf517c6fe1dc3bb8399/Shapely-1.8.4-cp37-cp37m-win_amd64.whl
SHAPELY_URL:=https://files.pythonhosted.org/packages/e2/87/b8b8d8d57b429b01aa56b7d723075c09f33c988b8091bb6f790c83436909/shapely-2.0.1-cp37-cp37m-win_amd64.whl
PILLOW_URL:=https://files.pythonhosted.org/packages/69/f5/9e802159d78b2eaf26bf1f8b94648605993f5ca7247ac8870f065063fc40/Pillow-9.2.0-cp37-cp37m-win_amd64.whl
endif
ifeq ($(PYVERSION), py39)
@@ -155,7 +155,7 @@ HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.7.5/download/win-64/hpp-f
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.5/download/win-64/eigenpy-2.6.5-py39h3ce40e6_0.tar.bz2
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/win-64/boost-1.74.0-py39hefe7e4c_3.tar.bz2
LXML_URL:=https://files.pythonhosted.org/packages/72/d4/426ecb8849c47c3e370c87aa0ac05d85768df917ffea27fcd6686a5e6495/lxml-4.6.3-cp39-cp39-win_amd64.whl
SHAPELY_URL:=https://files.pythonhosted.org/packages/72/c3/0d52812db25ca71203f6203891277e6ee4f0244b21a36c062123ece15f36/Shapely-1.8.4-cp39-cp39-win_amd64.whl
SHAPELY_URL:=https://files.pythonhosted.org/packages/a7/ae/eab645c4075093584b7a65ab71cb8ff4563a015bd935c9b55dba14b2c1eb/shapely-2.0.1-cp39-cp39-win_amd64.whl
PILLOW_URL:=https://files.pythonhosted.org/packages/19/3f/b4d4bcf05dbcbe07f2e9613a8f4180c297395e73a91d8ad22c32c6624f8c/Pillow-9.2.0-cp39-cp39-win_amd64.whl
endif
ifeq ($(PYVERSION), py310)
@@ -163,7 +163,7 @@ HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.8.0/download/win-64/hpp-f
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.11/download/win-64/eigenpy-2.6.11-py310hbd43d28_0.tar.bz2
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/win-64/boost-1.74.0-py310hc781a3c_5.tar.bz2
LXML_URL:=https://files.pythonhosted.org/packages/f6/71/65c80a4caa1617a4c6e8fe1500cebb179db96232e2f623bfe6a1f4294e39/lxml-4.8.0-cp310-cp310-win_amd64.whl
SHAPELY_URL:=https://files.pythonhosted.org/packages/24/b5/46d13799804b5fe9ede2eca8b19023410e29b78baca0bff727671a1df8c1/Shapely-1.8.4-cp310-cp310-win_amd64.whl
SHAPELY_URL:=https://files.pythonhosted.org/packages/81/8a/7ac076a86b2632f1872284c5e60ed5f2fc26094875a85b35d9fa17b52504/shapely-2.0.1-cp310-cp310-win_amd64.whl
PILLOW_URL:=https://files.pythonhosted.org/packages/02/55/67a3c17b9e7d972ed8c246f104da99ca4f3ea42fba566697e479011b84b6/Pillow-9.2.0-cp310-cp310-win_amd64.whl
endif
ASSIMP_URL:=https://anaconda.org/conda-forge/assimp/5.0.1/download/win-64/assimp-5.0.1-hc2aa0de_6.tar.bz2
@@ -171,6 +171,13 @@ OCTOMAP_URL:=https://anaconda.org/conda-forge/octomap/1.9.7/download/win-64/octo
ZLIB_URL:=https://anaconda.org/conda-forge/zlib/1.2.11/download/win-64/zlib-1.2.11-h62dcd97_1010.tar.bz2
endif
.PHONY: bump
bump:
cd . && $(SED) "s/$(OLD)/$(NEW)/" Makefile
cd ../ifcopenshell-python/ && $(SED) "s/$(OLD)/$(NEW)/" Makefile
cd ../ifcopenshell-python/docs/ifcconvert/ && $(SED) "s/$(OLD)/$(NEW)/" installation.rst
cd ../ifcopenshell-python/docs/ifcopenshell-python/ && $(SED) "s/$(OLD)/$(NEW)/" installation.rst
.PHONY: dist
dist:
ifndef PLATFORM
@@ -182,7 +189,7 @@ endif
cp -r blenderbim/* dist/blenderbim/
# Provides IfcOpenShell Python functionality
cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-$(PYNUMBER)-v0.7.0-fa6bbf2-$(PLATFORM)64.zip
cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-$(PYNUMBER)-v0.7.0-476ab50-$(PLATFORM)64.zip
cd dist/working && unzip ifcopenshell-python*
cp -r dist/working/ifcopenshell dist/blenderbim/libs/site/packages/
@@ -200,6 +207,7 @@ endif
cp -r dist/working/IfcOpenShell-0.7.0/src/ifcopenshell-python/ifcopenshell/api/* dist/blenderbim/libs/site/packages/ifcopenshell/api/
cp dist/working/IfcOpenShell-0.7.0/src/ifcopenshell-python/ifcopenshell/entity_instance.py dist/blenderbim/libs/site/packages/ifcopenshell/
cp dist/working/IfcOpenShell-0.7.0/src/ifcopenshell-python/ifcopenshell/file.py dist/blenderbim/libs/site/packages/ifcopenshell/
cp dist/working/IfcOpenShell-0.7.0/src/ifcopenshell-python/ifcopenshell/validate.py dist/blenderbim/libs/site/packages/ifcopenshell/
# Provides bcf functionality
cp -r dist/working/IfcOpenShell-0.7.0/src/bcf/src/bcf dist/blenderbim/libs/site/packages/
# Provides IFCClash functionality
@@ -229,6 +237,7 @@ endif
rm -rf dist/working
# Provides Mustache templating in construction documentation
# TODO: remove this dependency, it seems overkill and we seem to get by with str replaces
mkdir dist/working
cd dist/working && wget https://files.pythonhosted.org/packages/3f/e7/8750ba6c6101d6aa5ceeb20c013adf2c6f3554a12c71d75654b468404bfa/pystache-0.6.0.tar.gz
cd dist/working && tar -xzvf pystache*
@@ -270,6 +279,13 @@ endif
cp -r dist/working/decorator-5.0.9/src/decorator.py dist/blenderbim/libs/site/packages/
rm -rf dist/working
# Provides audio playback for costing
mkdir -p dist/working
git clone https://github.com/Andrej730/aud.git --branch master-reduced-size --depth 1 dist/working/aud
cd dist/working/aud && git checkout 529af208fbcb707c387bfb681d2605c0b6112de7
cp -r dist/working/aud/aud dist/blenderbim/libs/site/packages/
rm -rf dist/working
# Provides jsgantt-improved supports for web-based construction sequencing gantt charts
mkdir dist/working
cd dist/working && wget https://raw.githubusercontent.com/jsGanttImproved/jsgantt-improved/master/dist/jsgantt.js
@@ -312,6 +328,13 @@ endif
cp -r dist/working/xmlschema-2.0.2/xmlschema dist/blenderbim/libs/site/packages/
rm -rf dist/working
# Required by bcf
mkdir dist/working
cd dist/working && wget https://files.pythonhosted.org/packages/ad/c7/17c9d16320d8e2cfdb27d2fd298b5e8f7a3f211025b0c1bc7a39a85bd690/xsdata-22.11.tar.gz
cd dist/working && tar -xzvf xsdata*
cp -r dist/working/xsdata-22.11/xsdata dist/blenderbim/libs/site/packages/
rm -rf dist/working
# Required by bcf
mkdir dist/working
cd dist/working && wget https://files.pythonhosted.org/packages/11/bc/5afb61dd5d863e5cf77cd952445c50c17e65953405986f19e97e4389692a/elementpath-3.0.2.tar.gz
@@ -346,10 +369,10 @@ endif
cd dist/working && cp *.whl shapely.zip && unzip shapely.zip
cp -r dist/working/shapely dist/blenderbim/libs/site/packages/
ifeq ($(PLATFORM), win)
cp -r dist/working/Shapely.libs dist/blenderbim/libs/site/packages/
cp -r dist/working/shapely.libs dist/blenderbim/libs/site/packages/
endif
ifeq ($(PLATFORM), linux)
cp -r dist/working/Shapely.libs dist/blenderbim/libs/site/packages/
cp -r dist/working/shapely.libs dist/blenderbim/libs/site/packages/
endif
rm -rf dist/working
@@ -371,11 +394,13 @@ endif
cp -r dist/working/tqdm-4.50.0/tqdm dist/blenderbim/libs/site/packages/
rm -rf dist/working
# Required by IFCCityJSON
mkdir dist/working
cd dist/working && $(PYTHON) -m venv env
cd dist/working && mkdir site-packages
# Required by IFCCityJSON
cd dist/working && . env/bin/activate && pip install cjio --target=./site-packages
# Provides express rule validation for ifcopenshell.validate
cd dist/working && . env/bin/activate && pip install pytest --target=./site-packages
cd dist/working/site-packages/ && rm -r *dist-info*
cp -r dist/working/site-packages/* dist/blenderbim/libs/site/packages/
rm -rf dist/working
@@ -521,6 +546,7 @@ endif
rm -rf dist/working
# Required by IFCCOBie for XLSX support
# TODO: see if we can replace this with openpyxl which does both read/write
mkdir dist/working
cd dist/working && wget https://files.pythonhosted.org/packages/0c/bc/82d6783f83f65f56d8b77d052773c4a2f952fa86385f0cd54e1e006658d7/XlsxWriter-1.2.9.tar.gz
cd dist/working && tar -xzvf XlsxWriter*
@@ -624,6 +650,13 @@ endif
cd dist/working/ && cp -r pkg_resources ../blenderbim/libs/site/packages/
rm -rf dist/working
# Required for hipped roof generation
mkdir dist/working
cd dist/working && wget https://github.com/prochitecture/bpypolyskel/archive/refs/heads/master.zip
cd dist/working && unzip master.zip
cp -r dist/working/bpypolyskel-master/bpypolyskel dist/blenderbim/libs/site/packages/
rm -rf dist/working
cd dist/blenderbim && $(SED) "s/999999/$(VERSION)/" __init__.py
cd dist && zip -r blenderbim-$(VERSION)-$(PYVERSION)-$(PLATFORM).zip ./*
rm -rf dist/blenderbim
@@ -91,10 +91,12 @@ classes = [
operator.RemoveIfcFile,
operator.SelectDataDir,
operator.SelectIfcFile,
operator.ReloadSelectedIfcFile,
operator.SelectSchemaDir,
operator.SelectURIAttribute,
operator.EditBlenderCollection,
operator.BIM_OT_open_webbrowser,
operator.BIM_OT_show_description,
prop.StrProperty,
operator.BIM_OT_enum_property_search, # /!\ Register AFTER prop.StrProperty
prop.ObjProperty,
@@ -115,6 +117,7 @@ classes = [
ui.BIM_PT_project_info,
ui.BIM_PT_project_setup,
ui.BIM_PT_collaboration,
ui.BIM_PT_selection,
ui.BIM_PT_geometry,
ui.BIM_PT_services,
ui.BIM_PT_structural,
@@ -159,6 +162,8 @@ def register():
bpy.types.Camera.BIMMeshProperties = bpy.props.PointerProperty(type=prop.BIMMeshProperties)
bpy.types.PointLight.BIMMeshProperties = bpy.props.PointerProperty(type=prop.BIMMeshProperties)
bpy.types.SCENE_PT_unit.append(ui.ifc_units)
if hasattr(bpy.types, "UI_MT_button_context_menu"):
bpy.types.UI_MT_button_context_menu.append(ui.draw_custom_context_menu)
for mod in modules.values():
mod.register()
@@ -179,6 +184,8 @@ def unregister():
del bpy.types.Camera.BIMMeshProperties
del bpy.types.PointLight.BIMMeshProperties
bpy.types.SCENE_PT_unit.remove(ui.ifc_units)
if hasattr(bpy.types, "UI_MT_button_context_menu"):
bpy.types.UI_MT_button_context_menu.remove(ui.draw_custom_context_menu)
for mod in reversed(list(modules.values())):
mod.unregister()
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
@@ -1,13 +1,13 @@
ISO-10303-21;
HEADER;
FILE_DESCRIPTION(('ViewDefinition[DesignTransferView]'),'2;1');
FILE_NAME('/dev/null','2022-10-14T18:00:26+11:00',(),(),'IfcOpenShell v0.7.0-dc67287d','IfcOpenShell v0.7.0-dc67287d','Nobody');
FILE_NAME('/dev/null','2023-02-04T13:08:05+11:00',(),(),'IfcOpenShell v0.7.0-07ee62eb9','IfcOpenShell v0.7.0-07ee62eb9','Nobody');
FILE_SCHEMA(('IFC4'));
ENDSEC;
DATA;
#1=IFCPROJECT('0yOx2IKFL4HAbf$IjzE0Kp',$,'BlenderBIM Demo',$,$,$,$,(#12,#16),#7);
#2=IFCPROJECTLIBRARY('2jUXWJsnH2pxWl9Bl3Ljhd',$,'BlenderBIM Demo Library',$,$,$,$,$,$);
#3=IFCRELDECLARES('0Rn4fWoen7xB9hskGGEfEy',$,$,$,#1,(#2));
#1=IFCPROJECT('3ubSuEuavEPfXTf4v0gmbA',$,'BlenderBIM Demo',$,$,$,$,(#12,#16),#7);
#2=IFCPROJECTLIBRARY('1Qq43hq0v3B9Ko7EGW0hJ5',$,'BlenderBIM Demo Library',$,$,$,$,$,$);
#3=IFCRELDECLARES('0hU2lTJfX70AKkhyURrbam',$,$,$,#1,(#2));
#4=IFCSIUNIT(*,.LENGTHUNIT.,$,.METRE.);
#5=IFCSIUNIT(*,.AREAUNIT.,$,.SQUARE_METRE.);
#6=IFCSIUNIT(*,.VOLUMEUNIT.,$,.CUBIC_METRE.);
@@ -17,89 +17,89 @@ DATA;
#10=IFCDIRECTION((1.,0.,0.));
#11=IFCAXIS2PLACEMENT3D(#8,#9,#10);
#12=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Model',3,1.E-05,#11,$);
#13=IFCCARTESIANPOINT((0.,0.,0.));
#14=IFCDIRECTION((1.,0.,0.));
#13=IFCCARTESIANPOINT((0.,0.));
#14=IFCDIRECTION((1.,0.));
#15=IFCAXIS2PLACEMENT2D(#13,#14);
#16=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Plan',2,1.E-05,#15,$);
#17=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Body','Model',*,*,*,*,#12,$,.MODEL_VIEW.,$);
#18=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Annotation','Plan',*,*,*,*,#16,$,.PLAN_VIEW.,$);
#18=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Body','Plan',*,*,*,*,#16,$,.PLAN_VIEW.,$);
#19=IFCMATERIAL('Unknown',$,$);
#20=IFCWALLTYPE('0D2QFqmA18fvA6TUteIRXc',$,'WAL50',$,$,$,$,$,$,.NOTDEFINED.);
#20=IFCWALLTYPE('3yGmcLQT96CPWJXcV3vNPJ',$,'WAL50',$,$,$,$,$,$,.NOTDEFINED.);
#21=IFCMATERIALLAYERSET((#23),$,$);
#22=IFCRELASSOCIATESMATERIAL('0WbN7alqv8ix3w4MOdyl8F',$,$,$,(#20),#21);
#22=IFCRELASSOCIATESMATERIAL('0JMzROKjj7i8EKtMLZ2nXy',$,$,$,(#20),#21);
#23=IFCMATERIALLAYER(#19,0.05,$,$,$,$,$);
#24=IFCRELDECLARES('13kodoVGr08Bn7WfGWndV_',$,$,$,#2,(#37,#41,#76,#47,#58,#91,#25,#225,#71,#159,#29,#62,#95,#20,#53,#86,#33,#66,#81));
#25=IFCWALLTYPE('0qRxb2OqjDEfFI9i7b6qAw',$,'WAL100',$,$,$,$,$,$,.NOTDEFINED.);
#24=IFCRELDECLARES('1TTgyh7ynBQ8XpgpGSU6ao',$,$,$,#2,(#41,#76,#47,#58,#91,#25,#159,#225,#71,#29,#62,#95,#20,#53,#86,#33,#66,#81,#37));
#25=IFCWALLTYPE('20jY5HfLH4LBwMrDEoTdML',$,'WAL100',$,$,$,$,$,$,.NOTDEFINED.);
#26=IFCMATERIALLAYERSET((#28),$,$);
#27=IFCRELASSOCIATESMATERIAL('3BDm6HH553QhwCW7FPmjrR',$,$,$,(#25),#26);
#27=IFCRELASSOCIATESMATERIAL('2sICOUhur5WgrNUOi1pvhV',$,$,$,(#25),#26);
#28=IFCMATERIALLAYER(#19,0.1,$,$,$,$,$);
#29=IFCWALLTYPE('1KMENjmzj7cf_am9SYEaXW',$,'WAL200',$,$,$,$,$,$,.NOTDEFINED.);
#29=IFCWALLTYPE('0xhGqQenH5Ov9L_CiLywqN',$,'WAL200',$,$,$,$,$,$,.NOTDEFINED.);
#30=IFCMATERIALLAYERSET((#32),$,$);
#31=IFCRELASSOCIATESMATERIAL('1$36L06DrDYx1sZ9tBQ_WE',$,$,$,(#29),#30);
#31=IFCRELASSOCIATESMATERIAL('2pOhWSsWT4bQwtduxS8HNJ',$,$,$,(#29),#30);
#32=IFCMATERIALLAYER(#19,0.2,$,$,$,$,$);
#33=IFCWALLTYPE('2y5Y19etz3rhrt8P45fuGA',$,'WAL300',$,$,$,$,$,$,.NOTDEFINED.);
#33=IFCWALLTYPE('1HrJFcNVH4XQJIRrEmUD6Z',$,'WAL300',$,$,$,$,$,$,.NOTDEFINED.);
#34=IFCMATERIALLAYERSET((#36),$,$);
#35=IFCRELASSOCIATESMATERIAL('1WSONmheTD9xhiluqtfktr',$,$,$,(#33),#34);
#35=IFCRELASSOCIATESMATERIAL('0cAK6yBnXAnO$5LYcU3QQl',$,$,$,(#33),#34);
#36=IFCMATERIALLAYER(#19,0.3,$,$,$,$,$);
#37=IFCCOVERINGTYPE('1pAuiYopL5hxCATiIPV8QG',$,'COV10',$,$,$,$,$,$,.NOTDEFINED.);
#37=IFCCOVERINGTYPE('11MVutxgn20fTEibOhUiS8',$,'COV10',$,$,$,$,$,$,.NOTDEFINED.);
#38=IFCMATERIALLAYERSET((#40),$,$);
#39=IFCRELASSOCIATESMATERIAL('0ysRf97LT8fw28mDwt2vop',$,$,$,(#37),#38);
#39=IFCRELASSOCIATESMATERIAL('1$dvrpTAn6oPSVqcMtsvmV',$,$,$,(#37),#38);
#40=IFCMATERIALLAYER(#19,0.01,$,$,$,$,$);
#41=IFCCOVERINGTYPE('00LbT8PQf3XwImYkj_i_$4',$,'COV20',$,$,(#45),$,$,$,.NOTDEFINED.);
#41=IFCCOVERINGTYPE('1DWvr7poP3ggxFE7vxOot2',$,'COV20',$,$,(#45),$,$,$,.NOTDEFINED.);
#42=IFCMATERIALLAYERSET((#44),$,$);
#43=IFCRELASSOCIATESMATERIAL('0Kvr3quG19xw6$528t4NQi',$,$,$,(#41),#42);
#43=IFCRELASSOCIATESMATERIAL('2uf2C_54501xFgLlk_jqMG',$,$,$,(#41),#42);
#44=IFCMATERIALLAYER(#19,0.02,$,$,$,$,$);
#45=IFCPROPERTYSET('1IZqsXecj8NQZcNAIAY48P',$,'EPset_Parametric',$,(#46));
#45=IFCPROPERTYSET('0fSGHPSBH9U8H_ph5xpH65',$,'EPset_Parametric',$,(#46));
#46=IFCPROPERTYSINGLEVALUE('LayerSetDirection',$,IFCLABEL('AXIS2'),$);
#47=IFCCOVERINGTYPE('10YNsLMi9409CbEVFmDkJt',$,'COV30',$,$,(#51),$,$,$,.NOTDEFINED.);
#47=IFCCOVERINGTYPE('1ayqDfj8XDxB$G7ZFyRagM',$,'COV30',$,$,(#51),$,$,$,.NOTDEFINED.);
#48=IFCMATERIALLAYERSET((#50),$,$);
#49=IFCRELASSOCIATESMATERIAL('2jZVWXllT9ugYrdBaIBAnt',$,$,$,(#47),#48);
#49=IFCRELASSOCIATESMATERIAL('01vVE$oHTA3BLmh2imRGuu',$,$,$,(#47),#48);
#50=IFCMATERIALLAYER(#19,0.03,$,$,$,$,$);
#51=IFCPROPERTYSET('2M2_M1eSXFYR8vFE96Ecy1',$,'EPset_Parametric',$,(#52));
#51=IFCPROPERTYSET('3v7cOUGgT0qxrxlQkzwSIR',$,'EPset_Parametric',$,(#52));
#52=IFCPROPERTYSINGLEVALUE('LayerSetDirection',$,IFCLABEL('AXIS3'),$);
#53=IFCRAMPTYPE('09P6PVRiT44vIjup23Olfq',$,'RAM200',$,$,$,$,$,$,.NOTDEFINED.);
#53=IFCRAMPTYPE('0VEv7VZVnDzxHXC$QE2Crm',$,'RAM200',$,$,$,$,$,$,.NOTDEFINED.);
#54=IFCMATERIALLAYERSET((#56),$,$);
#55=IFCRELASSOCIATESMATERIAL('2Ghy1OMS1FLAOmdzrFp4rp',$,$,$,(#53),#54);
#55=IFCRELASSOCIATESMATERIAL('0ToQjR2aLAdgGPuqu6UH1o',$,$,$,(#53),#54);
#56=IFCMATERIALLAYER(#19,0.2,$,$,$,$,$);
#57=IFCCIRCLEPROFILEDEF(.AREA.,$,$,0.3);
#58=IFCPILETYPE('0X0OrCsUnDUPVeAu90N_K$',$,'P1',$,$,$,$,$,$,.NOTDEFINED.);
#58=IFCPILETYPE('26kmCVC0H4lOhCGn6cCLSQ',$,'P1',$,$,$,$,$,$,.NOTDEFINED.);
#59=IFCMATERIALPROFILESET($,$,(#61),$);
#60=IFCRELASSOCIATESMATERIAL('1FiRvXL$nBkuT8B3FfiGT9',$,$,$,(#58),#59);
#60=IFCRELASSOCIATESMATERIAL('2Ev3ucMBPDtwntRLG_4cBU',$,$,$,(#58),#59);
#61=IFCMATERIALPROFILE($,$,#19,#57,$,$);
#62=IFCSLABTYPE('1helZ086v1o8JD3Ow1eWAy',$,'FLR150',$,$,$,$,$,$,.NOTDEFINED.);
#62=IFCSLABTYPE('3cLRr85lHDAPjHswaO7574',$,'FLR150',$,$,$,$,$,$,.NOTDEFINED.);
#63=IFCMATERIALLAYERSET((#65),$,$);
#64=IFCRELASSOCIATESMATERIAL('3BbM02xKH6h97dBaMJp$Rr',$,$,$,(#62),#63);
#64=IFCRELASSOCIATESMATERIAL('07afdGZLHCFej$qXPfCLn9',$,$,$,(#62),#63);
#65=IFCMATERIALLAYER(#19,0.2,$,$,$,$,$);
#66=IFCSLABTYPE('1YAagoi$9D_hPiWtssbkcG',$,'FLR250',$,$,$,$,$,$,.NOTDEFINED.);
#66=IFCSLABTYPE('2WiuelwGf3Ah3gnVgDGEAn',$,'FLR250',$,$,$,$,$,$,.NOTDEFINED.);
#67=IFCMATERIALLAYERSET((#69),$,$);
#68=IFCRELASSOCIATESMATERIAL('0m1oulHjT1u889pxJG$XG_',$,$,$,(#66),#67);
#68=IFCRELASSOCIATESMATERIAL('03XoY4bYD4ZwZgvg3JqIIK',$,$,$,(#66),#67);
#69=IFCMATERIALLAYER(#19,0.3,$,$,$,$,$);
#70=IFCRECTANGLEPROFILEDEF(.AREA.,$,$,0.5,0.6);
#71=IFCCOLUMNTYPE('1lzPzyyFfFYRgjqMHnKZLx',$,'C1',$,$,$,$,$,$,.NOTDEFINED.);
#70=IFCRECTANGLEPROFILEDEF(.AREA.,'500x600',$,0.5,0.6);
#71=IFCCOLUMNTYPE('187MdL9O13_OzlwsOaT0zY',$,'C1',$,$,$,$,$,$,.NOTDEFINED.);
#72=IFCMATERIALPROFILESET($,$,(#74),$);
#73=IFCRELASSOCIATESMATERIAL('26fuP2VpfEnhlUsk0K1i4y',$,$,$,(#71),#72);
#73=IFCRELASSOCIATESMATERIAL('1CMVTn08P2Tu8afsZHQiUk',$,$,$,(#71),#72);
#74=IFCMATERIALPROFILE($,$,#19,#70,$,$);
#75=IFCCIRCLEHOLLOWPROFILEDEF(.AREA.,$,$,0.25,0.005);
#76=IFCCOLUMNTYPE('1qqu_K2Az3yvZSOapZmUrZ',$,'C2',$,$,$,$,$,$,.NOTDEFINED.);
#75=IFCCIRCLEHOLLOWPROFILEDEF(.AREA.,'500.0x5.0 CHS',$,0.25,0.005);
#76=IFCCOLUMNTYPE('3e6tIcskjDOuj1_UAWcT87',$,'C2',$,$,$,$,$,$,.NOTDEFINED.);
#77=IFCMATERIALPROFILESET($,$,(#79),$);
#78=IFCRELASSOCIATESMATERIAL('2YQz9JCGXAogjkmN3RZeKI',$,$,$,(#76),#77);
#78=IFCRELASSOCIATESMATERIAL('0ahT30pYb7zQ968gWY9q1A',$,$,$,(#76),#77);
#79=IFCMATERIALPROFILE($,$,#19,#75,$,$);
#80=IFCRECTANGLEHOLLOWPROFILEDEF(.AREA.,$,$,0.075,0.15,0.005,0.005,0.005);
#81=IFCCOLUMNTYPE('1rnXRgQ3X6pfk5O9j4HCLm',$,'C3',$,$,$,$,$,$,.NOTDEFINED.);
#80=IFCRECTANGLEHOLLOWPROFILEDEF(.AREA.,'150x75x2.0 RHS',$,0.075,0.15,0.002,0.005,0.005);
#81=IFCCOLUMNTYPE('0ZxXYg0BTFg8sbqXr50qPW',$,'C3',$,$,$,$,$,$,.NOTDEFINED.);
#82=IFCMATERIALPROFILESET($,$,(#84),$);
#83=IFCRELASSOCIATESMATERIAL('2CQLZ1qlzA9uN2eRrpg99c',$,$,$,(#81),#82);
#83=IFCRELASSOCIATESMATERIAL('0ul_xSpIH2MxsZcv$ZFXG7',$,$,$,(#81),#82);
#84=IFCMATERIALPROFILE($,$,#19,#80,$,$);
#85=IFCISHAPEPROFILEDEF(.AREA.,'DEMO-I',$,0.1,0.2,0.005,0.01,0.005,$,$);
#86=IFCBEAMTYPE('1xjrF860f2H9zb04Ui98Rt',$,'B1',$,$,$,$,$,$,.NOTDEFINED.);
#86=IFCBEAMTYPE('06Td_2IhD5OgyKW34Ikv$Z',$,'B1',$,$,$,$,$,$,.NOTDEFINED.);
#87=IFCMATERIALPROFILESET($,$,(#89),$);
#88=IFCRELASSOCIATESMATERIAL('03i9c81690fx2ZTBBXcqkL',$,$,$,(#86),#87);
#88=IFCRELASSOCIATESMATERIAL('2cT5kDtU11jv2sFtIKVfJa',$,$,$,(#86),#87);
#89=IFCMATERIALPROFILE($,$,#19,#85,$,$);
#90=IFCCSHAPEPROFILEDEF(.AREA.,'DEMO-C',$,0.2,0.1,0.0015,0.03,0.005);
#91=IFCBEAMTYPE('3mAc_7AGP06w4CzYOlYVL2',$,'B2',$,$,$,$,$,$,.NOTDEFINED.);
#91=IFCBEAMTYPE('1JyktsoBP8DgMbJiECPZ6d',$,'B2',$,$,$,$,$,$,.NOTDEFINED.);
#92=IFCMATERIALPROFILESET($,$,(#94),$);
#93=IFCRELASSOCIATESMATERIAL('2xvXgaSvL7KPR25fXz0fSE',$,$,$,(#91),#92);
#93=IFCRELASSOCIATESMATERIAL('2$_YKuP914sBZmflM_8l1s',$,$,$,(#91),#92);
#94=IFCMATERIALPROFILE($,$,#19,#90,$,$);
#95=IFCWINDOWTYPE('1kxjrhA4P2dwwueZ0_nSuu',$,'WT01',$,$,$,(#136,#158),$,$,.NOTDEFINED.,.NOTDEFINED.,$,$);
#95=IFCWINDOWTYPE('0g10V5zVP8c96j3mPV0Xvn',$,'WT01',$,$,$,(#136,#158),$,$,.NOTDEFINED.,.NOTDEFINED.,$,$);
#96=IFCINDEXEDPOLYGONALFACE((13,17,18,14));
#97=IFCINDEXEDPOLYGONALFACE((5,6,3,4));
#98=IFCINDEXEDPOLYGONALFACE((7,8,2,1));
@@ -157,13 +157,13 @@ DATA;
#150=IFCINDEXEDPOLYCURVE(#137,(IFCLINEINDEX((5,11))),$);
#151=IFCINDEXEDPOLYCURVE(#137,(IFCLINEINDEX((6,12))),$);
#152=IFCGEOMETRICCURVESET((#138,#139,#140,#141,#142,#143,#144,#145,#146,#147,#148,#149,#150,#151));
#153=IFCSHAPEREPRESENTATION(#18,'Annotation','Annotation2D',(#152));
#153=IFCSHAPEREPRESENTATION(#18,'Body','Annotation2D',(#152));
#154=IFCCARTESIANPOINT((0.,0.,0.));
#155=IFCDIRECTION((1.,0.,0.));
#156=IFCDIRECTION((0.,0.,1.));
#157=IFCAXIS2PLACEMENT3D(#154,#156,#155);
#158=IFCREPRESENTATIONMAP(#157,#153);
#159=IFCDOORTYPE('0s370BWH51r8Xg03pJ5OBE',$,'DT01',$,$,$,(#204,#224),$,$,.NOTDEFINED.,.NOTDEFINED.,$,$);
#159=IFCDOORTYPE('0nu4v3oPPFmQIz2RA1Vp_t',$,'DT01',$,$,$,(#204,#224),$,$,.NOTDEFINED.,.NOTDEFINED.,$,$);
#160=IFCINDEXEDPOLYGONALFACE((17,16,15,14));
#161=IFCINDEXEDPOLYGONALFACE((2,3,29,28));
#162=IFCINDEXEDPOLYGONALFACE((27,28,29,30,32,31));
@@ -223,13 +223,13 @@ DATA;
#216=IFCINDEXEDPOLYCURVE(#205,(IFCLINEINDEX((11,15))),$);
#217=IFCINDEXEDPOLYCURVE(#205,(IFCLINEINDEX((17,18)),IFCLINEINDEX((18,19)),IFCLINEINDEX((19,20)),IFCLINEINDEX((20,21)),IFCLINEINDEX((21,22)),IFCLINEINDEX((22,23)),IFCLINEINDEX((23,24)),IFCLINEINDEX((24,25)),IFCLINEINDEX((25,26)),IFCLINEINDEX((26,27)),IFCLINEINDEX((27,28)),IFCLINEINDEX((28,29))),$);
#218=IFCGEOMETRICCURVESET((#206,#207,#208,#209,#210,#211,#212,#213,#214,#215,#216,#217));
#219=IFCSHAPEREPRESENTATION(#18,'Annotation','Annotation2D',(#218));
#219=IFCSHAPEREPRESENTATION(#18,'Body','Annotation2D',(#218));
#220=IFCCARTESIANPOINT((0.,0.,0.));
#221=IFCDIRECTION((1.,0.,0.));
#222=IFCDIRECTION((0.,0.,1.));
#223=IFCAXIS2PLACEMENT3D(#220,#222,#221);
#224=IFCREPRESENTATIONMAP(#223,#219);
#225=IFCFURNITURETYPE('227RrM$yD30RGzlTgbBH3c',$,'BUN01',$,$,$,(#946,#988),$,$,.NOTDEFINED.,.NOTDEFINED.);
#225=IFCFURNITURETYPE('2YuVxsKJL9URJtwIhHY2GN',$,'BUN01',$,$,$,(#946,#988),$,$,.NOTDEFINED.,.NOTDEFINED.);
#226=IFCINDEXEDPOLYGONALFACE((187,278,44));
#227=IFCINDEXEDPOLYGONALFACE((21,52,60));
#228=IFCINDEXEDPOLYGONALFACE((91,100,31));
@@ -987,7 +987,7 @@ DATA;
#980=IFCINDEXEDPOLYCURVE(#947,(IFCLINEINDEX((60,61)),IFCLINEINDEX((61,62)),IFCLINEINDEX((62,63))),$);
#981=IFCINDEXEDPOLYCURVE(#947,(IFCLINEINDEX((64,31))),$);
#982=IFCGEOMETRICCURVESET((#948,#949,#950,#951,#952,#953,#954,#955,#956,#957,#958,#959,#960,#961,#962,#963,#964,#965,#966,#967,#968,#969,#970,#971,#972,#973,#974,#975,#976,#977,#978,#979,#980,#981));
#983=IFCSHAPEREPRESENTATION(#18,'Annotation','Annotation2D',(#982));
#983=IFCSHAPEREPRESENTATION(#18,'Body','Annotation2D',(#982));
#984=IFCCARTESIANPOINT((0.,0.,0.));
#985=IFCDIRECTION((1.,0.,0.));
#986=IFCDIRECTION((0.,0.,1.));
File diff suppressed because one or more lines are too long
@@ -1,13 +0,0 @@
ISO-10303-21;
HEADER;
FILE_DESCRIPTION((),'2;1');
FILE_NAME('EPset_Annotation.ifc','2020-01-01T00:00:00',(),(),'EPset_Annotation','EPset_Annotation',$);
FILE_SCHEMA(('IFC4'));
ENDSEC;
DATA;
#1=IFCPROPERTYSETTEMPLATE('3VuPUwdCD2Qx3XDDRs0R1N',$,'EPset_Annotation','',.PSET_OCCURRENCEDRIVEN.,'IfcAnnotation',(#2,#3));
#2=IFCSIMPLEPROPERTYTEMPLATE('2P7JN79n96Q9pElZ83LKe4',$,'ZIndex','',.P_SINGLEVALUE.,'IfcInteger',$,$,$,$,$,.READWRITE.);
#3=IFCSIMPLEPROPERTYTEMPLATE('1Wpx_r2xj1_9w5JpI0QRJy',$,'Symbol','',.P_ENUMERATEDVALUE.,'IfcLabel',$,#4,$,$,$,.READWRITE.);
#4=IFCPROPERTYENUMERATION('Symbol',(IFCLABEL('rectangle-tag'), IFCLABEL('door-tag')),$);
ENDSEC;
END-ISO-10303-21;
@@ -0,0 +1,16 @@
ISO-10303-21;
HEADER;
FILE_DESCRIPTION((),'2;1');
FILE_NAME('EPset_Annotation.ifc','2020-01-01T00:00:00',(),(),'EPset_Annotation','EPset_Annotation',$);
FILE_SCHEMA(('IFC4'));
ENDSEC;
DATA;
#1=IFCPROPERTYSETTEMPLATE('3VuPUwdCD2Qx3XDDRs0R1N',$,'EPset_Annotation','',.PSET_OCCURRENCEDRIVEN.,'IfcAnnotation',(#2,#3,#4));
#2=IFCSIMPLEPROPERTYTEMPLATE('2P7JN79n96Q9pElZ83LKe4',$,'ZIndex','',.P_SINGLEVALUE.,'IfcInteger',$,$,$,$,$,.READWRITE.);
#3=IFCSIMPLEPROPERTYTEMPLATE('1Wpx_r2xj1_9w5JpI0QRJy',$,'Symbol','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
#4=IFCSIMPLEPROPERTYTEMPLATE('3q0oxMUKP47vZ4jnyG$dDb',$,'Classes','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
#5=IFCPROPERTYSETTEMPLATE('0iKwujnQL9IevVQato8f7Z',$,'BBIM_Batting','',.PSET_OCCURRENCEDRIVEN.,'IfcAnnotation',(#6,#7));
#6=IFCSIMPLEPROPERTYTEMPLATE('0t2LEesGT1QRQtrIZUAR8L',$,'Thickness','Batting thickness',.P_SINGLEVALUE.,'IfcPositiveLengthMeasure',$,$,$,$,$,.READWRITE.);
#7=IFCSIMPLEPROPERTYTEMPLATE('082PndS6v2kBOiJoSboMnh',$,'Reverse pattern direction','Reverse batting pattern (swap starting and ending points)',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
ENDSEC;
END-ISO-10303-21;
@@ -19,11 +19,12 @@
*/
* { stroke-linecap: round; stroke-linejoin: round; }
text { fill: black; stroke: none; }
text { /* 2.5mm */ fill: black; stroke: none; font-family: 'OpenGost Type B TT', 'DejaVu Sans Condensed', 'Liberation Sans', 'Arial Narrow', 'Arial'; font-size: 4.13px; }
.cut { fill: black; stroke: black; stroke-linecap: 'round'; stroke-width: 0.35; fill-rule: evenodd; }
.projection { fill: white; stroke: black; stroke-linecap: 'round'; stroke-width: 0.25; }
.annotation { fill: none; stroke: black; stroke-linecap: 'round'; stroke-width: 0.25; }
.IfcAnnotation { fill: none; stroke: black; stroke-linecap: 'round'; stroke-width: 0.25; }
.IfcGeographicElement { fill: none; stroke: black; stroke-linecap: 'round'; stroke-width: 1; }
.PredefinedType-LINEWORK { stroke: black; stroke-width: 0.25; }
.PredefinedType-BACKGROUND { stroke: black; stroke-width: 0.18; }
.PredefinedType-GRID { marker-start: url(#grid-marker); marker-end: url(#grid-marker); }
@@ -37,13 +38,18 @@ text { fill: black; stroke: none; }
.PredefinedType-HIDDENLINE { stroke-dasharray: 3, 2; }
.PredefinedType-STAIRARROW { marker-start: url(#stair-marker-start); marker-end: url(#stair-marker-end); }
.PredefinedType-BOUNDARY { fill: none; stroke: red; stroke-width: 1; stroke-dasharray: 12, 4, 3, 4, 3, 4; }
.PredefinedType-BATTING { marker-start: url(#batting); stroke: none; }
.PredefinedType-SEALANT { fill: url(#crosshatch1); stroke-width: 0.25; }
.PredefinedType-FILLAREA { fill: white; stroke: none; }
.PredefinedType-FILLAREA { fill: white; stroke: black; }
.PredefinedType-BREAKLINE { fill: none; stroke: black; stroke-width: 0.25; marker-mid: url(#breakline-marker); }
.PredefinedType-TEXT { fill: black; stroke: none; }
path.PredefinedType-TEXTLEADER { marker-end: url(#leader-marker); }
text.PredefinedType-TEXTLEADER { fill: black; stroke: none; }
text.title { /* 7mm */ font-size: 11.55px; }
text.header { /* 5mm */ font-size: 8.25px; }
text.large { /* 3.5mm */ font-size: 5.78px; }
text.regular { /* 2.5mm */ font-size: 4.13px; }
text.small { /* 1.8mm */ font-size: 2.97px; }
text.GRID { /* 5mm */ font-size: 8.25px; }
.material-blank { fill: white; }
.material-diagonal1 { fill: url(#diagonal1); }
.material-diagonal2 { fill: url(#diagonal2); }
@@ -1,9 +1,7 @@
<?xml version="1.0" encoding="utf-8" ?>
<svg baseProfile="full" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:ev="http://www.w3.org/2001/xml-events" xmlns:xlink="http://www.w3.org/1999/xlink">
<marker id="batting" markerHeight="10" markerWidth="4" orient="auto" refX="0" refY="5" markerUnits="userSpaceOnUse">
<!-- Tip: https://svg-path-visualizer.netlify.app/ -->
<path style="fill: none; stroke:black; stroke-width:0.18" d="M 0 2 A 2 2 0 0 1 4 2 L 2 8 M 0 2 L 2 8 A 2 2 0 0 0 4 10 M 2 8 A 2 2 0 0 1 0 10" />
</marker>
<!-- Tip: https://www.svgviewer.dev/ -->
<marker id="eng-dimension-marker-start" markerHeight="14" markerWidth="11" orient="auto" refX="1" refY="7">
<g>
<path d="M 11 3.5 L 11 10.5 L 1 7" class="annotation" style="fill:black;" />

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

+2 -5
View File
@@ -26,8 +26,7 @@ from blenderbim.bim.module.drawing.prop import RasterStyleProperty
from bpy.app.handlers import persistent
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.module.owner.prop import get_user_person, get_user_organisation
from ifcopenshell.api.material.data import Data as MaterialData
from ifcopenshell.api.type.data import Data as TypeData
from blenderbim.bim.module.model.data import AuthoringData
global_subscription_owner = object()
@@ -66,7 +65,6 @@ def name_callback(obj, data):
if isinstance(obj, bpy.types.Material):
if obj.BIMObjectProperties.ifc_definition_id:
IfcStore.get_file().by_id(obj.BIMObjectProperties.ifc_definition_id).Name = obj.name
MaterialData.load_materials()
if obj.BIMMaterialProperties.ifc_style_id:
IfcStore.get_file().by_id(obj.BIMMaterialProperties.ifc_style_id).Name = obj.name
refresh_ui_data()
@@ -90,8 +88,6 @@ def name_callback(obj, data):
break
if grid_collection:
grid_collection.name = obj.name
if element.is_a("IfcTypeProduct"):
TypeData.purge()
element.Name = "/".join(obj.name.split("/")[1:])
refresh_ui_data()
@@ -301,3 +297,4 @@ def setDefaultProperties(scene):
drawing_style.name = "Blender Default"
drawing_style.render_type = "DEFAULT"
bpy.ops.bim.save_drawing_style(index="2")
AuthoringData.type_thumbnails = {}
+111 -34
View File
@@ -23,11 +23,12 @@ import math
import zipfile
import ifcopenshell
import ifcopenshell.util.attribute
from blenderbim.bim.prop import get_ifc_entity_doc_url
import ifcopenshell.util.element
from ifcopenshell.util.doc import get_attribute_doc, get_predefined_type_doc, get_property_doc
from mathutils import geometry
from mathutils import Vector
from blenderbim.bim.ifc import IfcStore
import blenderbim.tool as tool
from blenderbim.bim.ifc import IfcStore
def draw_attributes(props, layout, copy_operator=None):
@@ -43,16 +44,25 @@ def draw_attribute(attribute, layout, copy_operator=None):
return
if value_name == "enum_value":
prop_with_search(layout, attribute, "enum_value", text=attribute.name)
elif attribute.name in ["ScheduleDuration", "ActualDuration", "FreeFloat", "TotalFloat"]:
propis = bpy.context.scene.BIMWorkScheduleProperties
for item in propis.durations_attributes:
if item.name == attribute.name:
duration_props = item
layout.label(text=attribute.name)
layout.prop(duration_props, "years", text="Y")
layout.prop(duration_props, "months", text="M")
layout.prop(duration_props, "days", text="D")
layout.prop(duration_props, "hours", text="H")
layout.prop(duration_props, "minutes", text="Min")
layout.prop(duration_props, "seconds", text="S")
break
else:
layout.prop(
attribute,
value_name,
text=attribute.name,
)
if "ScheduleDuration" in attribute.name:
layout.prop(bpy.context.scene.BIMDuration, "duration_days", text="D")
layout.prop(bpy.context.scene.BIMDuration, "duration_hours", text="H")
layout.prop(bpy.context.scene.BIMDuration, "duration_minutes", text="M")
if attribute.is_optional:
layout.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
@@ -85,6 +95,7 @@ def import_attribute(attribute, props, data, callback=None):
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
if is_handled_by_callback:
@@ -92,19 +103,66 @@ def import_attribute(attribute, props, data, callback=None):
elif is_handled_by_callback is False:
props.remove(len(props) - 1)
elif data_type == "string":
new.string_value = "" if new.is_null else data[attribute.name()]
new.string_value = "" if new.is_null else str(data[attribute.name()])
if attribute.type_of_attribute().declared_type().name() == "IfcURIReference":
new.is_uri = True
elif data_type == "boolean":
new.bool_value = False if new.is_null else data[attribute.name()]
new.bool_value = False if new.is_null else bool(data[attribute.name()])
elif data_type == "integer":
new.int_value = 0 if new.is_null else data[attribute.name()]
new.int_value = 0 if new.is_null else int(data[attribute.name()])
elif data_type == "float":
new.float_value = 0.0 if new.is_null else data[attribute.name()]
new.float_value = 0.0 if new.is_null else float(data[attribute.name()])
elif data_type == "enum":
new.enum_items = json.dumps(ifcopenshell.util.attribute.get_enum_items(attribute))
if data[attribute.name()]:
new.enum_value = data[attribute.name()]
enum_items = ifcopenshell.util.attribute.get_enum_items(attribute)
new.enum_items = json.dumps(enum_items)
add_attribute_enum_items_descriptions(new, enum_items)
if data[new.name]:
new.enum_value = data[new.name]
add_attribute_description(new, data)
add_attribute_min_max(new)
ATTRIBUTE_MIN_MAX_CONSTRAINTS = {"IfcMaterialLayer": {"Priority": {"value_min": 0, "value_max": 100}}}
def add_attribute_min_max(attribute_blender):
if attribute_blender.ifc_class in ATTRIBUTE_MIN_MAX_CONSTRAINTS:
constraints = ATTRIBUTE_MIN_MAX_CONSTRAINTS[attribute_blender.ifc_class].get(attribute_blender.name, {})
for constraint, value in constraints.items():
setattr(attribute_blender, constraint, value)
setattr(attribute_blender, constraint + "_constraint", True)
def add_attribute_enum_items_descriptions(attribute_blender, enum_items):
attribute_blender.enum_descriptions.clear()
if isinstance(enum_items, dict):
enum_items = enum_items.keys()
version = tool.Ifc.get_schema()
for enum_item in enum_items:
new_enum_description = attribute_blender.enum_descriptions.add()
try:
description = get_predefined_type_doc(version, attribute_blender.ifc_class, enum_item) or ""
except KeyError: # TODO this only supports predefined type enums. Add support for other types of enums ?
description = ""
new_enum_description.name = description
def add_attribute_description(attribute_blender, attribute_ifc=None):
if not attribute_blender.name:
return
version = tool.Ifc.get_schema()
description = ""
try:
description = get_attribute_doc(version, attribute_blender.ifc_class, attribute_blender.name)
except RuntimeError: # It's not an Entity Attribute. Let's try a Property Set attribute.
doc = get_property_doc(version, attribute_blender.ifc_class, attribute_blender.name)
if doc:
description = doc.get("description", "")
else: # It's a custom property set. Check if this attribute has a description
if attribute_ifc is not None:
description = getattr(attribute_ifc, "Description", "")
if description:
attribute_blender.description = description
def export_attributes(props, callback=None):
@@ -120,29 +178,18 @@ def export_attributes(props, callback=None):
def prop_with_search(layout, data, prop_name, **kwargs):
# kwargs are layout.prop arguments (text, icon, etc.)
row = layout.row(align=True)
# Magick courtesy of https://blender.stackexchange.com/a/203443/86891
row.context_pointer_set(name="data", data=data)
row.prop(data, prop_name, **kwargs)
op = row.operator("bim.enum_property_search", text="", icon="VIEWZOOM")
op.prop_name = prop_name
add_entity_url_button(row, getattr(data, prop_name))
try:
if len(get_enum_items(data, prop_name)) > 10:
# Magick courtesy of https://blender.stackexchange.com/a/203443/86891
row.context_pointer_set(name="data", data=data)
op = row.operator("bim.enum_property_search", text="", icon="VIEWZOOM")
op.prop_name = prop_name
except TypeError: # Prop is not iterable
pass
def add_entity_url_button(layout, entity):
if entity:
try:
url = get_ifc_entity_doc_url(entity)
except KeyError:
# TODO : support attributes, pset, etc.
pass
else:
if url:
url_op = layout.operator("bim.open_webbrowser", icon="INFO", text="")
url_op.url = url
def get_enum_items(data, prop_name, context):
def get_enum_items(data, prop_name, context=None):
# Retrieve items from a dynamic EnumProperty, which is otherwise not supported
# Or throws an error in the console when the items callback returns an empty list
# See https://blender.stackexchange.com/q/215781/86891
@@ -152,7 +199,7 @@ def get_enum_items(data, prop_name, context):
return
if not isinstance(items, (list, tuple)):
# items are retrieved through a callback, not a static list :
items = items(data, context)
items = items(data, context or bpy.context)
return items
@@ -161,6 +208,12 @@ def get_obj_ifc_definition_id(context, obj, obj_type):
return bpy.data.objects.get(obj).BIMObjectProperties.ifc_definition_id
elif obj_type == "Material":
return bpy.data.materials.get(obj).BIMObjectProperties.ifc_definition_id
elif obj_type == "MaterialSet":
return ifcopenshell.util.element.get_material(
tool.Ifc.get_entity(bpy.data.objects.get(obj)), should_skip_usage=True
).id()
elif obj_type == "MaterialSetItem":
return bpy.data.objects.get(obj).BIMObjectMaterialProperties.active_material_set_item_id
elif obj_type == "Task":
return context.scene.BIMTaskTreeProperties.tasks[
context.scene.BIMWorkScheduleProperties.active_task_index
@@ -190,6 +243,30 @@ def close_operator_panel(event):
bpy.app.timers.register(move_back, first_interval=0.01)
def convert_property_group_from_si(property_group, skip_props=()):
"""Method converts property group values from si to current ifc project units
based on default values of the properties.
List of properties to skip can be supplied in `skip_props`."""
conversion_k = 1.0 / ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
skip_props = ("rna_type", "name") + skip_props
for prop_name in property_group.bl_rna.properties.keys():
if prop_name in skip_props:
continue
prop_bl_rna = property_group.bl_rna.properties[prop_name]
if prop_bl_rna.array_length > 0:
prop_value = prop_bl_rna.default_array
else:
prop_value = prop_bl_rna.default
if type(prop_value) is float:
prop_value = prop_value * conversion_k
elif type(prop_value) is bpy.types.bpy_prop_array:
prop_value = [el * conversion_k for el in prop_value]
setattr(property_group, prop_name, prop_value)
class IfcHeaderExtractor:
def __init__(self, filepath: str):
self.filepath = filepath
+10 -1
View File
@@ -51,6 +51,7 @@ class IfcStore:
history = []
future = []
schema_identifiers = ["IFC4", "IFC2X3", "IFC4X3"]
session_files = {}
@staticmethod
def purge():
@@ -71,6 +72,7 @@ class IfcStore:
IfcStore.history = []
IfcStore.future = []
IfcStore.schema_identifiers = ["IFC4", "IFC2X3", "IFC4X3"]
IfcStore.session_files = {}
@staticmethod
def get_file():
@@ -96,7 +98,14 @@ class IfcStore:
try:
IfcStore.cache = ifcopenshell.geom.serializers.hdf5(IfcStore.cache_path, cache_settings)
except:
return
if os.path.exists(IfcStore.cache_path):
os.remove(IfcStore.cache_path)
try:
IfcStore.cache = ifcopenshell.geom.serializers.hdf5(IfcStore.cache_path, cache_settings)
except:
return
else:
return
return IfcStore.cache
@staticmethod
+102 -67
View File
@@ -112,7 +112,7 @@ class MaterialCreator:
def parse_representation(self, representation):
has_parsed = False
representation_items = self.resolve_all_representation_items(representation)
representation_items = self.resolve_all_stylable_representation_items(representation)
for item in representation_items:
if self.parse_representation_item(item):
has_parsed = True
@@ -153,11 +153,19 @@ class MaterialCreator:
]
self.mesh.polygons.foreach_set("material_index", material_index)
def resolve_all_representation_items(self, representation):
def resolve_all_stylable_representation_items(self, representation):
items = []
for item in representation.Items:
if item.is_a("IfcMappedItem"):
items.extend(item.MappingSource.MappedRepresentation.Items)
if item.is_a("IfcBooleanResult"):
operand = item.FirstOperand
while True:
items.append(operand)
if operand.is_a("IfcBooleanResult"):
operand = operand.FirstOperand
else:
break
items.append(item)
return items
@@ -228,8 +236,6 @@ class IfcImporter:
self.profile_code("Create materials")
self.create_styles()
self.profile_code("Create styles")
self.create_annotations()
self.profile_code("Create annotation")
self.parse_native_elements()
self.profile_code("Parsing native elements")
self.create_native_elements()
@@ -329,11 +335,23 @@ class IfcImporter:
if self.ifc_import_settings.has_filter or offset or offset_limit < len(self.elements):
self.element_types = set([ifcopenshell.util.element.get_type(e) for e in self.elements])
else:
self.element_types = set(
self.file.by_type("IfcElementType")
+ self.file.by_type("IfcDoorStyle")
+ self.file.by_type("IfcWindowStyle")
)
if self.file.schema == "IFC2X3":
self.element_types = set(
self.file.by_type("IfcElementType")
+ self.file.by_type("IfcDoorStyle")
+ self.file.by_type("IfcWindowStyle")
)
elif self.file.schema == "IFC4":
self.element_types = set(
self.file.by_type("IfcElementType")
+ self.file.by_type("IfcDoorStyle")
+ self.file.by_type("IfcWindowStyle")
+ self.file.by_type("IfcSpatialElementType")
)
else:
self.element_types = set(
self.file.by_type("IfcElementType") + self.file.by_type("IfcSpatialElementType")
)
if self.ifc_import_settings.has_filter and self.ifc_import_settings.should_filter_spatial_elements:
self.spatial_elements = self.get_spatial_elements_filtered_by_elements(self.elements)
@@ -403,7 +421,7 @@ class IfcImporter:
items = representation["raw"].Items or [] # Be forgiving of invalid IFCs because Revit :(
if len(items) == 1 and items[0].is_a("IfcSweptDiskSolid"):
return True
elif len(items) and ( # See #2508 why we accommodate for invalid IFCs here
elif len(items) and ( # See #2508 why we accommodate for invalid IFCs here
items[0].is_a("IfcSweptDiskSolid")
and len({i.is_a() for i in items}) == 1
and len({i.Radius for i in items}) == 1
@@ -517,14 +535,21 @@ class IfcImporter:
def does_element_likely_have_geometry_far_away(self, element):
for representation in element.Representation.Representations:
for subelement in self.file.traverse(representation):
if subelement.is_a("IfcCartesianPointList3D"):
for point in subelement.CoordList:
if len(point) == 3 and self.is_point_far_away(point, is_meters=False):
items = []
for item in representation.Items:
if item.is_a("IfcMappedItem"):
items.extend(item.MappingSource.MappedRepresentation.Items)
else:
items.append(item)
for item in items:
for subelement in self.file.traverse(item):
if subelement.is_a("IfcCartesianPointList3D"):
for point in subelement.CoordList:
if len(point) == 3 and self.is_point_far_away(point, is_meters=False):
return True
if subelement.is_a("IfcCartesianPoint"):
if len(subelement.Coordinates) == 3 and self.is_point_far_away(subelement, is_meters=False):
return True
if subelement.is_a("IfcCartesianPoint"):
if len(subelement.Coordinates) == 3 and self.is_point_far_away(subelement, is_meters=False):
return True
def apply_blender_offset_to_matrix_world(self, obj, matrix):
props = bpy.context.scene.BIMGeoreferenceProperties
@@ -568,7 +593,7 @@ class IfcImporter:
if bpy.context.preferences.addons["blenderbim"].preferences.lock_grids_on_import:
grid_obj.lock_location = (True, True, True)
grid_obj.lock_rotation = (True, True, True)
collection = bpy.data.collections.new(self.get_name(grid))
collection = bpy.data.collections.new(tool.Loader.get_name(grid))
u_axes = bpy.data.collections.new("UAxes")
collection.children.link(u_axes)
v_axes = bpy.data.collections.new("VAxes")
@@ -610,11 +635,11 @@ class IfcImporter:
try:
shape = ifcopenshell.geom.create_shape(self.settings, representation_map.MappedRepresentation)
mesh = self.create_mesh(element, shape)
self.link_mesh(shape, mesh)
tool.Loader.link_mesh(shape, mesh)
self.meshes[mesh_name] = mesh
except:
self.ifc_import_settings.logger.error("Failed to generate shape for %s", element)
obj = bpy.data.objects.new(self.get_name(element), mesh)
obj = bpy.data.objects.new(tool.Loader.get_name(element), mesh)
self.link_element(element, obj)
self.material_creator.create(element, obj, mesh)
self.type_products[element.GlobalId] = obj
@@ -669,9 +694,6 @@ class IfcImporter:
def create_spatial_elements(self):
self.create_generic_elements(self.spatial_elements)
def create_annotations(self):
self.create_generic_elements(self.annotations)
def create_elements(self):
self.create_generic_elements(self.elements)
@@ -905,21 +927,21 @@ class IfcImporter:
pass
elif element.is_a("IfcAnnotation") and element.ObjectType == "DRAWING":
mesh = self.create_camera(element, shape)
self.link_mesh(shape, mesh)
tool.Loader.link_mesh(shape, mesh)
elif element.is_a("IfcAnnotation") and self.is_curve_annotation(element) and shape:
mesh = self.create_curve(element, shape)
self.link_mesh(shape, mesh)
tool.Loader.link_mesh(shape, mesh)
elif shape:
mesh_name = self.get_mesh_name(shape.geometry)
mesh_name = tool.Loader.get_mesh_name(shape.geometry)
mesh = self.meshes.get(mesh_name)
if mesh is None:
mesh = self.create_mesh(element, shape)
self.link_mesh(shape, mesh)
tool.Loader.link_mesh(shape, mesh)
self.meshes[mesh_name] = mesh
else:
mesh = None
obj = bpy.data.objects.new(self.get_name(element), mesh)
obj = bpy.data.objects.new(tool.Loader.get_name(element), mesh)
self.link_element(element, obj)
if shape:
@@ -972,9 +994,37 @@ class IfcImporter:
self.convert_representation(representation)
mesh = bpy.data.meshes.new("Native")
props = bpy.context.scene.BIMGeoreferenceProperties
mat = ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement)
if props.has_blender_offset and self.is_point_far_away(self.mesh_data["co"][0:3], is_meters=False):
offset_point = np.linalg.inv(mat) @ np.array(
(
float(props.blender_eastings),
float(props.blender_northings),
float(props.blender_orthogonal_height),
0.0,
)
)
verts = [None] * len(self.mesh_data["co"])
for i in range(0, len(self.mesh_data["co"]), 3):
verts[i], verts[i + 1], verts[i + 2] = ifcopenshell.util.geolocation.enh2xyz(
self.mesh_data["co"][i] * self.unit_scale,
self.mesh_data["co"][i + 1] * self.unit_scale,
self.mesh_data["co"][i + 2] * self.unit_scale,
offset_point[0] * self.unit_scale,
offset_point[1] * self.unit_scale,
offset_point[2] * self.unit_scale,
float(props.blender_x_axis_abscissa),
float(props.blender_x_axis_ordinate),
)
mesh["has_cartesian_point_offset"] = True
else:
verts = [c * self.unit_scale for c in self.mesh_data["co"]]
mesh["has_cartesian_point_offset"] = False
mesh.vertices.add(self.mesh_data["total_verts"])
mesh.vertices.foreach_set("co", [c * self.unit_scale for c in self.mesh_data["co"]])
# mesh.vertices.foreach_set("co", self.mesh_data["co"])
mesh.vertices.foreach_set("co", verts)
mesh.loops.add(len(self.mesh_data["vertex_index"]))
mesh.loops.foreach_set("vertex_index", self.mesh_data["vertex_index"])
mesh.polygons.add(self.mesh_data["total_polygons"])
@@ -1339,6 +1389,13 @@ class IfcImporter:
def create_spatial_decomposition_collections(self):
for rel_aggregate in self.project["ifc"].IsDecomposedBy or []:
self.create_spatial_decomposition_collection(self.project["blender"], rel_aggregate.RelatedObjects)
# Invalid IFCs may have orphaned spatial structure elements.
orphaned_spaces = [e for e in self.spatial_elements if e.GlobalId not in self.collections]
while orphaned_spaces:
self.create_spatial_decomposition_collection(self.project["blender"], orphaned_spaces)
orphaned_spaces = [e for e in self.spatial_elements if e.GlobalId not in self.collections]
self.create_views_collection()
self.create_type_collection()
@@ -1360,12 +1417,6 @@ class IfcImporter:
if not view_collection:
view_collection = bpy.data.collections.new("Views")
self.project["blender"].children.link(view_collection)
for element in self.file.by_type("IfcAnnotation"):
if element.ObjectType == "DRAWING":
group = [r for r in element.HasAssignments if r.is_a("IfcRelAssignsToGroup")][0].RelatingGroup
collection = bpy.data.collections.new("IfcGroup/" + group.Name)
self.collections[group.GlobalId] = collection
view_collection.children.link(collection)
def create_spatial_decomposition_collection(self, parent, related_objects):
for element in related_objects:
@@ -1379,7 +1430,7 @@ class IfcImporter:
collection = obj.users_collection[0]
self.collections[element.GlobalId] = collection
if not is_existing:
collection = bpy.data.collections.new(self.get_name(element))
collection = bpy.data.collections.new(tool.Loader.get_name(element))
self.collections[element.GlobalId] = collection
parent.children.link(collection)
if element.IsDecomposedBy:
@@ -1392,7 +1443,11 @@ class IfcImporter:
rel_aggregates += [e.Decomposes[0] for e in self.elements if e.Decomposes]
rel_aggregates = set(rel_aggregates)
else:
rel_aggregates = [a for a in self.file.by_type("IfcRelAggregates") if a.RelatingObject.is_a("IfcElement")]
rel_aggregates = [
a
for a in self.file.by_type("IfcRelAggregates")
if a.RelatingObject.is_a("IfcElement") or a.RelatingObject.is_a("IfcElementType")
]
if len(rel_aggregates) > 10000:
# More than 10,000 collections makes Blender unhappy
@@ -1403,7 +1458,7 @@ class IfcImporter:
aggregates = {}
for rel_aggregate in rel_aggregates:
element = rel_aggregate.RelatingObject
collection = bpy.data.collections.new(self.get_name(element))
collection = bpy.data.collections.new(tool.Loader.get_name(element))
aggregates[element.GlobalId] = {"element": element, "collection": collection}
self.collections[element.GlobalId] = collection
@@ -1415,6 +1470,10 @@ class IfcImporter:
parent = ifcopenshell.util.element.get_container(aggregate["element"])
if parent:
self.collections[parent.GlobalId].children.link(aggregate["collection"])
continue
if aggregate["element"].is_a("IfcElementType"):
self.type_collection.children.link(aggregate["collection"])
continue
def create_materials(self):
for material in self.file.by_type("IfcMaterial"):
@@ -1630,9 +1689,6 @@ class IfcImporter:
else:
blender_material.node_tree.links.new(coord.outputs["UV"], node.inputs["Vector"])
def get_name(self, element):
return "{}/{}".format(element.is_a(), element.Name)
def place_objects_in_collections(self):
for ifc_definition_id, obj in self.added_data.items():
if isinstance(obj, bpy.types.Object):
@@ -1703,7 +1759,7 @@ class IfcImporter:
def get_drawing_group(self, element):
for rel in element.HasAssignments or []:
if rel.is_a("IfcRelAssignsToGroup"):
if rel.is_a("IfcRelAssignsToGroup") and rel.RelatingGroup.ObjectType == "DRAWING":
return rel.RelatingGroup
def get_element_matrix(self, element):
@@ -1780,16 +1836,6 @@ class IfcImporter:
):
return representation.Items[0].MappingTarget
def get_mesh_name(self, geometry):
representation_id = geometry.id
if "-" in representation_id:
representation_id = int(re.sub(r"\D", "", representation_id.split("-")[0]))
else:
representation_id = int(re.sub(r"\D", "", representation_id))
representation = self.file.by_id(representation_id)
context_id = representation.ContextOfItems.id() if hasattr(representation, "ContextOfItems") else 0
return "{}/{}".format(context_id, representation_id)
def create_camera(self, element, shape):
if hasattr(shape, "geometry"):
geometry = shape.geometry
@@ -1804,7 +1850,7 @@ class IfcImporter:
height = max(y) - min(y)
depth = max(z) - min(z)
camera = bpy.data.cameras.new(self.get_mesh_name(geometry))
camera = bpy.data.cameras.new(tool.Loader.get_mesh_name(geometry))
camera.type = "ORTHO"
camera.ortho_scale = width if width > height else height
camera.clip_end = depth
@@ -1844,7 +1890,7 @@ class IfcImporter:
else:
geometry = shape
curve = bpy.data.curves.new(self.get_mesh_name(geometry), type="CURVE")
curve = bpy.data.curves.new(tool.Loader.get_mesh_name(geometry), type="CURVE")
curve.dimensions = "3D"
curve.resolution_u = 2
@@ -1870,7 +1916,7 @@ class IfcImporter:
else:
geometry = shape
mesh = bpy.data.meshes.new(self.get_mesh_name(geometry))
mesh = bpy.data.meshes.new(tool.Loader.get_mesh_name(geometry))
props = bpy.context.scene.BIMGeoreferenceProperties
if (
@@ -1985,17 +2031,6 @@ class IfcImporter:
self.added_data[element.id()] = obj
IfcStore.link_element(element, obj)
def link_mesh(self, shape, mesh):
if hasattr(shape, "geometry"):
geometry = shape.geometry
else:
geometry = shape
if "-" in geometry.id:
mesh.BIMMeshProperties.ifc_definition_id = int(geometry.id.split("-")[0])
else:
# TODO: See #2002
mesh.BIMMeshProperties.ifc_definition_id = int(geometry.id.replace(",", ""))
def set_matrix_world(self, obj, matrix_world):
obj.matrix_world = matrix_world
tool.Geometry.record_object_position(obj)
@@ -25,7 +25,6 @@ import blenderbim.bim.handler
import blenderbim.tool as tool
import blenderbim.core.attribute as core
from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.attribute.data import Data
class Operator:
@@ -51,8 +50,6 @@ class EnableEditingAttributes(bpy.types.Operator):
oprops = obj.BIMObjectProperties
props = obj.BIMAttributeProperties
props.attributes.clear()
if oprops.ifc_definition_id not in Data.products:
Data.load(IfcStore.get_file(), oprops.ifc_definition_id)
blenderbim.bim.helper.import_attributes2(tool.Ifc.get().by_id(oprops.ifc_definition_id), props.attributes)
props.is_editing_attributes = True
return {"FINISHED"}
@@ -88,33 +85,10 @@ class EditAttributes(bpy.types.Operator, Operator):
obj = bpy.data.objects.get(self.obj)
elif self.obj_type == "Material":
obj = bpy.data.materials.get(self.obj)
oprops = obj.BIMObjectProperties
props = obj.BIMAttributeProperties
attributes = {}
for attribute in Data.products[oprops.ifc_definition_id]:
blender_attribute = props.attributes.get(attribute["name"])
if not blender_attribute:
continue
if attribute["is_optional"] and blender_attribute.is_null:
attributes[attribute["name"]] = None
elif attribute["type"] == "string":
attributes[attribute["name"]] = blender_attribute.string_value
elif attribute["type"] == "list":
values = blender_attribute.string_value[1:-1].split(", ")
if attribute["list_type"] == "float":
values = [float(v) for v in values]
elif attribute["list_type"] == "integer":
values = [int(v) for v in values]
attributes[attribute["name"]] = values
elif attribute["type"] == "integer":
attributes[attribute["name"]] = blender_attribute.int_value
elif attribute["type"] == "float":
attributes[attribute["name"]] = blender_attribute.float_value
elif attribute["type"] == "enum":
attributes[attribute["name"]] = blender_attribute.enum_value
product = self.file.by_id(oprops.ifc_definition_id)
ifcopenshell.api.run("attribute.edit_attributes", self.file, **{"product": product, "attributes": attributes})
Data.load(IfcStore.get_file(), oprops.ifc_definition_id)
product = tool.Ifc.get_entity(obj)
attributes = blenderbim.bim.helper.export_attributes(props.attributes)
ifcopenshell.api.run("attribute.edit_attributes", self.file, product=product, attributes=attributes)
bpy.ops.bim.disable_editing_attributes(obj=obj.name, obj_type=self.obj_type)
return {"FINISHED"}
@@ -143,5 +117,5 @@ class CopyAttributeToSelection(bpy.types.Operator, Operator):
def _execute(self, context):
value = context.active_object.BIMAttributeProperties.attributes.get(self.name).get_value()
for obj in context.selected_objects:
for obj in tool.Blender.get_selected_objects():
core.copy_attribute_to_selection(tool.Ifc, name=self.name, value=value, obj=obj)
@@ -20,43 +20,44 @@ import bpy
from . import ui, prop, operator
classes = (
operator.NewBcfProject,
operator.LoadBcfProject,
operator.LoadBcfTopics,
operator.LoadBcfTopic,
operator.LoadBcfComments,
operator.EditBcfProjectName,
operator.EditBcfAuthor,
operator.EditBcfTopicName,
operator.EditBcfTopic,
operator.SaveBcfProject,
operator.AddBcfTopic,
operator.AddBcfHeaderFile,
operator.AddBcfBimSnippet,
operator.AddBcfReferenceLink,
operator.AddBcfDocumentReference,
operator.AddBcfLabel,
operator.AddBcfRelatedTopic,
operator.ViewBcfTopic,
operator.RemoveBcfTopic,
operator.RemoveBcfComment,
operator.RemoveBcfBimSnippet,
operator.RemoveBcfReferenceLink,
operator.RemoveBcfDocumentReference,
operator.RemoveBcfRelatedTopic,
operator.EditBcfReferenceLinks,
operator.EditBcfLabels,
operator.RemoveBcfLabel,
operator.EditBcfComment,
operator.AddBcfComment,
operator.ActivateBcfViewpoint,
operator.AddBcfBimSnippet,
operator.AddBcfComment,
operator.AddBcfDocumentReference,
operator.AddBcfHeaderFile,
operator.AddBcfLabel,
operator.AddBcfReferenceLink,
operator.AddBcfRelatedTopic,
operator.AddBcfTopic,
operator.AddBcfViewpoint,
operator.RemoveBcfViewpoint,
operator.RemoveBcfFile,
operator.EditBcfAuthor,
operator.EditBcfComment,
operator.EditBcfLabels,
operator.EditBcfProjectName,
operator.EditBcfReferenceLinks,
operator.EditBcfTopic,
operator.EditBcfTopicName,
operator.ExtractBcfFile,
operator.LoadBcfComments,
operator.LoadBcfProject,
operator.LoadBcfTopic,
operator.LoadBcfTopics,
operator.NewBcfProject,
operator.OpenBcfReferenceLink,
operator.SelectBcfHeaderFile,
operator.RemoveBcfBimSnippet,
operator.RemoveBcfComment,
operator.RemoveBcfDocumentReference,
operator.RemoveBcfFile,
operator.RemoveBcfLabel,
operator.RemoveBcfReferenceLink,
operator.RemoveBcfRelatedTopic,
operator.RemoveBcfTopic,
operator.RemoveBcfViewpoint,
operator.SaveBcfProject,
operator.SelectBcfBimSnippetReference,
operator.SelectBcfDocumentReference,
operator.SelectBcfHeaderFile,
operator.ViewBcfTopic,
prop.BcfReferenceLink,
prop.BcfLabel,
prop.BcfBimSnippet,
@@ -28,3 +28,7 @@ class BcfStore:
if not BcfStore.bcfxml:
BcfStore.bcfxml = bcf.v2.bcfxml.BcfXml()
return BcfStore.bcfxml
@classmethod
def set(cls, bcfxml):
cls.bcfxml = bcfxml
@@ -20,15 +20,20 @@ import os
import bpy
import bcf
import bcf.bcfxml
import bcf.v2.data
import bcf.v2.model
import uuid
import numpy as np
import tempfile
import webbrowser
import ifcopenshell
import ifcopenshell.util.unit
from . import bcfstore
import blenderbim.bim.module.bcf.prop as bcf_prop
from . import bcfstore
from pathlib import Path
from blenderbim.bim.ifc import IfcStore
from math import radians, degrees, atan, tan, cos, sin
from mathutils import Vector, Matrix, Euler, geometry
from xsdata.models.datatype import XmlDateTime
class NewBcfProject(bpy.types.Operator):
@@ -38,8 +43,8 @@ class NewBcfProject(bpy.types.Operator):
def execute(self, context):
context.scene.BCFProperties.is_loaded = False
bcfxml = bcfstore.BcfStore.get_bcfxml()
bcfxml.new_project()
bcfxml = bcf.v2.bcfxml.BcfXml.create_new("New Project")
bcfstore.BcfStore.set(bcfxml)
bpy.ops.bim.load_bcf_project()
context.scene.BCFProperties.is_loaded = True
return {"FINISHED"}
@@ -57,7 +62,6 @@ class LoadBcfProject(bpy.types.Operator):
if self.filepath:
bcfstore.BcfStore.bcfxml = bcf.bcfxml.load(self.filepath)
bcfxml = bcfstore.BcfStore.get_bcfxml()
bcfxml.get_project()
context.scene.BCFProperties.name = bcfxml.project.name
bpy.ops.bim.load_bcf_topics()
context.scene.BCFProperties.is_loaded = True
@@ -75,7 +79,6 @@ class LoadBcfTopics(bpy.types.Operator):
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
bcfxml.get_topics()
context.scene.BCFProperties.topics.clear()
for index, topic_guid in enumerate(bcfxml.topics.keys()):
new = context.scene.BCFProperties.topics.add()
@@ -92,51 +95,51 @@ class LoadBcfTopic(bpy.types.Operator):
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
topic = bcfxml.get_topic(self.topic_guid)
topic = bcfxml.topics[self.topic_guid]
bcfxml.get_header(self.topic_guid)
new = context.scene.BCFProperties.topics[self.topic_index]
data_map = {
"name": topic.guid,
"title": topic.title,
"type": topic.topic_type,
"status": topic.topic_status,
"priority": topic.priority,
"stage": topic.stage,
"creation_date": topic.creation_date,
"creation_author": topic.creation_author,
"modified_date": topic.modified_date,
"modified_author": topic.modified_author,
"assigned_to": topic.assigned_to,
"due_date": topic.due_date,
"description": topic.description,
"title": topic.topic.title,
"type": topic.topic.topic_type,
"status": topic.topic.topic_status,
"priority": topic.topic.priority,
"stage": topic.topic.stage,
"creation_date": topic.topic.creation_date,
"creation_author": topic.topic.creation_author,
"modified_date": topic.topic.modified_date,
"modified_author": topic.topic.modified_author,
"assigned_to": topic.topic.assigned_to,
"due_date": topic.topic.due_date,
"description": topic.topic.description,
}
for key, value in data_map.items():
if value is not None:
setattr(new, key, str(value))
new.reference_links.clear()
for reference_link in topic.reference_links:
for reference_link in topic.topic.reference_link:
new_reference_link = new.reference_links.add()
new_reference_link.name = reference_link
new.labels.clear()
for label in topic.labels:
for label in topic.topic.labels:
new_label = new.labels.add()
new_label.name = label
if topic.bim_snippet:
if topic.topic.bim_snippet:
data_map = {
"type": topic.bim_snippet.snippet_type,
"is_external": topic.bim_snippet.is_external,
"reference": topic.bim_snippet.reference,
"schema": topic.bim_snippet.reference_schema,
"type": topic.topic.bim_snippet.snippet_type,
"is_external": topic.topic.bim_snippet.is_external,
"reference": topic.topic.bim_snippet.reference,
"schema": topic.topic.bim_snippet.reference_schema,
}
for key, value in data_map.items():
if value is not None:
setattr(new.bim_snippet, key, value)
new.document_references.clear()
for doc in topic.document_references:
for doc in topic.topic.document_reference:
new_document_references = new.document_references.add()
data_map = {
"reference": doc.referenced_document,
@@ -149,7 +152,7 @@ class LoadBcfTopic(bpy.types.Operator):
setattr(new_document_references, key, value)
new.related_topics.clear()
for related_topic in topic.related_topics:
for related_topic in topic.topic.related_topic:
new_related_topic = new.related_topics.add()
new_related_topic.name = related_topic.guid
@@ -165,10 +168,9 @@ class LoadBcfComments(bpy.types.Operator):
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
bcfxml.get_comments(self.topic_guid)
blender_topic = context.scene.BCFProperties.topics.get(self.topic_guid)
blender_topic.comments.clear()
for comment in bcfxml.topics[self.topic_guid].comments.values():
for comment in bcfxml.topics[self.topic_guid].comments:
new = blender_topic.comments.add()
data_map = {
"name": comment.guid,
@@ -193,7 +195,6 @@ class EditBcfProjectName(bpy.types.Operator):
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
bcfxml.project.name = context.scene.BCFProperties.name
bcfxml.edit_project()
return {"FINISHED"}
@@ -219,7 +220,6 @@ class EditBcfTopicName(bpy.types.Operator):
bcfxml = bcfstore.BcfStore.get_bcfxml()
topic = bcfxml.topics[blender_topic.name]
topic.title = blender_topic.title
bcfxml.edit_topic(topic)
return {"FINISHED"}
@@ -243,7 +243,6 @@ class EditBcfTopic(bpy.types.Operator):
topic.topic_status = blender_topic.status or None
topic.topic_type = blender_topic.type or None
bcfxml.edit_topic(topic)
props.refresh_topic(context)
return {"FINISHED"}
@@ -257,7 +256,7 @@ class SaveBcfProject(bpy.types.Operator):
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
bcfxml.save_project(self.filepath)
bcfxml.save(self.filepath)
return {"FINISHED"}
def invoke(self, context, event):
@@ -276,7 +275,7 @@ class AddBcfTopic(bpy.types.Operator):
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
bcfxml.add_topic()
bcfxml.add_topic("New Topic", "", context.scene.BCFProperties.author)
bpy.ops.bim.load_bcf_topics()
return {"FINISHED"}
@@ -300,15 +299,17 @@ class AddBcfBimSnippet(bpy.types.Operator):
props = context.scene.BCFProperties
blender_topic = props.active_topic
topic = bcfxml.topics[blender_topic.name]
bim_snippet = bcf.v2.data.BimSnippet()
bim_snippet.reference = props.bim_snippet_reference
bim_snippet.reference_schema = props.bim_snippet_schema
bim_snippet.snippet_type = props.bim_snippet_type
bcfxml.add_bim_snippet(topic, bim_snippet)
is_external = "://" in props.bim_snippet_reference
bim_snippet = bcf.v2.model.BimSnippet(
reference=props.bim_snippet_reference if is_external else Path(props.bim_snippet_reference).name,
reference_schema=props.bim_snippet_schema,
snippet_type=props.bim_snippet_type,
is_external=is_external,
)
topic.topic.bim_snippet = bim_snippet
with open(props.bim_snippet_reference, "r") as f:
topic.bim_snippet = f.read()
bpy.ops.bim.load_bcf_topic(topic_guid=topic.guid, topic_index=props.active_topic_index)
bim_snippet.reference = ""
bim_snippet.reference_schema = ""
bim_snippet.snippet_type = ""
return {"FINISHED"}
@@ -327,15 +328,14 @@ class AddBcfRelatedTopic(bpy.types.Operator):
if props.related_topic == blender_topic.title:
# Prevent adding self as related topic
return False
related_topic = None
related_topic_guid = None
for topic in bcfxml.topics.values():
if topic.title == props.related_topic:
related_topic = bcf.v2.data.RelatedTopic()
related_topic.guid = topic.guid
if topic.topic.title == props.related_topic:
related_topic_guid = topic.guid
break
if not related_topic:
if not related_topic_guid:
return False
if str(related_topic.guid) in [t.name for t in blender_topic.related_topics]:
if str(related_topic_guid) in [t.name for t in blender_topic.related_topics]:
# Prevent adding the same related topic more than once
return False
return True
@@ -344,11 +344,12 @@ class AddBcfRelatedTopic(bpy.types.Operator):
bcfxml = bcfstore.BcfStore.get_bcfxml()
props = context.scene.BCFProperties
blender_topic = props.active_topic
related_topic = bcf.v2.data.RelatedTopic()
related_topic.guid = next((t for t in bcfxml.topics.values() if t.title == props.related_topic)).guid
topic = bcfxml.topics[blender_topic.name]
topic.related_topics.append(related_topic)
bcfxml.edit_topic(topic)
topic.topic.related_topic.append(
bcf.v2.model.TopicRelatedTopic(
guid=next((t for t in bcfxml.topics.values() if t.topic.title == props.related_topic)).guid
)
)
bpy.ops.bim.load_bcf_topic(topic_guid=topic.guid, topic_index=props.active_topic_index)
props.related_topic = ""
return {"FINISHED"}
@@ -368,15 +369,24 @@ class AddBcfHeaderFile(bpy.types.Operator):
props = context.scene.BCFProperties
blender_topic = props.active_topic
topic = bcfxml.topics[blender_topic.name]
header_file = bcf.v2.data.HeaderFile()
header_file.reference = props.file_reference
if not os.path.exists(header_file.reference):
header_file.filename = header_file.reference
if len(props.file_ifc_project) == 22:
header_file.ifc_project = props.file_ifc_project
if len(props.file_ifc_spatial_structure_element) == 22:
header_file.ifc_spatial_structure_element = props.file_ifc_spatial_structure_element
bcfxml.add_file(topic, header_file)
is_external = "://" in props.file_reference
filename = Path(props.file_reference).name if os.path.exists(props.file_reference) else None
if filename:
with open(props.file_reference, "r") as f:
topic.reference_files[filename] = f.read()
header_file = bcf.v2.model.HeaderFile(
filename=filename,
date=XmlDateTime.now(),
reference=props.file_reference if is_external else Path(props.file_reference).name,
ifc_project=props.file_ifc_project,
ifc_spatial_structure_element=props.file_ifc_spatial_structure_element,
is_external=is_external,
)
topic.header.file.append(header_file)
props.refresh_topic(context)
return {"FINISHED"}
@@ -410,27 +420,34 @@ class AddBcfViewpoint(bpy.types.Operator):
props = context.scene.BCFProperties
blender_topic = props.active_topic
topic = bcfxml.topics[blender_topic.name]
viewpoint = bcf.v2.data.Viewpoint()
if blender_camera.data.type == "ORTHO":
camera = bcf.v2.data.OrthogonalCamera()
camera.view_to_world_scale = blender_camera.data.ortho_scale
viewpoint.orthogonal_camera = camera
elif blender_camera.data.type == "PERSP":
camera = bcf.v2.data.PerspectiveCamera()
camera.field_of_view = degrees(blender_camera.data.angle)
viewpoint.perspective_camera = camera
camera.camera_view_point.x = blender_camera.location.x
camera.camera_view_point.y = blender_camera.location.y
camera.camera_view_point.z = blender_camera.location.z
direction = blender_camera.matrix_world.to_quaternion() @ Vector((0.0, 0.0, -1.0))
camera.camera_direction.x = direction.x
camera.camera_direction.y = direction.y
camera.camera_direction.z = direction.z
up = blender_camera.matrix_world.to_quaternion() @ Vector((0.0, 1.0, 0.0))
camera.camera_up_vector.x = up.x
camera.camera_up_vector.y = up.y
camera.camera_up_vector.z = up.z
camera_view_point = bcf.v2.model.Point(
x=blender_camera.location.x, y=blender_camera.location.y, z=blender_camera.location.z
)
camera_direction = bcf.v2.model.Direction(x=direction.x, y=direction.y, z=direction.z)
camera_up_vector = bcf.v2.model.Direction(x=up.x, y=up.y, z=up.z)
if blender_camera.data.type == "ORTHO":
camera = bcf.v2.model.OrthogonalCamera(
view_to_world_scale=blender_camera.data.ortho_scale,
camera_view_point=camera_view_point,
camera_direction=camera_direction,
camera_up_vector=camera_up_vector,
)
visualization_info = bcf.v2.model.VisualizationInfo(orthogonal_camera=camera)
elif blender_camera.data.type == "PERSP":
camera = bcf.v2.model.PerspectiveCamera(
field_of_view=degrees(blender_camera.data.angle),
camera_view_point=camera_view_point,
camera_direction=camera_direction,
camera_up_vector=camera_up_vector,
)
visualization_info = bcf.v2.model.VisualizationInfo(guid=str(uuid.uuid4()), perspective_camera=camera)
# TODO allow the user to enable or disable snapshotting
snapshot = None
blender_render = context.scene.render
old_file_format = blender_render.image_settings.file_format
@@ -438,9 +455,17 @@ class AddBcfViewpoint(bpy.types.Operator):
old_filepath = blender_render.filepath
blender_render.filepath = os.path.join(context.scene.BIMProperties.data_dir, "snapshot.png")
bpy.ops.render.opengl(write_still=True)
viewpoint.snapshot = blender_render.filepath
with open(blender_render.filepath, "rb") as f:
snapshot = f.read()
# viewpoint.snapshot = blender_render.filepath
# TODO we should handle selection and visibility state
vizinfo = bcf.v2.visinfo.VisualizationInfoHandler(visualization_info=visualization_info, snapshot=snapshot)
topic.viewpoints[vizinfo.guid + ".bcfv"] = vizinfo
topic.markup.viewpoints.append(
bcf.v2.model.ViewPoint(viewpoint=vizinfo.guid + ".bcfv", guid=vizinfo.guid, snapshot=vizinfo.guid + ".png")
)
bcfxml.add_viewpoint(topic, viewpoint)
blender_render.filepath = old_filepath
blender_render.image_settings.file_format = old_file_format
props.refresh_topic(context)
@@ -460,9 +485,15 @@ class RemoveBcfViewpoint(bpy.types.Operator):
bcfxml = bcfstore.BcfStore.get_bcfxml()
props = context.scene.BCFProperties
blender_topic = props.active_topic
viewpoint_guid = blender_topic.viewpoints
topic = bcfxml.topics[blender_topic.name]
bcfxml.delete_viewpoint(viewpoint_guid, topic)
for key, viewpoint in topic.viewpoints.items():
if viewpoint.guid == blender_topic.viewpoints:
break
del topic.viewpoints[key]
for i, viewpoint in enumerate(topic.markup.viewpoints):
if viewpoint.guid == blender_topic.viewpoints:
break
del topic.markup.viewpoints[i]
props.refresh_topic(context)
return {"FINISHED"}
@@ -478,7 +509,7 @@ class RemoveBcfFile(bpy.types.Operator):
props = context.scene.BCFProperties
blender_topic = props.active_topic
topic = bcfxml.topics[blender_topic.name]
bcfxml.delete_file(topic, self.index)
del topic.header.file[self.index]
props.refresh_topic(context)
return {"FINISHED"}
@@ -496,8 +527,7 @@ class RemoveBcfTopic(bpy.types.Operator):
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
props = context.scene.BCFProperties
topic_to_delete = props.active_topic
bcfxml.delete_topic(topic_to_delete.name)
del bcfxml.topics[props.active_topic.name]
bpy.ops.bim.load_bcf_topics()
return {"FINISHED"}
@@ -516,8 +546,7 @@ class AddBcfReferenceLink(bpy.types.Operator):
props = context.scene.BCFProperties
blender_topic = props.active_topic
topic = bcfxml.topics[blender_topic.name]
topic.reference_links.append(props.reference_link)
bcfxml.edit_topic(topic)
topic.topic.reference_link.append(props.reference_link)
bpy.ops.bim.load_bcf_topic(topic_guid=topic.guid, topic_index=props.active_topic_index)
props.reference_link = ""
return {"FINISHED"}
@@ -537,10 +566,23 @@ class AddBcfDocumentReference(bpy.types.Operator):
props = context.scene.BCFProperties
blender_topic = props.active_topic
topic = bcfxml.topics[blender_topic.name]
document_reference = bcf.v2.data.DocumentReference()
document_reference.referenced_document = props.document_reference
document_reference.description = props.document_reference_description or None
bcfxml.add_document_reference(topic, document_reference)
is_external = "://" in props.document_reference
filename = Path(props.document_reference).name if os.path.exists(props.document_reference) else None
if filename:
with open(props.document_reference, "rb") as f:
topic.document_references[filename] = f.read()
document_reference = bcf.v2.model.TopicDocumentReference(
referenced_document=props.document_reference if is_external else filename,
description=props.document_reference_description or None,
guid=str(uuid.uuid4()),
is_external=is_external,
)
topic.topic.document_reference.append(document_reference)
bpy.ops.bim.load_bcf_topic(topic_guid=topic.guid, topic_index=props.active_topic_index)
props.document_reference = ""
props.document_reference_description = ""
@@ -563,8 +605,7 @@ class AddBcfLabel(bpy.types.Operator):
topic = bcfxml.topics[blender_topic.name]
new = blender_topic.labels.add()
new.name = props.label
topic.labels.append(props.label)
bcfxml.edit_topic(topic)
topic.topic.labels.append(props.label)
props.label = ""
return {"FINISHED"}
@@ -579,11 +620,10 @@ class EditBcfReferenceLinks(bpy.types.Operator):
props = context.scene.BCFProperties
blender_topic = props.active_topic
topic = bcfxml.topics[blender_topic.name]
for index, reference_link in enumerate(topic.reference_links):
for index, reference_link in enumerate(topic.topic.reference_link):
if reference_link == blender_topic.reference_links[index].name:
continue
topic.reference_links[index] = blender_topic.reference_links[index].name
bcfxml.edit_topic(topic)
topic.topic.reference_link[index] = blender_topic.reference_links[index].name
return {"FINISHED"}
@@ -598,12 +638,11 @@ class EditBcfLabels(bpy.types.Operator):
blender_topic = props.active_topic
topic = bcfxml.topics[blender_topic.name]
for index, label in enumerate(blender_topic.labels):
if index >= len(topic.labels):
if index >= len(topic.topic.labels):
break
if label.name == topic.labels[index]:
if label.name == topic.topic.labels[index]:
continue
topic.labels[index] = blender_topic.labels[index].name
bcfxml.edit_topic(topic)
topic.topic.labels[index] = blender_topic.labels[index].name
return {"FINISHED"}
@@ -618,8 +657,7 @@ class RemoveBcfReferenceLink(bpy.types.Operator):
props = context.scene.BCFProperties
blender_topic = props.active_topic
topic = bcfxml.topics[blender_topic.name]
del topic.reference_links[self.index]
bcfxml.edit_topic(topic)
del topic.topic.reference_link[self.index]
blender_topic.reference_links.remove(self.index)
return {"FINISHED"}
@@ -635,8 +673,7 @@ class RemoveBcfLabel(bpy.types.Operator):
props = context.scene.BCFProperties
blender_topic = props.active_topic
topic = bcfxml.topics[blender_topic.name]
del topic.labels[self.index]
bcfxml.edit_topic(topic)
del topic.topic.labels[self.index]
blender_topic.labels.remove(self.index)
return {"FINISHED"}
@@ -651,7 +688,7 @@ class RemoveBcfBimSnippet(bpy.types.Operator):
props = context.scene.BCFProperties
blender_topic = props.active_topic
topic = bcfxml.topics[blender_topic.name]
bcfxml.delete_bim_snippet(topic)
topic.topic.bim_snippet = None
blender_topic.bim_snippet.schema = ""
blender_topic.bim_snippet.reference = ""
blender_topic.bim_snippet.type = ""
@@ -669,7 +706,7 @@ class RemoveBcfDocumentReference(bpy.types.Operator):
props = context.scene.BCFProperties
blender_topic = props.active_topic
topic = bcfxml.topics[blender_topic.name]
bcfxml.delete_document_reference(topic, self.index)
del topic.topic.document_reference[self.index]
bpy.ops.bim.load_bcf_topic(topic_guid=topic.guid, topic_index=props.active_topic_index)
return {"FINISHED"}
@@ -685,8 +722,7 @@ class RemoveBcfRelatedTopic(bpy.types.Operator):
props = context.scene.BCFProperties
blender_topic = props.active_topic
topic = bcfxml.topics[blender_topic.name]
del topic.related_topics[self.index]
bcfxml.edit_topic(topic)
del topic.topic.related_topic[self.index]
bpy.ops.bim.load_bcf_topic(topic_guid=topic.guid, topic_index=props.active_topic_index)
return {"FINISHED"}
@@ -702,7 +738,10 @@ class RemoveBcfComment(bpy.types.Operator):
props = context.scene.BCFProperties
blender_topic = props.active_topic
topic = bcfxml.topics[blender_topic.name]
bcfxml.delete_comment(self.comment_guid, topic)
for i, comment in enumerate(topic.comments):
if comment.guid == self.comment_guid:
break
del topic.comments[i]
bpy.ops.bim.load_bcf_comments(topic_guid=topic.guid)
return {"FINISHED"}
@@ -719,9 +758,11 @@ class EditBcfComment(bpy.types.Operator):
blender_topic = props.active_topic
blender_comment = blender_topic.comments.get(self.comment_guid)
topic = bcfxml.topics[blender_topic.name]
comment = topic.comments[self.comment_guid]
comment.comment = blender_comment.comment
bcfxml.edit_comment(comment, topic)
for i, comment in enumerate(topic.comments):
if comment.guid == self.comment_guid:
comment.comment = blender_comment.comment
comment.modified_date = XmlDateTime.now()
comment.modified_author = context.scene.BCFProperties.author
bpy.ops.bim.load_bcf_comments(topic_guid=topic.guid)
return {"FINISHED"}
@@ -746,12 +787,15 @@ class AddBcfComment(bpy.types.Operator):
props = context.scene.BCFProperties
blender_topic = props.active_topic
topic = bcfxml.topics[blender_topic.name]
comment = bcf.v2.data.Comment()
comment.comment = props.comment
comment = bcf.v2.model.Comment(
date=XmlDateTime.now(),
author=context.scene.BCFProperties.author,
comment=props.comment,
guid=str(uuid.uuid4()),
)
if props.has_related_viewpoint:
comment.viewpoint = bcf.v2.data.Viewpoint()
comment.viewpoint.guid = blender_topic.viewpoints
bcfxml.add_comment(topic, comment)
comment.viewpoint = bcf.v2.model.CommentViewpoint(guid=blender_topic.viewpoints)
topic.comments.append(comment)
bpy.ops.bim.load_bcf_comments(topic_guid=topic.guid)
props.comment = ""
props.has_related_viewpoint = False
@@ -792,11 +836,13 @@ class ActivateBcfViewpoint(bpy.types.Operator):
cam_height = context.scene.render.resolution_y
cam_aspect = cam_width / cam_height
obj.data.background_images.clear()
if viewpoint.snapshot:
obj.data.show_background_images = True
obj.data.background_images.clear()
background = obj.data.background_images.new()
background.image = bpy.data.images.load(os.path.join(bcfxml.filepath, topic.guid, viewpoint.snapshot))
with tempfile.NamedTemporaryFile(delete=False) as f:
f.write(viewpoint.snapshot)
background.image = bpy.data.images.load(f.name)
src_width = background.image.size[0]
src_height = background.image.size[1]
src_aspect = src_width / src_height
@@ -806,6 +852,9 @@ class ActivateBcfViewpoint(bpy.types.Operator):
else:
background.frame_method = "CROP"
background.display_depth = "FRONT"
else:
obj.data.show_background_images = False
area = next(area for area in context.screen.areas if area.type == "VIEW_3D")
area.spaces[0].region_3d.view_perspective = "CAMERA"
@@ -815,27 +864,27 @@ class ActivateBcfViewpoint(bpy.types.Operator):
gp = bpy.data.grease_pencils.get("BCF")
if gp:
bpy.data.grease_pencils.remove(gp)
if viewpoint.lines:
if viewpoint.visualization_info.lines:
self.draw_lines(viewpoint, context)
self.delete_clipping_planes(context)
if viewpoint.clipping_planes:
if viewpoint.visualization_info.clipping_planes:
self.create_clipping_planes(viewpoint)
self.delete_bitmaps(context)
if viewpoint.bitmaps:
if viewpoint.visualization_info.bitmap:
self.create_bitmaps(bcfxml, viewpoint, topic)
self.setup_camera(viewpoint, obj, cam_aspect, context)
return {"FINISHED"}
def setup_camera(self, viewpoint, obj, cam_aspect, context):
if viewpoint.orthogonal_camera:
camera = viewpoint.orthogonal_camera
if viewpoint.visualization_info.orthogonal_camera:
camera = viewpoint.visualization_info.orthogonal_camera
obj.data.type = "ORTHO"
obj.data.ortho_scale = viewpoint.orthogonal_camera.view_to_world_scale
elif viewpoint.perspective_camera:
camera = viewpoint.perspective_camera
obj.data.ortho_scale = viewpoint.visualization_info.orthogonal_camera.view_to_world_scale
elif viewpoint.visualization_info.perspective_camera:
camera = viewpoint.visualization_info.perspective_camera
obj.data.type = "PERSP"
if cam_aspect >= 1:
obj.data.angle = radians(camera.field_of_view)
@@ -876,15 +925,15 @@ class ActivateBcfViewpoint(bpy.types.Operator):
obj.matrix_world = Matrix(matrix.tolist())
def set_viewpoint_components(self, viewpoint, context):
if not viewpoint.components:
if not viewpoint.visualization_info.components:
return
# Operators with context overrides are used because they are
# significantly faster than looping through all objects
exception_global_ids = {v.ifc_guid for v in viewpoint.components.visibility.exceptions}
exception_global_ids = {v.ifc_guid for v in viewpoint.visualization_info.components.visibility.exceptions or []}
if viewpoint.components.visibility.default_visibility:
if viewpoint.visualization_info.components.visibility.default_visibility:
old = context.area.type
context.area.type = "VIEW_3D"
bpy.ops.object.hide_view_clear()
@@ -908,11 +957,13 @@ class ActivateBcfViewpoint(bpy.types.Operator):
bpy.ops.object.hide_view_set(context_override, unselected=True)
context.area.type = old
if viewpoint.components.view_setup_hints:
if not viewpoint.components.view_setup_hints.spaces_visible:
if viewpoint.visualization_info.components.view_setup_hints:
if not viewpoint.visualization_info.components.view_setup_hints.spaces_visible:
self.hide_spaces(context)
if viewpoint.components.view_setup_hints.openings_visible is not None:
self.set_openings_visibility(viewpoint.components.view_setup_hints.openings_visible, context)
if viewpoint.visualization_info.components.view_setup_hints.openings_visible is not None:
self.set_openings_visibility(
viewpoint.visualization_info.components.view_setup_hints.openings_visible, context
)
else:
self.hide_spaces(context)
self.set_openings_visibility(False, context)
@@ -928,11 +979,12 @@ class ActivateBcfViewpoint(bpy.types.Operator):
context.area.type = old
def set_openings_visibility(self, is_visible, context):
for collection in self.get_opening_collections(context):
collection.hide_viewport = not is_visible
pass # We no longer have an openings collection
def set_selection(self, viewpoint):
selected_global_ids = [s.ifc_guid for s in viewpoint.components.selection]
if not viewpoint.visualization_info.components or not viewpoint.visualization_info.components.selection:
return
selected_global_ids = [s.ifc_guid for s in viewpoint.visualization_info.components.selection.component or []]
bpy.ops.object.select_all(action="DESELECT")
for global_id in selected_global_ids:
obj = IfcStore.get_element(global_id)
@@ -941,7 +993,7 @@ class ActivateBcfViewpoint(bpy.types.Operator):
def set_colours(self, viewpoint):
global_id_colours = {}
for coloring in viewpoint.components.coloring:
for coloring in viewpoint.visualization_info.components.coloring or []:
for component in coloring.components:
global_id_colours.setdefault(component.ifc_guid, coloring.color)
for global_id, color in global_id_colours.items():
@@ -949,14 +1001,6 @@ class ActivateBcfViewpoint(bpy.types.Operator):
if obj:
obj.color = self.hex_to_rgb(color)
def get_opening_collections(self, context):
collections = []
for collection in context.view_layer.layer_collection.children:
opening_collection = collection.children.get("IfcOpeningElements")
if opening_collection:
collections.append(opening_collection)
return collections
def draw_lines(self, viewpoint, context):
gp = bpy.data.grease_pencils.new("BCF")
scene = context.scene
@@ -968,9 +1012,9 @@ class ActivateBcfViewpoint(bpy.types.Operator):
frame = layer.frames.new(1)
stroke = frame.strokes.new()
stroke.display_mode = "3DSPACE"
stroke.points.add(len(viewpoint.lines) * 2)
stroke.points.add(len(viewpoint.visualization_info.lines.line) * 2)
coords = []
for l in viewpoint.lines:
for l in viewpoint.visualization_info.lines.line:
coords.extend(
[l.start_point.x, l.start_point.y, l.start_point.z, l.end_point.x, l.end_point.y, l.end_point.z]
)
@@ -978,7 +1022,7 @@ class ActivateBcfViewpoint(bpy.types.Operator):
def create_clipping_planes(self, viewpoint):
n = 0
for plane in viewpoint.clipping_planes:
for plane in viewpoint.visualization_info.clipping_planes.clipping_plane:
bpy.ops.bim.add_section_plane()
if n == 0:
obj = bpy.data.objects["Section"]
@@ -1011,10 +1055,14 @@ class ActivateBcfViewpoint(bpy.types.Operator):
collection = bpy.data.collections.get("Bitmaps")
if not collection:
collection = bpy.data.collections.new("Bitmaps")
for bitmap in viewpoint.bitmaps:
for bitmap in viewpoint.visualization_info.bitmap:
obj = bpy.data.objects.new("Bitmap", None)
obj.empty_display_type = "IMAGE"
image = bpy.data.images.load(os.path.join(bcfxml.filepath, topic.guid, bitmap.reference))
# image = bpy.data.images.load(os.path.join(bcfxml.filepath, topic.guid, bitmap.reference))
with tempfile.NamedTemporaryFile(delete=False) as f:
topic.extract_file(bitmap, f.name)
# f.write(bitmap.what)
image = bpy.data.images.load(f.name)
src_width = image.size[0]
src_height = image.size[1]
if src_height > src_width:
@@ -1053,6 +1101,7 @@ class SelectBcfHeaderFile(bpy.types.Operator):
bl_label = "Select BCF Header File"
bl_options = {"REGISTER", "UNDO"}
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
filter_glob: bpy.props.StringProperty(default="*.ifc;*.ifczip;*.ifcxml;*.ifcjson", options={"HIDDEN"})
def execute(self, context):
if self.filepath:
@@ -1094,3 +1143,25 @@ class SelectBcfDocumentReference(bpy.types.Operator):
def invoke(self, context, event):
context.window_manager.fileselect_add(self)
return {"RUNNING_MODAL"}
class ExtractBcfFile(bpy.types.Operator):
bl_idname = "bim.extract_bcf_file"
bl_label = "Extract BCF Header File"
bl_options = {"REGISTER", "UNDO"}
entity_type: bpy.props.StringProperty()
index: bpy.props.IntProperty()
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
topic = bcfxml.topics[context.scene.BCFProperties.active_topic.name]
if self.entity_type == "HEADER_FILE":
entity = topic.header.file[self.index]
elif self.entity_type == "BIM_SNIPPET":
entity = topic.markup.topic.bim_snippet
elif self.entity_type == "DOCUMENT_REFERENCE":
entity = topic.markup.topic.document_reference[self.index]
webbrowser.open(str(topic.extract_file(entity).parent))
return {"FINISHED"}
@@ -95,8 +95,8 @@ def getBcfViewpoints(self, context, force_update=False):
props = context.scene.BCFProperties
bcfxml = bcfstore.BcfStore.get_bcfxml()
topic = props.active_topic
viewpoints = bcfxml.get_viewpoints(topic.name) if topic else {}
bcfviewpoints_enum.extend([(v, f"Viewpoint {i+1}", "") for i, v in enumerate(viewpoints.keys())])
viewpoints = bcfxml.topics[topic.name].viewpoints.keys() if topic else []
bcfviewpoints_enum.extend([(v, f"Viewpoint {i+1}", "") for i, v in enumerate(viewpoints)])
return bcfviewpoints_enum
+12 -9
View File
@@ -127,20 +127,21 @@ class BIM_PT_bcf_metadata(Panel):
layout.label(text="Header Files:")
if bcf_topic.header:
for index, f in enumerate(bcf_topic.header.files):
for index, f in enumerate(bcf_topic.header.file):
box = self.layout.box()
row = box.row(align=True)
row.label(text=f.filename, icon="FILE_BLANK")
if f.is_external:
row.operator("bim.open_uri", icon="URL", text="").uri = f.reference
else:
op = row.operator("bim.open_uri", icon="FILE_FOLDER", text="")
op.uri = os.path.join(bcfxml.filepath, topic.name, f.reference)
op = row.operator("bim.extract_bcf_file", icon="FILE_FOLDER", text="")
op.index = index
op.entity_type = "HEADER_FILE"
row.operator("bim.remove_bcf_file", icon="X", text="").index = index
row = box.row()
row.label(text="Date")
row.label(text=f.date)
row.label(text=str(f.date))
if f.ifc_project:
row = box.row()
@@ -196,8 +197,8 @@ class BIM_PT_bcf_metadata(Panel):
if topic.bim_snippet.is_external:
row.operator("bim.open_uri", icon="URL", text="").uri = topic.bim_snippet.reference
else:
op = row.operator("bim.open_uri", icon="FILE_FOLDER", text="")
op.uri = os.path.join(bcfxml.filepath, topic.name, topic.bim_snippet.reference)
op = row.operator("bim.extract_bcf_file", icon="FILE_FOLDER", text="")
op.entity_type = "BIM_SNIPPET"
row.operator("bim.remove_bcf_bim_snippet", icon="X", text="")
else:
row = layout.row(align=True)
@@ -218,8 +219,10 @@ class BIM_PT_bcf_metadata(Panel):
if doc.is_external:
row.operator("bim.open_uri", icon="URL", text="").uri = doc.reference
else:
op = row.operator("bim.open_uri", icon="FILE_FOLDER", text="")
op.uri = os.path.join(bcfxml.filepath, topic.name, doc.reference)
op = row.operator("bim.extract_bcf_file", icon="FILE_FOLDER", text="")
op.entity_type = "DOCUMENT_REFERENCE"
op.index = index
row.operator("bim.remove_bcf_document_reference", icon="X", text="").index = index
row = box.row(align=True)
row.prop(doc, "description")
@@ -236,7 +239,7 @@ class BIM_PT_bcf_metadata(Panel):
try:
row = layout.row(align=True)
op = row.operator(
"bim.view_bcf_topic", text=f"Select {bcfxml.topics[related_topic.name.lower()].title}"
"bim.view_bcf_topic", text=f"Select {bcfxml.topics[related_topic.name.lower()].topic.title}"
)
op.topic_guid = related_topic.name
row.operator("bim.remove_bcf_related_topic", icon="X", text="").index = index
@@ -20,18 +20,21 @@ import bpy
from . import ui, operator, prop
classes = (
operator.ColourByRelatedBuildingElement,
operator.DisableEditingBoundary,
operator.EditBoundaryAttributes,
operator.EnableEditingBoundary,
operator.LoadProjectSpaceBoundaries,
operator.LoadSpaceBoundaries,
operator.LoadBoundary,
operator.SelectRelatedElementBoundaries,
operator.SelectProjectBoundaries,
operator.ColourByRelatedBuildingElement,
operator.EnableEditingBoundary,
operator.DisableEditingBoundary,
operator.EditBoundaryAttributes,
operator.SelectRelatedElementTypeBoundaries,
operator.SelectSpaceBoundaries,
operator.UpdateBoundaryGeometry,
ui.BIM_PT_Boundary,
ui.BIM_PT_SpaceBoundaries,
ui.BIM_PT_SceneBoundaries,
ui.BIM_PT_SpaceBoundaries,
prop.BIMBoundaryProperties,
)
@@ -0,0 +1,43 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2023 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BlenderBIM Add-on.
#
# BlenderBIM Add-on 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.
#
# BlenderBIM Add-on 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 BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
import blenderbim.tool as tool
def refresh():
SpaceBoundariesData.is_loaded = False
class SpaceBoundariesData:
data = {}
is_loaded = False
@classmethod
def load(cls):
cls.data = {"boundaries": cls.boundaries()}
cls.is_loaded = True
@classmethod
def boundaries(cls):
results = []
element = tool.Ifc.get_entity(bpy.context.active_object)
for rel in element.BoundedBy or []:
description = f"{rel.id()} > {rel.RelatedBuildingElement.is_a()}/{rel.RelatedBuildingElement.Name}"
results.append({"id": rel.id(), "description": description})
return results
@@ -17,11 +17,14 @@
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
import bmesh
import mathutils
import logging
import ifcopenshell.api
import ifcopenshell.util.placement
import ifcopenshell.util.unit
import blenderbim.tool as tool
from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.boundary.data import Data
import blenderbim.bim.import_ifc as import_ifc
@@ -40,8 +43,8 @@ class Loader:
self.ifc_file = None
self.logger = None
self.ifc_importer = None
self.settings = None
self.load_settings()
self.settings = self.load_settings()
self.fallback_settings = self.load_fallback_settings()
self.load_importer()
def create_mesh(self, boundary):
@@ -49,18 +52,50 @@ class Loader:
if not boundary.ConnectionGeometry:
return None
surface = boundary.ConnectionGeometry.SurfaceOnRelatingElement
# workaround for invalid geometry provided by Revit. See https://github.com/IfcOpenShell/IfcOpenShell/issues/635#issuecomment-770366838
# Workaround for invalid geometry provided by Revit. See https://github.com/Autodesk/revit-ifc/issues/270
if surface.is_a("IfcCurveBoundedPlane") and not getattr(surface, "InnerBoundaries", None):
surface.InnerBoundaries = ()
shape = ifcopenshell.geom.create_shape(self.settings, surface)
mesh = self.ifc_importer.create_mesh(None, shape)
self.ifc_importer.link_mesh(shape, mesh)
try:
shape = ifcopenshell.geom.create_shape(self.settings, surface)
mesh = self.ifc_importer.create_mesh(None, shape)
tool.Loader.link_mesh(shape, mesh)
except RuntimeError:
# Fallback solution for invalid geometry provided by Revit. (InnerBoundaries cuting OuterBoundary)
print(f"Failed to create mesh from IfcRelSpaceBoundary with ID {boundary.id()}. Geometry might be invalid")
shape = ifcopenshell.geom.create_shape(self.fallback_settings, surface.OuterBoundary)
mesh = bpy.data.meshes.new(str(surface.id()))
bm = bmesh.new()
verts = [bm.verts.new(shape.verts[i : i + 3]) for i in range(0, len(shape.verts), 3)]
bm.faces.new(verts)
for inner_boundary in surface.InnerBoundaries:
shape = ifcopenshell.geom.create_shape(self.fallback_settings, inner_boundary)
verts = [bm.verts.new(shape.verts[i : i + 3]) for i in range(0, len(shape.verts), 3)]
for i in range(len(verts) - 1):
bm.edges.new(verts[i : i + 2])
bm.edges.new((verts[-1], verts[0]))
bm.to_mesh(mesh)
bm.free()
mesh.BIMMeshProperties.ifc_definition_id = surface.id()
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
matrix = mathutils.Matrix(
ifcopenshell.util.placement.get_axis2placement(surface.BasisSurface.Position).tolist()
)
matrix[0][3] *= unit_scale
matrix[1][3] *= unit_scale
matrix[2][3] *= unit_scale
mesh.transform(matrix)
return mesh
def load_settings(self):
self.settings = ifcopenshell.geom.settings()
self.settings.set(self.settings.EXCLUDE_SOLIDS_AND_SURFACES, False)
self.settings.set(self.settings.USE_BREP_DATA, False)
settings = ifcopenshell.geom.settings()
settings.set(settings.EXCLUDE_SOLIDS_AND_SURFACES, False)
settings.set(settings.USE_BREP_DATA, False)
return settings
def load_fallback_settings(self):
settings = ifcopenshell.geom.settings()
settings.set(settings.INCLUDE_CURVES, True)
return settings
def load_importer(self):
self.ifc_file = tool.Ifc.get()
@@ -69,8 +104,7 @@ class Loader:
self.ifc_importer = import_ifc.IfcImporter(ifc_import_settings)
self.ifc_importer.file = self.ifc_file
def load_boundary(self, boundary_id, blender_space):
boundary = self.ifc_file.by_id(boundary_id)
def load_boundary(self, boundary, blender_space):
obj = tool.Ifc.get_object(boundary)
if obj:
return obj
@@ -90,12 +124,8 @@ class LoadProjectSpaceBoundaries(bpy.types.Operator):
def execute(self, context):
loader = Loader()
if not Data.is_loaded:
Data.load(tool.Ifc.get())
for space_id, boundaries_id in Data.spaces.items():
blender_space = tool.Ifc.get_object(tool.Ifc.get().by_id(space_id))
for boundary_id in boundaries_id:
loader.load_boundary(boundary_id, blender_space)
for rel in tool.Ifc.get().by_type("IfcRelSpaceBoundary"):
loader.load_boundary(rel, tool.Ifc.get_object(rel.RelatingSpace))
return {"FINISHED"}
@@ -107,10 +137,9 @@ class LoadBoundary(bpy.types.Operator):
def execute(self, context):
loader = Loader()
blender_space = context.active_object
for obj in context.visible_objects:
obj.select_set(False)
obj = loader.load_boundary(self.boundary_id, blender_space)
obj = loader.load_boundary(tool.Ifc.get().by_id(self.boundary_id), context.active_object)
obj.select_set(True)
bpy.context.view_layer.objects.active = obj
return {"FINISHED"}
@@ -122,13 +151,74 @@ class LoadSpaceBoundaries(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
blender_space = context.active_object
oprops = context.active_object.BIMObjectProperties
loader = Loader()
if not Data.is_loaded:
Data.load(tool.Ifc.get())
for boundary_id in Data.spaces.get(oprops.ifc_definition_id, []):
loader.load_boundary(boundary_id, blender_space)
element = tool.Ifc.get_entity(context.active_object)
for rel in element.BoundedBy or []:
loader.load_boundary(rel, context.active_object)
return {"FINISHED"}
def get_element_boundaries(element):
if not element:
return ()
if element.is_a("IfcSpace"):
return (b for b in element.BoundedBy or ())
return (b for b in element.ProvidesBoundaries)
class SelectRelatedElementBoundaries(bpy.types.Operator):
bl_idname = "bim.select_related_element_boundaries"
bl_label = "Select related element space boundaries"
bl_options = {"REGISTER", "UNDO"}
related_element: bpy.props.IntProperty()
def execute(self, context):
for obj in context.visible_objects:
obj.select_set(False)
element = tool.Ifc.get().by_id(self.related_element)
for rel in get_element_boundaries(element):
obj = tool.Ifc.get_object(rel)
if obj:
obj.select_set(True)
return {"FINISHED"}
class SelectRelatedElementTypeBoundaries(bpy.types.Operator):
bl_idname = "bim.select_related_element_type_boundaries"
bl_label = "Select related element type space boundaries"
bl_options = {"REGISTER", "UNDO"}
related_element: bpy.props.IntProperty()
def execute(self, context):
for obj in context.visible_objects:
obj.select_set(False)
element = tool.Ifc.get().by_id(self.related_element)
if not element:
return {"FINISHED"}
element_type = tool.Root.get_element_type(element)
if not element_type:
return {"FINISHED"}
for child in tool.Type.get_type_occurrences(element_type):
for rel in get_element_boundaries(child):
obj = tool.Ifc.get_object(rel)
if obj:
obj.select_set(True)
return {"FINISHED"}
class SelectSpaceBoundaries(bpy.types.Operator):
bl_idname = "bim.select_space_boundaries"
bl_label = "Select all space boundaries"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
for obj in context.visible_objects:
obj.select_set(False)
element = tool.Ifc.get_entity(context.active_object)
for rel in element.BoundedBy or []:
obj = tool.Ifc.get_object(rel)
if obj:
obj.select_set(True)
return {"FINISHED"}
@@ -140,10 +230,8 @@ class SelectProjectBoundaries(bpy.types.Operator):
def execute(self, context):
for obj in context.visible_objects:
obj.select_set(False)
ifc_file = tool.Ifc.get()
for boundary_id in Data.boundaries:
boundary = ifc_file.by_id(boundary_id)
obj = tool.Ifc.get_object(boundary)
for rel in tool.Ifc.get().by_type("IfcRelSpaceBoundary"):
obj = tool.Ifc.get_object(rel)
if obj:
obj.select_set(True)
return {"FINISHED"}
@@ -184,11 +272,10 @@ class ColourByRelatedBuildingElement(bpy.types.Operator):
return result
def _execute(self, context):
self.file = tool.Ifc.get()
for obj in context.visible_objects:
if not obj.BIMObjectProperties.ifc_definition_id:
continue
element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
element = tool.Ifc.get_entity(obj)
if not element.is_a("IfcRelSpaceBoundary"):
continue
obj.color = get_colour(element)
@@ -285,6 +372,7 @@ class UpdateBoundaryGeometry(bpy.types.Operator):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
tool.Boundary.move_origin_to_space_origin(context.active_object)
settings = tool.Boundary.get_assign_connection_geometry_settings(context.active_object)
ifcopenshell.api.run("boundary.assign_connection_geometry", tool.Ifc.get(), **settings)
return {"FINISHED"}
@@ -21,7 +21,7 @@ import blenderbim.bim.helper
from bpy.types import Panel, UIList
from blenderbim.bim.ifc import IfcStore
import blenderbim.tool as tool
from ifcopenshell.api.boundary.data import Data
from blenderbim.bim.module.boundary.data import SpaceBoundariesData
class BIM_PT_SceneBoundaries(Panel):
@@ -67,7 +67,6 @@ class BIM_PT_Boundary(Panel):
def draw(self, context):
props = context.active_object.BIMObjectProperties
ifc_file = tool.Ifc.get()
element = ifc_file.by_id(props.ifc_definition_id)
boundary = ifc_file.by_id(props.ifc_definition_id)
self.bprops = context.active_object.bim_boundary_properties
if self.bprops.is_editing:
@@ -103,6 +102,11 @@ class BIM_PT_Boundary(Panel):
row.label(text=f"{entity.is_a()}/{entity.Name}")
op = row.operator("bim.select_global_id", text="", icon="OBJECT_DATA")
op.global_id = entity.GlobalId
if ifc_attribute in ("RelatingSpace", "RelatedBuildingElement"):
op = row.operator("bim.select_related_element_boundaries", text="", icon="RESTRICT_SELECT_OFF")
op.related_element = entity.id()
op = row.operator("bim.select_related_element_type_boundaries", text="", icon="ASSET_MANAGER")
op.related_element = entity.id()
else:
row.label(text="")
@@ -130,28 +134,23 @@ class BIM_PT_SpaceBoundaries(Panel):
return False
if not IfcStore.get_element(props.ifc_definition_id):
return False
valid_classes = ("IfcSpace", "IfcExternalSpatialElement")
entity = IfcStore.get_file().by_id(props.ifc_definition_id)
for ifc_class in valid_classes:
if entity.is_a(ifc_class):
element = tool.Ifc.get_entity(context.active_object)
for ifc_class in ("IfcSpace", "IfcExternalSpatialElement"):
if element.is_a(ifc_class):
return True
return False
def draw(self, context):
if not SpaceBoundariesData.is_loaded:
SpaceBoundariesData.load()
self.props = context.active_object.BIMObjectProperties
self.ifc_file = tool.Ifc.get()
row = self.layout.row()
row.operator("bim.load_space_boundaries")
if not Data.is_loaded:
Data.load(self.ifc_file)
for boundary_id in Data.spaces.get(self.props.ifc_definition_id, []):
boundary_data = Data.boundaries[boundary_id]
building_element = self.ifc_file.by_id(boundary_data["RelatedBuildingElement"])
row.operator("bim.select_space_boundaries", text="", icon="RESTRICT_SELECT_OFF")
for boundary in SpaceBoundariesData.data["boundaries"]:
row = self.layout.row()
if building_element:
bld_el_description = f"{building_element.is_a()}/{building_element.Name}"
else:
bld_el_description = None
row.label(text=f"{boundary_id} > {bld_el_description}", icon="GHOST_ENABLED")
row.label(text=boundary["description"], icon="GHOST_ENABLED")
op = row.operator("bim.load_boundary", text="", icon="RESTRICT_SELECT_OFF")
op.boundary_id = boundary_id
op.boundary_id = boundary["id"]
@@ -51,7 +51,6 @@ class BIM_PT_brickschema(Panel):
row = self.layout.row(align=True)
prop_with_search(row, self.props, "namespace", text="")
prop_with_search(row, self.props, "brick_equipment_class", text="")
row.prop(self.props, "brick_equipment_class", text="")
row.operator("bim.add_brick", text="", icon="ADD")
row = self.layout.row(align=True)
@@ -18,6 +18,7 @@
import os
import bpy
import blenderbim.tool as tool
import blenderbim.bim.module.type.prop as type_prop
from bpy.types import WorkSpaceTool
from blenderbim.bim.module.model.data import AuthoringData
@@ -51,9 +52,14 @@ class CadTool(WorkSpaceTool):
row = layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="", icon="EVENT_Q")
row.operator("bim.edit_extrusion_profile", text="Save Profile")
row.operator("bim.align_view_to_profile", text="", icon="AXIS_FRONT")
row.operator("bim.disable_editing_extrusion_profile", text="", icon="CANCEL")
if obj.BIMObjectProperties.ifc_definition_id:
row.operator("bim.edit_extrusion_profile", text="Save Profile")
row.operator("bim.align_view_to_profile", text="", icon="AXIS_FRONT")
row.operator("bim.disable_editing_extrusion_profile", text="", icon="CANCEL")
else:
row.operator("bim.edit_arbitrary_profile", text="Save Profile")
row.operator("bim.align_view_to_profile", text="", icon="AXIS_FRONT")
row.operator("bim.disable_editing_arbitrary_profile", text="", icon="CANCEL")
row = layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
@@ -180,7 +186,10 @@ class CadHotkey(bpy.types.Operator):
bpy.ops.bim.cad_offset(distance=self.props.distance)
def hotkey_S_Q(self):
bpy.ops.bim.edit_extrusion_profile()
if tool.Ifc.get_entity(bpy.context.active_object):
bpy.ops.bim.edit_extrusion_profile()
else:
bpy.ops.bim.edit_arbitrary_profile()
def hotkey_S_R(self):
if self.is_profile():
@@ -22,6 +22,7 @@ import json
import bmesh
import logging
import numpy as np
import ifcopenshell
from mathutils import Matrix
from math import radians
from blenderbim.bim.ifc import IfcStore
@@ -32,6 +33,7 @@ class ExportClashSets(bpy.types.Operator):
bl_label = "Export Clash Sets"
filename_ext = ".json"
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
filter_glob: bpy.props.StringProperty(default="*.json", options={"HIDDEN"})
def invoke(self, context, event):
self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".json")
@@ -64,6 +66,7 @@ class ImportClashSets(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
filename_ext = ".json"
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
filter_glob: bpy.props.StringProperty(default="*.json", options={"HIDDEN"})
def invoke(self, context, event):
self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".json")
@@ -194,11 +197,12 @@ class SelectSmartGroupedClashesPath(bpy.types.Operator):
class ExecuteIfcClash(bpy.types.Operator):
bl_idname = "bim.execute_ifc_clash"
bl_label = "Execute IFC Clash"
filename_ext = ".bcf"
filter_glob: bpy.props.StringProperty(default="*.bcf;*.json", options={"HIDDEN"})
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def invoke(self, context, event):
if ".json" not in bpy.data.filepath:
_, extension = os.path.splitext(self.filepath)
if extension != ".json":
self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".bcf")
WindowManager = context.window_manager
WindowManager.fileselect_add(self)
@@ -207,9 +211,10 @@ class ExecuteIfcClash(bpy.types.Operator):
def execute(self, context):
from ifcclash import ifcclash
settings = ifcclash.ClashSettings()
if ".json" not in self.filepath:
_, extension = os.path.splitext(self.filepath)
if extension != ".json":
self.filepath = bpy.path.ensure_ext(self.filepath, ".bcf")
settings = ifcclash.ClashSettings()
settings.output = self.filepath
settings.logger = logging.getLogger("Clash")
settings.logger.setLevel(logging.DEBUG)
@@ -271,6 +276,7 @@ class SelectIfcClashResults(bpy.types.Operator):
return {"RUNNING_MODAL"}
def execute(self, context):
self.file = IfcStore.get_file()
self.filepath = bpy.path.ensure_ext(self.filepath, ".json")
with open(self.filepath) as f:
clash_sets = json.load(f)
@@ -284,10 +290,25 @@ class SelectIfcClashResults(bpy.types.Operator):
return {"CANCELLED"}
for clash in clash_set["clashes"].values():
global_ids.extend([clash["a_global_id"], clash["b_global_id"]])
for obj in context.visible_objects:
if not obj.BIMObjectProperties.ifc_definition_id:
continue
element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
ifc_file = ""
for scene in obj.users_scene:
if scene.BIMProperties.ifc_file:
ifc_file = scene.BIMProperties.ifc_file
break
if ifc_file:
if ifc_file not in IfcStore.session_files:
IfcStore.session_files[ifc_file] = ifcopenshell.open(ifc_file)
element_file = IfcStore.session_files[ifc_file]
else:
element_file = self.file
element = element_file.by_id(obj.BIMObjectProperties.ifc_definition_id)
if element.GlobalId in global_ids:
obj.select_set(True)
return {"FINISHED"}
@@ -304,14 +325,14 @@ class SmartClashGroup(bpy.types.Operator):
return context.scene.BIMClashProperties.clash_results_path
def execute(self, context):
import ifcclash
from ifcclash import ifcclash
settings = ifcclash.IfcClashSettings()
settings = ifcclash.ClashSettings()
self.filepath = bpy.path.ensure_ext(context.scene.BIMClashProperties.clash_results_path, ".json")
settings.output = self.filepath
settings.logger = logging.getLogger("Clash")
settings.logger.setLevel(logging.DEBUG)
ifc_clasher = ifcclash.IfcClasher(settings)
ifc_clasher = ifcclash.Clasher(settings)
with open(self.filepath) as f:
clash_sets = json.load(f)
@@ -65,12 +65,13 @@ class ClassificationsData:
class ReferencesData:
@classmethod
def is_available_classification_added(cls):
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
name = IfcStore.classification_file.by_id(int(props.available_classifications)).Name
return name in [e.Name for e in tool.Ifc.get().by_type("IfcClassification")]
if name in [e.Name for e in tool.Ifc.get().by_type("IfcClassification")]:
return name
class ClassificationReferencesData(ReferencesData):
@@ -81,7 +82,7 @@ class ClassificationReferencesData(ReferencesData):
def load(cls):
cls.is_loaded = True
cls.data["references"] = cls.references()
cls.data["is_available_classification_added"] = cls.is_available_classification_added()
cls.data["active_classification_library"] = cls.active_classification_library()
@classmethod
def references(cls):
@@ -103,7 +104,7 @@ class MaterialClassificationsData(ReferencesData):
def load(cls):
cls.is_loaded = True
cls.data["references"] = cls.references()
cls.data["is_available_classification_added"] = cls.is_available_classification_added()
cls.data["active_classification_library"] = cls.active_classification_library()
@classmethod
def references(cls):
@@ -125,7 +126,7 @@ class CostClassificationsData(ReferencesData):
def load(cls):
cls.is_loaded = True
cls.data["references"] = cls.references()
cls.data["is_available_classification_added"] = cls.is_available_classification_added()
cls.data["active_classification_library"] = cls.active_classification_library()
@classmethod
def references(cls):
@@ -100,12 +100,16 @@ class ReferenceUI:
self.draw_reference_ui(reference)
def draw_add_ui(self, context):
if not self.data.data["is_available_classification_added"]:
if not self.data.data["active_classification_library"]:
row = self.layout.row(align=True)
row.label(text="No Active Classification Library")
row.operator("bim.load_classification_library", text="", icon="IMPORT")
return
row = self.layout.row(align=True)
row.prop(self.sprops, "available_classifications", text="")
row.label(text=f"Active Classification Library: {self.data.data['active_classification_library']}")
#row.prop(self.sprops, "available_classifications", text="")
if not self.sprops.available_library_references:
op = row.operator("bim.change_classification_level", text="", icon="IMPORT")
op = row.operator("bim.change_classification_level", text="", icon="GREASEPENCIL")
op.parent_id = int(self.sprops.available_classifications)
return
if self.sprops.active_library_referenced_source:
@@ -0,0 +1,59 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2023 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BlenderBIM Add-on.
#
# BlenderBIM Add-on 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.
#
# BlenderBIM Add-on 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 BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
import blenderbim.tool as tool
def refresh():
ConstraintsData.is_loaded = False
ObjectConstraintsData.is_loaded = False
class ConstraintsData:
data = {}
is_loaded = False
@classmethod
def load(cls):
cls.data = {"total_objectives": cls.total_objectives()}
cls.is_loaded = True
@classmethod
def total_objectives(cls):
return len(tool.Ifc.get().by_type("IfcObjective"))
class ObjectConstraintsData:
data = {}
is_loaded = False
@classmethod
def load(cls):
cls.data = {"constraints": cls.constraints()}
cls.is_loaded = True
@classmethod
def constraints(cls):
results = []
element = tool.Ifc.get_entity(bpy.context.active_object)
for rel in element.HasAssociations or []:
if rel.is_a("IfcRelAssociatesConstraint"):
constraint = rel.RelatingConstraint
results.append({"id": constraint.id(), "type": constraint.is_a(), "name": constraint.Name or "Unnamed"})
return results
@@ -21,8 +21,8 @@ import json
import ifcopenshell.api
import ifcopenshell.util.attribute
import blenderbim.bim.helper
import blenderbim.tool as tool
from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.constraint.data import Data
class LoadObjectives(bpy.types.Operator):
@@ -33,10 +33,10 @@ class LoadObjectives(bpy.types.Operator):
def execute(self, context):
props = context.scene.BIMConstraintProperties
props.constraints.clear()
for constraint_id, constraint in Data.objectives.items():
for constraint in tool.Ifc.get().by_type("IfcObjective"):
new = props.constraints.add()
new.name = constraint["Name"] or "Unnamed"
new.ifc_definition_id = constraint_id
new.name = constraint.Name or "Unnamed"
new.ifc_definition_id = constraint.id()
props.is_editing = "IfcObjective"
bpy.ops.bim.disable_editing_constraint()
return {"FINISHED"}
@@ -62,11 +62,7 @@ class EnableEditingConstraint(bpy.types.Operator):
def execute(self, context):
props = context.scene.BIMConstraintProperties
props.constraint_attributes.clear()
if props.is_editing == "IfcObjective":
data = Data.objectives[self.constraint]
blenderbim.bim.helper.import_attributes(props.is_editing, props.constraint_attributes, data)
blenderbim.bim.helper.import_attributes2(tool.Ifc.get().by_id(self.constraint), props.constraint_attributes)
props.active_constraint_id = self.constraint
return {"FINISHED"}
@@ -91,7 +87,6 @@ class AddObjective(bpy.types.Operator):
def _execute(self, context):
result = ifcopenshell.api.run("constraint.add_objective", IfcStore.get_file())
Data.load(IfcStore.get_file())
bpy.ops.bim.load_objectives()
bpy.ops.bim.enable_editing_constraint(constraint=result.id())
return {"FINISHED"}
@@ -121,7 +116,6 @@ class EditObjective(bpy.types.Operator):
self.file,
**{"objective": self.file.by_id(props.active_constraint_id), "attributes": attributes}
)
Data.load(IfcStore.get_file())
bpy.ops.bim.load_objectives()
return {"FINISHED"}
@@ -141,7 +135,6 @@ class RemoveConstraint(bpy.types.Operator):
ifcopenshell.api.run(
"constraint.remove_constraint", self.file, **{"constraint": self.file.by_id(self.constraint)}
)
Data.load(IfcStore.get_file())
if props.is_editing == "IfcObjective":
bpy.ops.bim.load_objectives()
return {"FINISHED"}
@@ -200,7 +193,6 @@ class AssignConstraint(bpy.types.Operator):
"constraint": self.file.by_id(self.constraint),
}
)
Data.load(self.file, obj_id)
return {"FINISHED"}
@@ -229,5 +221,4 @@ class UnassignConstraint(bpy.types.Operator):
"constraint": self.file.by_id(self.constraint),
}
)
Data.load(self.file, obj_id)
return {"FINISHED"}

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