Compare commits

...

884 Commits

Author SHA1 Message Date
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
Dion Moult 248dab936d Experimental reimplementation of reloading an updated IFC 2022-10-24 13:36:50 +11:00
Dion Moult a908fa62ba Loading model elements is now half aware of existing elements and will reuse where possible. 2022-10-24 13:07:45 +11:00
Dion Moult 2f7643424d IfcDiff now uses the iterator to compare geometry, which seems significantly faster. 2022-10-24 13:06:03 +11:00
Dion Moult 226c94381b IfcDiff now supports file objects as inputs instead of filepaths 2022-10-23 23:33:52 +11:00
Dion Moult 548a2972bc You can now add new profileset based types easily in the type manager. 2022-10-23 21:17:52 +11:00
Dion Moult 6263110733 You can now add layerset types in the type manager and access the manager on blank projects. 2022-10-23 20:02:34 +11:00
Dion Moult 89e8bf1ed6 You can now add new mesh based types in the type manager 2022-10-23 18:15:33 +11:00
Dion Moult 3809b5a34b You can now create new empty types, and select type objects from the type manager 2022-10-23 16:53:09 +11:00
Ryan Schultz 3cda25773d Maintaining the MaterialLayer thickness when X_Angle is used (#2525) 2022-10-23 15:35:17 +11:00
Ryan Schultz 77f2ad8f68 'X angle' rotates ifcslab/ifcramp in space. (#2522)
* 'X angle' rotates ifcslab/ifcramp in space.

* rotation w/ mathutils.Matrix.Rotation instead
per https://github.com/IfcOpenShell/IfcOpenShell/pull/2522#issuecomment-1287969832
2022-10-23 15:32:31 +11:00
Dion Moult 733b89be12 Link to C++ API reference from Python Sphinx docs 2022-10-23 12:15:11 +11:00
Thomas Krijnen 6346c5ba47 #1444 V3d_DirectionalLight compatibility fix 2022-10-22 20:30:13 +02:00
Thomas Krijnen dbb342d494 Small changes to code_examples.rst 2022-10-21 09:25:55 +02:00
Massimo Fabbro cb0ad67bcc Fixed errors and little improvement 2022-10-21 09:17:40 +02:00
Massimo Fabbro f0266a16a3 Little improve about code example doc and geometry setting doc 2022-10-21 09:17:40 +02:00
Thomas Krijnen cdde536651 Test cases for #2486 and #2517 2022-10-20 20:31:56 +02:00
Thomas Krijnen c1aca6ba8c #2517 Don't free parsed simple types as part of parent entity, but at file destruction 2022-10-20 20:30:48 +02:00
Thomas Krijnen f9391f80c4 #2408 test case 2022-10-20 13:36:00 +02:00
Thomas Krijnen f012286eda #2408 ifcopenshell.file.get_inverse(with_attribute_indices) 2022-10-20 13:35:02 +02:00
Dion Moult 45087e8822 The BIM Tool now provides similar slab-like editing tools for ramps and roofs 2022-10-20 20:51:32 +11:00
Dion Moult 8fa944d2a8 You can now have sloped slabs, good for doing roof slabs. 2022-10-20 19:56:15 +11:00
Dion Moult 67955b4e35 Batch reassign utility to address performance issues in #2408. 2022-10-20 18:25:14 +11:00
Dion Moult 9a0f29170a Fix #2518. Don't fail on non-existent schemas to allow for custom schemas 2022-10-20 15:03:33 +11:00
Dion Moult d09286eeca Schema migrator now supports IfcElectricDistributionPoint 2022-10-20 14:52:39 +11:00
Gorgious56 89f64eacef Remove info mode and streamline entity url fetching 2022-10-19 18:42:05 +02:00
Gorgious56 3334ea5c10 Fix error when toggling viewport annotations if there is no active camera in the current scene 2022-10-19 16:13:53 +02:00
Dion Moult 16b05d9981 Fix support for document references in IFC2X3 2022-10-19 20:17:10 +11:00
Thomas Krijnen 7f278fbde4 Import submodule explicitly 2022-10-19 10:44:29 +02:00
Dion Moult 2cad62554e Fix #2510. 2022-10-19 18:54:42 +11:00
Boris Brangeon 83bd0a973c Add function grouped_by in Selector (#2515) 2022-10-19 18:49:06 +11:00
Boris Brangeon b2c5153c1c Add IsGroupedBy in function get_decomposition (#2513)
* Add IsGroupedBy in function get_decomposition

selector example : "@ .IfcZone & .IfcSpace"

* Update element.py

* Change name function get_group

get_group to get_grouped_by
2022-10-19 18:48:55 +11:00
Dion Moult ec955ca717 You can now draw walls at a set RL 2022-10-19 17:54:25 +11:00
Dion Moult 9c28a95f41 Fix #2416. 2022-10-19 12:44:55 +11:00
Dion Moult 80020970e1 Purge old dynamic void system as Blender booleans are not sufficient. 2022-10-19 12:32:02 +11:00
Dion Moult f55b47dcb3 Include sample file generator in IfcTester doc generator 2022-10-19 11:54:30 +11:00
Dion Moult 7197db796c Fix #2508 2022-10-19 09:48:05 +11:00
Dion Moult a5aa970977 Add support for normalised ratio diffuse colours. See #2509. 2022-10-19 09:37:22 +11:00
Dion Moult 159e2ca779 Run black and minor fix. See #1990. 2022-10-19 09:17:37 +11:00
Gorgious56 ec1fbabd00 Dynamic Attribute Info (#1990)
* Add info mode toggle in addon prefs

* Add documentation property to Attribute

* Add webbrowser open operator

* Populate attribute doc on import

* Add attribute doc popup operator

* use path_resolve instead of eval

* Rename operator

* Add description field

* Panel UI is managed by operator

* Revert "Panel UI is managed by operator"

This reverts commit a7bbd06865.

* Revert "Revert "Panel UI is managed by operator""

This reverts commit d62115bf63.

* Add info popup to ifc class panel in object properties

* Implement fetching doc for non-ifc classes from jsonlike format

* Draw doc from non ifc classes

* Add utility function to draw info button on a given layout

* Add info buttons in the create project UI

* Display entity descriptions as tooltips in enum items

* Move predefined types fetching to data class

* Remove leftover prop code

* Use tool to get schema
2022-10-19 08:17:31 +11:00
Dion Moult b70bf5ed33 Cute developer function to spit out a debug object to help when coding. 2022-10-18 23:11:37 +11:00
Dion Moult d6be887bde Minor IfcTester test case fixes 2022-10-18 23:11:11 +11:00
Dion Moult 6098d94223 Fix #2507. 2022-10-18 22:47:42 +11:00
Dion Moult f045e86a38 Rebuild wall engine (again) to allow for sloped wall clippings. 2022-10-18 20:02:00 +11:00
Dion Moult 9ef65c9cc5 Add basic support for sloped walls, sloping along the local X axis 2022-10-17 22:52:44 +11:00
Ryan Schultz f8a8250429 searching for an IfcMaterial in IfcMaterialDefinitions (#2503)
* searching for an IfcMaterial in IfcMaterialDefinitions
A start to trying to address: https://community.osarch.org/discussion/comment/13024/#Comment_13024

* refactored to a tighter version. Thanks Thomas.
2022-10-17 20:24:05 +11:00
Dion Moult a75341b36e Fix #2505. Missing rotate button in UI. 2022-10-17 20:11:37 +11:00
Dion Moult 0a0271a30e Filled openings can now be based off a custom profile representation 2022-10-17 18:34:34 +11:00
Andrej730 871f6ebdf1 Updated windows batch script for setting up environment for blenderbim
Previous batch scripts was resulting in broken sym links (probably because mklink and some other comands were expecting full paths). New script provide required full paths and also use 1 line to set up blenderbim location.
2022-10-17 12:19:53 +06:00
Dion Moult 90f1a179bc You can now flip the direction of doors and windows 2022-10-16 23:50:00 +11:00
Andrej730 81d80ee415 Doc API is now accessible without setting up DocAPI class
Now you can access Doc API functions directly (like `ifcopenshell.util.doc.get_entity_doc(...)`) without setting up DocAPI class.
To make it work I've set up singleton DocDatabase class and made all paths relative to module location.
2022-10-16 16:54:05 +06:00
Dion Moult 1eeb820d26 Flipping walls with fillings now works. See #2491. 2022-10-16 21:05:05 +11:00
Andrej730 4dc860c019 Added IFC v4.0 json schema and added docs API for working with schemas
The schema for IFC4 the same as it was for IFC2x3.
Properties schema for IFC4 includes both quantity and propety sets.

Also cleaned up entities description for IFC2x3 from HTML code and IFC tags.
2022-10-16 13:32:22 +06:00
Dion Moult cb28a0b298 Flipping walls with openings now works. See #2491. 2022-10-16 17:58:06 +11:00
Dion Moult 76e6923a3e Filled openings now handle offset wall axes 2022-10-16 10:16:10 +11:00
Dion Moult 56a09e51bd Modeling dimensions now affect newly created walls and profiles 2022-10-15 22:53:19 +11:00
Dion Moult b90ab70658 Modeling dimensions now have sensible defaults depending on units and show prior to element creation 2022-10-15 11:06:21 +11:00
Andrej730 28aab490df IFC v2.3 json schema and parsing tool to regenerate it (#2501)
* IFC v2.3 json schema and parsing tool to regenerate it

Added entities and propeties schema in json format.

ifc2x3_entities.json format:
entity_name -> description, url, attributes;
attributes format: attribute_name -> attribute_descrption

ifc2x3_properties.json format:
property_set_name -> property_name -> description, children
children format: child_property_name -> child_property_description.

Also added doc.py script that can generate the same json schema files but requires docs IFC 2.3 docs in the same folder (https://github.com/buildingSMART/IFC/tree/Ifc2.3.0.1)

* Moved all json schema files to /util/schema

* Moved json files back to /util/, changed doc.py export location

* Added specification urls for entities and properties

Added specification urls for entities and properties.
Also added a function to parse actual property sets domain from the website to generate specification urls (domain on website and on github do not match).

Schema structure was changed:

ifc2x3_entities.json format:
entity_name -> description, spec_url, attributes;
attributes format: attribute_name -> attribute_descrption

ifc2x3_properties.json format:
property_set_name -> properties, spec_url
properties format: property_name -> description, children_properties
children_properties format: child_property_name -> child_property_description.

ifc2x3_property_sets_domains.json format:
property_set_name -> ifc_domain
2022-10-15 07:14:34 +11:00
Thomas Krijnen 0810fa7564 Escape \W in selector grammar 2022-10-14 11:30:11 +02:00
Dion Moult a34c0cb35c Fix a variety of IFC validation errors in entity creation and asset appending 2022-10-14 18:19:35 +11:00
fbpyr 10bde73c1a choco nightly path (#2498)
* fix choco nightly scripts path

* fix choco nightly scripts path
2022-10-14 17:16:50 +11:00
Massimo Fabbro 670399f327 Add gross volume calculation (#2487)
* Now gross volume calculation doesn't consider related voids

* Qto gross volume calculation minor fixes

* No need to create a new entity and better mesh creation

* Use bmesh to calculate volume so it's not necessary to create a new object
2022-10-14 16:50:04 +11:00
Dion Moult 241c5b1f48 Fix bug where unnamed types didn't show up in the UI. 2022-10-14 10:08:52 +11:00
Dion Moult 9ef3b4389b Change pip name to ifcopenshell. See #2468. 2022-10-14 08:25:15 +11:00
Dion Moult 2d37d3c914 More robust type switching of parametric objects 2022-10-13 22:51:06 +11:00
Thomas Krijnen ede9c5fca5 Fixes to svgfill/cgal/mpfr/gmp for -shared build on nix 2022-10-13 09:52:22 +02:00
Thomas Krijnen 4cccfd08d3 #671 check for zero magnitude vector in newells method 2022-10-13 09:34:14 +02:00
Thomas Krijnen 153c5a06e7 #671 use new faceset_helper api in IfcFace 2022-10-13 09:22:49 +02:00
Thomas Krijnen 9a4d3e706a #671 api in faceset_helper to obtain multiple bounds from single loop in case of intersections 2022-10-13 09:22:28 +02:00
Thomas Krijnen 0b20d3ae2c #671 correct check for NO_WIRE_INTERSECTION_CHECK setting 2022-10-13 09:21:50 +02:00
Thomas Krijnen 92fb46558f #671 perform fix on wires after wire_intersections() 2022-10-13 09:20:44 +02:00
Dion Moult 8be1a913ec Basic support for mesh booleans 2022-10-13 17:04:53 +11:00
Dion Moult c354de7b83 Minor fix 2022-10-13 11:50:33 +11:00
Dion Moult 19b718cdf5 Thumbnails auto load when projects are loaded. 2022-10-13 10:19:09 +11:00
Dion Moult cdffeba4cc Map units are now supported in converting to and from local to global coordinates 2022-10-12 20:32:16 +11:00
fbpyr e6e8db8a00 choco yesterday (#2495)
* fix check for available bbim releases yesterday

* fix check yesterday bbim releases after midnight

* fix check all releases url
2022-10-12 17:39:50 +11:00
Thomas Krijnen 8dab1810c7 #2486 #1861 Don't fail on overwriting header values 2022-10-11 10:48:53 +02:00
Dion Moult c5c9a192b0 You can now duplicate existing types in the type manager 2022-10-11 17:51:01 +11:00
Dion Moult 4279576d3c Type manager can now browse to type classes that don't have any types yet 2022-10-11 17:34:17 +11:00
Dion Moult dff35c173d You can now delete a type when managing types 2022-10-11 15:31:31 +11:00
Dion Moult 38f563d4f4 New timerless thumbnailed type manager. 2022-10-11 13:49:46 +11:00
Dion Moult 36fdd3f7fc Bundle pillow as dependency for thumbnail generator 2022-10-11 10:54:08 +11:00
fbpyr 47e0ec43c2 fix get sha256sum directly from release download (#2493) 2022-10-11 09:40:00 +11:00
Thomas Krijnen 0446fca56a --json --spf option in validate.py 2022-10-10 14:25:45 +02:00
Thomas Krijnen 785dc2a300 Test case for string formatting 2022-10-10 14:20:21 +02:00
Thomas Krijnen 64ff046e96 #2482 Use u32string for character encoder, remove CODECVT_BOOST_FALLBACK 2022-10-10 14:19:42 +02:00
Dion Moult 16582cf57f Experimental new thumbnail with smarter layer / profile thumbnails and no timers. 2022-10-10 23:13:37 +11:00
Dion Moult a5517071cc Make remove opening more accessible in UI and refactor 2022-10-10 11:45:50 +11:00
Gorgious 5d5ab3dafd Fix bad indenting in last commit
Sorry, I'm a bit rusty !
2022-10-09 22:05:56 +02:00
Gorgious ccfbeab51e Creating a wall with a IfcMaterialLayerSet with the BIM tool now makes it active (yellow outline)
+ deduplicate code under new function
2022-10-09 22:04:01 +02:00
Dion Moult c9214bf04a The BIM Tool workspace now intelligently reflects the active object 2022-10-09 23:51:40 +11:00
Dion Moult 492a535312 You can now set a fixed RL for inserted windows 2022-10-09 23:29:06 +11:00
Dion Moult 278931f5db Minor fix 2022-10-09 22:40:20 +11:00
Dion Moult d847f358ba Minor fix 2022-10-09 22:26:38 +11:00
Dion Moult ab31fd7e48 Copying filled openings is now possible. 2022-10-09 22:25:55 +11:00
Dion Moult 32f0911479 Elements with filled openings don't have their openings copied, as it depends on the fill 2022-10-09 22:22:12 +11:00
Dion Moult bdbd874fa8 Fix #2488. Copying objects now copy deep instead of adding afresh (and losing parametric data and openings). 2022-10-09 18:09:22 +11:00
Dion Moult 232252f61d Copy deep can now exclude things, useful for copying representation graphs where you don't want to duplicate the context 2022-10-09 17:15:39 +11:00
Dion Moult 3ee4b1f0e3 Fix #2489. Prevent openings from being added twice and only save changed openings. 2022-10-09 15:22:10 +11:00
Thomas Krijnen cd27d48701 #2482 entity_instance.to_string() 2022-10-08 17:18:47 +02:00
Thomas Krijnen 94ba1a121d #2486 store provenance to file also in simple type instances 2022-10-08 15:16:53 +02:00
Dion Moult 4110b81270 Decomposition mode now selects all children (not parenting) to move a host and all children easily 2022-10-08 23:42:03 +11:00
Dion Moult 37028857b4 Suspicious undo stack fix 2022-10-08 22:29:13 +11:00
Dion Moult 7c1f02cafe You can now reinsert windows and doors into other hosts 2022-10-08 22:28:46 +11:00
Dion Moult 7d779130e1 Removing products now handles purging a bunch more inverse relationships 2022-10-08 21:15:01 +11:00
fbpyr fb6d24b3ed add choco alpha version tag, description fixes (#2484) 2022-10-07 22:14:04 +11:00
Dion Moult c6ee1d1679 Deleting a fill auto deletes the opening 2022-10-07 20:55:46 +11:00
Dion Moult 1db5d3b06c Begin purging old dynamic void feature 2022-10-07 20:47:13 +11:00
garylzimmer 3b81c158f3 Added auto renaming for null project name (#2478)
Sorry it took me almost a year to implement @aothms excellent suggestion!
https://github.com/IfcOpenShell/IfcOpenShell/issues/1954#issuecomment-1002463624
2022-10-07 20:36:25 +11:00
Dion Moult 34aff01eb2 New prototype operator to regen fill openings 2022-10-07 17:06:19 +11:00
Dion Moult 7d1a41c1c7 Minor fix 2022-10-07 16:08:21 +11:00
Dion Moult d187c588d3 Minor fix 2022-10-07 16:04:06 +11:00
Dion Moult 82f847f5d4 Basic adding of filled openings reimplemented with new opening system 2022-10-07 16:00:17 +11:00
Dion Moult 27dc81544c Minor fixes to IfcTester testcases 2022-10-07 13:34:19 +11:00
Dion Moult 1677a6c83f IfcTester now supports new entityType data in partof facet 2022-10-07 13:11:52 +11:00
Dion Moult 61f4c48a4a Write docs for basic description of what's in an IFC 2022-10-06 21:07:58 +11:00
Dion Moult 0259b75d4c Write documentation for IOS code examples 2022-10-06 19:56:53 +11:00
Dion Moult 0db28917a4 Write docs on IOS geometry processing. See #671. 2022-10-06 17:06:37 +11:00
Dion Moult 15b4ab543b Update geom settings with newly added settings, and add example code. See #671. 2022-10-06 15:52:12 +11:00
Dion Moult 72147e51f5 Migrate old Doxygen geom markdown to new Sphinx rst docs. See #671 2022-10-06 11:18:23 +11:00
Sigma Dimensions 835e3f00b4 fix the bug fix on commit #88893a8 2022-10-05 13:59:41 +01:00
Thomas Krijnen 2dee2afdfa #1861 test case 2022-10-05 13:07:16 +02:00
Thomas Krijnen 3d82791a99 #1861 update globalid map on attribute change 2022-10-05 13:03:52 +02:00
Dion Moult 2cb8eebf83 Profiles now retain manual booleans alongside autogenerated clipping planes 2022-10-05 21:52:07 +11:00
Dion Moult 49d3ee2f73 Copying an opening now tracks it as a special objects in the scene 2022-10-05 20:04:19 +11:00
Dion Moult aeaef641bf Bump IOS build 2022-10-05 19:52:24 +11:00
Dion Moult e97d427e9f Copying elements with openings now also copies openings 2022-10-05 19:44:32 +11:00
Dion Moult d525dfa46f Deleting voided objects now works with new opening system 2022-10-05 18:22:35 +11:00
Dion Moult 272a00941e Openings now move with profiles, but stay fixed along the local Z axis 2022-10-05 17:57:51 +11:00
Dion Moult 1590e8c0e7 Openings locations are fixed relative to slab profile editing 2022-10-05 16:39:10 +11:00
Dion Moult ac4e2f142b Openings now move with the host, but also intuitively stay along the local X axis of the host 2022-10-05 15:26:27 +11:00
Dion Moult 428ee648b4 Wall representations now distinguish between auto and manual booleans and manual booleans move with the wall 2022-10-05 13:09:18 +11:00
Thomas Krijnen fa6bbf2d4a A bunch of not so pretty validate.py performance improvements 2022-10-04 15:16:34 +02:00
Dion Moult 12aa66fdcb Fix nasty bug related to inconsistent behaviour of wall intersection math 2022-10-05 00:14:12 +11:00
Thomas Krijnen f68db3d3be Fixes after 698c708bf6 2022-10-04 12:38:40 +02:00
Dion Moult 1f56b36bfa Minor fix 2022-10-04 20:16:36 +11:00
Thomas Krijnen be805a3e96 Fixes after 698c708bf6 2022-10-04 11:14:13 +02:00
Thomas Krijnen 5d95475970 #2471 test case 2022-10-04 10:23:27 +02:00
Thomas Krijnen f41ad0d5de #2471 work around file gc with instance refs 2022-10-04 10:23:08 +02:00
Dion Moult f81b22240c Minor fix 2022-10-04 18:35:01 +11:00
Dion Moult 7d63b03dd3 Fix bug where linking an element twice caused stacked undos to fail. See #2200. 2022-10-04 14:23:02 +11:00
Dion Moult e74eb0cc53 Fix bug where unlinking operations were not stored in the undo stack 2022-10-04 13:41:27 +11:00
Dion Moult 008d6bed5c PIP publish now only happens once a month, on a full moon 2022-10-04 10:41:25 +11:00
Dion Moult d0985a23d0 Fix critical bug where default context was not set on project load 2022-10-04 10:03:59 +11:00
Massimo Fabbro a5d49e81a0 Fix little bug in qto_calculator about net footprint area (#2470) 2022-10-04 09:59:50 +11:00
Dion Moult bfcc086aad Attempt to change PyPI name to ifcopenshell 2022-10-03 23:40:29 +11:00
Dion Moult 0eae3de6a2 Openings now move with the host by default. Small change, big impact. 2022-10-03 23:30:19 +11:00
Thomas Krijnen 1d39413717 Test cases for previous commits 2022-10-03 14:29:41 +02:00
Thomas Krijnen c57e5e2100 Capture lower level errors in validate.py reported in C++ 2022-10-03 14:19:29 +02:00
Thomas Krijnen ca5249a179 More resiliance against wrong attribute counts 2022-10-03 14:05:08 +02:00
Thomas Krijnen 698c708bf6 Reduce heap allocations during parse by reusing vector for top level instances 2022-10-03 14:04:42 +02:00
Thomas Krijnen 084151ed4c Augment invalid entity message with offset in file 2022-10-03 14:03:53 +02:00
Thomas Krijnen c56c7288e3 Clarify wrong attribute count message 2022-10-03 14:03:04 +02:00
Thomas Krijnen f5398c77d5 Clarify invalid entity message 2022-10-03 14:02:28 +02:00
Thomas Krijnen a724b5cb24 Clarify character offset in exception 2022-10-03 14:02:04 +02:00
Thomas Krijnen f3a15953b2 Missing include 2022-10-03 14:01:43 +02:00
Dion Moult 567548c7f3 Fix bug where adding an opening didn't create a relative placement 2022-10-03 22:35:53 +11:00
Dion Moult 8c25349b49 Add new opening functions to BIM tool workspace UI 2022-10-03 20:50:33 +11:00
Amoki 49cd966d43 remove-yacc 2022-10-03 11:16:42 +02:00
Dion Moult b552ea1376 Openings now can be explicitly edited and saved and use IOS instead of Blender modifiers 2022-10-03 01:02:08 +11:00
Dion Moult 9c91ee31e5 New booleans UI panel to manage representation boolean stack 2022-10-02 23:48:19 +11:00
Carlos Villagrasa e9a063ccbd Construction Types Browser - Multipreview now runs smoothly (#2463)
* Construction types multipreview now runs smoothly

* Minor fix

* Minor fix
2022-10-02 16:46:14 +11:00
Dion Moult e4e9db0b3e Implement show and hide openings and hide booleans operator 2022-10-01 23:45:49 +10:00
Dion Moult 2ec41bf3a0 Add support for processing rectangular profiles in the profile editor 2022-10-01 23:27:05 +10:00
Dion Moult 944cbeea1a Handle both 2D and 3D placement matrixes in IfcOpenShell utils 2022-10-01 23:25:34 +10:00
Dion Moult 4b8238a602 Openings are no longer loaded when loading fresh IFC projects 2022-10-01 20:32:40 +10:00
Dion Moult 8789a19f57 Basic show and remove representation boolean operators 2022-10-01 20:31:46 +10:00
Kristoffer Andersen 8c8e83d98f update ci-daily build use win2019 for upload 2022-10-01 10:04:24 +02:00
Dion Moult 527d26c1c6 Openings are now specially shaded, and you can now add half space clipping booleans
# Conflicts:
#	src/blenderbim/blenderbim/bim/module/model/ui.py
2022-10-01 14:33:33 +10:00
Massimo Fabbro e2a5b09d77 Add calculate all qtos button: initial fixing (#2456) 2022-10-01 10:55:42 +10:00
Kristoffer Andersen 98b6edf119 Merge pull request #2461 from Krande/fix-daily-conda-build
fix: daily builds of conda ifcopenshell
2022-09-30 19:42:34 +02:00
Carlos Villagrasa 0a05197d3e Construction Types Browser: Solution to most of the current issues (#2460)
* Delay asset generation

* bpy.app.is_job_running only in Blender >= 3.3

* First time preview on sidebar works again

* Avoid flickering

* Show multiple previews on the first run

* Preserve mouse position with hacky operator
2022-10-01 02:01:26 +10:00
krande 75645a837a fix: daily builds of conda
Ran into a lot of `build not compiled for windows` errors on windows. So using the host and dependencies from conda-forge meta.yaml and pin boost-cpp to <=1.74 to at least allow windows builds of ifcopenshell to be created while we figure out how to support boost-cpp >=1.78
2022-09-30 17:49:55 +02:00
Thomas Krijnen 40e463a55f #2457 uplift curves to wires in geometric sets 2022-09-30 13:59:31 +02:00
Dion Moult 7b817f7359 Minor fix 2022-09-30 18:53:14 +10:00
Dion Moult 22e69a606d CAD sketcher constraints can now be saved and loaded from IFC 2022-09-30 16:04:16 +10:00
Dion Moult 8c7b2ab2a1 Slab depth auto-updates on type/layer changes with new slab system 2022-09-30 13:43:23 +10:00
Dion Moult 3512a2ce36 Add undo support to profile and wall element operators 2022-09-30 13:08:39 +10:00
Dion Moult 9e76b2b9eb Footprints are now supported in the new profile editor 2022-09-30 13:00:40 +10:00
Dion Moult a08a78b3c9 Slabs are now first created directly in IFC instead of reverse engineering meshes 2022-09-30 12:08:57 +10:00
Dion Moult 9fc5f72831 Minor fix 2022-09-29 23:51:25 +10:00
Dion Moult 391e09c16c Convenience operator to draw rectangles when editing profiles 2022-09-29 23:31:11 +10:00
Dion Moult 60e4de5ffa You can now align the view to the edited profile, for angled profiles 2022-09-29 22:33:32 +10:00
Vukas Pajic a248f16cf8 indentation 2022-09-29 14:30:25 +02:00
Vukas Pajic 9240a10fa8 no need for Shapely.libs when using macos 2022-09-29 14:25:42 +02:00
Vukas Pajic 19d3208744 add Shapely.libs 2022-09-29 14:08:24 +02:00
Vukas Pajic c080567f53 update makefile for shapely dependancy (#2452)
* initial

* unzip instead of tar

Co-authored-by: Dion Moult <dion@thinkmoult.com>
2022-09-29 22:01:54 +10:00
Dion Moult ac72f41051 You can now change the radius of existing arcs. Minor interface cleanup. 2022-09-29 17:24:00 +10:00
Dion Moult fc9273c1d3 You can now change the radius of existing circles. Radius settings are remembered. 2022-09-29 16:47:40 +10:00
Dion Moult 43dc2da05f Minor fix 2022-09-29 15:43:19 +10:00
Dion Moult 121190c9b9 Vertex group indices in repeated operations do crazy stuff. This is more robust. 2022-09-29 15:41:06 +10:00
Dion Moult 0f2f6e2721 More robust arcindex generation and let user specify radius for circles 2022-09-29 11:41:52 +10:00
Carlos Villagrasa 05070c3758 Construction Types - Attempt to solve repeated object creation (#2450)
* Constr types dicts to Blender props

* Reduce update interval

* Base for not linking dummy asset objects to collections
2022-09-29 10:00:39 +10:00
Dion Moult d4d923e51b New feature to fillet profiles with true arcs 2022-09-28 23:35:29 +10:00
Dion Moult 766ffa71cd Operator to clear parametric vertices when editing profile 2022-09-28 23:04:07 +10:00
Dion Moult cbf213952b Minor fix 2022-09-28 22:45:03 +10:00
Vukas Pajic b7c76048ab comment out tests 2022-09-28 14:24:17 +02:00
Kristoffer Andersen 20fdb09a0c Update meta.yaml
Do not force boost version 1.80.0. It fails horribly
2022-09-28 14:21:47 +02:00
Vukas Pajic 1943d59339 Add additional quantity calculations to QtoCalculator (#2405) 2022-09-28 14:20:10 +02:00
Dion Moult d2836e07d5 Minor fix 2022-09-28 22:18:54 +10:00
Dion Moult 90a788082d New 2D CAD offset operator 2022-09-28 21:07:28 +10:00
Kristoffer Andersen eafad2862f Update meta.yaml
test using boost 1.80.0
2022-09-28 09:21:39 +02:00
Dion Moult 1c0fa13f39 Fix CAD arc bug where sometimes arc went the wrong way, and now auto-clean verts when creating arc 2022-09-28 11:02:49 +10:00
Carlos Villagrasa 2e5ce6b862 Fix bim tool crash (#2446)
* Attempt to wait for assets (triggers infinite loop)

* Using timer instead of sleep

* remove sleep import

* Reduce asset update interval

Co-authored-by: Dion Moult <dion@thinkmoult.com>
2022-09-28 09:30:52 +10:00
Ryan Schultz f63a33647b relocate remove drawing button per https://community.osarch.org/discussion/comment/12744/#Comment_12744 (#2444)
* added 3"=1'-0" scale (a common scale), and corrected 6"=1'-0" scale

* relocate remove drawing button per
https://community.osarch.org/discussion/comment/12744/#Comment_12744
2022-09-28 01:54:11 +10:00
Carlos Villagrasa 2f84c188f5 Fix bim tool crash (#2445)
* Attempt to wait for assets (triggers infinite loop)

* Using timer instead of sleep

* remove sleep import
2022-09-28 01:31:59 +10:00
Dion Moult 01b1b57f71 Mitre joints now auto merge and are accessible in the new profile editor 2022-09-27 23:35:40 +10:00
Dion Moult 1ad0ef82d4 Fix bug where CAD filleting sometimes led to crashes 2022-09-27 23:34:08 +10:00
Dion Moult 23212003e9 Add support for circles in profile editing 2022-09-27 17:16:26 +10:00
Dion Moult aff281d00b Add unit support in new profile editing tool 2022-09-26 22:40:33 +10:00
Dion Moult 4bbcf3fa1e Set and unset 3-point arcs only for profiles 2022-09-26 22:06:11 +10:00
Dion Moult 5e50a15b41 New smart profile editor for extruded elements inspired by CAD Sketcher 2022-09-26 18:00:05 +10:00
Massimo Fabbro b673fc94a5 Add calculate all qtos button (#2441)
* Add button to calculate all object qtos in qto n-panel

* Calculate all qtos button: now the schema is readed from the file

* Calculate all qtos button: now edit_qto runs only one time every object

* Add button to assign pset qto to selected objects

* Calculate all qtos button: small fixing and unable to execute if no selected objects

* Calculate all qtos button: first tool test

* Calculate all qtos button: use better built-in function to get ifc obj entity
2022-09-26 16:05:08 +10:00
Dion Moult 4c7a5e8a1a Experimental CAD Sketcher integration for simple arbitary polyline extrusions 2022-09-25 12:27:10 +10:00
Dion Moult 33ebcd7664 New convenient modifier to rotate beam/wall/column etc by 90 degrees 2022-09-24 21:46:32 +10:00
Dion Moult 76ce726f1d Implement refresh profiles on type and profile changes 2022-09-24 20:19:01 +10:00
Ryan Schultz 54da579891 Ifc text tweaks (#2431)
* added 3"=1'-0" scale (a common scale), and corrected 6"=1'-0" scale

* Allow float point values in smart tags, and avoid breaking when no assigned product See #2340, #2349
2022-09-24 20:07:32 +10:00
Dion Moult e024e9e622 You can now set cardinal point on creation and bulk change for profile elements 2022-09-24 16:21:20 +10:00
Dion Moult 56f4b72161 Reimplement profile cardinal points 1-9 with new profile engine 2022-09-24 15:42:50 +10:00
Dion Moult 7545313dee Removing an element now also purges connection relationships 2022-09-24 14:12:45 +10:00
Dion Moult 48eb6b70e3 You can now manually set the length/height of profiled extrusions 2022-09-23 23:53:12 +10:00
Dion Moult 5f2acb43b1 You can now recalculate connections manually for profiles 2022-09-23 23:41:37 +10:00
Dion Moult b7eaefee50 Fix bug when deleting elements may possibly create an invalid containment relationship 2022-09-23 23:26:29 +10:00
Dion Moult 13a387cc73 Add support for descriptions in path connection relationships 2022-09-23 23:00:27 +10:00
Dion Moult f139f2ba1e Fix bug where removing an element in an aggregation could leave the file in an invalid state 2022-09-23 23:00:07 +10:00
Dion Moult 1d8085f6c9 New profile engine now supports mitred profile joints. 2022-09-23 19:23:22 +10:00
Dion Moult 6699bfeb54 Mitred wall joints rebuilt on the new wall engine 2022-09-23 15:42:05 +10:00
Dion Moult 7cee0a85c6 3D axis representation contexts are now created by default for all new projects 2022-09-23 12:39:23 +10:00
Dion Moult 351dcfa241 Implement profile unjoin and start cleaning up legacy architectural tools panel 2022-09-23 12:38:49 +10:00
Dion Moult 28022581a7 You can now have connection aware L joints for profiled elements 2022-09-23 00:09:56 +10:00
Dion Moult 748aaa671d Extending profiled elements (e.g. beams and columns) now supports axis connectivity and uses new profile engine 2022-09-22 23:32:22 +10:00
Thomas Krijnen 95b474c37d Delete HdfSerializer.h~ 2022-09-22 13:13:57 +02:00
Thomas Krijnen 9bb9f91f57 Delete HdfSerializer.cpp~ 2022-09-22 13:13:47 +02:00
Dion Moult 7db967583b Profile extensions now use the new engine and support 3D axis representations 2022-09-22 16:05:35 +10:00
Dion Moult 5d43f6db37 Adding a profile now uses the new profile engine 2022-09-21 23:28:03 +10:00
Dion Moult 4cfa327159 New API functions for adding arbitrary profile and profile-based representations 2022-09-21 23:27:24 +10:00
Dion Moult 920177d8c6 Minor fix. See #2416. 2022-09-21 10:50:02 +10:00
Dion Moult 5f902600e3 Minor fix. See #2416 2022-09-21 00:04:02 +10:00
Dion Moult db9af13843 Fix #2427. Don't treat missing mandatory attributes as fatal in IFC version migration 2022-09-20 22:46:15 +10:00
Thomas Krijnen dd49c092c6 #2419 remove map erasure from destructor
de initialization order can result in map already destroyed when unique_ptr schemas are still being deleted
2022-09-20 11:35:08 +02:00
Thomas Krijnen d347a7f893 Don't run choco from other repos 2022-09-20 11:33:07 +02:00
Dion Moult 3c61804e8b Flipping walls now preserves connections, and flips around the reference axis 2022-09-20 17:21:11 +10:00
Dion Moult e179070978 New interface to show, select, and remove path connections 2022-09-20 16:25:52 +10:00
Dion Moult de2ada1141 Walls recalculate on type and layer thickness changes 2022-09-20 14:05:04 +10:00
Dion Moult eb48a68dd1 Copying elements do not copy connections to prevent mutually exclusive connectivity 2022-09-20 12:01:40 +10:00
Dion Moult fc6bf5e2bb You can now set an exact wall length 2022-09-20 10:48:44 +10:00
Dion Moult 3f2d86255d Merge projects now merges all entities, not just rooted ones. 2022-09-19 23:44:10 +10:00
Dion Moult d751ee9995 Fix critical typo in rendering styles creation that led to invisible styles 2022-09-19 23:10:56 +10:00
Dion Moult 83f70624a4 Attempt to get LXML working across all platforms. See #2422. 2022-09-19 22:23:09 +10:00
Dion Moult 356253c4f0 Minor fix 2022-09-19 21:40:53 +10:00
Dion Moult a459c39c97 Minor fix 2022-09-19 17:36:20 +10:00
Dion Moult e5719bd3c6 You can now change wall heights in bulk 2022-09-19 16:29:43 +10:00
Dion Moult 18387e7732 You can now switch contexts of of objects in bulk 2022-09-19 15:59:37 +10:00
Dion Moult 57e8e47061 Minor regression fix 2022-09-19 13:33:40 +10:00
Dion Moult 5c5bb03652 IfcPatch now allows passing in IFC objects instead of filepath strings 2022-09-19 13:31:33 +10:00
Dion Moult ea1d797605 Add documentation for IfcPatch 2022-09-19 12:25:35 +10:00
Dion Moult bc3bf07a60 Minor fix to 12DToRevitTINS patch 2022-09-19 10:49:03 +10:00
Dion Moult fbd4177d08 New feature to merge aligned walls and retain parametric connections 2022-09-19 09:12:42 +10:00
Thomas Krijnen 4842891fd7 #2322 Implement IfcSweptDiskSolidPolygonal.FilletRadius 2022-09-18 15:59:24 +02:00
Dion Moult f6056f9bc3 Shift-Y in the BIM tool for walls now regenerates wall connections 2022-09-18 23:26:18 +10:00
Dion Moult c7af6c4482 Fix #2420. Bug where project library assets sharing relationships with other assets would pull in other assets 2022-09-18 23:19:23 +10:00
Dion Moult c9c905fd3e Splitting walls now uses the new wall engine 2022-09-18 22:00:27 +10:00
Dion Moult 7f76488869 New recalculate wall operator to manually trigger an update of all wall connections 2022-09-18 15:57:07 +10:00
Dion Moult cd930d809a New wall engine slowly being wired up to wall join hotkeys 2022-09-18 15:56:23 +10:00
Dion Moult f49d7ec2cb Implement unjoin in new wall engine 2022-09-18 15:54:24 +10:00
Dion Moult 322abdff53 Minor fix 2022-09-17 22:19:49 +10:00
Dion Moult 984978bc00 Add axis support to new wall engine 2022-09-17 21:52:03 +10:00
Dion Moult ce8da8bd85 An axis context is now added by default for all new projects 2022-09-17 21:41:09 +10:00
Dion Moult 6d0fdb32c6 Fix #2416. 2022-09-17 14:51:50 +10:00
Dion Moult 0cb3c0a907 Update docs about new Apple M1 support 2022-09-16 22:29:04 +10:00
Dion Moult d9ea62a670 Distribute Apple M1 to PyPi 2022-09-16 22:21:58 +10:00
Dion Moult 2228104a8e Apple M1 builds. This stuff never works the first time. 2022-09-16 21:48:38 +10:00
Dion Moult e473574086 Setup BlenderBIM Apple M1 builds. Fingers crossed! 2022-09-16 20:30:33 +10:00
Dion Moult f93604d6b7 Implement connection aware wall L-joints 2022-09-16 18:30:57 +10:00
Dion Moult d106b0e017 Fix #2413. Bug where multiple tasks may accidentally override animation keyframes. 2022-09-16 11:53:25 +10:00
Dion Moult 90e35810d9 Prototype connection-aware wall extension and T-joints 2022-09-15 22:44:06 +10:00
Bruno Postle 4fb910fc44 wrapper is a bash script not a sh script 2022-09-15 07:42:00 +01:00
Dion Moult 280ce56431 Creating walls are more stable and now directly goes to the IFC representation instead of reverse engineering Blender meshes 2022-09-15 10:46:52 +10:00
Dion Moult 81e46b533b New API to directly create wall-like representations without Blender geometry 2022-09-15 10:44:43 +10:00
Dion Moult 171db4b241 Add support for connecting/disconnecting paths in API 2022-09-15 10:32:01 +10:00
Martin15135215 8fe9ddd599 Small Change
https://github.com/IfcOpenShell/IfcOpenShell/issues/2409#issuecomment-1246767831
2022-09-15 00:13:01 +10:00
Dirk Olbrich b299694ca0 doc: library flags for macos build 2022-09-14 15:19:53 +02:00
Dirk Olbrich ca76b77130 update doc for building on macOS 2022-09-14 15:19:53 +02:00
Thomas Krijnen e94a5c97df Merge remote-tracking branch 'remotes/origin/pr/1911' into v0.7.0 2022-09-14 14:57:27 +02:00
GisSpace df845653f3 fix IFC to xml - IFCGROUP in IFC cannot be found in xml #1154 2022-09-14 14:54:05 +02:00
Thomas Krijnen a3e499656c Don't log vertex counts in helper when they didn't change 2022-09-14 14:13:24 +02:00
Thomas Krijnen 79bb40092f Apparently --disable-static no longer works? 2022-09-14 10:57:08 +02:00
Thomas Krijnen 2c7a4ca880 Filter unsupported py versions on recent osx 2022-09-14 10:56:52 +02:00
Thomas Krijnen 6a4375cf54 Update build-all.py versions 2022-09-14 10:56:16 +02:00
Thomas Krijnen c74b79213b Remove asterisks before branch name 2022-09-14 10:55:32 +02:00
Ryan Schultz 806d147a85 A preference for locking grids upon import 2022-09-14 18:33:26 +10:00
Dion Moult 227b1391bd Clarify behaviour of funny characters and pattern matching on numbers 2022-09-14 10:15:19 +10:00
Bruno Postle 68e2bb1fa7 oops, correct version of wrapper that quotes special characters 2022-09-13 23:21:49 +10:00
Dion Moult fccc233f42 Merge pull request #2406 from brunopostle/desktop-files2
Configure blenderbim file associations and desktop icon
2022-09-13 23:06:04 +10:00
Bruno Postle 86785ab181 Configure blenderbim file associations and desktop icon 2022-09-13 13:57:13 +01:00
Dion Moult 3255053ac4 Implement all restrictions for IfcTester 2022-09-13 22:09:40 +10:00
Dion Moult 422cc4802c Implement optionality in IfcTester, and generate integration testcases 2022-09-13 19:50:51 +10:00
Dion Moult fc0b0ca188 Minor fix to ids.xsd 2022-09-13 19:44:36 +10:00
Dion Moult 3120b0c6e2 Add support for IfcTester and predefined properties 2022-09-12 23:03:50 +10:00
Dion Moult 91fac6d4bd Add IfcTester support and test cases for bounded, table, and reference props 2022-09-12 22:35:05 +10:00
Dion Moult c7c59d9496 IfcTester now supports list and enumerated properties 2022-09-12 21:34:06 +10:00
Dion Moult 73ac5d314b Complex properties and quantities are not supported in IDS 2022-09-12 20:10:25 +10:00
Dion Moult 3b2cbac9d1 Generate stable GlobalIds for IDS docs to prevent commit noise 2022-09-12 20:10:25 +10:00
Thomas Krijnen a63d6475b2 Keep moving schema agnostic code out of kernel 2022-09-12 10:34:11 +02:00
Thomas Krijnen 4cd0932d7d Fix to boolean utils, version detection, and behaviour on old versions 2022-09-12 10:34:11 +02:00
Dion Moult cb447fe902 IfcTester Add support for qto checks 2022-09-12 18:26:11 +10:00
Dion Moult 5ee4e2704d User can now explicitly choose the quantity type via the edit qto API 2022-09-12 18:05:13 +10:00
Dion Moult 5b5540494d Qtos now support being added to types and contexts (despite being rare) 2022-09-12 12:55:15 +10:00
Dion Moult 85e123fadb IfcTester now supports occurs attributes on facets 2022-09-12 11:51:03 +10:00
Dion Moult f745222d37 IfcTester now supports mandatory measures for properties 2022-09-12 11:21:47 +10:00
Sigma Dimensions 88893a8c2c BBIM - Fix cost item products not loading for 1st cost item created 2022-09-11 22:14:57 +01:00
Dion Moult eb8bf60e93 Fix #2398. Add support for IFC4X3 entities. 2022-09-11 22:17:41 +10:00
Dion Moult 319aada33b Fix #2388. 2022-09-11 22:14:11 +10:00
Dion Moult 77c9bf5bb6 Implement newly proposed IDS PartOf facet 2022-09-11 17:20:50 +10:00
Dion Moult e5e81c818e IfcTester now handles floating point precision checks 2022-09-10 23:31:21 +10:00
Dion Moult 7aad0fc119 Implement type casting for property checks in IDS, bump to IDS v0.9 2022-09-10 22:42:22 +10:00
Sigma Dimensions f7403e8eee BBIM - Feature to guess all quantities at once 2022-09-09 13:25:04 +01:00
Dion Moult e2df4f335d API now supports adding representations using the Sverchok geometry structure 2022-09-09 22:20:06 +10:00
Sigma Dimensions cbe944d030 BBIM - temp fix to refresh cost items products' quantities UI, when qto is edited 2022-09-09 11:43:28 +01:00
bosonprojets 87f0475b99 Quality - run black on api/sequence directory 2022-09-09 02:25:20 +01:00
bosonprojets 4689cc858a BBIM Feature to import resources data from CSV 2022-09-09 02:23:53 +01:00
bosonprojets 6d6b893db2 Core tests for Resource module 2022-09-09 02:18:08 +01:00
bosonprojets e2d4a3ae24 Refactor Resource module 2022-09-09 02:17:28 +01:00
Dion Moult e4c1f7518d Revert "Hiding all objects in the "Types" Collection "
This reverts commit 2c0ea75960.
2022-09-09 10:59:10 +10:00
Dion Moult 48f8c0e085 Merge pull request #2395 from IfcOpenShell/Hiding-all-objects-in-the-"Types"-Collection
Hiding all objects in the "Types" Collection
2022-09-09 10:56:26 +10:00
Ryan Schultz 2c0ea75960 Hiding all objects in the "Types" Collection
When all the objects in the "Types" collection are hidden, it makes it quicker to turn on, and isolate one type for viewing and/or toggling other types on to compare to.
2022-09-08 13:12:47 -05:00
Thomas Krijnen e643972669 #2390 Submodule 2022-09-08 13:54:17 +02:00
Dion Moult d7c4936255 New recipe to fix dense TINs from 12D loading properly in Revit 2022-09-08 16:36:12 +10:00
Sigma Dimensions 96d19952e9 Quality check - run black on api/resource 2022-09-08 03:15:41 +01:00
Sigma Dimensions 57caf6a31f IfcOpenShell API: Deep purge resources upon removal 2022-09-08 03:12:44 +01:00
Dion Moult 189fc6e1e4 Minor fix 2022-09-07 20:39:55 +10:00
Dion Moult 88117941c5 Fix #2387. Bundling for Blender makefile now works on Mac. Thanks dirkolbrich! 2022-09-07 20:26:44 +10:00
Dion Moult 0a6c8a4343 Write docs on creating a single object 2022-09-07 20:14:08 +10:00
Christian Martínez de la Rosa 5bfad88a75 [IfcConvert] Added IfcRelConnectsElements to xml export (#2197)
* [IfcConvert] Export tasks in the xml conversion

The exportion in the XML format exports in a new tag, named "tasks", all the tasks from the file with a hierarchy. Each task contains the IfcTaskTime object if it has it.

* [ifcConvert] Add related objects in tasks

* [IfcConvert] Added IsSuccessorFrom and IsPredecessorTo in xml task export

* Get IfcTasks from IfcWorkSchedule

* [IfcConver] Changed OperatesOn hierarchy level in xml task export

* Added task to property / quanty relationship

* [IfcConvert] Added calendars to xml output file

* [IfcConvert] Added Output objects in the XML file

* [IfcConvert] Fix crash when IfcWorkCalendar does not contain IfcWorkTime

* [IfcConvert] Added IfcWorkPlan to xml export

* [IfcConvert] Added IfcRelConnectsElements to xml export

* [IfcConvert] Added IfcRelConnectsElements to xml export

* [IfcConvert] Improved related and relating xml export
2022-09-05 14:58:22 +02:00
Dion Moult 72544419da External resource relationships are now also purged when removing classifications 2022-09-05 16:51:42 +10:00
Dion Moult d1baaa21e7 Update IfcFM to use actors, not contacts, support federated datasets, and be more true to spec 2022-09-05 12:41:06 +10:00
Dion Moult 5d9fc44a69 Fix #2374. Axis for walls should be in the 2D plan context. 2022-09-05 11:35:04 +10:00
Dion Moult 917344ee1d Fix bug where new instances may have a wrong Z coordinate 2022-09-05 11:13:31 +10:00
Dion Moult 68ef6458d7 Fix #2379. Regressions where context adding never had any tests and failed. 2022-09-05 11:13:31 +10:00
Sigma Dimensions 36576c8d40 IFC resources utility: batch import Resource Data from CSV 2022-09-05 01:51:53 +01:00
Sigma Dimensions b44a1cb2e2 Fix 4d import context 2022-09-05 01:46:19 +01:00
Thomas Krijnen 5c7cf4469f Compute meaningful hash and eq on simple type instances 2022-09-04 14:23:35 +02:00
Dion Moult 147216cb7b Revert "Fix bug where IFC data types were hashed as the same value in RecycleNonRootedElements IfcPatch recipe"
This reverts commit 3be8e4f1ce.
2022-09-04 22:22:54 +10:00
Dion Moult 62b1478af1 Merge pull request #2377 from Martin15135215/v0.7.0
From append methode to use numpy and ifcpatch bug
2022-09-04 20:13:51 +10:00
Dion Moult 0f0055b0f1 Merge branch 'v0.7.0' into v0.7.0 2022-09-04 20:13:38 +10:00
Dion Moult 3be8e4f1ce Fix bug where IFC data types were hashed as the same value in RecycleNonRootedElements IfcPatch recipe 2022-09-04 20:12:54 +10:00
Martin15135215 6aa4ca89fe Merge branch 'IfcOpenShell:v0.7.0' into v0.7.0 2022-09-04 09:22:58 +00:00
Dion Moult 5fb574cf5c Fix #1589. Saving beveled curves as rswept disk solids is now supported. 2022-09-04 17:46:34 +10:00
Dion Moult 85a502a038 Support multiple swept disk solids with equal radii as a native Blender object 2022-09-04 17:45:26 +10:00
Dion Moult a7c56e742b Fix UI bug where adding nested groups closed the UI 2022-09-04 11:17:55 +10:00
Martin15135215 f11f648630 From append methode to do with numpy 2022-09-04 10:25:02 +10:00
Martin15135215 74d1bd7e3e Added "or element.is_a('IfcPropertySingleValue')" 2022-09-03 19:19:02 +02:00
Martin15135215 248ac71215 From append methode to use numpy 2022-09-03 19:13:55 +02:00
Dion Moult af4cb22756 Fix bug where appended assets sometimes duplicated geometry contexts 2022-09-03 22:16:35 +10:00
Dion Moult 56393ef358 Fix #170. Add note about not mixing OpenCascade installations. 2022-09-03 20:16:33 +10:00
Dion Moult 5d4512033d Fix #2286. Classifications can now be added for cost items. 2022-09-03 17:11:14 +10:00
Dion Moult b9cb1ab012 Black 2022-09-03 15:34:52 +10:00
Dion Moult e34b737428 Fix #2304. You can now manage classifications for materials. Bulk classification now supported. 2022-09-03 15:33:52 +10:00
Dion Moult 557d3429eb Support bulk deletion of psets on selected objects 2022-09-03 14:32:39 +10:00
Dion Moult f9019a4007 Test non-rooted classifications in IDS 2022-09-03 11:54:12 +10:00
Dion Moult 0668eadc36 IDS doc generator now also generates a sample IDS file 2022-09-03 10:48:46 +10:00
Dion Moult fdfaf54698 Support resource level classifications in IfcOpenShell API 2022-09-02 18:23:49 +10:00
Dion Moult ffa3aae132 Fix UI bug where changing classification levels sometimes didn't display 2022-09-02 15:21:27 +10:00
Dion Moult 0293c133ab Fix #2364. Bug where classifications couldn't be added in IFC2X3. 2022-09-02 14:03:05 +10:00
Dion Moult f9db685753 Fix #2343. Pset template applicability is refreshed when you save your templates. 2022-09-01 18:25:00 +10:00
Dion Moult a800d1af7e Fix pset template UI bug where changing to another prop didn't cancel editing older props. Also add support for blank or falsey enums (e.g. a real of 0.0). 2022-09-01 18:06:33 +10:00
Dion Moult f2bd362f57 Fix bug where adding a pset template didn't auto refresh the UI to the newly added template 2022-09-01 18:06:33 +10:00
krande 87021ebbe7 fix: edit ci triggers for ifcopenshell.
Changes are made to the github actions workflow files to reduce unnecessary triggering of ifcopenshell compilation, testing and conda package building
2022-09-01 09:41:30 +02:00
Dion Moult 36f2ed6da6 Allow disabling undo on save
The transaction system does not scale on large scale or complex remove_deep deletions. So if you want to delete a mesh with many thousands of vertices, it becomes very, very slow. Unfortunately we cannot also clear Blender's undo history, so this advanced feature should be used carefully.

Tested:

 - deleting 250 average objects: 264s before, 246s after (6.8% faster)
 - deleting 1 object with 6,000 vertices: 5 minutes and counting (basically impractical) before, 11s after
2022-09-01 11:17:06 +10:00
Dion Moult a61c084f27 Minor fix 2022-09-01 10:17:45 +10:00
Dion Moult 6849bb8c17 OpenCascade is not practically optional for now 2022-09-01 09:07:40 +10:00
Dion Moult a26f60861e Add pip/docker install instructions 2022-09-01 09:05:06 +10:00
Thomas Krijnen 106b8fdd07 Update Dockerfile 2022-08-31 21:01:09 +02:00
Thomas Krijnen 87c6b06cfc Update CMakeLists.txt 2022-08-31 20:48:36 +02:00
Thomas Krijnen af4917efba Sync dockerfile libs with ci libs 2022-08-31 20:28:26 +02:00
Dion Moult 806b2e2714 Add PyPi badge and switch to Shields badge for Anaconda
See https://github.com/ContinuumIO/anaconda-issues/issues/10231
2022-08-31 18:15:33 +10:00
Dion Moult 4ae8e4c74b Rename PyPi package to ifcopenshell-python due to naming conflict 2022-08-31 17:18:18 +10:00
Dion Moult 8101ffe06a Try using existing PYPI_TOKEN as the ifcopenshell package name is blocked 2022-08-31 16:54:27 +10:00
Dion Moult f07b6e14b4 Second time lucky? 2022-08-31 16:06:07 +10:00
Dion Moult f2c31cc17d Add optimistic Github action to upload wheels to PyPi 2022-08-31 16:02:39 +10:00
Dion Moult 39a626edeb Super simple wheel creator for PyPi 2022-08-31 15:41:01 +10:00
Sigma Dimensions 02379c5110 BBIM - New Sequence Toolkit Panel & new feature to highlight tasks for current selected object 2022-08-31 02:26:01 +01:00
Sigma Dimensions 1e661c42fb BBIM Feature - expand and contract all tasks in a work schedule 2022-08-31 02:21:28 +01:00
Thomas Krijnen eb7e9bacbf Update README.md 2022-08-30 21:58:11 +02:00
Thomas Krijnen c38e608a7c Update README.md 2022-08-30 21:49:51 +02:00
Dion Moult 544ed696da Add install instructions for ArchLinux for IfcOpenShell/IfcConvert 2022-08-30 23:00:18 +10:00
Dion Moult 322e47c5b5 Add badges to readme 2022-08-30 22:57:54 +10:00
Dion Moult 3ee9e6f45c Remove mention of outdated 3DS max from install instructions 2022-08-30 22:27:09 +10:00
Thomas Krijnen 7df94a906d #2305 fix cone boolean op 2022-08-30 13:31:26 +02:00
Dion Moult af1a40c913 Fix bug where if an object placement was reused elsewhere a circular reference was created 2022-08-30 20:08:50 +10:00
Dion Moult 451512ddff #1812 Add batching support for remove_deep2 for faster removals 2022-08-30 20:08:03 +10:00
Dion Moult fbd33145e1 Fix #2360. Fix documentation on IfcConvert flags that also apply to STP. 2022-08-30 18:28:30 +10:00
Dion Moult 8b0b1699f6 Fix #2370. Asset preview code did not clean up after itself properly after generating objects. 2022-08-30 16:21:25 +10:00
Dion Moult 2e13ed77d8 Fix #2371. Bug where you couldn't edit psets of a hidden object. 2022-08-30 11:38:04 +10:00
Dion Moult 804c0f4a77 Partially write tests for sequence refactor 2022-08-30 11:08:26 +10:00
Sigma Dimensions 37dec500fd massive refactor of the sequence module 2022-08-29 21:55:37 +01:00
krande ac3eb6fbda Merge remote-tracking branch 'origin/v0.7.0' into v0.7.0 2022-08-29 14:03:26 +02:00
krande 6a0bc0cd91 feature: align conda daily builds of ifcopenshell feature-wise with IfcOpenBot builds 2022-08-29 14:02:51 +02:00
Sigma Dimensions 699c8bf6b6 fix path windows test-bim 2022-08-29 12:51:18 +01:00
Dion Moult 7806f989f3 Minor fix 2022-08-29 21:47:12 +10:00
Dion Moult a0ca387ae7 Minor fix 2022-08-29 21:34:29 +10:00
Dion Moult 2a716ec242 Minor fix 2022-08-29 17:28:44 +10:00
Dion Moult 93ab51fb5e Fix #1958 - document other useful Blender add-ons 2022-08-29 16:25:17 +10:00
Dion Moult 7c59fc0b83 Document installation and usage of IfcConvert 2022-08-29 13:04:37 +10:00
Dion Moult 819466a819 Port over compilation instructions to Sphinx 2022-08-29 11:40:38 +10:00
Dion Moult 33a8ba9834 Add install instructions for Conda / Chocolatey 2022-08-29 09:14:26 +10:00
Ryan Schultz 6ce86ae368 Closing the ends of a swept_disk_solid by default 2022-08-29 08:49:46 +10:00
luz paz 68bf7a08e0 BlenderBIM: typo fixes 2022-08-28 11:39:15 +10:00
luz paz 771ba4c337 Revert 3rd party code changes 2022-08-27 15:34:16 +02:00
luz paz 33199b4bb6 Fix various typos 2022-08-27 15:34:16 +02:00
krande 74d730f368 [fix] - conda-build - set explicit env python path in conda build script for unix 2022-08-26 14:19:41 +02:00
Dion Moult eb2a71bf48 Fix #2159. Disable JUnit in Behave in Blender due to Etree conflicts. 2022-08-26 13:40:34 +10:00
Dion Moult 4b4e2e1622 Improve instructions on how to run tests and set up a dev environment 2022-08-26 13:40:34 +10:00
Thomas Krijnen b5f4c8218d #2358 fix header entity parsing in case of too few fields 2022-08-24 11:57:06 +02:00
Christian Kimmig e3a22601d5 Set Position of extrusions with height = 0 2022-08-24 11:10:45 +02:00
Dion Moult f67856a2ae Minor fix 2022-08-24 10:13:30 +10:00
Dion Moult 6b71b32e63 Improve documentation for running tests 2022-08-24 09:54:15 +10:00
Dion Moult 60d99fe2fa Update dev install process docs and bump IfcOpenShell dep 2022-08-23 17:15:11 +10:00
Dion Moult 75e1786682 Purge IDS features from BIMTester as superseded by IfcTester 2022-08-23 16:37:15 +10:00
Dion Moult 9b3f964000 Fix #2060. Large lists of types in a library can now be filtered. 2022-08-23 13:26:38 +10:00
Dion Moult de6b578d9c Fix #1375. Spatial zones as non-hierarchical entities are now supported. 2022-08-22 17:31:44 +10:00
Dion Moult f50e790b11 Minor fix 2022-08-22 17:02:05 +10:00
Dion Moult 9821c3af67 Fix #2317 add graphical support for viewing and editing referenced structures 2022-08-22 13:52:12 +10:00
Dion Moult 0844d6f52e Fix #2354 - add support for selector queries for enumerated properties 2022-08-19 20:10:54 +10:00
Dion Moult 520e7653e4 #1375 #2317 add API support for referencing in spatial structures 2022-08-19 18:43:39 +10:00
Dion Moult 9512075506 Minor fix 2022-08-19 12:41:08 +10:00
Dion Moult bdd61713ea WIP refactor sequence module 2022-08-19 11:20:18 +10:00
Dion Moult 97cf1da4ff #2339 Read directly from raw data instead of using strings for IfcDiff output 2022-08-18 17:57:11 +10:00
Dion Moult 836a162d57 #2342 Support different IFC schema versions for obj2ifc 2022-08-18 17:15:39 +10:00
Dion Moult 09a6f56f47 #2319 Add documentation for IfcDiff 2022-08-18 16:12:10 +10:00
Ryan Schultz 35ce8c2848 Updated links to point to current branch 2022-08-17 16:12:35 -05:00
Dion Moult ae2fd67260 #2342 New Meshlab based OBJ2IFC converter 2022-08-17 14:33:48 +10:00
Dion Moult 9def041f27 You can now create multiple new IDSes side by side and close them 2022-08-16 17:14:54 +10:00
Dion Moult e9d86253ec Minor fixes to IfcTester 2022-08-16 11:52:01 +10:00
Dion Moult f95466ee0c IfcTester now integrated into BlenderBIM Add-on UI 2022-08-16 11:52:01 +10:00
Dion Moult e85f21ce7a Minor fix 2022-08-16 11:52:01 +10:00
Sigma Dimensions d3795c9836 Documentation for ifcopenshell.util.element 2022-08-15 19:40:51 +01:00
krande ab647b13d9 fix ci initializer to prevent accidentally triggering and add tagged release for conda-forge release 2022-08-15 16:59:32 +02:00
Kristoffer Andersen dcad4146b6 PR daily builds of ifcopenshell and upload to conda (#2132)
* WIP PR for daily builds of ifcopenshell

* fix indentation in yaml

* fix branch name

* test daily builds using minimum occt ==v7.5.3

* chore: WIP daily builds - add tests

* add test script bat/sh. Continue work in PR

* fix: add more packages to test requires

* fix: remove space in minimum version requirement of numpy

* use manually installed MacOSX SDK on github runner. Minor rewrite of dynamic/static zlib conditional for conda_build.

* fix MacOSX SDK address and made it platform conditional

* make tests pass

* try to skip ids tests for common icopenshell builds (caused failure on windows tests probably due to special character in filepath)

* add conditional upload and testing of all available platform versions on github actions

* fix wrong path to source files

* fix errors in tests

* remove test files and skip tests

* only do import of ifcopenshell as means of testing and rely on the successful completion of ci.yml as a triggering mechanism instead

* make conditional switch between original implementation of zlib suffix and new depending on conda packaging or not

* point to occt v0.7.6

* make conditional import of BRepAdaptor_HCompCurve

* try removing libxml2 include path

* minor changes

* Test nix builds

* Test nix builds using only a single python version (in order to limit cpu/memory usage)

* remove run check

* remove testing of nix builds

* Revert "Test nix builds using only a single python version (in order to limit cpu/memory usage)"

This reverts commit 22a552a9ac.

* Revert "ci-py-only test modification"

This reverts part of commit af1e832f

* update occt version to 7.6.2 in daily builds

* Revert "Test nix builds"

This reverts commit af1e832f81.

* Complete setups for local and cloud environments. And start by adding IfcPerson as a test case for the REST api communication

* Revert changing checkout version of MVD
2022-08-15 16:40:42 +02:00
Thomas Krijnen adb2883310 #2344 Disable /MP by default 2022-08-15 13:53:23 +02:00
Dion Moult 7867c71b6e Fix IFCTester handling of falsey properties 2022-08-11 16:38:44 +10:00
Gorgious a5b1a96dfd Revert hack to automatically close enum search popups
It was insta-closing for some users
2022-08-10 21:58:00 +02:00
Carlos Villagrasa 4e5503002a Construction Type Browser, polishing of 2nd iteration (#2336)
* Polish a bit the type browser UI

* Remove SELECT from type browser

* model and geometry module tests fixed

* Polishing of type authoring layout redesign

* Further polishing of type authoring layout
2022-08-10 21:23:13 +10:00
fbpyr fa8fc161da Choco action (#2335)
* check cron based trigger isolated

* check cron based trigger isolated

* fix args

* add scheduled choco build action

* revert to original

* fix action name

* fix action name

* remove apt install as required packages exist already in the container

* remove apt install as required packages exist already in the container
2022-08-10 21:22:26 +10:00
Dion Moult e4016313c4 IfcTester now supports HTML reports 2022-08-09 17:04:12 +10:00
Dion Moult 81fdfbacd0 OffsetObjectPlacements patch now allows 3D rotations, such as to fix wrong Z axis coming from software like Solidworks 2022-08-08 14:34:06 +10:00
Dion Moult 7c2c946b2f Fix bug in diff visualisation for non-rooted entities. Refactor diff UI to data class. 2022-08-05 10:59:07 +10:00
Dion Moult 5b34e08c37 #2321 Remove IDS from IfcOpenShell to prevent confusion as it is now refactored heavily and lives in ifctester 2022-08-05 09:29:01 +10:00
Vukas Pajic 0b16c07e3a return result when calling diff() 2022-08-04 16:45:10 +02:00
Vukas Pajic 0210131a56 enhance ui for ifc-diff 2022-08-04 16:44:22 +02:00
Vukas Pajic a44d7e997a typo 2022-08-04 10:25:30 +02:00
Vukas Pajic 1df8908689 fix commit mistake 2022-08-04 10:24:13 +02:00
Vukas Pajic f9daba9fba filter diff with IFCSelector 2022-08-04 10:04:06 +02:00
Vukas Pajic fd6f2f3fc4 Diff can be restricted by elements that match Selector query 2022-08-04 09:54:11 +02:00
Dion Moult a4d0378405 More IfcDiff optimisations (461s->247s), bump DeepDiff version, less false positives with more accurate number comparisons 2022-08-04 17:49:18 +10:00
Dion Moult 1629205c3f Some optimisations to IfcDiff for a 50% speed up (461s down to 300s on sample data) 2022-08-04 11:47:34 +10:00
befr d058f17c15 add logic to build blenderbim-nightly chocolatey package 2022-08-04 11:05:12 +10:00
Carlos Villagrasa bbb3b62b8a Construction Type Browser, 2nd iteration (#2334)
* Polish a bit the type browser UI

* Remove SELECT from type browser

* model and geometry module tests fixed

* Polishing of type authoring layout redesign
2022-08-04 11:04:40 +10:00
Dion Moult f9ef00fd45 WIP experimental layout for authoring tool with Carlos and Vukas 2022-08-03 19:03:47 +10:00
Dion Moult 709c530434 Fix bug where MergeDuplicateTypesByTag recipe would've left multiple IfcRelDefinesByType relationships which is no good 2022-08-03 17:33:23 +10:00
Dion Moult 0ee93294dd Fix bug where element types wouldn't load if a filter was applied 2022-08-03 17:11:56 +10:00
Dion Moult 73124dd597 Fix failing tests 2022-08-02 19:34:12 +10:00
Dion Moult 759e134e3b Check and correct for object mode in operator, not in poll, as operator may be called headlessly (e.g. in export function).
Note that after the operator has been refactored to use the core / tool design pattern, the poll code may return if necessary.
2022-08-02 19:34:12 +10:00
Dion Moult 1f11354188 Add support for adding non-library based classification references for IFC2X3 2022-08-02 19:34:12 +10:00
Vukas Pajic eaffb9d8c0 Remove outdated test in "search" 2022-08-02 10:53:02 +02:00
Vukas Pajic f297b1709d add search to pytest.ini 2022-08-02 10:52:13 +02:00
Carlos Villagrasa 1f637660d1 model and geometry module tests fixed 2022-08-02 18:13:31 +10:00
Carlos Villagrasa c349523ea9 Remove SELECT from type browser 2022-08-02 18:13:31 +10:00
Carlos Villagrasa 5cc11a170b Polish a bit the type browser UI 2022-08-02 18:13:31 +10:00
Dion Moult 21094c59d9 API now supports adding classifications by identifier instead of from a library 2022-08-02 10:10:02 +10:00
Vukas Pajic 8ca5dd747e Fix issues with updating groups 2022-08-01 18:32:19 +02:00
Dion Moult ffe9ac6614 Run black 2022-08-01 19:45:36 +10:00
Dion Moult d2083b9a3d Refactor: move UI code for construction types panel into UI file 2022-08-01 19:42:57 +10:00
Dion Moult 70d1ad59c2 Merge pull request #2315 from cvillagrasa/v0.7.0
Addition of Construction Type Browser
2022-08-01 18:42:09 +10:00
Carlos Villagrasa 53687e6b7c Merge branch 'IfcOpenShell:v0.7.0' into v0.7.0 2022-08-01 10:41:33 +02:00
Vukas Pajic 67993071a6 UIlist no longer closes after adding group to group 2022-08-01 10:32:05 +02:00
Vukas Pajic 29c98a5996 Support for nested IfcGroups 2022-08-01 10:16:27 +02:00
Dion Moult 5b5a4649c5 Fix #2310. Manually patch applicable class errors in official IFC pset templates. 2022-08-01 13:05:57 +10:00
Dion Moult d654110fc1 Support removing representations from IFC models that do not correctly follow the convention that mapped representations come from types 2022-08-01 11:23:35 +10:00
Carlos Villagrasa 735f308abd Merge branch 'IfcOpenShell:v0.7.0' into v0.7.0 2022-07-28 23:50:10 +02:00
Dion Moult f86f988d6e Fix #2318. Hotfix only. Needs proper code review. 2022-07-28 12:17:23 +10:00
Sigma Dimensions a1a684cd2b refactor BBIM Module Workplans (data loading) (Part 2of2) 2022-07-28 01:32:08 +01:00
Carlos Villagrasa 6180f5be33 Merge branch 'IfcOpenShell:v0.7.0' into v0.7.0 2022-07-27 08:37:39 +02:00
Carlos Villagrasa cd58b994dd Renaming AddConstrTypeInstance 2022-07-27 08:24:50 +02:00
Carlos Villagrasa 40854eb3d3 Renaming constr_type back to relating_type 2022-07-27 08:18:34 +02:00
Carlos Villagrasa 79453a6ae0 Renaming constr_class back to ifc_class 2022-07-27 08:11:52 +02:00
Bruno Postle eec4c23017 Transfer psets and style representations when appending a material 2022-07-27 09:53:22 +10:00
Sigma Dimensions beeba10b22 refactor BBIM sequence module - workplans (add, edit, delete) 2022-07-26 19:55:47 +01:00
Carlos Villagrasa 7a6005d0ea Merge branch 'IfcOpenShell:v0.7.0' into v0.7.0 2022-07-26 20:43:38 +02:00
Carlos Villagrasa 58150a296d Revert "Merge remote-tracking branch 'origin/master' into v0.7.0"
This reverts commit 14bca5d199, reversing
changes made to 7321138d57.
2022-07-26 20:20:29 +02:00
Dion Moult 7de454337d Minor fix 2022-07-26 10:33:40 +10:00
Carlos Villagrasa 3e25ef9222 Merge branch 'IfcOpenShell-v0.7.0' into v0.7.0 2022-07-26 01:39:49 +02:00
Carlos Villagrasa 9278e43d72 Merge branch 'v0.7.0' of https://github.com/IfcOpenShell/IfcOpenShell into IfcOpenShell-v0.7.0 2022-07-26 01:39:09 +02:00
Dion Moult fafdc76494 Minor fix 2022-07-26 09:30:04 +10:00
Carlos Villagrasa 99c47069cd Tests polished 2022-07-26 01:22:22 +02:00
Carlos Villagrasa cee48d7424 Some more renaming/refactoring 2022-07-26 01:21:51 +02:00
Ryan Schultz dc7062e182 fix broken links in Documentation 2022-07-25 14:55:37 -05:00
Vukas Pajic 5f6c584a58 fix error where prop name wasn't shown 2022-07-25 12:25:55 +02:00
Vukas Pajic 5f7630e527 added test for creating new ifcgroups 2022-07-25 11:13:15 +02:00
Gorgious 733e80737a Fix error messages when using the search operator on blank file (F3) 2022-07-23 15:03:26 +02:00
carlos 64752e52c9 Tuning feature tests 2022-07-23 00:39:17 +02:00
carlos 6c260ef5c9 Margins function for UILayouts 2022-07-23 00:38:26 +02:00
carlos e0e0572ff5 Minor renaming and bug fixing 2022-07-23 00:37:31 +02:00
Carlos Villagrasa a3942ee5e2 Merge branch 'IfcOpenShell:v0.7.0' into v0.7.0 2022-07-22 18:42:25 +02:00
Vukas Pajic 40d5a94e2d Added new test for Selector UI 2022-07-22 09:37:46 +02:00
Vukas Pajic f9fe1607ff load_query now closes automatically 2022-07-22 08:20:06 +02:00
Gorgious56 2a019e8090 Enum search popup now automatically closes on item selection
+ move close_operator_panel higher in hierarchy to access it
2022-07-21 17:50:43 +02:00
Gorgious56 901a582c6c Add tooltip to append library element operator 2022-07-21 16:57:31 +02:00
Vukas Pajic 29a4f78689 Integrate ifcgroups with selector 2022-07-21 16:10:10 +02:00
Vukas Pajic f1e0375b6a api: assign_group now expects product to be a list
* minor change to assign_group

* api: assign_group now expects product: list
2022-07-21 10:14:58 +02:00
Vukas Pajic 52e354d1b8 delete search.feature (not ready yet) 2022-07-21 09:31:54 +02:00
Vukas Pajic eef961a39e Merge branch 'v0.7.0' into v0.7.0 2022-07-21 08:37:44 +02:00
Vukas Pajic fcab5d445f test features 2022-07-21 08:20:25 +02:00
Dion Moult b534895ae3 Slightly more dodgy but more robust test dependency installation script 2022-07-21 15:44:11 +10:00
Vukas Pajic 5b76e83965 Selector: added new tests and improved UX
Selector: added tests to check non-standard characters and missing values
2022-07-21 07:37:48 +02:00
Dion Moult a21b73225a Fix #2296. Bug where material and profile psets couldn't be removed. 2022-07-21 15:12:00 +10:00
Dion Moult a11a4032f6 You can now audit and view basic results using the IfcTester webapp 2022-07-21 13:13:38 +10:00
carlos a09f864651 Fetch latest commits 2022-07-20 19:17:52 +02:00
Vukas Pajic 609fd35410 quality of life improvements :) 2022-07-20 14:54:03 +02:00
Vukas Pajic df0c136b6c users can now add selection to ifcgroup 2022-07-20 13:48:26 +02:00
Vukas Pajic ce6d4ea59a match elements when attribute is none and negation used 2022-07-20 09:33:58 +02:00
Vukas Pajic 29594b50b5 test when value does not exist 2022-07-20 09:06:04 +02:00
Vukas Pajic 2a2da56a0e added test to check non standard characters 2022-07-20 08:32:36 +02:00
Gorgious dd041478b3 Add utility method to retrieve dynamic enum items without needing a reference to the callback
This is useful because trying to directly access a dynamic enum with an empty items list throws an error in the console
+ Remove prop search from template_type in pset ui which has only 2 elements
2022-07-19 22:17:39 +02:00
Vukas Pajic 650e5b0cd3 Added UI for IFC Selector Tool
UI for IFC Selector tool
2022-07-19 14:54:39 +02:00
Vukas Pajic afa0db3c7e all tests in test_selector.py now passing 2022-07-19 14:51:48 +02:00
Gorgious56 b9f1f3438c Fix typo 2022-07-19 14:30:32 +02:00
Vukas Pajic 31f21dcb29 abstract away ui code from operator 2022-07-19 14:16:59 +02:00
Vukas Pajic 64a22fe5c6 further refinements 2022-07-19 13:55:20 +02:00
Dion Moult 3f85140d60 Continuing adding tests for the refactored property support 2022-07-19 20:38:53 +10:00
Gorgious56 1b1588359f Ifc class suggestion (#2290)
* You can now use suggestions in ifc class enum search popup

* Move data to data submodule

* Fix error when suggestion mapping contains only one value

* Add predefined types to the search popup

* Use defaultdict to be able to add suggestions later

* Support predefined types and format name to display the ifc class
and fix bug where the prop is not defined in the suggestion dictionary

* Move data retrieval to data module

* Simplify prop search items retrieval

* Support prop search for more enums
2022-07-19 10:19:58 +02:00
Vukas Pajic 4c562c1af0 turn ui into popup operator 2022-07-19 10:05:20 +02:00
Vukas Pajic a0fdb9f777 update according to review comments 2022-07-19 09:21:13 +02:00
Vukas Pajic 2c29f89fe9 Selector - update regex for pset filter 2022-07-19 09:19:32 +02:00
Dion Moult f4aac566d5 Handle cyclic task dependencies in schedule cascading and CPM analysis 2022-07-19 12:39:54 +10:00
Dion Moult 57521bd106 Whoops 2022-07-19 09:47:32 +10:00
Dion Moult 1413cf4f9d Fix bug when adding material sets to a non parametric object 2022-07-19 09:46:28 +10:00
Vukas Pajic 3fa74469f4 minor changes 2022-07-18 15:39:19 +02:00
Vukas Pajic e3d9dd747d draft
endofday 1207

240pm 14/07

draft version

cleanup
2022-07-18 15:11:27 +02:00
Vukas Pajic d71ebb8d25 fix typo 2022-07-18 14:44:13 +02:00
Vukas Pajic 0097d98669 fix #2289 2022-07-18 14:42:39 +02:00
Thomas Krijnen dd635340aa Retry schema macro rewriting 2022-07-18 13:51:45 +02:00
Thomas Krijnen 1b06cb8014 Retry schema macro rewriting 2022-07-18 13:04:21 +02:00
Thomas Krijnen 163e10b608 More schema macro rewriting 2022-07-18 11:57:02 +02:00
Thomas Krijnen dc67287d4b Correct extern function declaration names 2022-07-18 10:46:01 +02:00
Thomas Krijnen 73f959c45a Correct extern function declaration names 2022-07-18 10:08:46 +02:00
Thomas Krijnen 65e66352b7 CMake typo error 2022-07-18 09:31:12 +02:00
Thomas Krijnen 205ff0914c Respect tokens in macro string concatenation 2022-07-18 09:24:20 +02:00
Dion Moult 2e76a800f5 You can now download modified IDSes via the webapp 2022-07-18 14:06:29 +10:00
Dion Moult 1890bf4cb9 You can now load multiple Blends simultaneously when linking IFCs 2022-07-18 10:58:25 +10:00
Dion Moult 3fd472549e Fix bug where material categories don't exist in IFC2X3 2022-07-18 10:29:32 +10:00
Thomas Krijnen 28d15a6c9f Add IFC4x3 (https://standards.buildingsmart.org/IFC/RELEASE/IFC4_3/annex-a-express.html) 2022-07-17 21:45:07 +02:00
Thomas Krijnen 6026e037fa Update schema references 2022-07-17 21:13:46 +02:00
Vukas Pajic 0ca90817d8 Fix for Issue #2288 2022-07-17 10:08:24 +02:00
Dion Moult 3436f3e0bb Visualising diffs now show results from the actively loaded IFC 2022-07-16 11:53:08 +10:00
Dion Moult 71f0170966 IDS webapp with some basic read/write functionality 2022-07-15 15:58:39 +10:00
Dion Moult 97cd07226d Support JSON output for IDS results 2022-07-15 15:14:04 +10:00
Dion Moult 309ce20068 Added error sentences for IDS property facet results 2022-07-15 14:38:57 +10:00
Dion Moult 282c140c14 More aggressive refactoring for IfcTester to break up the monolithic codebase 2022-07-15 14:09:55 +10:00
Bruno Perdigão bdf46f9431 Update installation.rst
Minor correction
2022-07-15 12:42:19 +10:00
Dion Moult e79068854a Refactor IDS into IfcTester with less magic and less code
* String building code all deleted and replaced with more maintainable code
 * Obscure features like metaclasses deleted
 * Regular constructors used instead of factory create and parse methods
 * Use Python class naming convention
 * Use polymorphism more (e.g. asdict, init) for facet classes
 * No more boolean any/and as users may want to test everything and not fail early
 * Error state is stored instead of streamed to logger so it can be tested and more flexible formatting
2022-07-15 09:57:40 +10:00
carlos 7be93b5386 Add testing to construction type previews 2022-07-15 01:09:08 +02:00
carlos 1b4da58a15 Restore IFCFileSelector UI rendering 2022-07-14 13:59:47 +02:00
Carlos Villagrasa 356ce48af5 Merge branch 'IfcOpenShell:v0.7.0' into v0.7.0 2022-07-14 13:42:02 +02:00
carlos 3f24d54e8f Distinguish bt active and selected constr type + renaming ifc_class -> constr_type 2022-07-14 13:40:57 +02:00
Vukas Pajic 1586c85314 added option to import all library assets 2022-07-11 11:07:40 +02:00
carlos f9f9746a9a Element Type Browser UI refactoring and improved wording 2022-07-11 07:53:14 +02:00
carlos 493faf22ff Move col_with_margins utility to helper.py 2022-07-11 07:52:10 +02:00
Thomas Krijnen bab6313313 Work on more readable ast for express schema parser 2022-07-10 21:51:25 +02:00
carlos 618bed3889 Improved version with optional cascading 2022-07-10 07:46:30 +02:00
carlos 8c9115c0ff Merge remote-tracking branch 'origin/v0.7.0' into v0.7.0 2022-07-09 01:22:44 +02:00
Carlos Villagrasa 0a561d1a30 Merge branch 'IfcOpenShell:v0.7.0' into v0.7.0 2022-07-09 01:19:15 +02:00
dependabot[bot] 443af3f7e7 Bump lxml from 4.6.5 to 4.9.1 in /src/ifcopenshell-python/ifcopenshell
Bumps [lxml](https://github.com/lxml/lxml) from 4.6.5 to 4.9.1.
- [Release notes](https://github.com/lxml/lxml/releases)
- [Changelog](https://github.com/lxml/lxml/blob/master/CHANGES.txt)
- [Commits](https://github.com/lxml/lxml/compare/lxml-4.6.5...lxml-4.9.1)

---
updated-dependencies:
- dependency-name: lxml
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-07-07 09:29:42 +02:00
Thomas Krijnen 2f6ac0be9f Fix test_wall_opening invocation 2022-07-07 08:46:38 +02:00
Thomas Krijnen 07c152f513 Submodule 2022-07-06 22:09:13 +02:00
Thomas Krijnen 158c62bdca Update ci-py-only.yml 2022-07-06 22:01:15 +02:00
Thomas Krijnen 1029271e91 Update ci-py-only.yml 2022-07-06 18:20:32 +02:00
Thomas Krijnen 9b59d24fc7 Update test_wall_opening.py 2022-07-06 18:20:25 +02:00
Thomas Krijnen 1d0e8c59d2 Capital OR 2022-07-06 16:19:57 +02:00
Thomas Krijnen b479d82251 CI workflow with BUILD_CONVERT=Off 2022-07-06 16:09:52 +02:00
Thomas Krijnen 6b6ac38f96 Link serializers also with only BUILD_IFCPYTHON 2022-07-06 16:06:16 +02:00
Vukas Pajic 57fe895ae0 ensure modules are installed to blender python
thankyou to @Gorgious56 for help on this one ;)  https://blender.stackexchange.com/a/238995/130742
2022-07-06 13:56:24 +02:00
Gorgious56 4195d41e4c Enums from attributes can now be searched by name 2022-07-06 12:09:37 +02:00
Dion Moult 7eac5397cd Run black 2022-07-06 18:29:22 +10:00
Dion Moult 084823164e Fix #2264 refactor property data structures to be separate from attributes 2022-07-06 18:25:29 +10:00
Gorgious56 87f110bae5 Add search operator to material enum field 2022-07-05 15:24:10 +02:00
Thomas Krijnen 952719f19b #2260 correct init order 2022-07-05 10:27:44 +02:00
Thomas Krijnen 2ab4e44248 #2256 try explicit cast for clang 2022-07-05 09:46:19 +02:00
Vukas Pajic 6b9e670bf3 missing comma 2022-07-05 07:41:51 +02:00
Vukas Pajic 9e87f0f027 fix for issue #2261 2022-07-05 07:34:23 +02:00
Gorgious56 c7272065e6 Improve prop search v2 (#2262)
* Minor fixes

* Fix #2257 : enums with a different id and name now correctly show the name in the search field

* Add prop search to template_type
2022-07-04 23:09:03 +02:00
carlos 14bca5d199 Merge remote-tracking branch 'origin/master' into v0.7.0 2022-07-04 18:52:11 +02:00
Carlos Villagrasa dbeb7d392c Merge branch 'IfcOpenShell:v0.7.0' into v0.7.0 2022-07-04 18:50:53 +02:00
Vukas Pajic 22cb7b3b67 Merge pull request #2258 from vulevukusej/create-pset_templates-in-bbim
support enums in new pset_templates
2022-07-04 14:20:14 +02:00
Vukas Pajic 018be92063 support enums in new pset_templates 2022-07-04 13:48:51 +02:00
Gorgious56 37f91461d2 Add prop search to fields with a lot of enumerated values (#2253)
* Add prop_search layout function

* Display props operator

* display search for pset

* Display search for root

* Update operator and UI layout function

* Add search buttons to brick, geometry, patch, project, pset, pset_template, root and unit modules enums
2022-07-03 22:37:41 +02:00
Vukas Pajic c73fedaaaf fix error that stopped user from adding properties 2022-07-01 13:41:20 +02:00
Gorgious56 4ad4bdef3b Initial Commit 2022-06-30 09:44:00 +10:00
Vukas Pajic e50259f8fa fix error I accidentally introduced. 2022-06-29 18:07:32 +02:00
carlos 7321138d57 Adding a preview popup for IFC Type additions 2022-06-29 17:43:28 +02:00
carlos 319f63c861 Preventing error when IfcSpatialElement has been deleted and an IFC Type instance is added 2022-06-29 17:39:40 +02:00
carlos 662cd6232c Preventing edge case (vertical slabs & horizontal walls) 2022-06-29 17:37:42 +02:00
Vukas Pajic 3461a1e96f Submodule_mvd 2022-06-29 17:23:27 +10:00
Vukas Pajic 919c007509 Submodule 2022-06-29 17:23:27 +10:00
Vukas Pajic 5b77001568 updated according to comments 2022-06-29 17:23:27 +10:00
Vukas Pajic 1f4022a41b fix error from util/element.py 2022-06-29 17:23:27 +10:00
Vukas Pajic c8c9262090 initial commit
-Added support for adding and editing enums
-changed behaviour of template psets that have enums.
2022-06-29 17:23:27 +10:00
Thomas Krijnen 104cabfb67 Minor things for drawing profiling and a warning 2022-06-29 11:35:43 +10:00
Kristoffer Andersen 4978a67e19 Add more subdirs for blenderbim build trigger
Add more subdirectories to the github actions workflow to trigger builds whenever a change occurs in relevant directories.
2022-06-29 11:33:44 +10:00
dependabot[bot] e44e03f02e Bump numpy in /src/ifcopenshell-python/ifcopenshell
Bumps [numpy](https://github.com/numpy/numpy) from 1.21.0 to 1.22.0.
- [Release notes](https://github.com/numpy/numpy/releases)
- [Changelog](https://github.com/numpy/numpy/blob/main/doc/HOWTO_RELEASE.rst)
- [Commits](https://github.com/numpy/numpy/compare/v1.21.0...v1.22.0)

---
updated-dependencies:
- dependency-name: numpy
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-06-22 09:21:40 +02:00
Thomas Krijnen 5a636a925b #2244 file.add() Don't fail on project without unit info 2022-06-21 14:15:59 +02:00
Dion Moult d23f14f8aa Cleaner IFC doc generation showing all contextual entities not only those related to the target instance 2022-06-21 22:05:26 +10:00
Dion Moult 1706ca5293 Fix bug where adding heavy classification references led to duplicate classification systems 2022-06-21 19:28:53 +10:00
Dion Moult ff3e2f14e1 Inherited and overridden classification references per system is now supported 2022-06-21 19:28:29 +10:00
Dion Moult c4433489b7 IDS now supports type casting 2022-06-21 17:01:12 +10:00
Dion Moult 4761c58571 IDS now supports failing inverse checks 2022-06-21 17:00:46 +10:00
Dion Moult 1de6a7e792 Improve geolocation docs with rotation diagram and scale explanation 2022-06-19 17:53:37 +10:00
Thomas Krijnen 7d6ba89496 Fixes after restructuring 2022-06-15 15:21:46 +02:00
Thomas Krijnen c4dc3934fa Add missing file 2022-06-15 14:46:37 +02:00
Thomas Krijnen 25f1d21a85 Fixes for some of the header renames 2022-06-15 14:23:58 +02:00
Thomas Krijnen 2bd6d9343c Fixes for some of the header renames 2022-06-15 13:56:47 +02:00
Thomas Krijnen bc2d76e685 Include-what-you-use 2022-06-15 13:41:13 +02:00
Thomas Krijnen 124f786007 Update Kernel.cpp 2022-06-14 17:18:44 +02:00
Thomas Krijnen b49cfe9744 msvc /mp 2022-06-14 15:25:14 +02:00
Thomas Krijnen 6f5f189443 Missing files 2022-06-14 15:24:38 +02:00
Thomas Krijnen d6f8448c83 IfcGeom refactoring pt. 1 2022-06-14 15:02:54 +02:00
Thomas Krijnen 0f85890c72 #2237 workaround for openings on extrusions of compound profiles 2022-06-11 15:48:04 +02:00
Thomas Krijnen bf3eb68b67 template template template template template 2022-06-10 11:20:01 +02:00
Dion Moult ce3a6bba0f Fix bug where MS Project files with optional weekdays failed to import 2022-06-10 19:07:58 +10:00
Thomas Krijnen f1e3d9c573 typename typename typename typename typename typename 2022-06-10 11:07:40 +02:00
Thomas Krijnen 0a30d1a438 #2213 Read precision from rep context in create_shape() 2022-06-10 11:00:45 +02:00
Dion Moult 8311983d5e You can now toggle visibility and wireframe modes of linked models and fix bug where links with the same name were not unique 2022-06-09 21:48:13 +10:00
Dion Moult 97bf4e3d0d Fix bug where object class selection didn't work with linked models 2022-06-09 21:46:24 +10:00
Dion Moult 15661536dc Extract elements recipe now handles spatial elements as an upgrade to the ExtractSpaces recipe 2022-06-09 19:01:32 +10:00
Dion Moult f451c51451 Fix bug where some appended assets created invalid graphs due to poor purging 2022-06-09 18:59:20 +10:00
Dion Moult b5e27e4ced Fix bug where transaction history was polluted when starting a new file in an existing Blender session 2022-06-09 15:30:18 +10:00
Dion Moult 95881c6258 Fix embarrassing geolocation bug for showing / setting grid north 2022-06-09 10:22:02 +10:00
Thomas Krijnen cb8cfd7905 Update CMakeLists.txt 2022-06-08 13:59:29 +02:00
Dmitry Sedov 96fe1018ed implemented the minimal build version 2022-06-08 13:46:18 +02:00
Thomas Krijnen 73c006a977 Use OCCT github mirror for reliability 2022-06-08 13:37:48 +02:00
Thomas Krijnen 133fa5547b Use OCCT github mirror for reliability 2022-06-08 13:37:06 +02:00
Dion Moult c95d263ca5 #2224 Write documentation for dealing with large models 2022-06-08 19:10:20 +10:00
Dion Moult 3507920c09 #2224 Expose unload project in UI to aid in unlinking IFCs for coordination only 2022-06-08 19:09:55 +10:00
Dion Moult cdabc507e1 #2224 New headless import script 2022-06-08 14:09:02 +10:00
Dion Moult bcbee0659b #2224 Coordination mode now allows interspersed (much faster) object merging during model loading to crunch down large models, with a new by type merge mode 2022-06-08 12:10:01 +10:00
Dion Moult 053e4e1185 #2224 Enabling coordination mode now excludes types and openings 2022-06-08 12:06:59 +10:00
Dion Moult d76679a45f #2224 Cache seems unstable for some large models so allow option to disable 2022-06-08 11:56:22 +10:00
Dion Moult a1ed78e755 #2224 You can now specify element offset / limit ranges when loading models 2022-06-08 11:53:58 +10:00
Dion Moult d0f38bc919 #2224 You can now filter models by type when loading 2022-06-08 11:32:00 +10:00
Dion Moult 4681d3198f #841 #2224 Users can now enable native mesh processing with support for FBSM geometry 2022-06-08 10:19:55 +10:00
Dion Moult 96e4641ebf #2224 Optimise import by 10-40% by removing unnecessary slow traversal of representations 2022-06-08 09:14:36 +10:00
Dion Moult 2ab808a216 #2224 You can now bulk check/uncheck filter categories on import 2022-06-08 09:09:35 +10:00
Dion Moult 1ec0e1ce19 Fix bug with displaying inherited psets by accident. 2022-06-08 09:07:22 +10:00
bimworld bc734c263e Comment deconstructor IfcFile 2022-06-07 12:14:30 +02:00
Thomas Krijnen a96bffa341 Update schema.py 2022-06-06 10:31:33 +02:00
Thomas Krijnen fb3e9ad1b7 Quality IteratorSettings name in wrapper gen 2022-06-06 10:13:48 +02:00
Thomas Krijnen 4c854fbb62 Try to do less allocation during parse, favour upper case entity names in spf 2022-06-06 09:59:32 +02:00
Thomas Krijnen 0e8d7bdc97 Log result of unify() in boolean_operation() 2022-06-06 09:58:57 +02:00
Thomas Krijnen a92d37cd64 Quick fix py create_shape() setting propagation 2022-06-06 09:58:42 +02:00
Dion Moult 00e46ce39e More meaningful percentage completion in console when loading models 2022-06-06 09:59:43 +10:00
Esteban Dugueperoux 54b82fd149 Update README travis badge URL 2022-06-03 09:24:22 +02:00
Esteban Dugueperoux e508fb44f2 Update CMake release requirement 2022-06-02 13:33:06 +02:00
Dion Moult 89e2ad9262 Write documentation for georeferencing 2022-06-02 19:01:09 +10:00
Dion Moult 419901649e You can now configure the distance limit for false origin offsets 2022-06-01 14:25:30 +10:00
Dion Moult 18cb767a95 More IDS test structuring and add support for checking logical unknown 2022-05-31 22:27:37 +10:00
Thomas Krijnen a11b51c971 Revert "Temporarily disable release tag guard"
This reverts commit c6b86e45f3.
2022-05-31 11:30:25 +02:00
Thomas Krijnen d7f913d464 #2218 Decreate (hardcode) angular tolerance on boolean op extrusion detection 2022-05-31 11:24:27 +02:00
Dion Moult 0d982090e3 More descriptive test names and slightly more concise IDS doc layout 2022-05-31 19:13:39 +10:00
Thomas Krijnen be6617d9f9 Update release.yml 2022-05-31 10:31:56 +02:00
Thomas Krijnen fc0efa2339 Tweak release.yml configure opts 2022-05-31 10:21:45 +02:00
Thomas Krijnen c6b86e45f3 Temporarily disable release tag guard 2022-05-31 09:55:40 +02:00
Thomas Krijnen 53b6247bb8 Unify release.yml and ci.yml deps 2022-05-31 09:54:10 +02:00
Thomas Krijnen eab06bda6c Submodule 2022-05-31 09:43:53 +02:00
Thomas Krijnen 524fa58f05 Remove erroneous .gitmodule entry 2022-05-31 09:39:31 +02:00
Dion Moult 4c4846bc8f Support userdefined process types in util module 2022-05-31 16:23:13 +10:00
Dion Moult 0dfe1d995f Update IDS to support process types and require uppercase entities 2022-05-31 16:22:44 +10:00
Dion Moult a4434413d2 Fix bug where rooted tasks with no subactivities would not export correctly to P6 2022-05-31 15:36:13 +10:00
Dion Moult 5ca19ce2f7 WIP update to support current IDS v0.6 2022-05-31 15:35:47 +10:00
Dion Moult 3e1c2c4daf Code review and tests for georeferencing module refactor 2022-05-29 23:44:18 +10:00
Dion Moult 2d4e316df2 Fix bug where unit editing failed for named units 2022-05-29 17:25:28 +10:00
maxfb87 4b426e1fc0 Refactor Georeference Module 2022-05-29 15:54:06 +10:00
Dion Moult d2ad4b2b68 Allow rooted tasks when exporting schedules to P6XML 2022-05-29 11:15:37 +10:00
Dion Moult 67522e03d1 Fix import error bugs when importing P6XML to enterprise environments 2022-05-27 17:17:31 +10:00
AToradaIBIM 24f307130c BCFv3 "get_comments" error #2212
Fix for Issue #2212 

The method get_comments wasn't working as expected because on line 649, it was trying to access the element "Topics" instead of "Topic", based on the XSD "Topics" doesn't exist.
2022-05-25 11:19:53 +02:00
Dion Moult 0a3e5f65fa Fix multiple specifications in IDS 2022-05-25 17:49:12 +10:00
Dion Moult e302fea33b Resolve merge conflict. Still WIP IDS test cases. 2022-05-22 22:10:28 +10:00
Dion Moult c352181f8c Fix bug where underlays didn't work 2022-05-22 21:32:57 +10:00
Thomas Krijnen 3a1416536a Oops 2022-05-17 22:31:37 +02:00
Thomas Krijnen 4eec863cad Example for turning IDS test cases into spec. Sorry for butchering your code @Moult 2022-05-17 22:21:11 +02:00
Thomas Krijnen 96bcd31d99 #2202 Check for nested compound of solid in boolean result handling 2022-05-16 14:24:57 +02:00
Thomas Krijnen b09ff828de #2186 rerun codegen 2022-05-16 13:07:40 +02:00
Thomas Krijnen 3cb68ca388 #2186 optional attribute setter fix 2022-05-16 13:07:40 +02:00
Dion Moult c95d3693ea OpenSourceBIM contributors badge now in README 2022-05-16 11:35:39 +10:00
Dion Moult 67d2ab2577 Write documentation on materials, locations, and measurements 2022-05-16 08:15:31 +10:00
Dion Moult 970b24b63b #1153 New annotation objects have their placement Z in the drawing direction to allow for 2D geometry. 2022-05-15 11:45:47 +10:00
Dion Moult fdaae048c2 Implement IDS partOf facet 2022-05-12 14:33:44 +10:00
Dion Moult 4da5a38237 Implement IDS material null checks, inheritance checks, and set item checks 2022-05-12 12:06:23 +10:00
Dion Moult 0cf2d7e05e Implement IDS uri/use/instructions for material facet 2022-05-11 23:30:10 +10:00
Dion Moult 3122cf21db Implement IDS property facet measure conversions 2022-05-11 19:59:10 +10:00
Dion Moult 4bf25d5d9c Minor fix 2022-05-11 18:46:27 +10:00
Dion Moult dc10c7a360 Implement null check for IDS properties, and support restrictions 2022-05-11 17:10:22 +10:00
Dion Moult 94e082fc7d Support IDS uri/use/instructions for property facets 2022-05-11 15:30:39 +10:00
Dion Moult 9671df8491 You can now choose between lightweight and full classifications 2022-05-11 15:19:31 +10:00
Dion Moult 8aa38917eb New classification utility module 2022-05-11 15:19:17 +10:00
Dion Moult 0774100606 IDS classification facets not handle inheritance, full refs, and restrictions 2022-05-11 14:49:47 +10:00
Dion Moult 95b48b02e4 Add support for IDS classification uri, use, and instructions 2022-05-11 13:27:19 +10:00
Dion Moult c5de1d8727 Add IDS support for restrictions in attribute facets and handle inheritance 2022-05-11 13:06:15 +10:00
Dion Moult 7e09ab0ced Add support for IDS attribute use/instructions 2022-05-11 10:12:36 +10:00
Dion Moult eb3e137a6e #2196 Get psets now handleds inheritance by default 2022-05-11 09:18:23 +10:00
Dion Moult f528ad9744 Minor fix 2022-05-10 23:49:14 +10:00
Dion Moult 650f9eb7f5 Attribute filtering in IDS now checks null-like values too. 2022-05-10 22:42:21 +10:00
Dion Moult a78d6f42e5 Allow IDS to audit any entity, not just IfcObjects 2022-05-10 22:13:28 +10:00
Dion Moult 4326a74c64 Fix IDS to not allow subclasses and allow restrictions in the class name 2022-05-10 21:40:35 +10:00
Dion Moult 08fc62d4d9 Add support for userdefined predefined types in IDS entity facets 2022-05-10 21:20:10 +10:00
Dion Moult ae298de8dc Implement identifier, desc, and instructions for specification node in IDS 2022-05-10 19:26:12 +10:00
Dion Moult a24c4b269a Fix mandatory title and optional date and check invalid dates in IDS 2022-05-10 17:36:24 +10:00
Dion Moult 083d9d3e3e Implement attribute facet for IDS 2022-05-10 16:23:07 +10:00
Dion Moult 12e7ed356c Fix IDS namespace bug that prevented XML encoding, simplify XML writing, and new to_string() method to decrease dependency on the filesystem 2022-05-10 16:22:38 +10:00
Dion Moult 3f15dd71b1 Run black on ids module 2022-05-10 16:18:08 +10:00
Adrien SCHVALBERG 42448cae4e Add missing exports for ifcopenshell-python when we build shared libs. 2022-05-09 14:13:47 +02:00
Dion Moult 7e28d193ec #2185 Document installation and hello world examples for IfcOpenShell-python 2022-05-09 19:27:33 +10:00
Thomas Krijnen 8f2b32532c validate.py text result formatting improvements 2022-05-09 11:00:04 +02:00
Dion Moult f0a00cbc94 #2185 Set up auto generated API reference documentation via sphinx-autoapi 2022-05-09 15:35:52 +10:00
Dion Moult fd73b2cf0a #2185 Migrate web files to website repo 2022-05-08 19:51:17 +10:00
Adrien SCHVALBERG e5b3455533 Fix memory leak for HeaderEntity instances:
the getArgumentCount() call in IfcEntityInstanceData destructor isn't virtually dispatched so it returns 0 instead of correct number of arguments => memory leak
added IfcEntityInstanceData::clearArguments() which clears all arguments and resets the attributes_ field to nullptr

Remove unnecessary copy in IfcEntityInstanceData::setArgument() when argument is created at the call site (no more leak of original argument)

Fix memory leak in IfcFile: delete entities from both byid and entity_file_map
2022-05-08 10:21:46 +02:00
Adrien SCHVALBERG e677355438 Add IfcParse::clear_schemas() and individual Ifc___::clear_schema() to clear any resource allocated by registered schemas
Add missing virtual destructors in IfcSchema.h
2022-05-08 10:21:46 +02:00
Dion Moult 93fd3f97e3 #2111 New feature to select by style and see style usage 2022-05-08 18:03:18 +10:00
Dion Moult 910d189f35 #2188 support displaying inherited materials 2022-05-08 16:27:35 +10:00
Dion Moult c8d6d12442 The material manager now groups materials into categories 2022-05-08 15:48:09 +10:00
Dion Moult 1f3ba3d09d New makefile targets to specifically run tests of single modules 2022-05-08 15:45:47 +10:00
Dion Moult f67941221e Enable syntax highlighting in test suite for easier debugging 2022-05-08 15:24:06 +10:00
Dion Moult 8252ef82ad #2185 Write documentation about viewing construction types 2022-05-07 21:50:38 +10:00
Dion Moult 3b825e35ae #1153 You can now provide custom stylesheets, markers, symbols, and hatches 2022-05-07 20:28:24 +10:00
Dion Moult 4a4912e817 Editing URIReference properties now provides a file browse button 2022-05-07 20:26:25 +10:00
Dion Moult 5a38604ec2 #1153 Boilerplate styles and definitions now can be used with any drawing layer combination 2022-05-07 17:57:05 +10:00
Dion Moult b15e0d0e2b #2185 Commit logos and homepage mockup by Paulina Godoy and Meetlat 2022-05-07 11:48:20 +10:00
Dion Moult 5a5f52a5c2 #2185 Standardise graphic style for annotation images in documentation 2022-05-07 11:42:12 +10:00
Dion Moult 816a690519 Commit WIP website design by Paulina Godoy :) 2022-05-07 10:20:11 +10:00
Dion Moult 60303c6560 Minor fix 2022-05-06 20:51:36 +10:00
Dion Moult cc88e6f112 #2185 Write documentation about viewing attributes, pets, and qtos 2022-05-06 20:44:26 +10:00
Dion Moult 5f1b5d0c63 You can now manage actor assignments to products 2022-05-06 17:47:24 +10:00
Dion Moult b54caa8967 Person / organisation roles and acting actors are now shown 2022-05-06 16:21:12 +10:00
Dion Moult 73e048a9a8 #1153 You can now manage assigned products for all annotations 2022-05-06 15:35:39 +10:00
Dion Moult 316a2b7f53 #2185 Write documentation for viewing element classes 2022-05-06 12:46:29 +10:00
Dion Moult 42edcda8bd Deleting an element now also auto deletes its associated distribution ports 2022-05-06 10:38:38 +10:00
Dion Moult 92432f453d You can now remove ports 2022-05-06 10:35:12 +10:00
Dion Moult 4b6ebe7b1c Fix bug where deleted objects were not cleared after saving 2022-05-06 09:20:37 +10:00
Emad Aghajani 3f6d25ed43 Fixes #2175: Fixes a crash when blender is in background mode or is used as bpy Python module 2022-05-05 19:23:36 +10:00
Dion Moult d4b2cc7f2c Bump IfcOpenShell build 2022-05-05 18:00:58 +10:00
Dion Moult 53c050c6ed Port locations now sync relative to their parent even when not actively displayed in the viewport 2022-05-05 18:00:42 +10:00
Dion Moult 3213af2f48 #2185 Write documentation on the object outliner panel 2022-05-05 16:29:40 +10:00
Dion Moult 85122ba184 #2185 Document how to navigate through a model 2022-05-05 14:43:34 +10:00
Dion Moult b8791d43aa Minor documentation update for loading an IFC model 2022-05-05 12:19:40 +10:00
Dion Moult 6a82f27d48 Standalone Blender import script now available for debugging 2022-05-05 09:59:04 +10:00
Dion Moult 4e2400952a Fix #2147 #2143 Fix hanging when reloading a cache of a file with 2D and 3D data 2022-05-05 09:58:10 +10:00
Dion Moult de1dae8b6e Grids are now locked by default when loading IFCs 2022-05-05 09:28:36 +10:00
Dion Moult dd127db969 Minor documentation improvements 2022-05-04 16:04:18 +10:00
Dion Moult 194bc53bdb Minor refactor 2022-05-04 15:54:15 +10:00
Dion Moult 8c3e29c351 Split BlenderBIM and other docs to not overwhelm users. Use Furo theme for Sphinx. 2022-05-04 13:06:39 +10:00
Gorgious56 6e55a04550 Refactor Standalone section shader (#2172)
* Refactor Standalone section shader

* Remove leftover code
2022-05-03 18:21:51 +02:00
Dion Moult 9333839267 Copying an element now also copies its distribution ports 2022-05-03 18:05:27 +10:00
Dion Moult 280236290e Fix #2182. File export IFC menu item now defaults to save as to improve usability for new users 2022-05-03 17:36:55 +10:00
Dion Moult c5773b5665 Port locations are now auto synchronised when hiding ports 2022-05-03 16:50:55 +10:00
Dion Moult 96babc4506 Creating a flow segment now also creates 2 ports at either end 2022-05-03 15:48:41 +10:00
Dion Moult f8acde0801 You can now add ports to types, and ports are created when placing occurrences of that type 2022-05-03 14:32:18 +10:00
Dion Moult 32c101cf74 #1153 Implement angular dimension annotations 2022-05-02 09:20:40 +10:00
Bruno Perdigão 2a1aa535ba Added VSCode extension to Live Development section 2022-05-02 08:06:27 +10:00
Dion Moult 02903c4929 #1153 New arc from 3 points command for 2D CAD 2022-05-01 17:25:03 +10:00
Dion Moult d80e26e4ab Design new icon for CAD workspace tool 2022-05-01 15:30:37 +10:00
Dion Moult a88b83306e #1153 New arc from 2 points command for 2D CAD (based on view direction) 2022-05-01 14:43:03 +10:00
Dion Moult 9577c40386 Fix #2107. Materials and styles in the scene manager are now shown alphabetically 2022-05-01 12:01:24 +10:00
Dion Moult f54a5cf6e0 Fix #2048. Fix bug where material and style IFC links were severed after reloading a saved Blender file 2022-05-01 09:41:03 +10:00
Dion Moult abcb492e5a Fix #2174. Don't show meaningless context dropdown when reassigning class. 2022-05-01 08:57:23 +10:00
Dion Moult 4d447c169f Fix #2176 2022-05-01 08:50:25 +10:00
Dion Moult b465605b63 #1153 CAD tool now provides hotkey access to offset edges command 2022-04-30 22:55:45 +10:00
Dion Moult fb6dc45b1b #1153 New fillet command for 2D CAD supporting both bevel and chamfer 2022-04-30 22:44:37 +10:00
Dion Moult b2aca0ae36 #1153 New mitre command for 2D cad (like chamfer with 0 distance) 2022-04-30 18:17:39 +10:00
Dion Moult 03831d440c #1153 New CAD workspace with hotkeys for super fast CAD operations 2022-04-30 15:50:40 +10:00
Dion Moult ac4679a376 #1153 New CAD module with basic trim / extend tool for 2D linework 2022-04-30 11:40:19 +10:00
jakob-beetz 89023df4e5 Update readme.md 2022-01-07 10:37:40 +01:00
admin f794b647e1 merge v0.6.0 2021-12-06 09:59:18 +08:00
admin 2e81b2bd17 merage v0.6
ADD IFCGroup parse #1154
2021-12-02 10:19:47 +08:00
admin 8527d0fcbd merage new 0.6 2021-12-01 10:32:20 +08:00
admin bc32481185 merge 2021-07-05 10:30:06 +08:00
admin 58e9f1baf9 merage 2021-05-10 14:06:18 +08:00
admin db5432d902 merge master v0.6.0 2021-05-06 09:54:33 +08:00
admin 397b6ae5fe merage master v6.0.0 2021-04-12 10:26:10 +08:00
admin 72fdca9e82 merage 2021-04-01 16:41:28 +08:00
admin 29b8e67bc8 merge all 2021-03-25 10:04:25 +08:00
admin 33ec80e54d merge update 2021-01-28 13:56:47 +08:00
admin 371e4137f1 update mager 2021-01-21 13:43:19 +08:00
admin 555a2ce79e merge master v6.0 2021-01-11 11:39:36 +08:00
admin 699742880e fix IFC to xml - IFCGROUP in IFC cannot be found in xml #1154 2021-01-11 10:59:03 +08:00
GisSpace d99348f163 Merge pull request #3 from IfcOpenShell/v0.6.0
merge master
2020-12-31 15:44:04 +08:00
GisSpace d7034c6a40 Merge pull request #2 from IfcOpenShell/v0.6.0
merge
2020-12-21 11:13:59 +08:00
GisSpace ac632d0698 Merge pull request #1 from IfcOpenShell/v0.6.0
同步源库
2020-12-18 16:23:04 +08:00
admin f3696788cb #1097
fix:IFC file conversion obj file, some cases will be rotated 90 °
2020-12-10 18:44:23 +08:00
883 changed files with 204187 additions and 27730 deletions
+92
View File
@@ -0,0 +1,92 @@
name: Publish-blenderbim-chocolatey package
on:
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: "30 0 * * *" # 30min past utc midnight
env:
major: 0
minor: 0
name: blenderbim
choco_version: 1.1.0
CHOCO_TOKEN: ${{ secrets.CHOCO_TOKEN }}
jobs:
activate:
runs-on: ubuntu-latest
if: |
github.repository == 'IfcOpenShell/IfcOpenShell'
steps:
- name: Set env
run: echo ok go
build:
needs: activate
name: ${{ matrix.config.name }}-${{ matrix.pyver }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
pyver: [py310]
config:
- {
name: "Windows Build",
short_name: win,
}
steps:
- 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: Check in published releases if we should do a choco release
id: do_choco
run: |
echo "::set-output name=choco_release::$(python3 /home/runner/work/IfcOpenShell/IfcOpenShell/choco/blenderbim/check_repo_infos.py --do_choco_release?)"
- name: Fill chocolatey scripts on win with latest blender python
if: ${{steps.do_choco.outputs.choco_release}} == 'do_choco_release'
run: |
yesterday_non_iso="$(date --date='yesterday' +'%y%m%d' | tr -d '\n')" &&
target_os=${{ matrix.config.short_name }} &&
latest_blender_python_version_maj_min=$(python3 /home/runner/work/IfcOpenShell/IfcOpenShell/choco/blenderbim/check_repo_infos.py --latest_blender_python_version_maj_min?) &&
echo "latest_blender_python_version_maj_min?: $latest_blender_python_version_maj_min" &&
pyver=$(python3 /home/runner/work/IfcOpenShell/IfcOpenShell/choco/blenderbim/check_repo_infos.py --pyver?) &&
export latest_blender_version_maj_min=$(python3 /home/runner/work/IfcOpenShell/IfcOpenShell/choco/blenderbim/check_repo_infos.py --latest_blender_release_maj_min?) &&
export latest_blender_version_maj_min_pat=$(python3 /home/runner/work/IfcOpenShell/IfcOpenShell/choco/blenderbim/check_repo_infos.py --latest_blender_release_maj_min_pat?) &&
echo latest_blender_version_maj_min_pat?: $latest_blender_version_maj_min_pat &&
export blenderbim_build_version="${{ env.major }}.${{ env.minor }}.$yesterday_non_iso" &&
export url_blenderbim_py310_win_zip="https://github.com/IfcOpenShell/IfcOpenShell/releases/download/blenderbim-$yesterday_non_iso/blenderbim-$yesterday_non_iso-$pyver-$target_os.zip" &&
wget $url_blenderbim_py310_win_zip --no-verbose &&
export sha256sum_blenderbim_py310_win_zip=$( sha256sum blenderbim-$yesterday_non_iso-$pyver-$target_os.zip --tag | cut -d ' ' -f 4 | tr -d '\n') &&
echo sha256sum_blenderbim_py310_win_zip: $sha256sum_blenderbim_py310_win_zip &&
python3 choco/blenderbim/fill_dynamic_parameters.py &&
echo __build choco with mono &&
wget "https://github.com/chocolatey/choco/archive/refs/tags/$choco_version.tar.gz" --quiet &&
tar -xzf "$choco_version.tar.gz" &&
cd choco-$choco_version &&
chmod +x build.sh zip.sh &&
./build.sh &&
cp -r build_output/chocolatey /opt/chocolatey &&
cd /home/runner/work/IfcOpenShell/IfcOpenShell/choco/blenderbim/ &&
echo __choco pack &&
mono /opt/chocolatey/choco.exe --version &&
mono /opt/chocolatey/choco.exe pack --allow-unofficial &&
echo __choco set apiKey &&
mono /opt/chocolatey/choco.exe setapikey --key="$CHOCO_TOKEN" --source="https://push.chocolatey.org/" --allow-unofficial # &&
echo __choco push &&
mono /opt/chocolatey/choco.exe push --source="https://push.chocolatey.org/" --key="$CHOCO_TOKEN" --allow-unofficial --verbose
- name: Inform user about not packaging
if: ${{steps.do_choco.outputs.choco_release}} != 'do_choco_release'
run: |
echo "no releases found today ${{ env.DATE }}, therefore choco packaging is skipped."
+15 -1
View File
@@ -3,8 +3,18 @@ name: Publish-blenderbim-multiplatform
on:
push:
paths:
- 'src/blenderbim/**'
- '.github/workflows/ci-blenderbim-matrix.yml'
- 'src/blenderbim/**'
- 'src/ifcopenshell-python/ifcopenshell/**'
- 'src/bcf/src/bcf/**'
- 'src/ifcclash/ifcclash/**'
- 'src/ifccobie/**'
- 'src/ifcdiff/**'
- 'src/ifccsv/**'
- 'src/ifcpatch/ifcpatch/**'
- 'src/ifc4d/ifc4d/**'
- 'src/ifc5d/ifc5d/**'
- 'src/ifccityjson/**'
branches:
- v0.7.0
@@ -43,6 +53,10 @@ jobs:
name: "MacOS Build",
short_name: macos
}
- {
name: "MacOS ARM Build",
short_name: macosm1
}
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2 # https://github.com/actions/setup-python
@@ -0,0 +1,65 @@
name: ci-ifcopenshell-conda-daily
on:
workflow_run:
workflows: ["ci"]
types:
- completed
jobs:
activate:
runs-on: ubuntu-latest
if: |
github.repository == 'IfcOpenShell/IfcOpenShell'
steps:
- name: Set env
run: echo ok go
test:
name: ${{ matrix.platform.distver }}-${{ matrix.pyver.name }}
needs: activate
runs-on: ${{ matrix.platform.distver }}
defaults:
run:
shell: bash -l {0}
strategy:
fail-fast: false
matrix:
pyver: [
# { name: py39, distver: '3.9' },
{ 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: 'true' },
{ name: Linux, distver: ubuntu-22.04, upload: 'false' },
{ name: macOS, distver: macos-11, upload: 'true' },
{ name: macOS, distver: macos-12, upload: 'false' }
]
steps:
- uses: actions/checkout@v2
with:
submodules: recursive
- name: Download MacOSX SDK
if: ${{ matrix.platform.name == 'macOS' }}
run: |
curl -o MacOSX10.13.sdk.tar.xz -L https://github.com/phracker/MacOSX-SDKs/releases/download/11.3/MacOSX10.13.sdk.tar.xz && \
tar xf MacOSX10.13.sdk.tar.xz && \
sudo mv -v MacOSX10.13.sdk /opt/ && \
ls /opt/
- uses: seanmiddleditch/gha-setup-ninja@master
- uses: conda-incubator/setup-miniconda@v2 # https://github.com/conda-incubator/setup-miniconda
with:
activate-environment: conda-build
python-version: ${{ matrix.pyver.distver }}
environment-file: conda/environment.yml
- 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
- name: build & test ifcopenshell
if: ${{ matrix.platform.upload == 'false' }}
run: |
conda-build . --python ${{ matrix.pyver.distver }} -c conda-forge --no-remove-work-dir
@@ -0,0 +1,73 @@
name: ci-ifcopenshell-pypi
on:
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 1 * *" # 11min before utc midnight
env:
major: 0
minor: 0
name: ifcopenshell
jobs:
activate:
runs-on: ubuntu-latest
if: |
github.repository == 'IfcOpenShell/IfcOpenShell'
steps:
- name: Set env
run: echo ok go
build:
needs: activate
name: ${{ matrix.config.name }}-${{ matrix.pyver }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
pyver: [py36, py37, py38, py39, py310]
config:
- {
name: "Windows Build",
short_name: win,
}
- {
name: "Linux Build",
short_name: linux
}
- {
name: "MacOS Build",
short_name: macos
}
- {
name: "MacOS ARM Build",
short_name: macosm1
}
steps:
- 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
id: date
run: echo "::set-output name=date::$(date +'%y%m%d')"
- name: Compile
run: |
pip install build
cp -r src/ifcopenshell-python src/ifcopenshell_${{ matrix.config.short_name }}_${{ matrix.pyver }} &&
cd src/ifcopenshell_${{ matrix.config.short_name }}_${{ matrix.pyver }} &&
make dist PLATFORM=${{ matrix.config.short_name }} PYVERSION=${{ matrix.pyver }}
- name: Publish a Python distribution to PyPI
uses: ortega2247/pypi-upload-action@master
with:
user: __token__
password: ${{ secrets.PYPI_API_TOKEN }}
packages_dir: src/ifcopenshell_${{ matrix.config.short_name }}_${{ matrix.pyver }}/dist
+95
View File
@@ -0,0 +1,95 @@
name: ci_py_only
on:
push:
paths:
- 'src/**'
- 'test/**'
- 'conda/**'
- 'cmake/**'
- '.github/workflows/ci_py_only.yml'
pull_request:
jobs:
activate:
runs-on: ubuntu-latest
if: |
github.repository == 'IfcOpenShell/IfcOpenShell' &&
!contains(github.event.head_commit.message, 'skip ci')
steps:
- run: echo ok go
build:
runs-on: ubuntu-20.04
needs: activate
steps:
- uses: actions/checkout@v2
with:
submodules: recursive
- name: Install C++ dependencies
run: |
sudo apt update
sudo apt-get install --no-install-recommends \
git cmake gcc g++ \
libboost-date-time-dev \
libboost-filesystem-dev \
libboost-iostreams-dev \
libboost-program-options-dev \
libboost-regex-dev \
libboost-system-dev \
libboost-thread-dev \
python3-all-dev python3-pip \
swig libpcre3-dev libxml2-dev \
libtbb-dev nlohmann-json3-dev \
libocct-foundation-dev libocct-modeling-algorithms-dev libocct-modeling-data-dev libocct-ocaf-dev libocct-visualization-dev libocct-data-exchange-dev \
libhdf5-dev libcgal-dev
- name: ccache
uses: hendrikmuhs/ccache-action@v1
- name: Build ifcopenshell
run: |
mkdir build && cd build
cmake \
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_PREFIX_PATH=/usr \
-DCMAKE_SYSTEM_PREFIX_PATH=/usr \
-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 \
-DCOLLADA_SUPPORT=Off \
"-DSCHEMA_VERSIONS=2x3;4" \
-DBUILD_CONVERT=Off \
-DGLTF_SUPPORT=On \
-DJSON_INCLUDE_DIR=/usr/include \
-DCGAL_INCLUDE_DIR=/usr/include \
-DGMP_INCLUDE_DIR=/usr/include \
-DMPFR_INCLUDE_DIR=/usr/include \
-DGMP_LIBRARY_DIR=/usr/lib/x86_64-linux-gnu \
-DMPFR_LIBRARY_DIR=/usr/lib/x86_64-linux-gnu \
-DHDF5_INCLUDE_DIR=/usr/include/hdf5/serial \
../cmake
sudo make -j $(nproc)
sudo make install
- name: Install Python dependencies
run: |
sudo /usr/bin/python -m pip install -U pip
sudo /usr/bin/python -m pip install xmlschema numpy lxml
sudo /usr/bin/python -m pip install src/bcf
sudo /usr/bin/python -m pip install pytest
sudo /usr/bin/python -m pip install isodate
sudo /usr/bin/python -m pip install lark
sudo /usr/bin/python -m pip install networkx
- name: Test
run: |
cd test
sudo /usr/bin/python tests.py
cd ../src/ifcopenshell-python
mv ifcopenshell ifcopenshell-local # Force testing on installed module
make test
+14 -3
View File
@@ -3,8 +3,19 @@ name: ci
on:
push:
paths:
- 'src/**'
- 'test/**'
- 'src/ifcblender/**'
- 'src/ifcconvert/**'
- 'src/ifcgeom/**'
- 'src/ifcgeom_schema_agnostic/**'
- 'src/ifcgeomserver/**'
- 'src/ifcjni/**'
- 'src/ifcmax/**'
- 'src/ifcopenshell-python/**'
- '!src/ifcopenshell-python/docs/**'
- 'src/ifcparse/**'
- 'src/ifcwrap/**'
- 'src/qtviewer/**'
- 'src/serializers/**'
- 'conda/**'
- 'cmake/**'
- '.github/workflows/ci.yml'
@@ -62,7 +73,7 @@ jobs:
-DPYTHON_INCLUDE_DIR:PATH=/usr/include/python3.8 \
-DPYTHON_LIBRARY:FILEPATH=/usr/lib/x86_64-linux-gnu/libpython3.8.so \
-DCOLLADA_SUPPORT=Off \
"-DSCHEMA_VERSIONS=2x3;4" \
"-DSCHEMA_VERSIONS=2x3;4;4x3" \
-DGLTF_SUPPORT=On \
-DJSON_INCLUDE_DIR=/usr/include \
-DCGAL_INCLUDE_DIR=/usr/include \
+24 -6
View File
@@ -19,13 +19,23 @@ jobs:
uses: actions/checkout@v2
with:
submodules: recursive
-
name: Update ubuntu
- name: Install C++ dependencies
run: |
sudo apt update
sudo apt-get install git cmake gcc g++ libboost1.67-all-dev python3-all-dev swig libpcre3-dev libxml2-dev
sudo apt-get install 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
sudo apt-get install --no-install-recommends \
git cmake gcc g++ \
libboost-date-time-dev \
libboost-filesystem-dev \
libboost-iostreams-dev \
libboost-program-options-dev \
libboost-regex-dev \
libboost-system-dev \
libboost-thread-dev \
python3-all-dev python3-pip \
swig libpcre3-dev libxml2-dev \
libtbb-dev nlohmann-json3-dev \
libocct-foundation-dev libocct-modeling-algorithms-dev libocct-modeling-data-dev libocct-ocaf-dev libocct-visualization-dev libocct-data-exchange-dev \
libhdf5-dev libcgal-dev opencollada-dev
- name: Build
env:
@@ -44,10 +54,18 @@ jobs:
-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 \
-DCOLLADA_SUPPORT=Off \
-DLIBXML2_INCLUDE_DIR=/usr/include/libxml2 \
-DLIBXML2_LIBRARIES=/usr/lib/x86_64-linux-gnu/libxml2.so \
-DGLTF_SUPPORT=On \
-DJSON_INCLUDE_DIR=/usr/include \
-DCGAL_INCLUDE_DIR=/usr/include \
-DGMP_INCLUDE_DIR=/usr/include \
-DMPFR_INCLUDE_DIR=/usr/include \
-DGMP_LIBRARY_DIR=/usr/lib/x86_64-linux-gnu \
-DMPFR_LIBRARY_DIR=/usr/lib/x86_64-linux-gnu \
-DHDF5_INCLUDE_DIR=/usr/include/hdf5/serial \
-DOPENCOLLADA_INCLUDE_DIR=/usr/include/opencollada \
-DOPENCOLLADA_LIBRARY_DIR=/usr/lib/opencollada/ \
../cmake
make -j $(nproc)
make install
+6
View File
@@ -67,3 +67,9 @@ htmlcov
# Blender
*.blend1
# Sphinx
_build/
# IDS Docs
src/ifcopenshell-python/test/build
+1 -4
View File
@@ -8,9 +8,6 @@
[submodule "src/svgfill"]
path = src/svgfill
url = https://github.com/IfcOpenShell/svgfill
[submodule "src/ifcopenshell-python/ifcopenshell/Sample-BIM-Files"]
path = src/ifcopenshell-python/ifcopenshell/Sample-BIM-Files
url = https://github.com/atomczak/Sample-BIM-Files
[submodule "src/ifcopenshell-python/test/Sample-BIM-Files"]
path = src/ifcopenshell-python/test/Sample-BIM-Files
url = https://github.com/atomczak/Sample-BIM-Files
url = https://github.com/IfcOpenShell/ids-test-files
+5 -3
View File
@@ -23,9 +23,11 @@ RUN echo "deb http://archive.ubuntu.com/ubuntu focal-proposed main restricted" |
echo "deb http://archive.ubuntu.com/ubuntu focal-proposed multiverse" | tee -a /etc/apt/sources.list; \
apt-get -qq update; \
apt-get -y install tzdata dos2unix rsync; \
apt-get -y install python3 libxml2 liboce-foundation11 liboce-modeling11 liboce-ocaf11 liboce-visualization11 \
liboce-ocaf-lite11 libpython3.8 libboost-system1.67.0 libboost-program-options1.67.0 \
libboost-regex1.67.0 libboost-thread1.67.0 libboost-date-time1.67.0; \
apt-get -y install python3 libxml2 libpython3.8 \
libboost-all-dev \
libocct-foundation-dev libocct-modeling-algorithms-dev libocct-modeling-data-dev \
libocct-ocaf-dev libocct-visualization-dev libocct-data-exchange-dev \
libhdf5-serial-dev ; \
rm -rf /var/lib/apt/lists/* ;
COPY . /home/IfcOpenShell/
+59 -291
View File
@@ -1,305 +1,73 @@
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.
[![Build Status](https://travis-ci.org/IfcOpenShell/IfcOpenShell.svg?branch=v0.6.0)](https://travis-ci.org/IfcOpenShell/IfcOpenShell)
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.
Prerequisites
-------------
* Git
* CMake (2.6 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)
For more information, see:
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 tesselated 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.
* [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)
Building IfcOpenShell
---------------------
| 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) |
| 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) |
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 recommeded 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 swig cmake ftgl cgal gmp libaec opencascade
```
**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/```)
```{shell}
$ cd /path/to/IfcOpenShell
$ mkdir build && cd build
$ cmake ../cmake -DOCC_LIBRARY_DIR=/usr/local/lib/ \
-DOCC_INCLUDE_DIR=/usr/local/include/opencascade/ \
-DCOLLADA_SUPPORT=0 \
-DCGAL_INCLUDE_DIR=/usr/local/include/ \
-DGMP_LIBRARY_DIR=/usr/local/lib/ \
-DMPFR_LIBRARY_DIR=/usr/local/lib/
$ make -j -lboost_options
```
Note: Make sure to compile using XCode, 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"
+34
View File
@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Do not remove this test for UTF-8: if “Ω” doesnt appear as greek uppercase omega letter enclosed in quotation marks, you should use an editor that supports UTF-8, not this one. -->
<package xmlns="http://schemas.microsoft.com/packaging/2015/06/nuspec.xsd">
<metadata>
<id>blenderbim-nightly</id>
<version>blenderbim_build_version-alpha</version>
<packageSourceUrl>https://github.com/IfcOpenShell/IfcOpenShell</packageSourceUrl>
<owners>fbpyr</owners>
<!-- == SOFTWARE SPECIFIC SECTION == -->
<title>BlenderBIM install nightly</title>
<authors>Dion Moult and many more, see: https://github.com/IfcOpenShell/IfcOpenShell/graphs/contributors</authors>
<projectUrl>https://github.com/IfcOpenShell/IfcOpenShell</projectUrl>
<iconUrl>https://rawcdn.githack.com/IfcOpenShell/IfcOpenShell/c6ee1d1679d1298de0d7447ff108c22709c68e54/choco/blenderbim/blenderbim.png</iconUrl>
<!-- <copyright>Year Software Vendor</copyright> -->
<licenseUrl>https://github.com/IfcOpenShell/IfcOpenShell/blob/v0.7.0/COPYING</licenseUrl>
<requireLicenseAcceptance>true</requireLicenseAcceptance>
<projectSourceUrl>https://github.com/IfcOpenShell/IfcOpenShell</projectSourceUrl>
<docsUrl>https://blenderbim.org/docs/</docsUrl>
<!--<mailingListUrl></mailingListUrl>-->
<bugTrackerUrl>https://github.com/IfcOpenShell/IfcOpenShell/issues</bugTrackerUrl>
<tags>blender bim blenderbim ifc python opensource foss</tags>
<summary>opensource bim</summary>
<description>opensource bim addon for blender - source code at: https://github.com/IfcOpenShell/IfcOpenShell</description>
<!-- <releaseNotes>__REPLACE_OR_REMOVE__MarkDown_Okay</releaseNotes> -->
<dependencies>
<dependency id="blender" version="latest_blender_version_maj_min_pat" />
<dependency id="7zip" version="22.1"/>
</dependencies>
</metadata>
<files>
<file src="tools\**" target="tools" />
</files>
</package>
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

+80
View File
@@ -0,0 +1,80 @@
import datetime
import re
import sys
from urllib import request
def request_repo_info(url: str):
req = request.Request(url)
resp = request.urlopen(req)
if not resp.status == 200:
print(f"[ERROR] could not contact server: {url}")
quit(1)
return resp
if sys.argv[1] == "--do_choco_release?":
now = datetime.datetime.now()
blenderbim_date = (now - datetime.timedelta(days=1)).strftime("%y%m%d")
url = "https://github.com/IfcOpenShell/IfcOpenShell/releases"
resp = request_repo_info(url)
text = str(resp.read())
if blenderbim_date in text:
print("do_choco_release", end="")
elif sys.argv[1] == "--latest_blender_release_maj_min_pat?":
re_blender_version_min_maj_pat = r"Latest Version.+<span>Blender (\d+\.\d+\.\d+)</span>"
url = "https://community.chocolatey.org/packages/blender"
resp = request_repo_info(url)
html_txt = str(resp.read())
found = re.findall(re_blender_version_min_maj_pat, html_txt)
if found:
print(found[0], end="")
elif sys.argv[1] == "--latest_blender_release_maj_min?":
re_blender_version_min_maj = r"Latest Version.+<span>Blender (\d+\.\d+)\..+</span>"
url = "https://community.chocolatey.org/packages/blender"
resp = request_repo_info(url)
html_txt = str(resp.read())
found = re.findall(re_blender_version_min_maj, html_txt)
if found:
print(found[0], end="")
elif sys.argv[1] == "--latest_blender_python_version_maj_min?":
# get latest blender version first
re_blender_version_min_maj_pat = r"Latest Version.+<span>Blender (\d+\.\d+\.\d+)</span>"
url = "https://community.chocolatey.org/packages/blender"
resp = request_repo_info(url)
html_txt = str(resp.read())
found = re.findall(re_blender_version_min_maj_pat, html_txt)
if found:
latest_blender_version_tag = f"v{found[0]}"
re_blender_python_version_maj_min = r"SET\(PYTHON_VERSION (\d+.\d+) "
url = f"https://raw.githubusercontent.com/blender/blender/{latest_blender_version_tag}/build_files/cmake/Modules/FindPythonLibsUnix.cmake"
resp = request_repo_info(url)
html_txt = str(resp.read())
found = re.findall(re_blender_python_version_maj_min, html_txt)
if found:
print(found[0], end="")
elif sys.argv[1] == "--pyver?":
# get latest blender version first
re_blender_version_min_maj_pat = r"Latest Version.+<span>Blender (\d+\.\d+\.\d+)</span>"
url = "https://community.chocolatey.org/packages/blender"
resp = request_repo_info(url)
html_txt = str(resp.read())
found = re.findall(re_blender_version_min_maj_pat, html_txt)
if found:
latest_blender_version_tag = f"v{found[0]}"
re_blender_python_version_maj_min = r"SET\(PYTHON_VERSION (\d+.\d+) "
url = f"https://raw.githubusercontent.com/blender/blender/{latest_blender_version_tag}/build_files/cmake/Modules/FindPythonLibsUnix.cmake"
resp = request_repo_info(url)
html_txt = str(resp.read())
found = re.findall(re_blender_python_version_maj_min, html_txt)
if found:
print(f"py{found[0].replace('.', '')}", end="")
@@ -0,0 +1,49 @@
import os
from pathlib import Path
print("[INFO] inserting dynamic chocolatey package parameters")
HERE_DIR = Path(__file__).parent.absolute()
# print(f"[INFO] HERE_DIR: {HERE_DIR}")
topics = {
"spec": {
"path": HERE_DIR / "blenderbim.nuspec",
"env_vars": {
"latest_blender_version_maj_min_pat",
"blenderbim_build_version",
},
},
"install": {
"path": HERE_DIR / "tools" / "chocolateyinstall.ps1",
"env_vars": {
"url_blenderbim_py310_win_zip",
"sha256sum_blenderbim_py310_win_zip",
"latest_blender_version_maj_min",
},
},
"uninstall": {
"path": HERE_DIR / "tools" / "chocolateyuninstall.ps1",
"env_vars": {
"latest_blender_version_maj_min",
},
},
}
for topic, info in topics.items():
print(f"[INFO] {topic}:")
for env_var_name in info["env_vars"]:
print(f"[INFO] {env_var_name} exists?: {os.environ[env_var_name]}")
with open(info["path"], encoding="utf-8") as txt:
content = txt.read()
for env_var_name in info["env_vars"]:
# print(f"[INFO] replace: {env_var_name}")
content = content.replace(env_var_name, os.environ[env_var_name])
with open(info["path"], "w", encoding="utf-8") as txt:
txt.write(content)
print(f"[INFO] written: {info['path']}")
print("[INFO] inserting dynamic chocolatey package parameters successful")
@@ -0,0 +1,11 @@
$processName = "blender"
$blenderIsRunning = Get-Process -Name $processName -ErrorAction SilentlyContinue
if($blenderIsRunning -eq $null) {
echo "Blender is not running ready for next action."
}
else {
Write-Warning "$processName is still running - installer cannot safely proceed to next action."
Write-Warning "Please retry after closing all running blender applications."
exit 1
}
@@ -0,0 +1,29 @@
$ErrorActionPreference = 'Stop'
$url64 = 'url_blenderbim_py310_win_zip'
$checksum64 = 'sha256sum_blenderbim_py310_win_zip'
$checksumType64 = 'sha256'
$appDataUserDir = [System.Environment]::GetEnvironmentVariable('appdata')
$unzipTargetDir = "$appDataUserDir\Blender Foundation\Blender\latest_blender_version_maj_min\scripts\addons"
$chocoBaseDir = [System.Environment]::GetEnvironmentVariable('ChocolateyInstall')
$addonEnable = "$chocoBaseDir\lib\blenderbim-nightly\tools\enable_blenderbim_addon.py"
$programFilesDir = [System.Environment]::GetEnvironmentVariable('ProgramFiles')
$blenderExePath = "$env:ProgramFiles\Blender Foundation\Blender latest_blender_version_maj_min\blender.exe"
$processName = "blender"
$blenderIsRunning = Get-Process -Name $processName -ErrorAction SilentlyContinue
if($blenderIsRunning -eq $null) {
echo "attempting to install blenderbim."
Install-ChocolateyZipPackage -PackageName $env:ChocolateyPackageName -Url64 $url64 -Checksum64 $checksum64 -checksumType $checksumType64 -UnzipLocation $unzipTargetDir
Start-Process -FilePath $blenderExePath -ArgumentList "-b", "-y", "--python", "$addonEnable"
}
else {
Write-Warning "$processName is still running - blenderbim cannot be safely installed."
Write-Warning "Please retry after closing all running blender applications."
exit 1
}
@@ -0,0 +1,22 @@
$appDataUserDir = [System.Environment]::GetEnvironmentVariable('appdata')
$unzippedDir = "$appDataUserDir\Blender Foundation\Blender\latest_blender_version_maj_min\scripts\addons\blenderbim"
$chocoBaseDir = [System.Environment]::GetEnvironmentVariable('ChocolateyInstall')
$addonDisable = "$chocoBaseDir\lib\blenderbim-nightly\tools\disable_blenderbim_addon.py"
$programFilesDir = [System.Environment]::GetEnvironmentVariable('ProgramFiles')
$blenderExePath = "$env:ProgramFiles\Blender Foundation\Blender latest_blender_version_maj_min\blender.exe"
$processName = "blender"
$blenderIsRunning = Get-Process -Name $processName -ErrorAction SilentlyContinue
if($blenderIsRunning -eq $null) {
echo "attempting to uninstall blenderbim."
Start-Process -FilePath $blenderExePath -ArgumentList "-b", "-y", "--python", "$addonDisable"
Remove-Item -LiteralPath $unzippedDir -Force -Recurse
}
else {
Write-Warning "$processName is still running - blenderbim cannot be safely uninstalled."
Write-Warning "Please retry after closing all running blender applications."
exit 1
}
@@ -0,0 +1,8 @@
import bpy
bpy.ops.preferences.addon_disable(module='blenderbim')
bpy.ops.wm.save_userpref()
print("disabled blenderbim addon successfully.")
@@ -0,0 +1,8 @@
import bpy
bpy.ops.preferences.addon_enable(module='blenderbim')
bpy.ops.wm.save_userpref()
print("enabled blenderbim addon successfully.")
+85 -35
View File
@@ -38,7 +38,7 @@ MESSAGE(STATUS "Autodesk 3ds Max SDK found at ${max_sdk}")
set(HAS_MAX TRUE)
endif()
endforeach()
OPTION(MINIMAL_BUILD "The build is to make a minimal version of IFC converter from OCCT into IFC." OFF)
OPTION(COLLADA_SUPPORT "Build IfcConvert with COLLADA support (requires OpenCOLLADA)." ON)
OPTION(IFCXML_SUPPORT "Build IfcParse with ifcXML support (requires libxml2)." ON)
OPTION(ENABLE_BUILD_OPTIMIZATIONS "Enable certain compiler and linker optimizations on RelWithDebInfo and Release builds." OFF)
@@ -53,6 +53,7 @@ OPTION(HDF5_SUPPORT "Enable HDF5 support (requires HDF5, zlib)" ON)
OPTION(USE_VLD "Use Visual Leak Detector for debugging memory leaks, MSVC-only." OFF)
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)
if (${HAS_MAX})
OPTION(BUILD_IFCMAX "Build IfcMax, a 3ds Max plug-in, Windows-only." OFF)
endif()
@@ -66,6 +67,10 @@ IF(NOT CMAKE_BUILD_TYPE)
SET(CMAKE_BUILD_TYPE "Release")
ENDIF()
if(MSVC AND MSVC_PARALLEL_BUILD)
add_definitions("/MP")
endif()
# QtViewer requires Qt5
OPTION(BUILD_QTVIEWER "Build IfcOpenShell Qt GUI Viewer" OFF)
include(GNUInstallDirs)
@@ -121,6 +126,7 @@ ENDMACRO()
UNIFY_ENVVARS_AND_CACHE(OCC_INCLUDE_DIR)
UNIFY_ENVVARS_AND_CACHE(OCC_LIBRARY_DIR)
if (NOT MINIMAL_BUILD)
UNIFY_ENVVARS_AND_CACHE(OPENCOLLADA_INCLUDE_DIR)
UNIFY_ENVVARS_AND_CACHE(OPENCOLLADA_LIBRARY_DIR)
UNIFY_ENVVARS_AND_CACHE(LIBXML2_INCLUDE_DIR)
@@ -130,17 +136,20 @@ UNIFY_ENVVARS_AND_CACHE(PYTHON_EXECUTABLE)
UNIFY_ENVVARS_AND_CACHE(HDF5_INCLUDE_DIR)
UNIFY_ENVVARS_AND_CACHE(HDF5_LIBRARY_DIR)
UNIFY_ENVVARS_AND_CACHE(HDF5_LIBRARIES)
endif()
UNIFY_ENVVARS_AND_CACHE(BOOST_ROOT)
UNIFY_ENVVARS_AND_CACHE(BOOST_LIBRARYDIR)
if (NOT MINIMAL_BUILD)
UNIFY_ENVVARS_AND_CACHE(CGAL_INCLUDE_DIR)
UNIFY_ENVVARS_AND_CACHE(CGAL_LIBRARY_DIR)
UNIFY_ENVVARS_AND_CACHE(GMP_INCLUDE_DIR)
UNIFY_ENVVARS_AND_CACHE(GMP_LIBRARY_DIR)
UNIFY_ENVVARS_AND_CACHE(MPFR_INCLUDE_DIR)
UNIFY_ENVVARS_AND_CACHE(MPFR_LIBRARY_DIR)
endif()
if (GLTF_SUPPORT AND BUILD_CONVERT)
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)
IF(json_hpp)
@@ -201,10 +210,12 @@ if(USE_MMAP)
add_definitions(-DUSE_MMAP)
endif()
if (NOT MINIMAL_BUILD)
# libxml2 is required for IFCXML (optional) and SVGFILL (mandatory)
find_package(LibXml2 REQUIRED)
find_package(LibXml2 REQUIRED)
endif()
if (IFCXML_SUPPORT)
if (NOT MINIMAL_BUILD AND IFCXML_SUPPORT)
add_definitions(-DWITH_IFCXML)
set(SWIG_DEFINES ${SWIG_DEFINES} -DWITH_IFCXML)
endif()
@@ -346,7 +357,7 @@ endif()
endif(BUILD_IFCGEOM)
IF(COLLADA_SUPPORT)
IF(NOT MINIMAL_BUILD AND COLLADA_SUPPORT)
# Find OpenCOLLADA
IF("${OPENCOLLADA_INCLUDE_DIR}" STREQUAL "")
MESSAGE(STATUS "No OpenCOLLADA include directory specified")
@@ -426,7 +437,7 @@ IF(COLLADA_SUPPORT)
ENDIF()
ENDIF()
if(HDF5_SUPPORT)
if(NOT MINIMAL_BUILD AND HDF5_SUPPORT)
IF("${HDF5_INCLUDE_DIR}" STREQUAL "")
MESSAGE(STATUS "No HDF5 include directory specified")
ElSE()
@@ -441,30 +452,60 @@ if(HDF5_SUPPORT)
if (HDF5_LIBRARY_DIR)
# result of the HDF5 ctest package
if (WIN32)
set(zlib_post lib)
set(lib_ext lib)
else()
set(lib_ext a)
# Find zlib using cmake find_library. How should this be implemented?
# FIND_LIBRARY(NAMES z libz libz_debug PATHS ... NO_DEFAULT_PATH)
if ("$ENV{CONDA_BUILD}" STREQUAL "")
# result of the HDF5 ctest package
if (WIN32)
set(zlib_post lib)
set(lib_ext lib)
else()
set(lib_ext a)
endif()
if ("${CMAKE_BUILD_TYPE}" STREQUAL "Debug")
set(debug_postfix "_debug")
endif()
SET(HDF5_LIBRARIES
"${HDF5_LIBRARY_DIR}/libhdf5_cpp${debug_postfix}.${lib_ext}"
"${HDF5_LIBRARY_DIR}/libhdf5${debug_postfix}.${lib_ext}"
"${HDF5_LIBRARY_DIR}/libz${zlib_post}${debug_postfix}.${lib_ext}"
"${HDF5_LIBRARY_DIR}/libsz${debug_postfix}.${lib_ext}"
"${HDF5_LIBRARY_DIR}/libaec${debug_postfix}.${lib_ext}"
)
ELSE()
MESSAGE(STATUS "Packaging hdf5 and zlib for conda distribution")
if (WIN32)
# Windows
set(zlib_post zlib)
set(lib_ext lib)
elseif(${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
# macOS
set(zlib_post libz)
set(lib_ext dylib)
else()
# linux
set(zlib_post libz)
set(lib_ext so)
endif()
SET(HDF5_LIBRARIES
"${HDF5_LIBRARY_DIR}/libhdf5_cpp.${lib_ext}"
"${HDF5_LIBRARY_DIR}/libhdf5.${lib_ext}"
"${HDF5_LIBRARY_DIR}/${zlib_post}.${lib_ext}"
)
endif()
if ("${CMAKE_BUILD_TYPE}" STREQUAL "Debug")
set(debug_postfix "_debug")
endif()
SET(HDF5_LIBRARIES
"${HDF5_LIBRARY_DIR}/libhdf5_cpp${debug_postfix}.${lib_ext}"
"${HDF5_LIBRARY_DIR}/libhdf5${debug_postfix}.${lib_ext}"
"${HDF5_LIBRARY_DIR}/libz${zlib_post}${debug_postfix}.${lib_ext}"
"${HDF5_LIBRARY_DIR}/libsz${debug_postfix}.${lib_ext}"
"${HDF5_LIBRARY_DIR}/libaec${debug_postfix}.${lib_ext}"
)
endif()
if (NOT HDF5_LIBRARIES)
# debian default
SET(HDF5_LIBRARIES
/usr/lib/x86_64-linux-gnu/hdf5/serial/libhdf5_cpp.so
/usr/lib/x86_64-linux-gnu/hdf5/serial/libhdf5.so
@@ -579,13 +620,16 @@ 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")
set(SCHEMA_VERSIONS "2x3" "4" "4x1" "4x2" "4x3_rc1" "4x3_rc2" "4x3_rc3" "4x3_rc4" "4x3")
endif()
foreach(s ${SCHEMA_VERSIONS})
add_definitions(-DHAS_SCHEMA_${s})
endforeach()
string(REPLACE ";" ")(" schema_version_seq "(${SCHEMA_VERSIONS})")
ADD_DEFINITIONS(-DSCHEMA_SEQ=${schema_version_seq})
if(COMPILE_SCHEMA)
# @todo, this appears to be untested at the moment
@@ -655,7 +699,7 @@ if (BUILD_IFCGEOM)
endforeach()
set(IFCOPENSHELL_LIBRARIES ${IFCOPENSHELL_LIBRARIES} IfcGeom ${IFCGEOM_SCHEMA_LIBRARIES} IfcGeom ${IFCGEOM_SCHEMA_LIBRARIES})
endif()
if (BUILD_CONVERT)
if (BUILD_CONVERT OR BUILD_IFCPYTHON)
foreach(s ${SCHEMA_VERSIONS})
set(SERIALIZER_SCHEMA_LIBRARIES ${SERIALIZER_SCHEMA_LIBRARIES} Serializers_ifc${s})
endforeach()
@@ -728,7 +772,7 @@ TARGET_LINK_LIBRARIES(IfcGeom IfcParse ${IFCGEOM_SCHEMA_LIBRARIES} ${CMAKE_THREA
endif(BUILD_IFCGEOM)
if (BUILD_CONVERT)
if (BUILD_CONVERT OR BUILD_IFCPYTHON)
# Serializers
file(GLOB SERIALIZERS_H_FILES ../src/serializers/*.h)
@@ -749,6 +793,10 @@ set_target_properties(Serializers PROPERTIES COMPILE_FLAGS "-DIFC_GEOM_EXPORTS $
TARGET_LINK_LIBRARIES(Serializers ${SERIALIZER_SCHEMA_LIBRARIES} ${OPENCOLLADA_LIBRARIES})
endif(BUILD_CONVERT or BUILD_IFCPYTHON)
if (BUILD_CONVERT)
# IfcConvert
file(GLOB IFCCONVERT_CPP_FILES ../src/ifcconvert/*.cpp)
file(GLOB IFCCONVERT_H_FILES ../src/ifcconvert/*.h)
@@ -774,7 +822,7 @@ INSTALL(TARGETS IfcConvert
endif(BUILD_CONVERT)
# IfcGeomServer
if(BUILD_GEOMSERVER)
if(NOT MINIMAL_BUILD AND BUILD_GEOMSERVER)
file(GLOB CPP_FILES ../src/ifcgeomserver/*.cpp)
file(GLOB H_FILES ../src/ifcgeomserver/*.h)
@@ -805,7 +853,8 @@ if (GIT_FOUND)
)
string(REPLACE "\n" ";" git_branch_list "${git_branches}")
foreach(git_branch_candidate IN ITEMS ${git_branch_list})
string(STRIP "${git_branch_candidate}" git_branch_candidate_2)
string(REPLACE "*" "" git_branch_candidate_temp "${git_branch_candidate}")
string(STRIP "${git_branch_candidate_temp}" git_branch_candidate_2)
if (NOT git_branch_candidate_2 MATCHES "^HEAD$")
set(git_branch ${git_branch_candidate_2})
endif()
@@ -828,7 +877,7 @@ IF(BUILD_DOCUMENTATION)
ADD_SUBDIRECTORY(../docs docs)
ENDIF()
IF(BUILD_IFCPYTHON)
IF(NOT MINIMAL_BUILD AND BUILD_IFCPYTHON)
ADD_SUBDIRECTORY(../src/ifcwrap ifcwrap)
ENDIF()
@@ -836,11 +885,12 @@ IF(BUILD_EXAMPLES)
ADD_SUBDIRECTORY(../src/examples examples)
ENDIF()
IF(BUILD_IFCMAX)
IF(NOT MINIMAL_BUILD AND BUILD_IFCMAX)
ADD_SUBDIRECTORY(../src/ifcmax ifcmax)
ENDIF()
if (NOT MINIMAL_BUILD)
ADD_SUBDIRECTORY(../src/svgfill svgfill)
endif()
# CMake installation targets
INSTALL(FILES ${IFCPARSE_H_FILES}
@@ -912,12 +962,12 @@ set(CPACK_SOURCE_GENERATOR "TGZ")
FOREACH(COMPONENT IN ITEMS ${BOOST_COMPONENTS})
string(REPLACE "_" "-" COMP ${COMPONENT})
set(BOOST_DEPS "${BOOST_DEPS}, libboost-${COMP}1.67.0")
set(BOOST_DEPS "${BOOST_DEPS}, libboost-${COMP}-dev")
endforeach(COMPONENT)
set(CPACK_DEBIAN_PACKAGE_NAME "${PROJECT_NAME}")
set(CPACK_DEBIAN_PACKAGE_MAINTAINER "${CPACK_PACKAGE_CONTACT}")
set(CPACK_DEBIAN_PACKAGE_DEPENDS "python3, libxml2, liboce-foundation11, liboce-modeling11, liboce-ocaf11, liboce-visualization11, liboce-ocaf-lite11, libpython3.8 ${BOOST_DEPS}")
set(CPACK_DEBIAN_PACKAGE_DEPENDS "python3, libxml2, libocct-foundation-dev, libocct-modeling-algorithms-dev, libocct-modeling-data-dev, libocct-ocaf-dev, libocct-visualization-dev, libocct-data-exchange-dev, libhdf5-serial-dev, libpython3.8 ${BOOST_DEPS}")
set(CPACK_DEBIAN_PACKAGE_DESCRIPTION_SUMMARY "${CPACK_PACKAGE_DESCRIPTION_SUMMARY}")
set(CPACK_DEBIAN_PACKAGE_DESCRIPTION "${CPACK_PACKAGE_DESCRIPTION}")
set(CPACK_DEBIAN_PACKAGE_PRIORITY "optional")
+8
View File
@@ -0,0 +1,8 @@
compile:
conda activate conda-build && conda-build -c conda-forge . --keep-old-work --python 3.10.5
debug:
conda activate conda-build && conda-debug -c conda-forge . --python 3.10.4
env:
conda env update --f environment.yml --prune
+38 -23
View File
@@ -1,29 +1,44 @@
mkdir build && cd build
REM Remove dot from PY_VER for use in library name
REM From https://github.com/tpaviot/pythonocc-core/blob/master/ci/conda/bld.bat
set MY_PY_VER=%PY_VER:.=%
set LIBXML2="%LIBRARY_PREFIX%/lib/libxml2.lib"
cmake -G "NMake Makefiles" ^
-DCMAKE_INSTALL_PREFIX="%LIBRARY_PREFIX%" ^
-DCMAKE_BUILD_TYPE=Release ^
-DCMAKE_PREFIX_PATH="%LIBRARY_PREFIX%" ^
-DCMAKE_SYSTEM_PREFIX_PATH="%LIBRARY_PREFIX%" ^
-DPYTHON_EXECUTABLE="%PYTHON%" ^
-DPYTHON_INCLUDE_DIR="%PREFIX%"/include ^
-DPYTHON_LIBRARY="%PREFIX%"/libs/python%MY_PY_VER%.lib ^
-DBOOST_LIBRARYDIR="%LIBRARY_PREFIX%\lib" ^
-DBOOST_INCLUDEDIR="%LIBRARY_PREFIX%\include" ^
-DOCC_INCLUDE_DIR="%LIBRARY_PREFIX%\include\oce" ^
-DOCC_LIBRARY_DIR="%LIBRARY_PREFIX%\lib" ^
-DCOLLADA_SUPPORT=Off ^
-DBUILD_EXAMPLES=Off ^
-DBUILD_GEOMSERVER=Off ^
-DBUILD_CONVERT=Off ^
../cmake
if errorlevel 1 exit 1
cmake --build . --target INSTALL --config Release
cmake -G "Ninja" ^
-D CMAKE_BUILD_TYPE:STRING=Release ^
-D CMAKE_INSTALL_PREFIX:FILEPATH="%LIBRARY_PREFIX%" ^
-D CMAKE_PREFIX_PATH:FILEPATH="%LIBRARY_PREFIX%" ^
-D CMAKE_SYSTEM_PREFIX_PATH:FILEPATH="%LIBRARY_PREFIX%" ^
-D OCC_INCLUDE_DIR:FILEPATH="%LIBRARY_PREFIX%\include\opencascade" ^
-D OCC_LIBRARY_DIR:FILEPATH="%LIBRARY_PREFIX%\lib" ^
-D CGAL_INCLUDE_DIR:FILEPATH="%LIBRARY_PREFIX%\include" ^
-D GMP_INCLUDE_DIR:FILEPATH="%LIBRARY_PREFIX%\include" ^
-D GMP_LIBRARY_DIR:FILEPATH="%LIBRARY_PREFIX%\lib" ^
-D MPFR_LIBRARY_DIR:FILEPATH="%LIBRARY_PREFIX%\lib" ^
-D COLLADA_SUPPORT=OFF ^
-D HDF5_SUPPORT=ON ^
-D HDF5_INCLUDE_DIR="%LIBRARY_PREFIX%\include" ^
-D HDF5_LIBRARY_DIR="%LIBRARY_PREFIX%\lib" ^
-D JSON_INCLUDE_DIR="%LIBRARY_PREFIX%\include" ^
-D PYTHON_INCLUDE_DIR=%PREFIX%\include ^
-D PYTHON_EXECUTABLE:FILEPATH=%PREFIX%\python.exe ^
-D PYTHON_LIBRARY:FILEPATH="%PREFIX%"\libs/python%MY_PY_VER%.lib ^
-D BUILD_IFCPYTHON=ON ^
-D BUILD_IFCGEOM=ON ^
-D COLLADA_SUPPORT:BOOL=OFF ^
-D BUILD_EXAMPLES:BOOL=OFF ^
-D BUILD_GEOMSERVER:BOOL=OFF ^
-D GLTF_SUPPORT:BOOL=ON ^
-D BUILD_CONVERT:BOOL=ON ^
-D BUILD_IFCMAX:BOOL=OFF ^
-D IFCXML_SUPPORT:BOOL=ON ^
-D Boost_LIBRARYDIR:FILEPATH="%LIBRARY_PREFIX%\lib" ^
-D Boost_INCLUDEDIR:FILEPATH="%LIBRARY_PREFIX%\include" ^
-D Boost_USE_STATIC_LIBS:BOOL=OFF ^
%SRC_DIR%/cmake
if errorlevel 1 exit 1
:: Build and install
cmake --build . -- install
if errorlevel 1 exit 1
+34 -24
View File
@@ -1,34 +1,44 @@
# From https://github.com/tpaviot/pythonocc-core/blob/master/ci/conda/build.sh
if [ "$PY3K" == "1" ]; then
MY_PY_VER="${PY_VER}m"
else
MY_PY_VER="${PY_VER}"
#!/bin/bash
declare -a CMAKE_PLATFORM_FLAGS
if [[ ${HOST} =~ .*linux.* ]]; then
CMAKE_PLATFORM_FLAGS+=(-DCMAKE_TOOLCHAIN_FILE=$RECIPE_DIR/cross-linux.cmake)
fi
if [ `uname` == Darwin ]; then
PY_LIB="libpython${MY_PY_VER}.dylib"
export CFLAGS="$CFLAGS -Wl,-flat_namespace,-undefined,suppress"
export CXXFLAGS="$CXXFLAGS -Wl,-flat_namespace,-undefined,suppress"
export LDFLAGS="$LDFLAGS -Wl,-flat_namespace,-undefined,suppress"
else
PY_LIB="libpython${MY_PY_VER}.so"
export CFLAGS="$CFLAGS -Wl,-flat_namespace,-undefined,suppress"
export CXXFLAGS="$CXXFLAGS -Wl,-flat_namespace,-undefined,suppress"
export LDFLAGS="$LDFLAGS -Wl,-flat_namespace,-undefined,suppress"
fi
mkdir build && cd build
cmake \
-DCMAKE_INSTALL_PREFIX=$PREFIX \
cmake -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX=$PREFIX \
${CMAKE_PLATFORM_FLAGS[@]} \
-DCMAKE_PREFIX_PATH=$PREFIX \
-DCMAKE_SYSTEM_PREFIX_PATH=$PREFIX \
-DOCC_INCLUDE_DIR=$PREFIX/include/oce \
-DOCC_LIBRARY_DIR=$PREFIX/lib \
-DPYTHON_EXECUTABLE:FILEPATH=$PYTHON \
-DPYTHON_INCLUDE_DIR:PATH=$PREFIX/include/python$MY_PY_VER \
-DPYTHON_LIBRARY:FILEPATH=$PREFIX/lib/${PY_LIB} \
-DCOLLADA_SUPPORT=Off \
../cmake
-DGMP_LIBRARY_DIR=$PREFIX/lib \
-DMPFR_LIBRARY_DIR=$PREFIX/lib \
-DOCC_INCLUDE_DIR=$PREFIX/include/opencascade \
-DOCC_LIBRARY_DIR=$PREFIX/lib \
-DHDF5_SUPPORT:BOOL=ON \
-DHDF5_INCLUDE_DIR=$PREFIX/include \
-DHDF5_LIBRARY_DIR=$PREFIX/lib \
-DJSON_INCLUDE_DIR=$PREFIX/include \
-DCGAL_INCLUDE_DIR=$PREFIX/include \
-DCOLLADA_SUPPORT=0 \
-DBUILD_EXAMPLES:BOOL=OFF \
-DIFCXML_SUPPORT:BOOL=ON \
-DGLTF_SUPPORT:BOOL=ON \
-DBUILD_CONVERT:BOOL=ON \
-DBUILD_IFCPYTHON:BOOL=ON \
-DBUILD_IFCGEOM:BOOL=ON \
-DBUILD_GEOMSERVER:BOOL=OFF \
-DBOOST_USE_STATIC_LIBS:BOOL=OFF \
./cmake
make -j$CPU_COUNT _ifcopenshell_wrapper
cd ifcwrap
make install/local
ninja
ninja install
+2
View File
@@ -0,0 +1,2 @@
CONDA_BUILD_SYSROOT:
- /opt/MacOSX10.13.sdk # [osx]
+16
View File
@@ -0,0 +1,16 @@
# this one is important
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_PLATFORM Linux)
# specify the cross compiler
set(CMAKE_C_COMPILER $ENV{CC})
set(CMAKE_CXX_COMPILER $ENV{CXX})
# where is the target environment
set(CMAKE_FIND_ROOT_PATH $ENV{PREFIX} $ENV{BUILD_PREFIX}/$ENV{HOST}/sysroot)
# search for programs in the build host directories
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
# for libraries and headers in the target directories
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
+14
View File
@@ -0,0 +1,14 @@
name: conda-build
channels:
- conda-forge
dependencies:
- conda-build
- conda-verify
- anaconda-client
- ninja
- doxygen
- ripgrep
- pip
- pip:
- --extra-index-url https://lief.s3-website.fr-par.scw.cloud/latest
- lief==0.13.0.dev0
+50 -30
View File
@@ -1,42 +1,62 @@
{% set name = "ifcopenshell" %}
{% set version = "0.7.0" %}
{% set occt_version = "7.6.2" %}
{% set cgal_cpp_version = "5.3" %}
{% set hdf5_version = "1.12.1" %}
package:
name: ifcopenshell
version: "0.6.0a1"
name: {{ name }}
version: {{ version }}
source:
git_rev: "v0.6.0a1"
git_url: https://github.com/IfcOpenShell/IfcOpenShell
path: ..
build:
number: 0
features:
- vc9 # [win and py27]
- vc10 # [win and py34]
- vc14 # [win and py35]
- vc14 # [win and py36]
binary_relocation: false [osx]
requirements:
build:
- gcc # [osx]
- make
- python
- oce ==0.18.3
- {{ compiler('c') }}
- {{ compiler('cxx') }}
- ninja >=1.10.2
- cmake
- swig >=3.0.9
- libboost
- icu
- xmlschema
- lxml
- numpy
run:
- libgcc # [osx]
- swig >=4.0.2
host:
- python
- oce ==0.18.3
- libboost
- icu
- xmlschema
- lxml
- numpy
- boost-cpp
- occt
- libxml2
- cgal-cpp
- hdf5
- mpfr
- gmp # [unix]
- mpir # [win]
- nlohmann_json
run:
- python
- boost-cpp
- occt
- libxml2
- cgal-cpp
- hdf5
- mpfr
- gmp # [unix]
- mpir # [win]
- nlohmann_json
test:
imports:
- ifcopenshell
about:
home: http://ifcopenshell.org
license: LGPL
license: LGPL-3.0-or-later
license_file: COPYING
summary: 'IfcOpenShell is a library to support the IFC file format'
description: |
IfcOpenShell is an open source (LGPL) software library for
working with the Industry Foundation Classes (IFC) file format.
doc_url: http://ifcopenshell.org/
dev_url: https://github.com/IfcOpenShell/IfcOpenShell
+16 -12
View File
@@ -23,7 +23,7 @@
# This script builds IfcOpenShell and its dependencies #
# #
# Prerequisites for this script to function correctly: #
# * cmake * git * bzip2 * tar * c(++) compilers * yacc * autoconf #
# * cmake * git * bzip2 * tar * c(++) compilers * autoconf #
# #
# if building with USE_OCCT additionally: #
# * freetype * glx.h #
@@ -72,7 +72,7 @@ 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.12", "3.8.12", "3.9.7", "3.10.0"]
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"
@@ -83,8 +83,8 @@ SWIG_VERSION = "4.0.2"
OPENCOLLADA_VERSION = "v1.6.68"
HDF5_VERSION = "1.12.1"
GMP_VERSION = "6.1.2"
MPFR_VERSION = "3.1.5" # latest is 4.1.0
GMP_VERSION = "6.2.1"
MPFR_VERSION = "3.1.6" # latest is 4.1.0
CGAL_VERSION = "5.3"
# binaries
@@ -97,7 +97,6 @@ cc = "cc"
cplusplus = "c++"
autoconf = "autoconf"
automake = "automake"
yacc = "yacc"
make = "make"
date = "date"
curl = "curl"
@@ -220,7 +219,7 @@ print("Building:", *sorted(targets, key=lambda t: len(list(v(t)))))
# Check that required tools are in PATH
for cmd in [git, bunzip2, tar, cc, cplusplus, autoconf, automake, yacc, make, "patch", "cmake"]:
for cmd in [git, bunzip2, tar, cc, cplusplus, autoconf, automake, make, "patch", "cmake"]:
if which(cmd) is None:
raise ValueError(f"Required tool '{cmd}' not installed or not added to PATH")
@@ -263,6 +262,11 @@ def run(cmds, cwd=None):
return stdout.strip()
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)]
BOOST_VERSION_UNDERSCORE = BOOST_VERSION.replace(".", "_")
OCE_LOCATION = f"https://github.com/tpaviot/oce/archive/OCE-{OCE_VERSION}.tar.gz"
@@ -508,7 +512,7 @@ if USE_OCCT and "occ" in targets:
"-DBUILD_MODULE_Draw=0",
"-DBUILD_RELEASE_DISABLE_EXCEPTIONS=Off"
],
download_url = "https://git.dev.opencascade.org/repos/occt.git",
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",
@@ -577,7 +581,7 @@ if "python" in targets and not USE_CURRENT_PYTHON_VERSION:
# with the system python because of some threading initialization
PYTHON_CONFIGURE_ARGS = []
if platform.system() == "Darwin":
PYTHON_CONFIGURE_ARGS = ["--disable-static", "--enable-shared"]
PYTHON_CONFIGURE_ARGS = ["--enable-shared"]
for PYTHON_VERSION in PYTHON_VERSIONS:
try:
@@ -630,7 +634,7 @@ if "cgal" in targets:
build_dependency(
name=f"gmp-{GMP_VERSION}",
mode="autoconf",
build_tool_args=["--disable-shared", "--with-pic"],
build_tool_args=[ENABLE_FLAG, DISABLE_FLAG, "--with-pic"],
download_url="https://ftp.gnu.org/gnu/gmp/",
download_name=f"gmp-{GMP_VERSION}.tar.bz2"
)
@@ -638,7 +642,7 @@ if "cgal" in targets:
build_dependency(
name=f"mpfr-{MPFR_VERSION}",
mode="autoconf",
build_tool_args=["--disable-shared", f"--with-gmp={DEPS_DIR}/install/gmp-{GMP_VERSION}"],
build_tool_args=[ENABLE_FLAG, DISABLE_FLAG, 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"
)
@@ -647,9 +651,9 @@ if "cgal" in targets:
name=f"cgal-{CGAL_VERSION}",
mode="cmake",
build_tool_args=[
f"-DGMP_LIBRARIES={DEPS_DIR}/install/gmp-{GMP_VERSION}/lib/libgmp.a",
f"-DGMP_LIBRARIES={DEPS_DIR}/install/gmp-{GMP_VERSION}/lib/libgmp.{LIBRARY_EXT}",
f"-DGMP_INCLUDE_DIR={DEPS_DIR}/install/gmp-{GMP_VERSION}/include",
f"-DMPFR_LIBRARIES={DEPS_DIR}/install/mpfr-{MPFR_VERSION}/lib/libmpfr.a" ,
f"-DMPFR_LIBRARIES={DEPS_DIR}/install/mpfr-{MPFR_VERSION}/lib/libmpfr.{LIBRARY_EXT}" ,
f"-DMPFR_INCLUDE_DIR={DEPS_DIR}/install/mpfr-{MPFR_VERSION}/include",
f"-DBoost_INCLUDE_DIR={DEPS_DIR}/install/boost-{BOOST_VERSION}",
f"-DCMAKE_INSTALL_PREFIX={DEPS_DIR}/install/cgal-{CGAL_VERSION}/",
+5 -5
View File
@@ -223,10 +223,10 @@ class BcfXml:
def edit_topic(self, topic):
if not topic.creation_date:
topic.creation_date = datetime.utcnow().isoformat()
topic.creation_date = datetime.utcnow().replace(microsecond=0).isoformat()
topic.creation_author = self.author
else:
topic.modified_date = datetime.utcnow().isoformat()
topic.modified_date = datetime.utcnow().replace(microsecond=0).isoformat()
topic.modified_author = self.author
self.document = minidom.Document()
@@ -345,10 +345,10 @@ class BcfXml:
def edit_comment(self, comment, topic):
if not comment.date:
comment.date = datetime.utcnow().isoformat()
comment.date = datetime.utcnow().replace(microsecond=0).isoformat()
comment.author = self.author
else:
comment.modified_date = datetime.utcnow().isoformat()
comment.modified_date = datetime.utcnow().replace(microsecond=0).isoformat()
comment.modified_author = self.author
self.edit_topic(topic)
@@ -595,7 +595,7 @@ class BcfXml:
copyfile(header_file.reference, os.path.join(topic_filepath, header_file.filename))
header_file.reference = header_file.filename
header_file.is_external = False
header_file.date = datetime.utcnow().isoformat()
header_file.date = datetime.utcnow().replace(microsecond=0).isoformat()
if not topic.header:
topic.header = bcf.v2.data.Header()
topic.header.files.append(header_file)
+1 -1
View File
@@ -646,7 +646,7 @@ class BcfXml:
def get_comments(self, guid):
comments = {}
data = self._read_xml(os.path.join(guid, "markup.bcf"), "markup.xsd")
if "Comments" not in data["Topics"]:
if "Comments" not in data["Topic"]:
return comments
for item in data["Topic"]["Comments"].get("Comment", []):
+131 -17
View File
@@ -16,6 +16,24 @@
# 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/>.
PYTHON:=python
PATCH:=patch
SED:=sed -i
ifeq ($(OS),Windows_NT)
HOSTOS:=win
else
UNAME_S:=$(shell uname -s)
ifeq ($(UNAME_S),Linux)
HOSTOS:=linux
endif
ifeq ($(UNAME_S),Darwin)
HOSTOS:=macos
PYTHON:=python3
PATCH:=patch -d
SED:=sed -i '' -e
endif
endif
VERSION:=`date '+%y%m%d'`
PYVERSION:=py37
@@ -38,18 +56,24 @@ 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
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)
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.7.5/download/linux-64/hpp-fcl-1.7.5-py39hbcdfc36_0.tar.bz2
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
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)
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.8.0/download/linux-64/hpp-fcl-1.8.0-py310hdaf7e41_1.tar.bz2
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
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
OCTOMAP_URL:=https://anaconda.org/conda-forge/octomap/1.9.7/download/linux-64/octomap-1.9.7-h4bd325d_0.tar.bz2
@@ -62,42 +86,85 @@ 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
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)
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.7.5/download/osx-64/hpp-fcl-1.7.5-py39h1e32b98_0.tar.bz2
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
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)
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.8.0/download/osx-64/hpp-fcl-1.8.0-py310h651ac30_1.tar.bz2
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
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
OCTOMAP_URL:=https://anaconda.org/conda-forge/octomap/1.9.7/download/osx-64/octomap-1.9.7-h940c156_0.tar.bz2
ZLIB_URL:=https://anaconda.org/conda-forge/zlib/1.2.11/download/osx-64/zlib-1.2.11-h7795811_1010.tar.bz2
endif
ifeq ($(PLATFORM), macosm1)
ifeq ($(PYVERSION), py37)
# Warning: py37 not supported on Apple M1
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.7.5/download/osx-64/hpp-fcl-1.7.5-py37h2d7f23a_0.tar.bz2
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
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)
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.7.5/download/osx-arm64/hpp-fcl-1.7.5-py39ha69f3c1_2.tar.bz2
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
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)
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.8.0/download/osx-arm64/hpp-fcl-1.8.0-py310h5699539_2.tar.bz2
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
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
OCTOMAP_URL:=https://anaconda.org/conda-forge/octomap/1.9.7/download/osx-arm64/octomap-1.9.7-hc021e02_0.tar.bz2
ZLIB_URL:=https://anaconda.org/conda-forge/zlib/1.2.11/download/osx-arm64/zlib-1.2.11-h90dfc92_1014.tar.bz2
endif
ifeq ($(PLATFORM), win)
ifeq ($(PYVERSION), py37)
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.7.5/download/win-64/hpp-fcl-1.7.5-py37h839d6b1_0.tar.bz2
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
PILLOW_URL:=https://files.pythonhosted.org/packages/69/f5/9e802159d78b2eaf26bf1f8b94648605993f5ca7247ac8870f065063fc40/Pillow-9.2.0-cp37-cp37m-win_amd64.whl
endif
ifeq ($(PYVERSION), py39)
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.7.5/download/win-64/hpp-fcl-1.7.5-py39h2e7c763_0.tar.bz2
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
PILLOW_URL:=https://files.pythonhosted.org/packages/19/3f/b4d4bcf05dbcbe07f2e9613a8f4180c297395e73a91d8ad22c32c6624f8c/Pillow-9.2.0-cp39-cp39-win_amd64.whl
endif
ifeq ($(PYVERSION), py310)
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.8.0/download/win-64/hpp-fcl-1.8.0-py310hc5a3c62_1.tar.bz2
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
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
OCTOMAP_URL:=https://anaconda.org/conda-forge/octomap/1.9.7/download/win-64/octomap-1.9.7-h5362a0b_0.tar.bz2
@@ -115,7 +182,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-2985bba-$(PLATFORM)64.zip
cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-$(PYNUMBER)-v0.7.0-cdde536-$(PLATFORM)64.zip
cd dist/working && unzip ifcopenshell-python*
cp -r dist/working/ifcopenshell dist/blenderbim/libs/site/packages/
@@ -138,11 +205,13 @@ endif
# Provides IFCClash functionality
cp -r dist/working/IfcOpenShell-0.7.0/src/ifcclash/ifcclash dist/blenderbim/libs/site/packages/
# Provides BIMTester functionality
cd dist/working && python -m venv env
cd dist/working && $(PYTHON) -m venv env
cd dist/working && . env/bin/activate && pip install pybabel
cd dist/working && . env/bin/activate && pip install babel
cd dist/working && . env/bin/activate && ./env/bin/pybabel compile -d ./IfcOpenShell-0.7.0/src/ifcbimtester/bimtester/locale/
cp -r dist/working/IfcOpenShell-0.7.0/src/ifcbimtester/bimtester dist/blenderbim/libs/site/packages/
# Provides IFCTester functionality
cp -r dist/working/IfcOpenShell-0.7.0/src/ifctester/ifctester dist/blenderbim/libs/site/packages/
# Provides IFCCOBie functionality
cp -r dist/working/IfcOpenShell-0.7.0/src/ifccobie/* dist/blenderbim/libs/site/packages/
# Provides IFCDiff functionality
@@ -161,9 +230,9 @@ endif
# Provides Mustache templating in construction documentation
mkdir dist/working
cd dist/working && wget https://files.pythonhosted.org/packages/d6/fd/eb8c212053addd941cc90baac307c00ac246ac3fce7166b86434c6eae963/pystache-0.5.4.tar.gz
cd dist/working && wget https://files.pythonhosted.org/packages/3f/e7/8750ba6c6101d6aa5ceeb20c013adf2c6f3554a12c71d75654b468404bfa/pystache-0.6.0.tar.gz
cd dist/working && tar -xzvf pystache*
cd dist/working/pystache-0.5.4/ && python setup.py build && cp -r build/lib/pystache ../../blenderbim/libs/site/packages/
cd dist/working/pystache-0.6.0/ && $(PYTHON) setup.py build && cp -r build/lib/pystache ../../blenderbim/libs/site/packages/
rm -rf dist/working
# Provides SVG export in construction documentation
@@ -210,9 +279,9 @@ endif
# Required by IFCDiff
mkdir dist/working
cd dist/working && wget https://github.com/Moult/deepdiff/archive/master.zip
cd dist/working && unzip master.zip
cp -r dist/working/deepdiff-master/deepdiff dist/blenderbim/libs/site/packages/
cd dist/working && wget https://files.pythonhosted.org/packages/0f/ca/caead2949fbb824c7142e3774fa841aa853bb4d4331b440da8c8514dfc6f/deepdiff-5.8.1.tar.gz
cd dist/working && tar -xzvf deepdiff*
cp -r dist/working/deepdiff-5.8.1/deepdiff dist/blenderbim/libs/site/packages/
rm -rf dist/working
# Required by deepdiff
@@ -238,16 +307,16 @@ endif
# Required by bcf
mkdir dist/working
cd dist/working && wget https://files.pythonhosted.org/packages/bb/41/ad9ce53bb978b68af8ae415293cafc89b165b8ad55a593725299dca76729/xmlschema-1.1.1.tar.gz
cd dist/working && wget https://files.pythonhosted.org/packages/5b/ef/f97c3e1a7efa00e989a793fe15297214fc95ad7d9e3810586bd08ce9f0f3/xmlschema-2.0.2.tar.gz
cd dist/working && tar -xzvf xmlschema*
cp -r dist/working/xmlschema-1.1.1/xmlschema dist/blenderbim/libs/site/packages/
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/12/f9/f9960222d5274944b01391749e55e4dcdf28d8f0c108b64ac931ceff6fdb/elementpath-1.4.3.tar.gz
cd dist/working && wget https://files.pythonhosted.org/packages/11/bc/5afb61dd5d863e5cf77cd952445c50c17e65953405986f19e97e4389692a/elementpath-3.0.2.tar.gz
cd dist/working && tar -xzvf elementpath*
cp -r dist/working/elementpath-1.4.3/elementpath dist/blenderbim/libs/site/packages/
cp -r dist/working/elementpath-3.0.2/elementpath dist/blenderbim/libs/site/packages/
rm -rf dist/working
# Required by bcf
@@ -271,6 +340,29 @@ endif
cp -r dist/working/PyP6Xer-1.13.0/xerparser dist/blenderbim/libs/site/packages/
rm -rf dist/working
# Required by QTOCalculator
mkdir dist/working
cd dist/working && wget $(SHAPELY_URL)
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/
endif
ifeq ($(PLATFORM), linux)
cp -r dist/working/Shapely.libs dist/blenderbim/libs/site/packages/
endif
rm -rf dist/working
# Required by the BIM tool thumbnail generator
mkdir dist/working
cd dist/working && wget $(PILLOW_URL)
cd dist/working && cp *.whl pillow.zip && unzip pillow.zip
cp -r dist/working/PIL dist/blenderbim/libs/site/packages/
ifeq ($(PLATFORM), linux)
cp -r dist/working/Pillow.libs dist/blenderbim/libs/site/packages/
endif
rm -rf dist/working
# Required by xerparser and IFC4D
# TODO: remove this dependency. It's only used to show a progress bar.
mkdir dist/working
@@ -281,7 +373,7 @@ endif
# Required by IFCCityJSON
mkdir dist/working
cd dist/working && python -m venv env
cd dist/working && $(PYTHON) -m venv env
cd dist/working && mkdir site-packages
cd dist/working && . env/bin/activate && pip install cjio --target=./site-packages
cd dist/working/site-packages/ && rm -r *dist-info*
@@ -389,16 +481,30 @@ endif
# See bug #1294
cd dist/working/ && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.7.0/src/ifcbimtester/patch/model_core.patch
cd dist/working/ && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.7.0/src/ifcbimtester/patch/runner_util.patch
cd dist/working/ && patch ../blenderbim/libs/site/packages/behave/model_core.py < model_core.patch
cd dist/working/ && patch ../blenderbim/libs/site/packages/behave/runner_util.py < runner_util.patch
cd dist/working/ && $(PATCH) ../blenderbim/libs/site/packages/behave/model_core.py < model_core.patch
cd dist/working/ && $(PATCH) ../blenderbim/libs/site/packages/behave/runner_util.py < runner_util.patch
# See bug #2159
cd dist/working/ && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.7.0/src/ifcbimtester/patch/junit.patch
cd dist/working/ && $(PATCH) ../blenderbim/libs/site/packages/behave/reporter/junit.py < junit.patch
rm -rf dist/working
# Required by ids - uh, not any more, but I'm trying it out in the drawing module
ifeq ($(PLATFORM), macosm1)
# No wheels available for MacOS M1, but turns out this Anaconda build works
mkdir dist/working
cd dist/working && wget $(LXML_URL)
cd dist/working && tar -xf lxml*
cp -r dist/working/lib/$(PYLIBDIR)/site-packages/lxml dist/blenderbim/libs/site/packages/
rm -rf dist/working
else
# Wheels are preferred for other OSes. A first attempt at using Anaconda
# builds for everything shows it breaks on windows. See #2422.
mkdir dist/working
cd dist/working && wget $(LXML_URL)
cd dist/working && cp *.whl lxml.zip && unzip lxml.zip
cp -r dist/working/lxml dist/blenderbim/libs/site/packages/
rm -rf dist/working
endif
# Required by behave
mkdir dist/working
@@ -470,14 +576,14 @@ endif
cp -r dist/working/IFC2JSON_python-master/file_converters/ifcjson dist/blenderbim/libs/site/packages/
rm -rf dist/working
# Provides OpenLCA fucntionality
# Provides OpenLCA functionality
mkdir dist/working
cd dist/working && wget https://files.pythonhosted.org/packages/85/33/f91b96e9e8608ff65a003b692e8a9cdd60f2178f60617e5b1d21334c009c/olca-ipc-0.0.10.tar.gz
cd dist/working && tar -xzvf olca-ipc*
cd dist/working/olca-ipc-0.0.10/ && cp -r olca ../../blenderbim/libs/site/packages/
rm -rf dist/working
# Provides Brickschema fucntionality
# Provides Brickschema functionality
mkdir dist/working
cd dist/working && wget https://files.pythonhosted.org/packages/ca/37/8309da9a72407d2f5eb5489c197ac3cfe96ee3ccba1902d2a0ecc7843f92/brickschema-0.5.1.tar.gz
cd dist/working && tar -xzvf brickschema*
@@ -518,7 +624,7 @@ endif
cd dist/working/ && cp -r pkg_resources ../blenderbim/libs/site/packages/
rm -rf dist/working
cd dist/blenderbim && sed -i "s/999999/$(VERSION)/" __init__.py
cd dist/blenderbim && $(SED) "s/999999/$(VERSION)/" __init__.py
cd dist && zip -r blenderbim-$(VERSION)-$(PYVERSION)-$(PLATFORM).zip ./*
rm -rf dist/blenderbim
@@ -534,11 +640,19 @@ test-core:
.PHONY: test-bim
test-bim:
ifndef MODULE
pytest test/bim
else
pytest test/bim -m "$(MODULE)" ./ --maxfail=1
endif
.PHONY: test-tool
test-tool:
ifndef MODULE
pytest test/tool
else
pytest test/tool/test_$(MODULE).py
endif
.PHONY: qa
qa:
+12 -1
View File
@@ -27,6 +27,7 @@ modules = {
"root": None,
"unit": None,
"model": None,
"cad": None,
"georeference": None,
"context": None,
"drawing": None,
@@ -61,6 +62,7 @@ modules = {
"clash": None,
"lca": None,
"csv": None,
"tester": None,
"bimtester": None,
"diff": None,
"patch": None,
@@ -91,7 +93,11 @@ classes = [
operator.SelectIfcFile,
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,
prop.Attribute,
prop.ModuleVisibility,
@@ -130,7 +136,8 @@ for mod in modules.values():
def on_register(scene):
handler.setDefaultProperties(scene)
bpy.app.handlers.depsgraph_update_post.remove(on_register)
if not bpy.app.background:
bpy.app.handlers.depsgraph_update_post.remove(on_register)
def register():
@@ -153,6 +160,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()
@@ -173,6 +182,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 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','2021-11-08T10:06:41+11:00',(),(),'IfcOpenShell v0.7.0-ab506098','IfcOpenShell v0.7.0-ab506098','Nobody');
FILE_NAME('/dev/null','2022-10-14T18:00:26+11:00',(),(),'IfcOpenShell v0.7.0-dc67287d','IfcOpenShell v0.7.0-dc67287d','Nobody');
FILE_SCHEMA(('IFC4'));
ENDSEC;
DATA;
#1=IFCPROJECT('1oJZZVd$b1PA6ggg_hh4cE',$,'BlenderBIM Demo',$,$,$,$,(#12,#16),#7);
#2=IFCPROJECTLIBRARY('3jWhYPJYL2eOMZ9UZerKqx',$,'BlenderBIM Demo Library',$,$,$,$,$,$);
#3=IFCRELDECLARES('1Wjv8iy5XBshHpEwChjCFa',$,$,$,#1,(#2));
#1=IFCPROJECT('0yOx2IKFL4HAbf$IjzE0Kp',$,'BlenderBIM Demo',$,$,$,$,(#12,#16),#7);
#2=IFCPROJECTLIBRARY('2jUXWJsnH2pxWl9Bl3Ljhd',$,'BlenderBIM Demo Library',$,$,$,$,$,$);
#3=IFCRELDECLARES('0Rn4fWoen7xB9hskGGEfEy',$,$,$,#1,(#2));
#4=IFCSIUNIT(*,.LENGTHUNIT.,$,.METRE.);
#5=IFCSIUNIT(*,.AREAUNIT.,$,.SQUARE_METRE.);
#6=IFCSIUNIT(*,.VOLUMEUNIT.,$,.CUBIC_METRE.);
@@ -24,82 +24,82 @@ DATA;
#17=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Body','Model',*,*,*,*,#12,$,.MODEL_VIEW.,$);
#18=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Annotation','Plan',*,*,*,*,#16,$,.PLAN_VIEW.,$);
#19=IFCMATERIAL('Unknown',$,$);
#20=IFCWALLTYPE('24Js5pn1PCeA_DXGWh67_f',$,'DEMO50',$,$,$,$,$,$,$);
#20=IFCWALLTYPE('0D2QFqmA18fvA6TUteIRXc',$,'WAL50',$,$,$,$,$,$,.NOTDEFINED.);
#21=IFCMATERIALLAYERSET((#23),$,$);
#22=IFCRELASSOCIATESMATERIAL('3gPuTuaVf1AAgeln9$alDb',$,$,$,(#20),#21);
#22=IFCRELASSOCIATESMATERIAL('0WbN7alqv8ix3w4MOdyl8F',$,$,$,(#20),#21);
#23=IFCMATERIALLAYER(#19,0.05,$,$,$,$,$);
#24=IFCRELDECLARES('3AJpFBgAf4OQzrklK7UbEn',$,$,$,#2,(#37,#41,#76,#47,#58,#91,#25,#225,#71,#159,#29,#62,#95,#20,#53,#86,#33,#66,#81));
#25=IFCWALLTYPE('1HCqnc9S51NhU$e0Xceg6o',$,'DEMO100',$,$,$,$,$,$,$);
#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.);
#26=IFCMATERIALLAYERSET((#28),$,$);
#27=IFCRELASSOCIATESMATERIAL('2Ikoi$G2PCXvk61V36wh3N',$,$,$,(#25),#26);
#27=IFCRELASSOCIATESMATERIAL('3BDm6HH553QhwCW7FPmjrR',$,$,$,(#25),#26);
#28=IFCMATERIALLAYER(#19,0.1,$,$,$,$,$);
#29=IFCWALLTYPE('3UPlZcHuTCi8CevFkzQjZ_',$,'DEMO200',$,$,$,$,$,$,$);
#29=IFCWALLTYPE('1KMENjmzj7cf_am9SYEaXW',$,'WAL200',$,$,$,$,$,$,.NOTDEFINED.);
#30=IFCMATERIALLAYERSET((#32),$,$);
#31=IFCRELASSOCIATESMATERIAL('3uAo69i4j4nwMM7VRW3TQn',$,$,$,(#29),#30);
#31=IFCRELASSOCIATESMATERIAL('1$36L06DrDYx1sZ9tBQ_WE',$,$,$,(#29),#30);
#32=IFCMATERIALLAYER(#19,0.2,$,$,$,$,$);
#33=IFCWALLTYPE('1y03E8PfTAJPit3967Y5Uf',$,'DEMO300',$,$,$,$,$,$,$);
#33=IFCWALLTYPE('2y5Y19etz3rhrt8P45fuGA',$,'WAL300',$,$,$,$,$,$,.NOTDEFINED.);
#34=IFCMATERIALLAYERSET((#36),$,$);
#35=IFCRELASSOCIATESMATERIAL('2pvy9lWJL62BoA9QC5KCOG',$,$,$,(#33),#34);
#35=IFCRELASSOCIATESMATERIAL('1WSONmheTD9xhiluqtfktr',$,$,$,(#33),#34);
#36=IFCMATERIALLAYER(#19,0.3,$,$,$,$,$);
#37=IFCCOVERINGTYPE('3ppA8IYrP2nffVvxie6ghZ',$,'DEMO10',$,$,$,$,$,$,$);
#37=IFCCOVERINGTYPE('1pAuiYopL5hxCATiIPV8QG',$,'COV10',$,$,$,$,$,$,.NOTDEFINED.);
#38=IFCMATERIALLAYERSET((#40),$,$);
#39=IFCRELASSOCIATESMATERIAL('2Lg3S9QRH4kPgHpYrtokt6',$,$,$,(#37),#38);
#39=IFCRELASSOCIATESMATERIAL('0ysRf97LT8fw28mDwt2vop',$,$,$,(#37),#38);
#40=IFCMATERIALLAYER(#19,0.01,$,$,$,$,$);
#41=IFCCOVERINGTYPE('3KuCqMbY16DRUygYOrLyYE',$,'DEMO20',$,$,(#45),$,$,$,$);
#41=IFCCOVERINGTYPE('00LbT8PQf3XwImYkj_i_$4',$,'COV20',$,$,(#45),$,$,$,.NOTDEFINED.);
#42=IFCMATERIALLAYERSET((#44),$,$);
#43=IFCRELASSOCIATESMATERIAL('27BA_9uQT6OfxSGgEbsKY4',$,$,$,(#41),#42);
#43=IFCRELASSOCIATESMATERIAL('0Kvr3quG19xw6$528t4NQi',$,$,$,(#41),#42);
#44=IFCMATERIALLAYER(#19,0.02,$,$,$,$,$);
#45=IFCPROPERTYSET('11$WHz$79EMw60BdaUh77w',$,'EPset_Parametric',$,(#46));
#45=IFCPROPERTYSET('1IZqsXecj8NQZcNAIAY48P',$,'EPset_Parametric',$,(#46));
#46=IFCPROPERTYSINGLEVALUE('LayerSetDirection',$,IFCLABEL('AXIS2'),$);
#47=IFCCOVERINGTYPE('2cYyQpe9f1vBW8C4D58QZS',$,'DEMO30',$,$,(#51),$,$,$,$);
#47=IFCCOVERINGTYPE('10YNsLMi9409CbEVFmDkJt',$,'COV30',$,$,(#51),$,$,$,.NOTDEFINED.);
#48=IFCMATERIALLAYERSET((#50),$,$);
#49=IFCRELASSOCIATESMATERIAL('1ir_2IiXX4Ax4VH5lcXGEu',$,$,$,(#47),#48);
#49=IFCRELASSOCIATESMATERIAL('2jZVWXllT9ugYrdBaIBAnt',$,$,$,(#47),#48);
#50=IFCMATERIALLAYER(#19,0.03,$,$,$,$,$);
#51=IFCPROPERTYSET('2IalIx1bj02AXv21tZC0Sz',$,'EPset_Parametric',$,(#52));
#51=IFCPROPERTYSET('2M2_M1eSXFYR8vFE96Ecy1',$,'EPset_Parametric',$,(#52));
#52=IFCPROPERTYSINGLEVALUE('LayerSetDirection',$,IFCLABEL('AXIS3'),$);
#53=IFCRAMPTYPE('23wMhiydD6yw5XpaFs6XCA',$,'DEMO200',$,$,$,$,$,$,$);
#53=IFCRAMPTYPE('09P6PVRiT44vIjup23Olfq',$,'RAM200',$,$,$,$,$,$,.NOTDEFINED.);
#54=IFCMATERIALLAYERSET((#56),$,$);
#55=IFCRELASSOCIATESMATERIAL('2SLXH7fU1D6OIOdV8Gq6T_',$,$,$,(#53),#54);
#55=IFCRELASSOCIATESMATERIAL('2Ghy1OMS1FLAOmdzrFp4rp',$,$,$,(#53),#54);
#56=IFCMATERIALLAYER(#19,0.2,$,$,$,$,$);
#57=IFCCIRCLEPROFILEDEF(.AREA.,$,$,0.3);
#58=IFCPILETYPE('3J5r90lrL3kv3b8uqOGsY1',$,'DEMO1',$,$,$,$,$,$,$);
#58=IFCPILETYPE('0X0OrCsUnDUPVeAu90N_K$',$,'P1',$,$,$,$,$,$,.NOTDEFINED.);
#59=IFCMATERIALPROFILESET($,$,(#61),$);
#60=IFCRELASSOCIATESMATERIAL('2vYxThgzHDSOHXKehbpw4S',$,$,$,(#58),#59);
#60=IFCRELASSOCIATESMATERIAL('1FiRvXL$nBkuT8B3FfiGT9',$,$,$,(#58),#59);
#61=IFCMATERIALPROFILE($,$,#19,#57,$,$);
#62=IFCSLABTYPE('0OWxf_nd97uvH$Sr4QbAVW',$,'DEMO150',$,$,$,$,$,$,$);
#62=IFCSLABTYPE('1helZ086v1o8JD3Ow1eWAy',$,'FLR150',$,$,$,$,$,$,.NOTDEFINED.);
#63=IFCMATERIALLAYERSET((#65),$,$);
#64=IFCRELASSOCIATESMATERIAL('3WSlLxuwH3Sfsul8DwR0LU',$,$,$,(#62),#63);
#64=IFCRELASSOCIATESMATERIAL('3BbM02xKH6h97dBaMJp$Rr',$,$,$,(#62),#63);
#65=IFCMATERIALLAYER(#19,0.2,$,$,$,$,$);
#66=IFCSLABTYPE('0wVKQLFFb2oAHccogmWWWy',$,'DEMO250',$,$,$,$,$,$,$);
#66=IFCSLABTYPE('1YAagoi$9D_hPiWtssbkcG',$,'FLR250',$,$,$,$,$,$,.NOTDEFINED.);
#67=IFCMATERIALLAYERSET((#69),$,$);
#68=IFCRELASSOCIATESMATERIAL('01R$pwjx14qOlDDfB34EBC',$,$,$,(#66),#67);
#68=IFCRELASSOCIATESMATERIAL('0m1oulHjT1u889pxJG$XG_',$,$,$,(#66),#67);
#69=IFCMATERIALLAYER(#19,0.3,$,$,$,$,$);
#70=IFCRECTANGLEPROFILEDEF(.AREA.,$,$,0.5,0.6);
#71=IFCCOLUMNTYPE('2OGUvw6Tj9deH3dTY4Hk3T',$,'DEMO1',$,$,$,$,$,$,$);
#71=IFCCOLUMNTYPE('1lzPzyyFfFYRgjqMHnKZLx',$,'C1',$,$,$,$,$,$,.NOTDEFINED.);
#72=IFCMATERIALPROFILESET($,$,(#74),$);
#73=IFCRELASSOCIATESMATERIAL('1f717ijc53DgmnoMRFBZ62',$,$,$,(#71),#72);
#73=IFCRELASSOCIATESMATERIAL('26fuP2VpfEnhlUsk0K1i4y',$,$,$,(#71),#72);
#74=IFCMATERIALPROFILE($,$,#19,#70,$,$);
#75=IFCCIRCLEHOLLOWPROFILEDEF(.AREA.,$,$,0.25,0.005);
#76=IFCCOLUMNTYPE('3_jg43QeDAWvPCl1D0Lw_q',$,'DEMO2',$,$,$,$,$,$,$);
#76=IFCCOLUMNTYPE('1qqu_K2Az3yvZSOapZmUrZ',$,'C2',$,$,$,$,$,$,.NOTDEFINED.);
#77=IFCMATERIALPROFILESET($,$,(#79),$);
#78=IFCRELASSOCIATESMATERIAL('3mX_vYbF92vxfOrjZFAuYk',$,$,$,(#76),#77);
#78=IFCRELASSOCIATESMATERIAL('2YQz9JCGXAogjkmN3RZeKI',$,$,$,(#76),#77);
#79=IFCMATERIALPROFILE($,$,#19,#75,$,$);
#80=IFCRECTANGLEHOLLOWPROFILEDEF(.AREA.,$,$,0.075,0.15,0.005,0.005,0.005);
#81=IFCCOLUMNTYPE('2ZJTQa2Db0MAv2N5bjUuXK',$,'DEMO3',$,$,$,$,$,$,$);
#81=IFCCOLUMNTYPE('1rnXRgQ3X6pfk5O9j4HCLm',$,'C3',$,$,$,$,$,$,.NOTDEFINED.);
#82=IFCMATERIALPROFILESET($,$,(#84),$);
#83=IFCRELASSOCIATESMATERIAL('0Yslq2hlz9Qe3PO16oqESz',$,$,$,(#81),#82);
#83=IFCRELASSOCIATESMATERIAL('2CQLZ1qlzA9uN2eRrpg99c',$,$,$,(#81),#82);
#84=IFCMATERIALPROFILE($,$,#19,#80,$,$);
#85=IFCISHAPEPROFILEDEF(.AREA.,'DEMO-I',$,0.1,0.2,0.005,0.01,0.005,$,$);
#86=IFCBEAMTYPE('2bUnl1uHnB$QSMF_fVajUw',$,'DEMO1',$,$,$,$,$,$,$);
#86=IFCBEAMTYPE('1xjrF860f2H9zb04Ui98Rt',$,'B1',$,$,$,$,$,$,.NOTDEFINED.);
#87=IFCMATERIALPROFILESET($,$,(#89),$);
#88=IFCRELASSOCIATESMATERIAL('08d9DxbyHAyRgRPc6W4Qsn',$,$,$,(#86),#87);
#88=IFCRELASSOCIATESMATERIAL('03i9c81690fx2ZTBBXcqkL',$,$,$,(#86),#87);
#89=IFCMATERIALPROFILE($,$,#19,#85,$,$);
#90=IFCCSHAPEPROFILEDEF(.AREA.,'DEMO-C',$,0.2,0.1,0.0015,0.03,0.005);
#91=IFCBEAMTYPE('0f1mNiwDTAiuSgrvotHh5$',$,'DEMO2',$,$,$,$,$,$,$);
#91=IFCBEAMTYPE('3mAc_7AGP06w4CzYOlYVL2',$,'B2',$,$,$,$,$,$,.NOTDEFINED.);
#92=IFCMATERIALPROFILESET($,$,(#94),$);
#93=IFCRELASSOCIATESMATERIAL('2cO0XhlFf8MxIg_eNDWX_X',$,$,$,(#91),#92);
#93=IFCRELASSOCIATESMATERIAL('2xvXgaSvL7KPR25fXz0fSE',$,$,$,(#91),#92);
#94=IFCMATERIALPROFILE($,$,#19,#90,$,$);
#95=IFCWINDOWTYPE('0ihTx2Iw1E2gRAq1N4pWVg',$,'DEMO1',$,$,$,(#136,#158),$,$,$,$,$,$);
#95=IFCWINDOWTYPE('1kxjrhA4P2dwwueZ0_nSuu',$,'WT01',$,$,$,(#136,#158),$,$,.NOTDEFINED.,.NOTDEFINED.,$,$);
#96=IFCINDEXEDPOLYGONALFACE((13,17,18,14));
#97=IFCINDEXEDPOLYGONALFACE((5,6,3,4));
#98=IFCINDEXEDPOLYGONALFACE((7,8,2,1));
@@ -126,14 +126,14 @@ DATA;
#119=IFCPOLYGONALFACESET(#118,$,(#96,#97,#98,#99,#100,#102,#103,#104,#105,#106,#107,#108,#109,#110,#111,#112),$);
#120=IFCPOLYGONALFACESET(#118,$,(#101,#113,#114,#115,#116,#117),$);
#121=IFCSHAPEREPRESENTATION(#17,'Body','Tessellation',(#119,#120));
#122=IFCSURFACESTYLE('Frame',.BOTH.,(#125));
#123=IFCCOLOURRGB($,0.0429765619337559,0.0429765619337559,0.0429765619337559);
#122=IFCSURFACESTYLE('Frame',.BOTH.,(#123));
#123=IFCSURFACESTYLERENDERING(#124,0.,#125,$,$,$,IFCNORMALISEDRATIOMEASURE(0.),IFCSPECULARROUGHNESS(0.4),.NOTDEFINED.);
#124=IFCCOLOURRGB($,0.0429765619337559,0.0429765619337559,0.0429765619337559);
#125=IFCSURFACESTYLERENDERING(#123,0.,#124,$,$,$,$,$,$);
#126=IFCSURFACESTYLE('Glass',.BOTH.,(#129));
#127=IFCCOLOURRGB($,0.800000011920929,1.,1.);
#125=IFCCOLOURRGB($,0.800000011920929,0.800000011920929,0.800000011920929);
#126=IFCSURFACESTYLE('Glass',.BOTH.,(#127));
#127=IFCSURFACESTYLERENDERING(#128,0.,#129,$,$,$,IFCNORMALISEDRATIOMEASURE(0.),IFCSPECULARROUGHNESS(0.5),.NOTDEFINED.);
#128=IFCCOLOURRGB($,0.800000011920929,1.,1.);
#129=IFCSURFACESTYLERENDERING(#127,0.799999997019768,#128,$,$,$,$,$,$);
#129=IFCCOLOURRGB($,0.800000011920929,0.800000011920929,0.800000011920929);
#130=IFCSTYLEDITEM(#119,(#122),'Frame');
#131=IFCSTYLEDITEM(#120,(#126),'Glass');
#132=IFCCARTESIANPOINT((0.,0.,0.));
@@ -163,7 +163,7 @@ DATA;
#156=IFCDIRECTION((0.,0.,1.));
#157=IFCAXIS2PLACEMENT3D(#154,#156,#155);
#158=IFCREPRESENTATIONMAP(#157,#153);
#159=IFCDOORTYPE('0HLh8Cp3f58hay4OhB7SHy',$,'DEMO1',$,$,$,(#204,#224),$,$,$,$,$,$);
#159=IFCDOORTYPE('0s370BWH51r8Xg03pJ5OBE',$,'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));
@@ -194,14 +194,14 @@ DATA;
#187=IFCPOLYGONALFACESET(#186,$,(#161,#162,#163,#164,#165,#166,#169,#170,#171,#172,#173,#174,#175,#176,#179,#180,#182,#183,#184,#185),$);
#188=IFCPOLYGONALFACESET(#186,$,(#160,#167,#168,#177,#178,#181),$);
#189=IFCSHAPEREPRESENTATION(#17,'Body','Tessellation',(#187,#188));
#190=IFCSURFACESTYLE('Frame',.BOTH.,(#193));
#191=IFCCOLOURRGB($,0.0429765619337559,0.0429765619337559,0.0429765619337559);
#190=IFCSURFACESTYLE('Frame',.BOTH.,(#191));
#191=IFCSURFACESTYLERENDERING(#192,0.,#193,$,$,$,IFCNORMALISEDRATIOMEASURE(0.),IFCSPECULARROUGHNESS(0.4),.NOTDEFINED.);
#192=IFCCOLOURRGB($,0.0429765619337559,0.0429765619337559,0.0429765619337559);
#193=IFCSURFACESTYLERENDERING(#191,0.,#192,$,$,$,$,$,$);
#194=IFCSURFACESTYLE('Panel',.BOTH.,(#197));
#195=IFCCOLOURRGB($,0.184475064277649,0.184475019574165,0.184475019574165);
#193=IFCCOLOURRGB($,0.800000011920929,0.800000011920929,0.800000011920929);
#194=IFCSURFACESTYLE('Panel',.BOTH.,(#195));
#195=IFCSURFACESTYLERENDERING(#196,0.,#197,$,$,$,IFCNORMALISEDRATIOMEASURE(0.),IFCSPECULARROUGHNESS(0.5),.NOTDEFINED.);
#196=IFCCOLOURRGB($,0.184475064277649,0.184475019574165,0.184475019574165);
#197=IFCSURFACESTYLERENDERING(#195,0.,#196,$,$,$,$,$,$);
#197=IFCCOLOURRGB($,0.800000011920929,0.800000011920929,0.800000011920929);
#198=IFCSTYLEDITEM(#187,(#190),'Frame');
#199=IFCSTYLEDITEM(#188,(#194),'Panel');
#200=IFCCARTESIANPOINT((0.,0.,0.));
@@ -229,7 +229,7 @@ DATA;
#222=IFCDIRECTION((0.,0.,1.));
#223=IFCAXIS2PLACEMENT3D(#220,#222,#221);
#224=IFCREPRESENTATIONMAP(#223,#219);
#225=IFCFURNITURETYPE('1wgo6Pw4jDJO4F1GA9Qlmx',$,'BUNNY',$,$,$,(#946,#988),$,$,$,$);
#225=IFCFURNITURETYPE('227RrM$yD30RGzlTgbBH3c',$,'BUN01',$,$,$,(#946,#988),$,$,.NOTDEFINED.,.NOTDEFINED.);
#226=IFCINDEXEDPOLYGONALFACE((187,278,44));
#227=IFCINDEXEDPOLYGONALFACE((21,52,60));
#228=IFCINDEXEDPOLYGONALFACE((91,100,31));
File diff suppressed because one or more lines are too long
@@ -5,7 +5,7 @@ FILE_NAME('EPset_Drawing.ifc','2020-01-01T00:00:00',(),(),'EPset_Drawing','EPset
FILE_SCHEMA(('IFC4'));
ENDSEC;
DATA;
#1=IFCPROPERTYSETTEMPLATE('2JhNIvqZrFnAgxfhK0XVQX',$,'EPset_Drawing','',.PSET_OCCURRENCEDRIVEN.,'IfcAnnotation',(#2,#3,#4,#5,#6,#7,#8,#9,#10,#11));
#1=IFCPROPERTYSETTEMPLATE('2JhNIvqZrFnAgxfhK0XVQX',$,'EPset_Drawing','',.PSET_OCCURRENCEDRIVEN.,'IfcAnnotation',(#2,#3,#4,#5,#6,#7,#8,#9,#10,#11,#12,#13,#14,#15));
#2=IFCSIMPLEPROPERTYTEMPLATE('23JavTMk98ZxXhrUEnjAcf',$,'TargetView','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
#3=IFCSIMPLEPROPERTYTEMPLATE('1yVWUt5H9DAOuu0OaMMLpe',$,'Scale','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
#4=IFCSIMPLEPROPERTYTEMPLATE('3gsuPBtU93b8f0gg1pjkq6',$,'HumanScale','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
@@ -16,5 +16,9 @@ DATA;
#9=IFCSIMPLEPROPERTYTEMPLATE('10hT_1zrzEbRRKMXYAWvtD',$,'Metadata','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
#10=IFCSIMPLEPROPERTYTEMPLATE('3Z0BXPSG5CWgtI33ioV7aj',$,'Include','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
#11=IFCSIMPLEPROPERTYTEMPLATE('1RVts_g3PAw98PJA2yL3bO',$,'Exclude','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
#12=IFCSIMPLEPROPERTYTEMPLATE('0c1$8NpYDEaBiJrj16jHIo',$,'Stylesheet','',.P_SINGLEVALUE.,'IfcURIReference',$,$,$,$,$,.READWRITE.);
#13=IFCSIMPLEPROPERTYTEMPLATE('3mRF52q81FQB$h4oTh7M45',$,'Markers','',.P_SINGLEVALUE.,'IfcURIReference',$,$,$,$,$,.READWRITE.);
#14=IFCSIMPLEPROPERTYTEMPLATE('1rhr_0N3LDtuORcEJP0KXM',$,'Symbols','',.P_SINGLEVALUE.,'IfcURIReference',$,$,$,$,$,.READWRITE.);
#15=IFCSIMPLEPROPERTYTEMPLATE('2sHDBuW7P4TROy$hL2w7ct',$,'Patterns','',.P_SINGLEVALUE.,'IfcURIReference',$,$,$,$,$,.READWRITE.);
ENDSEC;
END-ISO-10303-21;
@@ -31,10 +31,11 @@ text { fill: black; stroke: none; }
.PredefinedType-SECTIONLEVEL { marker-start: url(#section-level-marker); stroke-dasharray: 12.5, 3, 3, 3; }
.PredefinedType-PLANLEVEL { marker-end: url(#plan-level-marker); }
.PredefinedType-DIMENSION { marker-start: url(#dimension-marker-start); marker-end: url(#dimension-marker-end); }
.PredefinedType-ANGLE { marker-start: url(#angle-marker-start); marker-end: url(#angle-marker-end); }
.PredefinedType-RADIUS { marker-end: url(#radius-marker-end); }
.PredefinedType-DIAMETER { marker-start: url(#diameter-marker-start); marker-end: url(#diameter-marker-end); }
.PredefinedType-HIDDENLINE { stroke-dasharray: 3, 2; }
.PredefinedType-STAIR { marker-start: url(#stair-marker-start); marker-end: url(#stair-marker-end); }
.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; }
@@ -21,6 +21,18 @@
<path d="M 0 3.5 L 0 10.5 L 10 7" class="annotation" style="fill:black;" />
</g>
</marker>
<marker id="angle-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;" />
<path d="M 1 0 L 1 14" class="annotation" style="stroke-width:1;" />
</g>
</marker>
<marker id="angle-marker-end" markerHeight="14" markerWidth="11" orient="auto" refX="10" refY="7">
<g>
<path d="M 0 3.5 L 0 10.5 L 10 7" class="annotation" style="fill:black;" />
<path d="M 10 0 L 10 14" class="annotation" style="stroke-width:1;" />
</g>
</marker>
<marker id="diameter-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: 4.8 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

+2 -12
View File
@@ -97,6 +97,7 @@ class IfcExporter:
except:
continue
ifcopenshell.api.run("root.remove_product", self.file, **{"product": product})
IfcStore.deleted_ids.clear()
return results
def sync_all_objects(self):
@@ -148,7 +149,7 @@ class IfcExporter:
return checksum != str([s.id() for s in tool.Geometry.get_styles(obj) if s])
def sync_object_placement(self, obj):
if not self.has_object_moved(obj):
if not tool.Ifc.is_moved(obj):
return
blender_matrix = np.array(obj.matrix_world)
element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
@@ -162,17 +163,6 @@ class IfcExporter:
blenderbim.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj)
return element
def has_object_moved(self, obj):
if not obj.BIMObjectProperties.location_checksum:
return True # Let's be conservative
loc_check = np.frombuffer(eval(obj.BIMObjectProperties.location_checksum))
rot_check = np.frombuffer(eval(obj.BIMObjectProperties.rotation_checksum))
loc_real = np.array(obj.matrix_world.translation).flatten()
rot_real = np.array(obj.matrix_world.to_3x3()).flatten()
if np.allclose(loc_check, loc_real, atol=1e-4) and np.allclose(rot_check, rot_real, atol=1e-2):
return False
return True
def sync_grid_axis_object_placement(self, obj, element):
grid = (element.PartOfU or element.PartOfV or element.PartOfW)[0]
grid_obj = tool.Ifc.get_object(grid)
+2
View File
@@ -26,6 +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 blenderbim.bim.module.model.data import AuthoringData
from ifcopenshell.api.material.data import Data as MaterialData
from ifcopenshell.api.type.data import Data as TypeData
@@ -301,3 +302,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 = {}
+123 -8
View File
@@ -16,14 +16,17 @@
# 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/>.
# from datetime import date
import bpy
import json
import math
import zipfile
import ifcopenshell
import ifcopenshell.util.attribute
from ifcopenshell.util.doc import get_attribute_doc, get_predefined_type_doc, get_property_doc
from mathutils import geometry
from mathutils import Vector
import blenderbim.tool as tool
from blenderbim.bim.ifc import IfcStore
@@ -38,11 +41,19 @@ def draw_attribute(attribute, layout, copy_operator=None):
if not value_name:
layout.label(text=attribute.name)
return
layout.prop(
attribute,
value_name,
text=attribute.name,
)
if value_name == "enum_value":
prop_with_search(layout, attribute, "enum_value", text=attribute.name)
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="")
if copy_operator:
@@ -74,7 +85,9 @@ 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:
pass # Our job is done
elif is_handled_by_callback is False:
@@ -90,9 +103,49 @@ def import_attribute(attribute, props, data, callback=None):
elif data_type == "float":
new.float_value = 0.0 if new.is_null else 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)
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)
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)
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):
if not attribute_blender.name:
return
version = tool.Ifc.get_schema()
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.
description = get_property_doc(version, attribute_blender.ifc_class, attribute_blender.name).get("description")
if description:
attribute_blender.description = description
def export_attributes(props, callback=None):
@@ -105,6 +158,68 @@ def export_attributes(props, callback=None):
return attributes
def prop_with_search(layout, data, prop_name, **kwargs):
# kwargs are layout.prop arguments (text, icon, etc.)
row = layout.row(align=True)
row.prop(data, prop_name, **kwargs)
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 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
prop = data.__annotations__[prop_name]
items = prop.keywords.get("items")
if items is None:
return
if not isinstance(items, (list, tuple)):
# items are retrieved through a callback, not a static list :
items = items(data, context or bpy.context)
return items
def get_obj_ifc_definition_id(context, obj, obj_type):
if obj_type == "Object":
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 == "Task":
return context.scene.BIMTaskTreeProperties.tasks[
context.scene.BIMWorkScheduleProperties.active_task_index
].ifc_definition_id
elif obj_type == "Cost":
return context.scene.BIMCostProperties.cost_items[
context.scene.BIMCostProperties.active_cost_item_index
].ifc_definition_id
elif obj_type == "Resource":
return context.scene.BIMResourceTreeProperties.resources[
context.scene.BIMResourceProperties.active_resource_index
].ifc_definition_id
elif obj_type == "Profile":
return context.scene.BIMProfileProperties.profiles[
context.scene.BIMProfileProperties.active_profile_index
].ifc_definition_id
elif obj_type == "WorkSchedule":
return context.scene.BIMWorkScheduleProperties.active_work_schedule_id
# hack to close popup
# https://blender.stackexchange.com/a/202576/130742
def close_operator_panel(event):
x, y = event.mouse_x, event.mouse_y
bpy.context.window.cursor_warp(10, 10)
move_back = lambda: bpy.context.window.cursor_warp(x, y)
bpy.app.timers.register(move_back, first_interval=0.01)
class IfcHeaderExtractor:
def __init__(self, filepath: str):
self.filepath = filepath
+70 -12
View File
@@ -39,6 +39,8 @@ class IfcStore:
edited_objs = set()
pset_template_path = ""
pset_template_file = None
classification_path = ""
classification_file = None
library_path = ""
library_file = None
element_listeners = set()
@@ -48,7 +50,7 @@ class IfcStore:
last_transaction = ""
history = []
future = []
schema_identifiers = ["IFC4", "IFC2X3"]
schema_identifiers = ["IFC4", "IFC2X3", "IFC4X3"]
@staticmethod
def purge():
@@ -65,7 +67,10 @@ class IfcStore:
IfcStore.pset_template_file = None
IfcStore.library_path = ""
IfcStore.library_file = None
IfcStore.schema_identifiers = ["IFC4", "IFC2X3"]
IfcStore.last_transaction = ""
IfcStore.history = []
IfcStore.future = []
IfcStore.schema_identifiers = ["IFC4", "IFC2X3", "IFC4X3"]
@staticmethod
def get_file():
@@ -220,7 +225,11 @@ class IfcStore:
try:
value.name
except:
obj = bpy.data.objects.get(IfcStore.undo_redo_stack_object_names[key])
# TODO not so sure about this obj_name check
obj_name = IfcStore.undo_redo_stack_object_names.get(key, None)
if not obj_name:
continue
obj = bpy.data.objects.get(obj_name)
if not obj or not obj.BIMObjectProperties.ifc_definition_id:
continue
element = file.by_id(obj.BIMObjectProperties.ifc_definition_id)
@@ -229,23 +238,35 @@ class IfcStore:
data["guid"] = element.GlobalId
IfcStore.commit_link_element(data)
@staticmethod
def relink_all_objects():
file = IfcStore.get_file()
if not file:
if not IfcStore.get_file():
return
for obj in bpy.data.objects:
if not obj:
continue
if not obj.BIMObjectProperties.ifc_definition_id:
continue
element = file.by_id(obj.BIMObjectProperties.ifc_definition_id)
IfcStore.relink_object(obj)
for obj in bpy.data.materials:
IfcStore.relink_object(obj)
@staticmethod
def relink_object(obj):
if not obj:
return
if obj.BIMObjectProperties.ifc_definition_id:
try:
element = IfcStore.get_file().by_id(obj.BIMObjectProperties.ifc_definition_id)
except:
return
data = {"id": element.id(), "obj": obj.name}
if hasattr(element, "GlobalId"):
data["guid"] = element.GlobalId
IfcStore.commit_link_element(data)
if hasattr(obj, "BIMMaterialProperties") and obj.BIMMaterialProperties.ifc_style_id:
try:
element = IfcStore.get_file().by_id(obj.BIMMaterialProperties.ifc_style_id)
except:
return
data = {"id": element.id(), "obj": obj.name}
IfcStore.commit_link_element(data)
@staticmethod
def delete_element(element):
@@ -266,6 +287,15 @@ class IfcStore:
@staticmethod
def link_element(element, obj):
existing_obj = IfcStore.id_map.get(element.id(), None)
if existing_obj == obj:
return
elif existing_obj:
try:
existing_obj.name
IfcStore.unlink_element(obj=existing_obj)
except:
pass
IfcStore.id_map[element.id()] = obj
if hasattr(element, "GlobalId"):
IfcStore.guid_map[element.GlobalId] = obj
@@ -300,6 +330,8 @@ class IfcStore:
@staticmethod
def commit_link_element(data):
obj = bpy.data.objects.get(data["obj"])
if not obj:
obj = bpy.data.materials.get(data["obj"])
IfcStore.id_map[data["id"]] = obj
if "guid" in data:
IfcStore.guid_map[data["guid"]] = obj
@@ -310,6 +342,21 @@ class IfcStore:
# TODO Listeners are not re-registered. Does this cause nasty problems to debug later on?
# TODO We're handling id_map and guid_map, but what about edited_objs? This might cause big problems.
@staticmethod
def rollback_unlink_element(data):
if "id" not in data or "obj" not in data:
return
obj = bpy.data.objects.get(data["obj"])
IfcStore.id_map[data["id"]] = obj
if data["guid"]:
IfcStore.guid_map[data["guid"]] = obj
@staticmethod
def commit_unlink_element(data):
del IfcStore.id_map[data["id"]]
if data["guid"]:
del IfcStore.guid_map[data["guid"]]
@staticmethod
def unlink_element(element=None, obj=None):
if element is None:
@@ -345,6 +392,17 @@ class IfcStore:
elif obj:
obj.BIMObjectProperties.ifc_definition_id = 0
if IfcStore.history:
data = {}
if element:
data["id"] = element.id()
data["guid"] = getattr(element, "GlobalId", None)
if obj:
data["obj"] = obj.name
IfcStore.history[-1]["operations"].append(
{"rollback": IfcStore.rollback_unlink_element, "commit": IfcStore.commit_unlink_element, "data": data}
)
@staticmethod
def execute_ifc_operator(operator, context):
is_top_level_operator = not bool(IfcStore.current_transaction)
+291 -132
View File
@@ -121,7 +121,14 @@ class MaterialCreator:
def parse_representation_item(self, item):
if not item.StyledByItem:
return
style_ids = [e.id() for e in self.ifc_importer.file.traverse(item.StyledByItem[0]) if e.is_a("IfcSurfaceStyle")]
style_ids = []
styles = list(item.StyledByItem[0].Styles)
while styles:
style = styles.pop()
if style.is_a("IfcSurfaceStyle"):
style_ids.append(style.id())
elif style.is_a("IfcPresentationStyleAssignment"):
styles.extend(style.Styles)
if not style_ids:
return
for style_id in style_ids:
@@ -168,12 +175,13 @@ class IfcImporter:
self.settings_native.set(self.settings_native.INCLUDE_CURVES, True)
self.settings_2d = ifcopenshell.geom.settings()
self.settings_2d.set(self.settings_2d.INCLUDE_CURVES, True)
self.settings_2d.set(self.settings.STRICT_TOLERANCE, True)
self.project = None
self.has_existing_project = False
self.collections = {}
self.elements = set()
self.type_collection = None
self.type_products = {}
self.openings = {}
self.meshes = {}
self.mesh_shapes = {}
self.time = 0
@@ -181,6 +189,7 @@ class IfcImporter:
self.added_data = {}
self.native_elements = set()
self.native_data = {}
self.progress = 0
self.material_creator = MaterialCreator(ifc_import_settings, self)
@@ -198,7 +207,6 @@ class IfcImporter:
def execute(self):
bpy.context.window_manager.progress_begin(0, 100)
self.progress = 0
self.profile_code("Starting import process")
self.load_file()
self.profile_code("Loading file")
@@ -216,8 +224,6 @@ class IfcImporter:
self.profile_code("Process context filter")
self.create_collections()
self.profile_code("Create collections")
self.create_openings_collection()
self.profile_code("Create opening collection")
self.create_materials()
self.profile_code("Create materials")
self.create_styles()
@@ -236,24 +242,19 @@ class IfcImporter:
self.profile_code("Create spatial elements")
self.create_structural_items()
self.profile_code("Create structural items")
self.create_type_products()
self.profile_code("Create type products")
if not self.ifc_import_settings.is_coordinating:
self.create_element_types()
self.profile_code("Create element types")
self.place_objects_in_collections()
self.profile_code("Place objects in collections")
if self.ifc_import_settings.should_merge_by_class:
self.merge_by_class()
self.profile_code("Merging by class")
elif self.ifc_import_settings.should_merge_by_material:
self.merge_by_material()
self.profile_code("Merging by material")
if self.ifc_import_settings.should_merge_materials_by_colour or len(self.material_creator.materials) > 300:
self.merge_materials_by_colour()
self.profile_code("Merging by colour")
self.add_project_to_scene()
self.profile_code("Add project to scene")
if self.ifc_import_settings.should_clean_mesh and len(self.file.by_type("IfcElement")) < 1000:
self.clean_mesh()
self.profile_code("Mesh cleaning")
if self.ifc_import_settings.should_merge_materials_by_colour or len(self.material_creator.materials) > 300:
self.merge_materials_by_colour()
self.profile_code("Merging by colour")
self.set_default_context()
self.profile_code("Setting default context")
self.update_progress(100)
@@ -269,7 +270,9 @@ class IfcImporter:
def is_point_far_away(self, point, is_meters=True):
# Locations greater than 1km are not considered "small sites" according to the georeferencing guide
limit = 1000 if is_meters else (1000 / self.unit_scale)
# Users can configure this if they have to handle larger sites but beware of surveying precision
limit = self.ifc_import_settings.distance_limit
limit = limit if is_meters else (limit / self.unit_scale)
coords = point
if hasattr(point, "Coordinates"):
coords = point.Coordinates
@@ -304,14 +307,37 @@ class IfcImporter:
self.settings_2d.set_context_ids(self.plan_contexts)
def process_element_filter(self):
offset = self.ifc_import_settings.element_offset
offset_limit = offset + self.ifc_import_settings.element_limit
if self.ifc_import_settings.has_filter:
self.elements = set(self.ifc_import_settings.elements)
self.elements = self.ifc_import_settings.elements
if isinstance(self.elements, set):
self.elements = list(self.elements)
# TODO: enable filtering for annotations
self.annotations = set(self.file.by_type("IfcAnnotation"))
else:
self.elements = set(self.file.by_type("IfcElement"))
self.elements = self.file.by_type("IfcElement")
self.annotations = set(self.file.by_type("IfcAnnotation"))
self.elements = [e for e in self.elements if not e.is_a("IfcFeatureElement")]
if self.ifc_import_settings.is_coordinating:
self.elements = [e for e in self.elements if e.Representation]
self.elements = set(self.elements[offset:offset_limit])
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:
if self.file.schema in ("IFC2X3", "IFC4"):
self.element_types = set(
self.file.by_type("IfcElementType")
+ self.file.by_type("IfcDoorStyle")
+ self.file.by_type("IfcWindowStyle")
)
else:
self.element_types = set(self.file.by_type("IfcElementType"))
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)
else:
@@ -354,21 +380,38 @@ class IfcImporter:
"type": "IfcSweptDiskSolid",
}
return True
if not self.ifc_import_settings.should_use_native_meshes:
return False # Performance improvements only occur on edge cases currently
# FacetedBreps (without voids) are meshes. See #841.
# Commented out as seems currently too slow.
# if self.is_native_faceted_brep(representations):
# self.native_data[element.GlobalId] = {
# "representations": representations,
# "representation": self.get_body_representation(element.Representation.Representations),
# "type": "IfcFacetedBrep",
# }
# return True
if self.is_native_faceted_brep(representations):
self.native_data[element.GlobalId] = {
"representations": representations,
"representation": self.get_body_representation(element.Representation.Representations),
"type": "IfcFacetedBrep",
}
return True
if self.is_native_face_based_surface_model(representations):
self.native_data[element.GlobalId] = {
"representations": representations,
"representation": self.get_body_representation(element.Representation.Representations),
"type": "IfcFaceBasedSurfaceModel",
}
return True
def is_native_swept_disk_solid(self, representations):
for representation in representations:
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
items[0].is_a("IfcSweptDiskSolid")
and len({i.is_a() for i in items}) == 1
and len({i.Radius for i in items}) == 1
):
return True
return False
def is_native_faceted_brep(self, representations):
@@ -378,6 +421,13 @@ class IfcImporter:
return False
return True
def is_native_face_based_surface_model(self, representations):
for representation in representations:
for i in representation["raw"].Items:
if i.is_a() != "IfcFaceBasedSurfaceModel":
return False
return True
def get_products_from_shape_representation(self, element):
products = [pr.ShapeOfProduct[0] for pr in element.OfProductRepresentation]
for rep_map in element.RepresentationMap:
@@ -391,7 +441,7 @@ class IfcImporter:
props = bpy.context.scene.BIMGeoreferenceProperties
if props.has_blender_offset:
return
if self.ifc_import_settings.should_offset_model:
if self.ifc_import_settings.false_origin:
return self.set_manual_blender_offset()
if self.file.schema == "IFC2X3":
project = self.file.by_type("IfcProject")[0]
@@ -407,9 +457,9 @@ class IfcImporter:
def set_manual_blender_offset(self):
props = bpy.context.scene.BIMGeoreferenceProperties
props.blender_eastings = str(self.ifc_import_settings.model_offset_coordinates[0])
props.blender_northings = str(self.ifc_import_settings.model_offset_coordinates[1])
props.blender_orthogonal_height = str(self.ifc_import_settings.model_offset_coordinates[2])
props.blender_eastings = str(self.ifc_import_settings.false_origin[0])
props.blender_northings = str(self.ifc_import_settings.false_origin[1])
props.blender_orthogonal_height = str(self.ifc_import_settings.false_origin[2])
props.has_blender_offset = True
def guess_georeferencing(self, element):
@@ -518,6 +568,9 @@ class IfcImporter:
if grid.Representation:
shape = ifcopenshell.geom.create_shape(self.settings_2d, grid)
grid_obj = self.create_product(grid, shape)
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))
u_axes = bpy.data.collections.new("UAxes")
collection.children.link(u_axes)
@@ -535,23 +588,20 @@ class IfcImporter:
shape = ifcopenshell.geom.create_shape(self.settings_2d, axis.AxisCurve)
mesh = self.create_mesh(axis, shape)
obj = bpy.data.objects.new(f"IfcGridAxis/{axis.AxisTag}", mesh)
if bpy.context.preferences.addons["blenderbim"].preferences.lock_grids_on_import:
obj.lock_location = (True, True, True)
obj.lock_rotation = (True, True, True)
self.link_element(axis, obj)
self.set_matrix_world(obj, grid_obj.matrix_world)
grid_collection.objects.link(obj)
def create_type_products(self):
# TODO allow filtering of spatial elements too
if self.ifc_import_settings.has_filter:
type_products = set([ifcopenshell.util.element.get_type(e) for e in self.elements])
else:
type_products = self.file.by_type("IfcTypeProduct")
for type_product in type_products:
if not type_product:
def create_element_types(self):
for element_type in self.element_types:
if not element_type:
continue
self.create_type_product(type_product)
self.create_element_type(element_type)
def create_type_product(self, element):
def create_element_type(self, element):
self.ifc_import_settings.logger.info("Creating object %s", element)
representation_map = self.get_type_product_body_representation_map(element)
mesh = None
@@ -585,13 +635,20 @@ class IfcImporter:
return representation_map
def create_native_elements(self):
total = 0
progress = 0
checkpoint = time.time()
total = len(self.native_elements)
for element in self.native_elements:
total += 1
if total % 250 == 0:
print("{} elements processed in {:.2f}s ...".format(total, time.time() - checkpoint))
progress += 1
if progress % 250 == 0:
percent = round(progress / total * 100)
print(
"{} / {} ({}%) elements processed in {:.2f}s ...".format(
progress, total, percent, time.time() - checkpoint
)
)
checkpoint = time.time()
self.incrementally_merge_objects()
native_data = self.native_data[element.GlobalId]
representation = native_data["representation"]
if not representation:
@@ -604,6 +661,8 @@ class IfcImporter:
mesh = self.create_native_swept_disk_solid(element, mesh_name)
elif native_data["type"] == "IfcFacetedBrep":
mesh = self.create_native_faceted_brep(element, mesh_name)
elif native_data["type"] == "IfcFaceBasedSurfaceModel":
mesh = self.create_native_faceted_brep(element, mesh_name)
mesh.BIMMeshProperties.ifc_definition_id = representation.id()
mesh.name = mesh_name
self.meshes[mesh_name] = mesh
@@ -643,34 +702,40 @@ class IfcImporter:
)
else:
iterator = ifcopenshell.geom.iterator(self.settings, self.file, include=products)
cache = IfcStore.get_cache()
if cache:
iterator.set_cache(cache)
if self.ifc_import_settings.should_cache:
cache = IfcStore.get_cache()
if cache:
iterator.set_cache(cache)
valid_file = iterator.initialize()
if not valid_file:
return results
checkpoint = time.time()
total = 0
progress = 0
total = len(products)
start_progress = self.progress
progress_range = 85 - start_progress
while True:
total += 1
if total % 250 == 0:
progress += 1
if progress % 250 == 0:
percent_created = round(progress / total * 100)
percent_preprocessed = iterator.progress()
percent_average = (percent_created + percent_preprocessed) / 2
print(
"{} ({}%) elements processed in {:.2f}s ...".format(
total, iterator.progress(), time.time() - checkpoint
"{} / {} ({}% created, {}% preprocessed) elements processed in {:.2f}s ...".format(
progress, total, percent_created, percent_preprocessed, time.time() - checkpoint
)
)
checkpoint = time.time()
self.update_progress((iterator.progress() / 100 * progress_range) + start_progress)
self.update_progress((percent_average / 100 * progress_range) + start_progress)
self.incrementally_merge_objects()
shape = iterator.get()
if shape:
product = self.file.by_id(shape.guid)
product = self.file.by_id(shape.id)
if self.body_contexts:
self.create_product(product, shape)
results.add(product)
else:
if shape.context not in ["Body", "Facetation"] and IfcStore.get_element(shape.guid):
if shape.context not in ["Body", "Facetation"] and IfcStore.get_element(shape.id):
# We only load a single context, and we prioritise the Body context. See #1290.
pass
else:
@@ -681,6 +746,19 @@ class IfcImporter:
print("Done creating geometry")
return results
def incrementally_merge_objects(self):
if not self.ifc_import_settings.is_coordinating:
return
if self.ifc_import_settings.merge_mode == "IFC_CLASS":
self.merge_by_class()
self.profile_code("Merging by class")
elif self.ifc_import_settings.merge_mode == "IFC_TYPE":
self.merge_by_type()
self.profile_code("Merging by type")
elif self.ifc_import_settings.merge_mode == "MATERIAL":
self.merge_by_material()
self.profile_code("Merging by material")
def create_structural_items(self):
# Create structural collections
self.structural_member_collection = bpy.data.collections.new("Members")
@@ -791,9 +869,10 @@ class IfcImporter:
)
else:
iterator = ifcopenshell.geom.iterator(self.settings_2d, self.file, include=products)
cache = IfcStore.get_cache()
if cache:
iterator.set_cache(cache)
if self.ifc_import_settings.should_cache:
cache = IfcStore.get_cache()
if cache:
iterator.set_cache(cache)
valid_file = iterator.initialize()
if not valid_file:
return results
@@ -806,8 +885,8 @@ class IfcImporter:
checkpoint = time.time()
shape = iterator.get()
if shape:
product = self.file.by_id(shape.guid)
self.create_product(self.file.by_id(shape.guid), shape)
product = self.file.by_id(shape.id)
self.create_product(product, shape)
results.add(product)
if not iterator.next():
break
@@ -818,6 +897,11 @@ class IfcImporter:
if element is None:
return
if self.has_existing_project:
obj = tool.Ifc.get_object(element)
if obj:
return obj
self.ifc_import_settings.logger.info("Creating object %s", element)
if mesh:
@@ -825,7 +909,7 @@ class IfcImporter:
elif element.is_a("IfcAnnotation") and element.ObjectType == "DRAWING":
mesh = self.create_camera(element, shape)
self.link_mesh(shape, mesh)
elif element.is_a("IfcAnnotation") and self.is_curve_annotation(element):
elif element.is_a("IfcAnnotation") and self.is_curve_annotation(element) and shape:
mesh = self.create_curve(element, shape)
self.link_mesh(shape, mesh)
elif shape:
@@ -855,17 +939,18 @@ class IfcImporter:
elif hasattr(element, "ObjectPlacement"):
self.set_matrix_world(obj, self.apply_blender_offset_to_matrix_world(obj, self.get_element_matrix(element)))
self.add_opening_relation(element, obj)
if element.is_a("IfcOpeningElement"):
obj.display_type = "WIRE"
return obj
def get_representation_item_material_name(self, item):
if not item.StyledByItem:
return
style_ids = [e.id() for e in self.file.traverse(item.StyledByItem[0]) if e.is_a("IfcSurfaceStyle")]
return style_ids[0] if style_ids else None
styles = list(item.StyledByItem[0].Styles)
while styles:
style = styles.pop()
if style.is_a("IfcSurfaceStyle"):
return style.id()
elif style.is_a("IfcPresentationStyleAssignment"):
styles.extend(style.Styles)
def create_native_faceted_brep(self, element, mesh_name):
# TODO: georeferencing?
@@ -914,22 +999,32 @@ class IfcImporter:
self.convert_representation(item.MappingSource.MappedRepresentation)
elif item.is_a() == "IfcFacetedBrep":
self.convert_representation_item_faceted_brep(item)
elif item.is_a() == "IfcFaceBasedSurfaceModel":
self.convert_representation_item_face_based_surface_model(item)
def convert_representation_item_face_based_surface_model(self, item):
mesh = item.get_info_2(recursive=True)
for face_set in mesh["FbsmFaces"]:
self.convert_representation_item_face_set(item, face_set)
def convert_representation_item_faceted_brep(self, item):
mesh = item.get_info_2(recursive=True)
return self.convert_representation_item_face_set(item, mesh["Outer"])
def convert_representation_item_face_set(self, item, mesh):
# On a few occasions, we flatten a list. This seems to be the most efficient way to do it.
# https://stackoverflow.com/questions/20112776/how-do-i-flatten-a-list-of-lists-nested-lists
mesh = item.get_info_2(recursive=True)
# For huge face sets it might be better to do a "flatmap" instead of sum()
# bounds = sum((f["Bounds"] for f in mesh["Outer"]["CfsFaces"] if len(f["Bounds"]) == 1), ())
bounds = tuple(chain.from_iterable(f["Bounds"] for f in mesh["Outer"]["CfsFaces"] if len(f["Bounds"]) == 1))
bounds = tuple(chain.from_iterable(f["Bounds"] for f in mesh["CfsFaces"] if len(f["Bounds"]) == 1))
# Here are some untested alternatives, are they faster?
# bounds = tuple((f["Bounds"] for f in mesh["Outer"]["CfsFaces"] if len(f["Bounds"]) == 1))[0]
# bounds = chain.from_iterable(f["Bounds"] for f in mesh["Outer"]["CfsFaces"] if len(f["Bounds"]) == 1)
polygons = [[(p["id"], p["Coordinates"]) for p in b["Bound"]["Polygon"]] for b in bounds]
for face in mesh["Outer"]["CfsFaces"]:
for face in mesh["CfsFaces"]:
# Blender cannot handle faces with holes.
if len(face["Bounds"]) > 1:
inner_bounds = []
@@ -1027,6 +1122,7 @@ class IfcImporter:
polyline.points[-1].co = representation["matrix"] @ mathutils.Vector(v2)
curve.bevel_depth = self.unit_scale * item.Radius
curve.use_fill_caps = True
return curve
def create_native_annotation(self, element, mesh_name):
@@ -1059,33 +1155,74 @@ class IfcImporter:
def merge_by_class(self):
merge_set = {}
for obj in self.added_data.values():
if not isinstance(obj, bpy.types.Object):
id_set = {}
for ifc_definition_id, obj in self.added_data.items():
if not isinstance(obj, bpy.types.Object) or not obj.data:
continue
if "/" not in obj.name or "IfcRelAggregates" in obj.users_collection[0].name:
element = self.file.by_id(ifc_definition_id)
if not element.is_a("IfcElement"):
continue
merge_set.setdefault(obj.name.split("/")[0], []).append(obj)
self.merge_objects(merge_set)
merge_set.setdefault(element.is_a(), []).append(obj)
id_set.setdefault(element.is_a(), []).append(ifc_definition_id)
self.merge_objects(merge_set, id_set)
def merge_by_type(self):
merge_set = {}
id_set = {}
for ifc_definition_id, obj in self.added_data.items():
if not isinstance(obj, bpy.types.Object) or not obj.data:
continue
element = self.file.by_id(ifc_definition_id)
if not element.is_a("IfcElement"):
continue
element_type = ifcopenshell.util.element.get_type(element)
if not element_type:
continue
merge_key = str(element_type.id()) + "-" + element_type.Name or "Unnamed"
merge_set.setdefault(merge_key, []).append(obj)
id_set.setdefault(merge_key, []).append(ifc_definition_id)
self.merge_objects(merge_set, id_set)
def merge_by_material(self):
merge_set = {}
for obj in self.added_data.values():
if not isinstance(obj, bpy.types.Object):
id_set = {}
for ifc_definition_id, obj in self.added_data.items():
if not isinstance(obj, bpy.types.Object) or not obj.data:
continue
if "/" not in obj.name or "IfcRelAggregates" in obj.users_collection[0].name:
element = self.file.by_id(ifc_definition_id)
if not element.is_a("IfcElement"):
continue
if not obj.material_slots:
merge_set.setdefault("no-material", []).append(obj)
else:
merge_set.setdefault(obj.material_slots[0].name, []).append(obj)
self.merge_objects(merge_set)
merge_key = obj.material_slots[0].name if obj.material_slots else "no-material"
merge_set.setdefault(merge_key, []).append(obj)
id_set.setdefault(merge_key, []).append(ifc_definition_id)
self.merge_objects(merge_set, id_set)
def merge_objects(self, merge_set):
for ifc_class, objs in merge_set.items():
context_override = {}
context_override["object"] = context_override["active_object"] = objs[0]
context_override["selected_objects"] = context_override["selected_editable_objects"] = objs
bpy.ops.object.join(context_override)
def merge_objects(self, merge_set, id_set):
total_objs = sum([len(o) for o in merge_set.values()])
cumulative_total = 0
merge_set = {k: v for k, v in sorted(merge_set.items(), key=lambda i: len(i[1]), reverse=True)}
for group_name, objs in merge_set.items():
total_group_objs = len(objs)
if total_group_objs < 10:
continue
merge_potential = total_objs - cumulative_total
if merge_potential < 250:
print("Merge target achieved")
return
cumulative_total += total_group_objs
print(f"Merging {total_group_objs} objects, {merge_potential} potentially remaining -", group_name)
try:
target = objs[0]
target.data = target.data.copy()
context_override = {}
context_override["object"] = context_override["active_object"] = target
context_override["selected_objects"] = context_override["selected_editable_objects"] = objs
bpy.ops.object.join(context_override)
target.data.name += "-merge"
for ifc_definition_id in id_set[group_name][1:]:
del self.added_data[ifc_definition_id]
except:
print("Merge failed")
def merge_materials_by_colour(self):
cleaned_materials = {}
@@ -1117,7 +1254,6 @@ class IfcImporter:
# Occurs when reloading a project
pass
project_collection = bpy.context.view_layer.layer_collection.children[self.project["blender"].name]
project_collection.children[self.opening_collection.name].hide_viewport = True
project_collection.children[self.type_collection.name].hide_viewport = True
def clean_mesh(self):
@@ -1140,11 +1276,6 @@ class IfcImporter:
bpy.ops.object.editmode_toggle(context_override)
IfcStore.edited_objs.clear()
def add_opening_relation(self, element, obj):
if not element.is_a("IfcOpeningElement"):
return
self.openings[element.GlobalId] = obj
def load_file(self):
self.ifc_import_settings.logger.info("loading file %s", self.ifc_import_settings.input_file)
if not bpy.context.scene.BIMProperties.ifc_file:
@@ -1184,7 +1315,13 @@ class IfcImporter:
)
def create_project(self):
self.project = {"ifc": self.file.by_type("IfcProject")[0]}
project = self.file.by_type("IfcProject")[0]
self.project = {"ifc": project}
obj = tool.Ifc.get_object(project)
if obj:
self.project["blender"] = obj.users_collection[0]
self.has_existing_project = True
return
self.project["blender"] = bpy.data.collections.new(
"{}/{}".format(self.project["ifc"].is_a(), self.project["ifc"].Name)
)
@@ -1237,10 +1374,17 @@ class IfcImporter:
for element in related_objects:
if element not in self.spatial_elements:
continue
global_id = element.GlobalId
collection = bpy.data.collections.new(self.get_name(element))
self.collections[global_id] = collection
parent.children.link(collection)
is_existing = False
if self.has_existing_project:
obj = tool.Ifc.get_object(element)
if obj:
is_existing = True
collection = obj.users_collection[0]
self.collections[element.GlobalId] = collection
if not is_existing:
collection = bpy.data.collections.new(self.get_name(element))
self.collections[element.GlobalId] = collection
parent.children.link(collection)
if element.IsDecomposedBy:
for rel_aggregate in element.IsDecomposedBy:
self.create_spatial_decomposition_collection(collection, rel_aggregate.RelatedObjects)
@@ -1275,10 +1419,6 @@ class IfcImporter:
if parent:
self.collections[parent.GlobalId].children.link(aggregate["collection"])
def create_openings_collection(self):
self.opening_collection = bpy.data.collections.new("IfcOpeningElements")
self.project["blender"].children.link(self.opening_collection)
def create_materials(self):
for material in self.file.by_type("IfcMaterial"):
self.create_material(material)
@@ -1295,11 +1435,18 @@ class IfcImporter:
for material_definition_representation in self.file.by_type("IfcMaterialDefinitionRepresentation"):
material = material_definition_representation.RepresentedMaterial
blender_material = self.material_creator.materials[material.id()]
for representation in material_definition_representation.Representations:
for style in [e for e in self.file.traverse(representation) if e.is_a("IfcSurfaceStyle")]:
blender_material = self.material_creator.materials[material.id()]
self.create_style(style, blender_material)
parsed_styles.add(style.id())
styles = []
for styled_item in representation.Items:
styles.extend(styled_item.Styles)
while styles:
style = styles.pop()
if style.is_a("IfcSurfaceStyle"):
self.create_style(style, blender_material)
parsed_styles.add(style.id())
elif style.is_a("IfcPresentationStyleAssignment"):
styles.extend(style.Styles)
for style in self.file.by_type("IfcSurfaceStyle"):
if style.id() in parsed_styles:
@@ -1348,13 +1495,21 @@ class IfcImporter:
if surface_style.ReflectanceMethod in ["PHYSICAL", "NOTDEFINED"]:
blender_material.use_nodes = True
bsdf = blender_material.node_tree.nodes["Principled BSDF"]
if surface_style.DiffuseColour and surface_style.DiffuseColour.is_a("IfcColourRgb"):
bsdf.inputs["Base Color"].default_value = (
surface_style.DiffuseColour.Red,
surface_style.DiffuseColour.Green,
surface_style.DiffuseColour.Blue,
1,
)
if surface_style.DiffuseColour:
if surface_style.DiffuseColour.is_a("IfcColourRgb"):
bsdf.inputs["Base Color"].default_value = (
surface_style.DiffuseColour.Red,
surface_style.DiffuseColour.Green,
surface_style.DiffuseColour.Blue,
1,
)
elif surface_style.DiffuseColour.is_a("IfcNormalisedRatioMeasure"):
bsdf.inputs["Base Color"].default_value = (
surface_style.SurfaceColour.Red * surface_style.DiffuseColour.wrappedValue,
surface_style.SurfaceColour.Green * surface_style.DiffuseColour.wrappedValue,
surface_style.SurfaceColour.Blue * surface_style.DiffuseColour.wrappedValue,
1,
)
if surface_style.SpecularColour and surface_style.SpecularColour.is_a("IfcNormalisedRatioMeasure"):
bsdf.inputs["Metallic"].default_value = surface_style.SpecularColour.wrappedValue
if surface_style.SpecularHighlight and surface_style.SpecularHighlight.is_a("IfcSpecularRoughness"):
@@ -1516,8 +1671,6 @@ class IfcImporter:
return self.collections[element.GlobalId].objects.link(obj)
elif element.is_a("IfcTypeObject"):
return self.type_collection.objects.link(obj)
elif element.is_a("IfcOpeningElement"):
return self.opening_collection.objects.link(obj)
elif element.is_a("IfcStructuralMember"):
return self.structural_member_collection.objects.link(obj)
elif element.is_a("IfcStructuralConnection"):
@@ -1828,7 +1981,7 @@ class IfcImporter:
def set_default_context(self):
for subcontext in self.file.by_type("IfcGeometricRepresentationSubContext"):
if subcontext.ContextIdentifier == "Body":
bpy.context.scene.BIMProperties.contexts = str(subcontext.id())
bpy.context.scene.BIMRootProperties.contexts = str(subcontext.id())
break
def link_element(self, element, obj):
@@ -1857,14 +2010,18 @@ class IfcImportSettings:
self.input_file = None
self.diff_file = None
self.should_use_cpu_multiprocessing = True
self.should_merge_by_class = False
self.should_merge_by_material = False
self.merge_mode = None
self.should_merge_materials_by_colour = False
self.should_use_native_meshes = False
self.should_clean_mesh = True
self.should_cache = True
self.is_coordinating = True
self.deflection_tolerance = 0.001
self.angular_tolerance = 0.5
self.should_offset_model = False
self.model_offset_coordinates = (0, 0, 0)
self.distance_limit = 1000
self.false_origin = None
self.element_offset = 0
self.element_limit = 30000
self.has_filter = None
self.should_filter_spatial_elements = True
self.elements = set()
@@ -1882,16 +2039,18 @@ class IfcImportSettings:
settings.diff_file = scene_diff.diff_json_file
settings.collection_mode = props.collection_mode
settings.should_use_cpu_multiprocessing = props.should_use_cpu_multiprocessing
settings.should_merge_by_class = props.should_merge_by_class
settings.should_merge_by_material = props.should_merge_by_material
settings.merge_mode = props.merge_mode
settings.should_merge_materials_by_colour = props.should_merge_materials_by_colour
settings.should_use_native_meshes = props.should_use_native_meshes
settings.should_clean_mesh = props.should_clean_mesh
settings.should_cache = props.should_cache
settings.is_coordinating = props.is_coordinating
settings.deflection_tolerance = props.deflection_tolerance
settings.angular_tolerance = props.angular_tolerance
settings.should_offset_model = props.should_offset_model
settings.model_offset_coordinates = (
[float(o) for o in props.model_offset_coordinates.split(",")]
if props.model_offset_coordinates
else (0, 0, 0)
)
settings.distance_limit = props.distance_limit
settings.false_origin = [float(o) for o in props.false_origin.split(",")] if props.false_origin else None
if settings.false_origin == [0, 0, 0]:
settings.false_origin = None
settings.element_offset = props.element_offset
settings.element_limit = props.element_limit
return settings
@@ -768,6 +768,8 @@ class ActivateBcfViewpoint(bpy.types.Operator):
bcfxml = bcfstore.BcfStore.get_bcfxml()
props = context.scene.BCFProperties
blender_topic = props.active_topic
if blender_topic is None:
return False
topic = bcfxml.topics[blender_topic.name]
return topic.viewpoints
@@ -880,7 +882,7 @@ class ActivateBcfViewpoint(bpy.types.Operator):
# 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.components.visibility.exceptions}
if viewpoint.components.visibility.default_visibility:
old = context.area.type
@@ -95,7 +95,7 @@ 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)
viewpoints = bcfxml.get_viewpoints(topic.name) if topic else {}
bcfviewpoints_enum.extend([(v, f"Viewpoint {i+1}", "") for i, v in enumerate(viewpoints.keys())])
return bcfviewpoints_enum
@@ -62,7 +62,7 @@ def refreshScenarios(self, context):
class BimTesterProperties(PropertyGroup):
feature: StringProperty(default="", name="Feature / IDS", update=refreshScenarios)
feature: StringProperty(default="", name="Feature", update=refreshScenarios)
steps: StringProperty(default="", name="Custom Steps")
ifc_file: StringProperty(default="", name="IFC File")
audit_ifc_class: EnumProperty(items=get_ifc_classes, name="Audit Class")
@@ -49,7 +49,7 @@ class Loader:
if not boundary.ConnectionGeometry:
return None
surface = boundary.ConnectionGeometry.SurfaceOnRelatingElement
# workaround for unvalid 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/IfcOpenShell/IfcOpenShell/issues/635#issuecomment-770366838
if surface.is_a("IfcCurveBoundedPlane") and not getattr(surface, "InnerBoundaries", None):
surface.InnerBoundaries = ()
shape = ifcopenshell.geom.create_shape(self.settings, surface)
@@ -101,7 +101,7 @@ class BIM_PT_Boundary(Panel):
entity = getattr(boundary, ifc_attribute)
if entity:
row.label(text=f"{entity.is_a()}/{entity.Name}")
op = row.operator("bim.select_global_id", text="", icon="TRACKER")
op = row.operator("bim.select_global_id", text="", icon="OBJECT_DATA")
op.global_id = entity.GlobalId
else:
row.label(text="")
@@ -18,6 +18,7 @@
import blenderbim.tool as tool
from bpy.types import Panel, UIList
from blenderbim.bim.helper import prop_with_search
from blenderbim.bim.module.brick.data import BrickschemaData, BrickschemaReferencesData
@@ -48,7 +49,8 @@ class BIM_PT_brickschema(Panel):
row.operator("bim.close_brick_project", text="", icon="CANCEL")
row = self.layout.row(align=True)
row.prop(self.props, "namespace", text="")
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")
@@ -101,7 +103,7 @@ class BIM_PT_ifc_brickschema_references(Panel):
return
row = self.layout.row(align=True)
row.prop(self.props, "libraries")
prop_with_search(row, self.props, "libraries")
row.operator("bim.convert_brick_project", text="", icon="ADD")
row = self.layout.row(align=True)
@@ -0,0 +1,47 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2022 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
from . import operator, prop, workspace
classes = (
operator.AlignViewToProfile,
operator.AddIfcArcIndexFillet,
operator.AddIfcCircle,
operator.AddRectangle,
operator.CadArcFrom2Points,
operator.CadArcFrom3Points,
operator.CadFillet,
operator.CadMitre,
operator.CadOffset,
operator.CadTrimExtend,
prop.BIMCadProperties,
workspace.CadHotkey,
)
def register():
if not bpy.app.background:
bpy.utils.register_tool(workspace.CadTool, after={"builtin.transform"}, separator=True, group=True)
bpy.types.Scene.BIMCadProperties = bpy.props.PointerProperty(type=prop.BIMCadProperties)
def unregister():
if not bpy.app.background:
bpy.utils.unregister_tool(workspace.CadTool)
del bpy.types.Scene.BIMCadProperties
@@ -0,0 +1,813 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2020, 2021 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 math
import bmesh
import mathutils
import bpy_extras
import blenderbim.tool as tool
from mathutils import Vector, Matrix
from math import pi, radians, sin, cos, sqrt
messages = {
"SHARED_VERTEX": "Shared Vertex, no intersection possible",
"PARALLEL_EDGES": "Edges Parallel, no intersection possible",
"NON_PLANAR_EDGES": "Non Planar Edges, no clean intersection point",
}
class CadTrimExtend(bpy.types.Operator):
bl_idname = "bim.cad_trim_extend"
bl_label = "CAD Trim / Extend"
@classmethod
def poll(cls, context):
obj = context.active_object
return bool(obj) and obj.type == "MESH"
def cancel_message(self, msg):
self.report({"WARNING"}, msg)
return {"CANCELLED"}
def execute(self, context):
# final attempt to enter unfragmented bm/mesh
# ghastly, but what can I do? it works with these
# fails without.
bpy.ops.object.mode_set(mode="OBJECT")
bpy.ops.object.mode_set(mode="EDIT")
obj = context.active_object
me = obj.data
bm = bmesh.from_edit_mesh(me)
bm.verts.ensure_lookup_table()
bm.edges.ensure_lookup_table()
edges = [e for e in bm.edges if e.select and not e.hide]
if len(edges) == 2:
message = tool.Cad.do_vtx_if_appropriate(bm, edges, mode="T")
if isinstance(message, set):
msg = messages.get(message.pop())
return self.cancel_message(msg)
bm = message
else:
return self.cancel_message("select two edges!")
bmesh.ops.remove_doubles(bm, verts=list(set(edges[0].verts) | set(edges[1].verts)), dist=1e-5)
bm.verts.index_update()
bm.edges.index_update()
bmesh.update_edit_mesh(me, loop_triangles=True)
return {"FINISHED"}
class CadMitre(bpy.types.Operator):
bl_idname = "bim.cad_mitre"
bl_label = "CAD Mitre"
@classmethod
def poll(cls, context):
obj = context.active_object
return bool(obj) and obj.type == "MESH"
def cancel_message(self, msg):
self.report({"WARNING"}, msg)
return {"CANCELLED"}
def execute(self, context):
# final attempt to enter unfragmented bm/mesh
# ghastly, but what can I do? it works with these
# fails without.
bpy.ops.object.mode_set(mode="OBJECT")
bpy.ops.object.mode_set(mode="EDIT")
obj = context.active_object
me = obj.data
bm = bmesh.from_edit_mesh(me)
bm.verts.ensure_lookup_table()
bm.edges.ensure_lookup_table()
edges = [e for e in bm.edges if e.select and not e.hide]
if len(edges) == 2:
message = tool.Cad.do_vtx_if_appropriate(bm, edges, mode="V")
if isinstance(message, set):
msg = messages.get(message.pop())
return self.cancel_message(msg)
bm = message
else:
return self.cancel_message("select two edges!")
bmesh.ops.remove_doubles(bm, verts=list(set(edges[0].verts) | set(edges[1].verts)), dist=1e-5)
bm.verts.index_update()
bm.edges.index_update()
bmesh.update_edit_mesh(me, loop_triangles=True)
return {"FINISHED"}
class CadFillet(bpy.types.Operator):
bl_idname = "bim.cad_fillet"
bl_label = "CAD Fillet"
bl_options = {"REGISTER", "UNDO"}
resolution: bpy.props.IntProperty(name="Arc Resolution", min=0, default=1)
radius: bpy.props.FloatProperty(name="Radius", default=0.1)
@classmethod
def poll(cls, context):
obj = context.active_object
return obj and obj.type == "MESH" and context.mode == "EDIT_MESH"
def draw(self, context):
layout = self.layout
for prop in self.__class__.__annotations__.keys():
layout.prop(self, prop)
def execute(self, context):
bpy.ops.object.mode_set(mode="OBJECT")
bpy.ops.object.mode_set(mode="EDIT")
obj = bpy.context.active_object
cursor = obj.matrix_world.inverted() @ bpy.context.scene.cursor.location
mesh = obj.data
bm = bmesh.from_edit_mesh(mesh)
selected_edges = [e for e in bm.edges if e.select]
if len(selected_edges) != 2:
return {"CANCELLED"}
# Assume the user has selected two edges sharing a vert, but merge verts
# just in case the vertex is coincident but not shared.
all_verts = list(set(selected_edges[0].verts) | set(selected_edges[1].verts))
bmesh.ops.remove_doubles(bm, verts=all_verts, dist=1e-4)
# Subdivide one of the edges to give us an extra vert to play with
# without damaging any faces that already exist.
cut = bmesh.ops.bisect_edges(bm, edges=[selected_edges[1]], cuts=1)
bm.edges.index_update()
bmesh.update_edit_mesh(mesh)
is_wire = selected_edges[0].is_wire
# Recover the original intention of the selected edges after our bisect operation
cut_edges = cut["geom_split"][1:]
selected_edges = [e for e in bm.edges if e.select and e not in cut_edges]
if set(selected_edges[0].verts) & set(cut_edges[0].verts):
selected_edges.append(cut_edges[0])
elif set(selected_edges[0].verts) & set(cut_edges[1].verts):
selected_edges.append(cut_edges[1])
# Calculate the distance to slide each edge to make space for the fillet arc
shared_vert = list(set(selected_edges[0].verts) & set(selected_edges[1].verts))[0]
v1 = selected_edges[0].other_vert(shared_vert)
v2 = selected_edges[1].other_vert(shared_vert)
dir1 = (v1.co - shared_vert.co).normalized()
dir2 = (v2.co - shared_vert.co).normalized()
edge_angle = dir1.angle(dir2)
slide_distance = self.radius / math.tan(edge_angle / 2)
angle = math.pi - edge_angle
fillet_v1 = shared_vert.co + (dir1 * slide_distance)
fillet_v2 = shared_vert.co + (dir2 * slide_distance)
normal = mathutils.geometry.normal([v1.co, shared_vert.co, v2.co])
center = mathutils.geometry.intersect_line_line(
fillet_v1, fillet_v1 + normal.cross(dir1), fillet_v2, fillet_v2 + normal.cross(dir2)
)[0]
v = bm.verts.new(fillet_v1)
steps = self.resolution * 4 if self.resolution else 1
spin = bmesh.ops.spin(bm, geom=[v], axis=normal, cent=center, steps=steps, angle=angle)
bmesh.ops.pointmerge(bm, verts=[shared_vert, v], merge_co=v.co)
bmesh.ops.pointmerge(bm, verts=[v2, spin["geom_last"][0]], merge_co=spin["geom_last"][0].co)
# If faces are involved, then the chamfer edge protects existing faces from the newly created fillet.
# If no faces are involved, we delete the chamfer edge.
if is_wire:
chamfer_edge = [e for e in bm.edges if shared_vert in e.verts and v2 in e.verts][0]
bm.edges.remove(chamfer_edge)
bm.verts.index_update()
bm.edges.index_update()
bmesh.update_edit_mesh(mesh)
return {"FINISHED"}
class CadArcFrom2Points(bpy.types.Operator):
bl_idname = "bim.cad_arc_from_2_points"
bl_label = "CAD Arc from 2 Points"
bl_options = {"REGISTER", "UNDO"}
resolution: bpy.props.IntProperty(name="Arc Resolution", min=1, default=1)
should_flip: bpy.props.BoolProperty(name="Flip", description="Flip arc", default=False)
def draw(self, context):
layout = self.layout
for prop in self.__class__.__annotations__.keys():
layout.prop(self, prop)
def execute(self, context):
bpy.ops.object.mode_set(mode="OBJECT")
bpy.ops.object.mode_set(mode="EDIT")
if bpy.context.mode != "EDIT_MESH":
return {"CANCELLED"}
obj = bpy.context.active_object
region = bpy.context.region
region_3d = bpy.context.area.spaces.active.region_3d
cursor = bpy.context.scene.cursor.location
center = bpy_extras.view3d_utils.location_3d_to_region_2d(region, region_3d, cursor)
if not center:
return {"CANCELLED"}
mesh = obj.data
mw = obj.matrix_world
bm = bmesh.from_edit_mesh(mesh)
selected_verts = [v for v in bm.verts if v.select]
if len(selected_verts) != 2:
return {"CANCELLED"}
v1 = bpy_extras.view3d_utils.location_3d_to_region_2d(region, region_3d, mw @ selected_verts[0].co)
v2 = bpy_extras.view3d_utils.location_3d_to_region_2d(region, region_3d, mw @ selected_verts[1].co)
l1 = v1 - center
l2 = v2 - center
angle = l1.angle_signed(l2)
if self.should_flip:
if angle > 0:
angle = -((math.pi * 2) - angle)
else:
angle = (math.pi * 2) + angle
v = selected_verts[0]
bm.verts.remove(selected_verts[1])
axis = region_3d.view_rotation @ Vector((0, 0, 1))
bmesh.ops.spin(
bm,
geom=[v],
axis=mw.inverted().to_quaternion() @ axis,
cent=mw.inverted() @ cursor,
steps=self.resolution * 4,
angle=-angle,
)
bmesh.update_edit_mesh(mesh)
mesh.update()
return {"FINISHED"}
class CadArcFrom3Points(bpy.types.Operator):
bl_idname = "bim.cad_arc_from_3_points"
bl_label = "CAD Arc from 3 Points"
bl_options = {"REGISTER", "UNDO"}
resolution: bpy.props.IntProperty(name="Arc Resolution", min=1, default=1)
only_recalculate_center: bpy.props.BoolProperty(name="Only Recalculate Center", default=False)
@classmethod
def poll(cls, context):
obj = context.active_object
return obj and obj.type == "MESH" and context.mode == "EDIT_MESH"
def draw(self, context):
layout = self.layout
for prop in self.__class__.__annotations__.keys():
layout.prop(self, prop)
def execute(self, context):
bpy.ops.object.mode_set(mode="OBJECT")
bpy.ops.object.mode_set(mode="EDIT")
if bpy.context.mode != "EDIT_MESH":
return {"CANCELLED"}
obj = bpy.context.edit_object
mesh = obj.data
bm = bmesh.from_edit_mesh(mesh)
arc = [v for v in bm.verts if v.select]
if len(arc) != 3:
return {"CANCELLED"}
elif len([e for e in bm.edges if e.select]) != 2:
return {"CANCELLED"}
sorted_arc = [None, None, None]
for v1 in arc:
connections = 0
for link_edge in v1.link_edges:
v2 = link_edge.other_vert(v1)
if v2 in arc:
connections += 1
if connections == 2: # Midpoint
sorted_arc[1] = v1
else:
sorted_arc[2 if sorted_arc[2] is None else 0] = v1
points = [tuple(v.co) for v in sorted_arc]
verts, _ = tool.Cad.create_arc_segments(points, num_verts=(self.resolution * 4) + 1, make_edges=False)
bm.verts.index_update()
bm.edges.index_update()
index_offset = len(bm.verts)
new_verts = [bm.verts.new(v) for v in verts]
new_edges = [bm.edges.new((new_verts[i], new_verts[i + 1])) for i in range(len(new_verts) - 1)]
bm.verts.remove(sorted_arc[1])
bmesh.ops.remove_doubles(bm, verts=new_verts + [sorted_arc[0], sorted_arc[2]], dist=1e-5)
bmesh.update_edit_mesh(mesh)
return {"FINISHED"}
class CadOffset(bpy.types.Operator):
bl_idname = "bim.cad_offset"
bl_label = "CAD Offset"
bl_options = {"REGISTER", "UNDO"}
distance: bpy.props.FloatProperty(name="Distance", default=0.1)
@classmethod
def poll(self, context):
return context.mode == "EDIT_MESH"
def draw(self, context):
layout = self.layout
for prop in self.__class__.__annotations__.keys():
layout.prop(self, prop)
def cancel_message(self, msg):
self.report({"WARNING"}, msg)
return {"CANCELLED"}
def execute(self, context):
# This is not the same as Mesh Tools or Sverchok. Mesh Tools is based on
# a 3D "rail" which allows fancy 3D edge offsets, but will get different
# results to traditional CAD, which expects offsets to occur on a
# defined 2D workplane. This CAD offset does a pure 2D operation, so it
# always looks correct from the desired "2D workplane". This also
# reduces the settings down to one parameter instead of the huge config
# dialog that Mesh Tools has. Sverchok is 2D based, but has a weird side
# effect of converting an unclosed loop into a closed loop which is also
# not what users expect.
bpy.ops.object.mode_set(mode="OBJECT")
bpy.ops.object.mode_set(mode="EDIT")
obj = context.active_object
mw = obj.matrix_world
bm = bmesh.from_edit_mesh(obj.data)
bm.verts.ensure_lookup_table()
bm.edges.ensure_lookup_table()
edges = [e for e in bm.edges if e.select and not e.hide]
if len(edges) < 1:
return self.cancel_message("NO_EDGES")
verts = set()
[verts.update(e.verts) for e in edges]
# Use the viewport angle to determine the offset direction
for area in bpy.context.screen.areas:
if area.type == "VIEW_3D":
# Don't ask me, I don't know.
# view_matrix = area.spaces.active.region_3d.view_matrix
x = area.spaces.active.region_3d.view_rotation @ Vector((1, 0, 0))
y = area.spaces.active.region_3d.view_rotation @ Vector((0, 1, 0))
z = area.spaces.active.region_3d.view_rotation @ Vector((0, 0, 1))
wp = Matrix([x, y, z, Vector((0, 0, 0))]).to_4x4().transposed()
break
rotation = Matrix.Rotation(pi / 2, 2, "Z")
rotation_i = Matrix.Rotation(-pi / 2, 2, "Z")
# Create loops from edges
loop_edges = set(edges)
loops = []
while loop_edges:
edge = loop_edges.pop()
loop = [edge]
has_found_connected_edge = True
while has_found_connected_edge:
has_found_connected_edge = False
for edge in loop_edges.copy():
edge_verts = set(edge.verts)
if edge_verts & set(loop[0].verts):
loop.insert(0, edge)
loop_edges.remove(edge)
has_found_connected_edge = True
elif edge_verts & set(loop[-1].verts):
loop.append(edge)
loop_edges.remove(edge)
has_found_connected_edge = True
loops.append(loop)
for loop in loops:
all_verts = {v.index for e in loop for v in e.verts}
possible_v1s = []
is_closed = True
for edge in loop:
# If we have an open loop, start at either end instead
v = edge.verts[0]
if len(v.link_edges) == 1:
possible_v1s.append(v)
is_closed = False
else:
for link_edge in v.link_edges:
if link_edge.other_vert(v).index not in all_verts:
possible_v1s.append(v)
is_closed = False
break
v = edge.verts[1]
if len(v.link_edges) == 1:
possible_v1s.append(v)
is_closed = False
else:
for link_edge in v.link_edges:
if link_edge.other_vert(v).index not in all_verts:
possible_v1s.append(v)
is_closed = False
break
# Always start at the same vertex, so that when the operator is
# refreshed with new parameters the axes don't randomly get flipped.
if is_closed:
bm.verts.ensure_lookup_table()
bm.edges.ensure_lookup_table()
start = bm.verts[min(all_verts)]
else:
start = possible_v1s[0] if possible_v1s[0].index < possible_v1s[1].index else possible_v1s[1]
v1 = start
new_verts = []
processed_verts = set()
# [v0] --> v1 --> v2
while v1:
if v1.index in processed_verts:
break
link_verts = []
if len(v1.link_edges) == 1:
v = v1.link_edges[0].other_vert(v1)
link_verts.append(v) if v.index in all_verts else None
else:
v = v1.link_edges[0].other_vert(v1)
link_verts.append(v) if v.index in all_verts else None
v = v1.link_edges[1].other_vert(v1)
link_verts.append(v) if v.index in all_verts else None
if len(link_verts) == 1:
v2 = link_verts[0]
v0 = None if v2.index not in processed_verts else v2
v2 = None if v2.index in processed_verts else v2
else:
v2 = link_verts[0]
v0 = link_verts[1]
if v2.index in processed_verts:
if is_closed and v0.index in processed_verts:
v3 = start
v0 = v0 if v2 == v3 else v2
v2 = v3
else:
v0, v2 = v2, v0
normals = []
v1co = (wp.inverted() @ mw @ v1.co).to_2d()
if v2:
v2co = (wp.inverted() @ mw @ v2.co).to_2d()
direction = (v2co - v1co).normalized()
local_direction = (rotation @ direction).normalized()
normals.append(local_direction)
if v0:
v0co = (wp.inverted() @ mw @ v0.co).to_2d()
direction = (v0co - v1co).normalized()
local_direction = (rotation_i @ direction).normalized()
normals.append(local_direction)
if len(normals) == 2:
# https://stackoverflow.com/a/54042831/9627415
new_normal = (normals[0].lerp(normals[1], 0.5)).normalized()
offset_length = self.distance / sqrt((1 + normals[0].dot(normals[1])) / 2)
offset = mw.inverted().to_quaternion() @ (wp.to_quaternion() @ (new_normal * offset_length).to_3d())
new_vert = v1.co + offset
new_verts.append(bm.verts.new(new_vert))
else:
normal = (normals[0] * self.distance).to_3d()
offset = mw.inverted().to_quaternion() @ (wp.to_quaternion() @ normal)
new_vert = v1.co + offset
new_verts.append(bm.verts.new(new_vert))
processed_verts.add(v1.index)
if v2 in processed_verts:
break
v1 = v2
[bm.edges.new((new_verts[i], new_verts[i + 1])) for i in range(len(new_verts) - 1)]
if is_closed:
bm.edges.new((new_verts[len(new_verts) - 1], new_verts[0]))
bm.verts.index_update()
bm.edges.index_update()
bmesh.update_edit_mesh(obj.data)
return {"FINISHED"}
class AddIfcCircle(bpy.types.Operator):
bl_idname = "bim.add_ifccircle"
bl_label = "Add IfcCircle"
radius: bpy.props.FloatProperty(name="Radius", default=0.1)
@classmethod
def poll(cls, context):
obj = context.active_object
return bool(obj) and obj.type == "MESH"
def execute(self, context):
if self.has_selected_existing_circle(context):
self.change_radius(context)
else:
self.create_circle(context)
return {"FINISHED"}
def has_selected_existing_circle(self, context):
obj = context.active_object
bm = bmesh.from_edit_mesh(obj.data)
verts = [v for v in bm.verts if v.select and not v.hide]
if len(verts) != 2:
return False
groups = []
for i, group in enumerate(obj.vertex_groups):
if "IFCCIRCLE" in group.name:
groups.append(i)
bm.verts.layers.deform.verify()
deform_layer = bm.verts.layers.deform.active
for group in groups:
if group in verts[0][deform_layer] and group in verts[1][deform_layer]:
return True
def change_radius(self, context):
obj = context.active_object
bm = bmesh.from_edit_mesh(obj.data)
verts = [v for v in bm.verts if v.select and not v.hide]
center = verts[0].co.lerp(verts[1].co, 0.5)
verts[0].co = center + ((verts[0].co - center).normalized() * self.radius)
verts[1].co = center + ((verts[1].co - center).normalized() * self.radius)
bm.verts.index_update()
bm.edges.index_update()
bmesh.update_edit_mesh(obj.data)
def create_circle(self, context):
obj = context.active_object
bpy.ops.object.mode_set(mode="OBJECT")
# The last group may be the result of a prior run of this operator.
# I tried looping through all groups but Blender group indices seem to behave unpredictably.
if len(obj.vertex_groups):
last_group = obj.vertex_groups[-1]
verts_in_group = [v for v in obj.data.vertices if last_group.index in [vg.group for vg in v.groups]]
if "IFCCIRCLE" in last_group.name and len(verts_in_group) != 2:
obj.vertex_groups.remove(last_group)
group = obj.vertex_groups.new(name="IFCCIRCLE")
bpy.ops.object.mode_set(mode="EDIT")
bm = bmesh.from_edit_mesh(obj.data)
bm.verts.layers.deform.verify()
deform_layer = bm.verts.layers.deform.active
center = obj.matrix_world.inverted() @ context.scene.cursor.location
v1 = bm.verts.new((center[0], center[1] + self.radius, 0.0))
v2 = bm.verts.new((center[0], center[1] - self.radius, 0.0))
bm.verts.index_update()
bm.edges.new((v1, v2))
for vert in [v1, v2]:
vert[deform_layer][group.index] = 1
bm.verts.index_update()
bm.edges.index_update()
bmesh.update_edit_mesh(obj.data)
class AddIfcArcIndexFillet(bpy.types.Operator):
bl_idname = "bim.add_ifcarcindex_fillet"
bl_label = "Add Arc Index Fillet"
bl_options = {"REGISTER", "UNDO"}
radius: bpy.props.FloatProperty(name="Radius", default=0.1)
@classmethod
def poll(cls, context):
obj = context.active_object
return obj and obj.type == "MESH" and context.mode == "EDIT_MESH"
def draw(self, context):
layout = self.layout
for prop in self.__class__.__annotations__.keys():
layout.prop(self, prop)
def execute(self, context):
if self.has_selected_existing_arc(context):
self.change_radius(context)
else:
self.create_arc(context)
return {"FINISHED"}
def has_selected_existing_arc(self, context):
obj = context.active_object
bm = bmesh.from_edit_mesh(obj.data)
verts = [v for v in bm.verts if v.select and not v.hide]
if len(verts) != 3:
return False
groups = []
for i, group in enumerate(obj.vertex_groups):
if "IFCARCINDEX" in group.name:
groups.append(i)
bm.verts.layers.deform.verify()
deform_layer = bm.verts.layers.deform.active
for group in groups:
try:
if group in verts[0][deform_layer] and group in verts[1][deform_layer]:
return True
except:
pass # Potentially fail if the vert has been removed in the previous operation
def change_radius(self, context):
obj = context.active_object
bm = bmesh.from_edit_mesh(obj.data)
edges = [e for e in bm.edges if e.select and not e.hide]
mid = list(set(edges[0].verts) & set(edges[1].verts))[0]
v1 = edges[0].other_vert(mid)
v2 = edges[1].other_vert(mid)
center = tool.Cad.get_center_of_arc([v1.co, mid.co, v2.co])
if len(v1.link_edges) != 2 or len(v2.link_edges) != 2:
return
v3 = v1.link_edges[1].other_vert(v1) if mid in v1.link_edges[0].verts else v1.link_edges[0].other_vert(v1)
v4 = v2.link_edges[1].other_vert(v2) if mid in v2.link_edges[0].verts else v2.link_edges[0].other_vert(v2)
dir1 = (v3.co - v1.co).normalized()
dir2 = (v4.co - v2.co).normalized()
intersect = mathutils.geometry.intersect_line_line(v3.co, v1.co, v4.co, v2.co)[0]
edge_angle = dir1.angle(dir2)
slide_distance = self.radius / math.tan(edge_angle / 2)
v1.co = intersect + (dir1 * slide_distance)
v2.co = intersect + (dir2 * slide_distance)
normal = mathutils.geometry.normal([v1.co, intersect, v2.co])
center = mathutils.geometry.intersect_line_line(
v1.co, v1.co + normal.cross(dir1), v2.co, v2.co + normal.cross(dir2)
)[0]
mid.co = center + ((v1.co.lerp(v2.co, 0.5) - center).normalized() * self.radius)
bm.verts.index_update()
bm.edges.index_update()
bmesh.update_edit_mesh(obj.data)
def create_arc(self, context):
obj = bpy.context.active_object
bpy.ops.object.mode_set(mode="OBJECT")
# The last group may be the result of a prior run of this operator.
# I tried looping through all groups but Blender group indices seem to behave unpredictably.
if len(obj.vertex_groups):
last_group = obj.vertex_groups[-1]
verts_in_group = [v for v in obj.data.vertices if last_group.index in [vg.group for vg in v.groups]]
if "IFCARCINDEX" in last_group.name and len(verts_in_group) != 3:
obj.vertex_groups.remove(last_group)
group = obj.vertex_groups.new(name="IFCARCINDEX")
bpy.ops.object.mode_set(mode="EDIT")
bm = bmesh.from_edit_mesh(obj.data)
bm.verts.layers.deform.verify()
deform_layer = bm.verts.layers.deform.active
selected_edges = [e for e in bm.edges if e.select]
if len(selected_edges) != 2:
return {"CANCELLED"}
# Assume the user has selected two edges sharing a vert, but merge verts
# just in case the vertex is coincident but not shared.
all_verts = list(set(selected_edges[0].verts) | set(selected_edges[1].verts))
bmesh.ops.remove_doubles(bm, verts=all_verts, dist=1e-4)
# Calculate the distance to slide each edge to make space for the fillet arc
shared_vert = list(set(selected_edges[0].verts) & set(selected_edges[1].verts))[0]
v1 = selected_edges[0].other_vert(shared_vert)
v2 = selected_edges[1].other_vert(shared_vert)
dir1 = (v1.co - shared_vert.co).normalized()
dir2 = (v2.co - shared_vert.co).normalized()
edge_angle = dir1.angle(dir2)
slide_distance = self.radius / math.tan(edge_angle / 2)
angle = math.pi - edge_angle
fillet_v1co = shared_vert.co + (dir1 * slide_distance)
fillet_v2co = shared_vert.co + (dir2 * slide_distance)
normal = mathutils.geometry.normal([v1.co, shared_vert.co, v2.co])
center = mathutils.geometry.intersect_line_line(
fillet_v1co, fillet_v1co + normal.cross(dir1), fillet_v2co, fillet_v2co + normal.cross(dir2)
)[0]
fillet_v1 = bm.verts.new(fillet_v1co)
fillet_v2 = bm.verts.new(fillet_v2co)
midpointco = center + ((fillet_v1co.lerp(fillet_v2.co, 0.5) - center).normalized() * self.radius)
midpoint = bm.verts.new(midpointco)
bm.edges.new((v1, fillet_v1))
bm.edges.new((v2, fillet_v2))
bm.edges.new((fillet_v1, midpoint))
bm.edges.new((fillet_v2, midpoint))
bmesh.ops.delete(bm, geom=[shared_vert], context="VERTS")
for vert in [fillet_v1, midpoint, fillet_v2]:
vert[deform_layer][group.index] = 1
bm.verts.index_update()
bm.edges.index_update()
bmesh.update_edit_mesh(obj.data)
class AlignViewToProfile(bpy.types.Operator):
bl_idname = "bim.align_view_to_profile"
bl_label = "Align View To Profile"
# __ ___
# .'. -- . '.
# /U) __ (O|
# /.' ()() '.\._
# .',/;,_.--._.;;) . '--..__
# / ,///|.__.|.\\\ \ '. '.''---..___
# /'._ '' || || '' _'\ : \ ' . '.
# / || || '., ) ) : \
# :'-.__ _ || || _ __.' _\_ .' ' ' ,)
# ( ' |' ( __= ___..-._ ( (.\\
# ('\ .___ ___. /'.___= \.\.\
# \\\-..____________..-'///
@classmethod
def poll(cls, context):
obj = context.active_object
return bool(obj) and obj.type == "MESH"
def execute(self, context):
bpy.ops.object.mode_set(mode="OBJECT")
bpy.ops.view3d.view_axis(type="TOP", align_active=True)
region_3d = bpy.context.area.spaces.active.region_3d
region_3d.view_perspective = "ORTHO"
bpy.ops.object.mode_set(mode="EDIT")
return {"FINISHED"}
class AddRectangle(bpy.types.Operator):
bl_idname = "bim.add_rectangle"
bl_label = "Add Rectangle"
x: bpy.props.FloatProperty(name="X", default=0.1)
y: bpy.props.FloatProperty(name="Y", default=0.1)
@classmethod
def poll(cls, context):
obj = context.active_object
return bool(obj) and obj.type == "MESH"
def execute(self, context):
obj = context.active_object
bm = bmesh.from_edit_mesh(obj.data)
cursor = obj.matrix_world.inverted() @ context.scene.cursor.location
new_verts = [
bm.verts.new((cursor[0], cursor[1], 0.0)),
bm.verts.new((cursor[0] + self.x, cursor[1], 0.0)),
bm.verts.new((cursor[0] + self.x, cursor[1] + self.y, 0.0)),
bm.verts.new((cursor[0], cursor[1] + self.y, 0.0)),
]
[bm.edges.new((new_verts[i], new_verts[i + 1])) for i in range(len(new_verts) - 1)]
bm.edges.new((new_verts[len(new_verts) - 1], new_verts[0]))
bm.verts.index_update()
bm.edges.index_update()
bmesh.update_edit_mesh(obj.data)
return {"FINISHED"}
@@ -0,0 +1,30 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2022 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
from blenderbim.bim.module.model.data import AuthoringData
from blenderbim.bim.module.model.root import ConstrTypeEntityNotFound
from bpy.types import PropertyGroup
class BIMCadProperties(PropertyGroup):
resolution: bpy.props.IntProperty(name="Arc Resolution", min=1, default=1)
radius: bpy.props.FloatProperty(name="Radius", default=0.1)
distance: bpy.props.FloatProperty(name="Distance", default=0.1)
x: bpy.props.FloatProperty(name="X", default=0.2)
y: bpy.props.FloatProperty(name="Y", default=0.1)
@@ -0,0 +1,213 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2022 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 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
class CadTool(WorkSpaceTool):
bl_space_type = "VIEW_3D"
bl_context_mode = "EDIT_MESH"
bl_idname = "bim.cad_tool"
bl_label = "CAD Tool"
bl_description = "Gives you CAD authoring related superpowers"
bl_icon = os.path.join(os.path.dirname(__file__), "ops.authoring.cad")
bl_widget = None
# https://docs.blender.org/api/current/bpy.types.KeyMapItems.html
bl_keymap = (
("bim.cad_hotkey", {"type": "C", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_C")]}),
("bim.cad_hotkey", {"type": "E", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_E")]}),
("bim.cad_hotkey", {"type": "F", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_F")]}),
("bim.cad_hotkey", {"type": "O", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_O")]}),
("bim.cad_hotkey", {"type": "Q", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_Q")]}),
("bim.cad_hotkey", {"type": "R", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_R")]}),
("bim.cad_hotkey", {"type": "T", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_T")]}),
("bim.cad_hotkey", {"type": "V", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_V")]}),
("bim.cad_hotkey", {"type": "X", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_X")]}),
)
def draw_settings(context, layout, tool):
obj = context.active_object
if obj and obj.data and hasattr(obj.data, "BIMMeshProperties") and obj.data.BIMMeshProperties.is_profile:
row = layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="", icon="EVENT_Q")
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")
row.label(text="", icon="EVENT_E")
row.operator("bim.cad_hotkey", text="Extend").hotkey = "S_E"
row = layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="", icon="EVENT_T")
row.operator("bim.cad_hotkey", text="Mitre").hotkey = "S_T"
row = layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="", icon="EVENT_F")
row.operator("bim.cad_hotkey", text="Fillet").hotkey = "S_F"
row = layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="", icon="EVENT_O")
row.operator("bim.cad_hotkey", text="Offset").hotkey = "S_O"
row = layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="", icon="EVENT_R")
row.operator("bim.add_rectangle", text="Rectangle")
row = layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="", icon="EVENT_C")
row.operator("bim.add_ifccircle", text="Circle")
row = layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="", icon="EVENT_V")
row.operator("bim.set_arc_index", text="3-Point Arc")
row = layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="", icon="EVENT_X")
row.operator("bim.reset_vertex", text="Reset Vertex")
else:
row = layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="", icon="EVENT_E")
row.operator("bim.cad_hotkey", text="Extend").hotkey = "S_E"
row = layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="", icon="EVENT_T")
row.operator("bim.cad_hotkey", text="Mitre").hotkey = "S_T"
row = layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="", icon="EVENT_F")
row.operator("bim.cad_hotkey", text="Fillet").hotkey = "S_F"
row = layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="", icon="EVENT_O")
row.operator("bim.cad_hotkey", text="Offset").hotkey = "S_O"
row = layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="2-Point Arc", icon="EVENT_C")
row = layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="", icon="EVENT_V")
row.operator("bim.cad_hotkey", text="3-Point Arc").hotkey = "S_V"
class CadHotkey(bpy.types.Operator):
bl_idname = "bim.cad_hotkey"
bl_label = "CAD Hotkey"
bl_options = {"REGISTER", "UNDO"}
hotkey: bpy.props.StringProperty()
def execute(self, context):
self.props = context.scene.BIMCadProperties
getattr(self, f"hotkey_{self.hotkey}")()
return {"FINISHED"}
def draw(self, context):
props = context.scene.BIMCadProperties
if self.hotkey == "S_C":
if self.is_profile():
row = self.layout.row()
row.prop(props, "radius")
elif self.hotkey == "S_F":
if not self.is_profile():
row = self.layout.row()
row.prop(props, "resolution")
row = self.layout.row()
row.prop(props, "radius")
elif self.hotkey == "S_O":
row = self.layout.row()
row.prop(props, "distance")
elif self.hotkey == "S_R":
row = self.layout.row()
row.prop(props, "x")
row = self.layout.row()
row.prop(props, "y")
elif self.hotkey == "S_V":
if not self.is_profile():
row = self.layout.row()
row.prop(props, "resolution")
def hotkey_S_C(self):
if self.is_profile():
bpy.ops.bim.add_ifccircle(radius=self.props.radius)
else:
bpy.ops.bim.cad_arc_from_2_points()
def hotkey_S_E(self):
bpy.ops.bim.cad_trim_extend()
def hotkey_S_F(self):
if self.is_profile():
bpy.ops.bim.add_ifcarcindex_fillet(radius=self.props.radius)
else:
bpy.ops.bim.cad_fillet(resolution=self.props.resolution, radius=self.props.radius)
def hotkey_S_O(self):
bpy.ops.bim.cad_offset(distance=self.props.distance)
def hotkey_S_Q(self):
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():
bpy.ops.bim.add_rectangle(x=self.props.x, y=self.props.y)
def hotkey_S_T(self):
bpy.ops.bim.cad_mitre()
def hotkey_S_V(self):
if self.is_profile():
bpy.ops.bim.set_arc_index()
else:
bpy.ops.bim.cad_arc_from_3_points(resolution=self.props.resolution)
def hotkey_S_X(self):
if self.is_profile():
bpy.ops.bim.reset_vertex()
def is_profile(self):
obj = bpy.context.active_object
return obj and obj.data and hasattr(obj.data, "BIMMeshProperties") and obj.data.BIMMeshProperties.is_profile
@@ -20,23 +20,26 @@ import bpy
from . import ui, prop, operator
classes = (
operator.LoadClassificationLibrary,
operator.AddClassification,
operator.RemoveClassification,
operator.EnableEditingClassification,
operator.DisableEditingClassification,
operator.EditClassification,
operator.RemoveClassificationReference,
operator.EnableEditingClassificationReference,
operator.DisableEditingClassificationReference,
operator.EditClassificationReference,
operator.AddClassificationReference,
operator.ChangeClassificationLevel,
operator.DisableEditingClassification,
operator.DisableEditingClassificationReference,
operator.DisableEditingClassificationReferences,
operator.EditClassification,
operator.EditClassificationReference,
operator.EnableEditingClassification,
operator.EnableEditingClassificationReference,
operator.LoadClassificationLibrary,
operator.RemoveClassification,
operator.RemoveClassificationReference,
prop.ClassificationReference,
prop.BIMClassificationProperties,
prop.BIMClassificationReferenceProperties,
ui.BIM_PT_classifications,
ui.BIM_PT_classification_references,
ui.BIM_PT_material_classifications,
ui.BIM_PT_cost_classifications,
ui.BIM_UL_classifications,
)
@@ -0,0 +1,143 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2022 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 ifcopenshell
import ifcopenshell.util.date
import ifcopenshell.util.classification
import blenderbim.tool as tool
from blenderbim.bim.ifc import IfcStore
def refresh():
ClassificationsData.is_loaded = False
ClassificationReferencesData.is_loaded = False
MaterialClassificationsData.is_loaded = False
CostClassificationsData.is_loaded = False
class ClassificationsData:
data = {}
is_loaded = False
@classmethod
def load(cls):
cls.is_loaded = True
cls.data["has_classification_file"] = cls.has_classification_file()
cls.data["classifications"] = cls.classifications()
cls.data["available_classifications"] = cls.available_classifications()
@classmethod
def has_classification_file(cls):
return bool(IfcStore.classification_file)
@classmethod
def classifications(cls):
results = []
for element in tool.Ifc.get().by_type("IfcClassification"):
data = element.get_info()
if tool.Ifc.get().schema == "IFC2X3" and element.EditionDate:
data["EditionDate"] = ifcopenshell.util.date.ifc2datetime(data["EditionDate"])
results.append(data)
return results
@classmethod
def available_classifications(cls):
if not IfcStore.classification_file:
return []
return [(str(e.id()), e.Name, "") for e in IfcStore.classification_file.by_type("IfcClassification")]
class ReferencesData:
@classmethod
def is_available_classification_added(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")]
class ClassificationReferencesData(ReferencesData):
data = {}
is_loaded = False
@classmethod
def load(cls):
cls.is_loaded = True
cls.data["references"] = cls.references()
cls.data["is_available_classification_added"] = cls.is_available_classification_added()
@classmethod
def references(cls):
results = []
element = tool.Ifc.get_entity(bpy.context.active_object)
if element:
for reference in ifcopenshell.util.classification.get_references(element):
data = reference.get_info()
del data["ReferencedSource"]
results.append(data)
return results
class MaterialClassificationsData(ReferencesData):
data = {}
is_loaded = False
@classmethod
def load(cls):
cls.is_loaded = True
cls.data["references"] = cls.references()
cls.data["is_available_classification_added"] = cls.is_available_classification_added()
@classmethod
def references(cls):
results = []
element = tool.Ifc.get_entity(bpy.context.active_object.active_material)
if element:
for reference in ifcopenshell.util.classification.get_references(element):
data = reference.get_info()
del data["ReferencedSource"]
results.append(data)
return results
class CostClassificationsData(ReferencesData):
data = {}
is_loaded = False
@classmethod
def load(cls):
cls.is_loaded = True
cls.data["references"] = cls.references()
cls.data["is_available_classification_added"] = cls.is_available_classification_added()
@classmethod
def references(cls):
results = []
element = tool.Ifc.get().by_id(
bpy.context.scene.BIMCostProperties.cost_items[
bpy.context.scene.BIMCostProperties.active_cost_item_index
].ifc_definition_id
)
if element:
for reference in ifcopenshell.util.classification.get_references(element):
data = reference.get_info()
del data["ReferencedSource"]
results.append(data)
return results
@@ -1,5 +1,5 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
# Copyright (C) 2020-2022 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BlenderBIM Add-on.
#
@@ -18,46 +18,40 @@
import bpy
import json
import ifcopenshell
import ifcopenshell.api
import blenderbim.tool as tool
import blenderbim.bim.helper
from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.classification.data import Data
from blenderbim.bim.module.classification.prop import getClassifications, getReferences
class LoadClassificationLibrary(bpy.types.Operator):
class LoadClassificationLibrary(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.load_classification_library"
bl_label = "Load Classification Library"
filename_ext = ".ifc"
filter_glob: bpy.props.StringProperty(default="*.ifc;*.ifczip;*.ifcxml", options={"HIDDEN"})
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def execute(self, context):
Data.load_library(self.filepath)
getClassifications(self, context)
return {"FINISHED"}
def invoke(self, context, event):
context.window_manager.fileselect_add(self)
return {"RUNNING_MODAL"}
def _execute(self, context):
IfcStore.classification_file = ifcopenshell.open(self.filepath)
class AddClassification(bpy.types.Operator):
class AddClassification(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.add_classification"
bl_label = "Add Classification"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
props = context.scene.BIMClassificationProperties
ifcopenshell.api.run(
"classification.add_classification",
IfcStore.get_file(),
**{"classification": Data.library_file.by_id(int(props.available_classifications))},
tool.Ifc.get(),
classification=IfcStore.classification_file.by_id(int(props.available_classifications)),
)
Data.load(IfcStore.get_file())
return {"FINISHED"}
class EnableEditingClassification(bpy.types.Operator):
@@ -67,19 +61,21 @@ class EnableEditingClassification(bpy.types.Operator):
classification: bpy.props.IntProperty()
def execute(self, context):
def callback(name, prop, data):
if name == "ReferenceTokens":
new = bpy.context.scene.BIMGeoreferenceProperties.projected_crs.add()
new.name = name
new.data_type = "string"
new.is_null = data[name] is None
new.is_optional = True
new.string_value = "" if new.is_null else json.dumps(data[name])
return True
props = context.scene.BIMClassificationProperties
props.classification_attributes.clear()
classification_data = Data.classifications[self.classification]
for attribute in IfcStore.get_schema().declaration_by_name("IfcClassification").all_attributes():
new = props.classification_attributes.add()
new.name = attribute.name()
new.is_null = classification_data[attribute.name()] is None
new.is_optional = attribute.optional()
new.data_type = "string"
if attribute.name() == "ReferenceTokens":
new.string_value = "" if new.is_null else json.dumps(classification_data[attribute.name()])
else:
new.string_value = "" if new.is_null else classification_data[attribute.name()]
blenderbim.bim.helper.import_attributes2(
tool.Ifc.get().by_id(self.classification), props.classification_attributes, callback
)
props.active_classification_id = self.classification
return {"FINISHED"}
@@ -94,34 +90,26 @@ class DisableEditingClassification(bpy.types.Operator):
return {"FINISHED"}
class RemoveClassification(bpy.types.Operator):
class RemoveClassification(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.remove_classification"
bl_label = "Remove Classification"
bl_options = {"REGISTER", "UNDO"}
classification: bpy.props.IntProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"classification.remove_classification",
self.file,
**{"classification": self.file.by_id(self.classification)},
tool.Ifc.get(),
classification=self.file.by_id(self.classification),
)
Data.load(IfcStore.get_file())
return {"FINISHED"}
class EditClassification(bpy.types.Operator):
class EditClassification(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.edit_classification"
bl_label = "Edit Classification"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
props = context.scene.BIMClassificationProperties
attributes = {}
@@ -138,9 +126,7 @@ class EditClassification(bpy.types.Operator):
self.file,
**{"classification": self.file.by_id(props.active_classification_id), "attributes": attributes},
)
Data.load(IfcStore.get_file())
bpy.ops.bim.disable_editing_classification()
return {"FINISHED"}
class EnableEditingClassificationReference(bpy.types.Operator):
@@ -154,16 +140,7 @@ class EnableEditingClassificationReference(bpy.types.Operator):
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
props = obj.BIMClassificationReferenceProperties
props.reference_attributes.clear()
reference_data = Data.references[self.reference]
for attribute in IfcStore.get_schema().declaration_by_name("IfcClassificationReference").all_attributes():
if attribute.name() == "ReferencedSource":
continue
new = props.reference_attributes.add()
new.name = attribute.name()
new.is_null = reference_data[attribute.name()] is None
new.is_optional = attribute.optional()
new.data_type = "string"
new.string_value = "" if new.is_null else reference_data[attribute.name()]
blenderbim.bim.helper.import_attributes2(tool.Ifc.get().by_id(self.reference), props.reference_attributes)
props.active_reference_id = self.reference
return {"FINISHED"}
@@ -180,41 +157,47 @@ class DisableEditingClassificationReference(bpy.types.Operator):
return {"FINISHED"}
class RemoveClassificationReference(bpy.types.Operator):
class RemoveClassificationReference(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.remove_classification_reference"
bl_label = "Remove Classification Reference"
bl_options = {"REGISTER", "UNDO"}
reference: bpy.props.IntProperty()
obj: bpy.props.StringProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
obj_type: bpy.props.StringProperty()
def _execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"classification.remove_reference",
self.file,
**{
"reference": self.file.by_id(self.reference),
"product": self.file.by_id(obj.BIMObjectProperties.ifc_definition_id),
},
)
Data.load(IfcStore.get_file(), obj.BIMObjectProperties.ifc_definition_id)
Data.load(IfcStore.get_file())
return {"FINISHED"}
if self.obj_type == "Object":
if context.selected_objects:
objects = [o.name for o in context.selected_objects]
else:
objects = [context.active_object.name]
else:
objects = [self.obj]
identification = tool.Ifc.get().by_id(self.reference)[0]
for obj in objects:
ifc_definition_id = blenderbim.bim.helper.get_obj_ifc_definition_id(context, obj, self.obj_type)
element = tool.Ifc.get().by_id(ifc_definition_id)
references = ifcopenshell.util.classification.get_references(element, should_inherit=False)
for reference in references:
if reference[0] == identification:
ifcopenshell.api.run(
"classification.remove_reference",
tool.Ifc.get(),
reference=reference,
product=element,
)
break
class EditClassificationReference(bpy.types.Operator):
class EditClassificationReference(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.edit_classification_reference"
bl_label = "Edit Classification Reference"
bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
props = obj.BIMClassificationReferenceProperties
@@ -228,48 +211,47 @@ class EditClassificationReference(bpy.types.Operator):
ifcopenshell.api.run(
"classification.edit_reference",
self.file,
**{"reference": self.file.by_id(props.active_reference_id), "attributes": attributes},
reference=self.file.by_id(props.active_reference_id),
attributes=attributes,
)
Data.load(IfcStore.get_file())
bpy.ops.bim.disable_editing_classification_reference()
return {"FINISHED"}
class AddClassificationReference(bpy.types.Operator):
class AddClassificationReference(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.add_classification_reference"
bl_label = "Add Classification Reference"
bl_options = {"REGISTER", "UNDO"}
reference: bpy.props.IntProperty()
obj: bpy.props.StringProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
obj_type: bpy.props.StringProperty()
def _execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
self.file = IfcStore.get_file()
classification = None
if self.obj_type == "Object":
if context.selected_objects:
objects = [o.name for o in context.selected_objects]
else:
objects = [context.active_object.name]
else:
objects = [self.obj]
props = context.scene.BIMClassificationProperties
classification_name = Data.library_classifications[int(props.available_classifications)]
for classification_id, classification in Data.classifications.items():
if classification["Name"] == classification_name:
classification = self.file.by_id(classification_id)
classification = None
classification_name = IfcStore.classification_file.by_id(int(props.available_classifications)).Name
for element in tool.Ifc.get().by_type("IfcClassification"):
if element.Name == classification_name:
classification = element
break
ifcopenshell.api.run(
"classification.add_reference",
self.file,
**{
"reference": Data.library_file.by_id(self.reference),
"product": self.file.by_id(obj.BIMObjectProperties.ifc_definition_id),
"classification": classification,
},
)
Data.load(IfcStore.get_file(), obj.BIMObjectProperties.ifc_definition_id)
Data.load(IfcStore.get_file())
return {"FINISHED"}
for obj in objects:
ifc_definition_id = blenderbim.bim.helper.get_obj_ifc_definition_id(context, obj, self.obj_type)
if not ifc_definition_id:
continue
ifcopenshell.api.run(
"classification.add_reference",
tool.Ifc.get(),
reference=IfcStore.classification_file.by_id(self.reference),
product=tool.Ifc.get().by_id(ifc_definition_id),
classification=classification,
)
class ChangeClassificationLevel(bpy.types.Operator):
@@ -279,5 +261,28 @@ class ChangeClassificationLevel(bpy.types.Operator):
parent_id: bpy.props.IntProperty()
def execute(self, context):
getReferences(self, context, parent_id=self.parent_id)
props = context.scene.BIMClassificationProperties
props.available_library_references.clear()
for reference in IfcStore.classification_file.by_id(self.parent_id).HasReferences:
new = props.available_library_references.add()
new.identification = reference.Identification or ""
new.name = reference.Name or ""
new.ifc_definition_id = reference.id()
new.has_references = bool(reference.HasReferences)
new.referenced_source
if reference.ReferencedSource.is_a("IfcClassificationReference"):
props.active_library_referenced_source = reference.ReferencedSource.ReferencedSource.id()
else:
props.active_library_referenced_source = 0
return {"FINISHED"}
class DisableEditingClassificationReferences(bpy.types.Operator):
bl_idname = "bim.disable_editing_classification_references"
bl_label = "Disable Editing Classification References"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
props = context.scene.BIMClassificationProperties
props.available_library_references.clear()
return {"FINISHED"}
@@ -17,8 +17,9 @@
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.prop import StrProperty, Attribute
from ifcopenshell.api.classification.data import Data
from blenderbim.bim.module.classification.data import ClassificationsData
from bpy.types import PropertyGroup
from bpy.props import (
PointerProperty,
@@ -31,42 +32,11 @@ from bpy.props import (
CollectionProperty,
)
classification_enum = []
def purge():
global classification_enum
classification_enum = []
def getClassifications(self, context):
global classification_enum
if len(classification_enum) < 1:
classification_enum.clear()
classification_enum.extend([(str(i), n, "") for i, n in Data.library_classifications.items()])
if classification_enum:
getReferences(self, context, parent_id=int(classification_enum[0][0]))
return classification_enum
def updateClassification(self, context):
getReferences(self, context, parent_id=int(self.available_classifications))
def getReferences(self, context, parent_id=None):
props = context.scene.BIMClassificationProperties
props.available_library_references.clear()
for reference in Data.library_file.by_id(parent_id).HasReferences:
new = props.available_library_references.add()
new.identification = reference.Identification or ""
new.name = reference.Name or ""
new.ifc_definition_id = reference.id()
new.has_references = bool(reference.HasReferences)
new.referenced_source
if reference.ReferencedSource.is_a("IfcClassificationReference"):
props.active_library_referenced_source = reference.ReferencedSource.ReferencedSource.id()
else:
props.active_library_referenced_source = 0
def get_available_classifications(self, context):
if not ClassificationsData.is_loaded:
ClassificationsData.load()
return ClassificationsData.data["available_classifications"]
class ClassificationReference(PropertyGroup):
@@ -78,9 +48,7 @@ class ClassificationReference(PropertyGroup):
class BIMClassificationProperties(PropertyGroup):
available_classifications: EnumProperty(
items=getClassifications, name="Available Classifications", update=updateClassification
)
available_classifications: EnumProperty(items=get_available_classifications, name="Available Classifications")
classification_attributes: CollectionProperty(name="Classification Attributes", type=Attribute)
active_classification_id: IntProperty(name="Active Classification Id")
available_library_references: CollectionProperty(name="Available Library References", type=ClassificationReference)
@@ -16,10 +16,17 @@
# 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 blenderbim.bim.helper
import blenderbim.tool as tool
import blenderbim.bim.module.classification.prop as classification_prop
from bpy.types import Panel, UIList
from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.classification.data import Data
from blenderbim.bim.module.classification.data import (
ClassificationsData,
ClassificationReferencesData,
MaterialClassificationsData,
CostClassificationsData,
)
class BIM_PT_classifications(Panel):
@@ -33,15 +40,15 @@ class BIM_PT_classifications(Panel):
@classmethod
def poll(cls, context):
return IfcStore.get_file()
return tool.Ifc.get()
def draw(self, context):
if not Data.is_loaded:
Data.load(IfcStore.get_file())
if not ClassificationsData.is_loaded:
ClassificationsData.load()
self.props = context.scene.BIMClassificationProperties
if Data.library_file:
if ClassificationsData.data["has_classification_file"]:
row = self.layout.row(align=True)
row.prop(self.props, "available_classifications", text="")
row.operator("bim.load_classification_library", text="", icon="IMPORT")
@@ -51,36 +58,100 @@ class BIM_PT_classifications(Panel):
row.label(text="No Active Classification Library")
row.operator("bim.load_classification_library", text="", icon="IMPORT")
for classification_id, classification in Data.classifications.items():
if self.props.active_classification_id == classification_id:
self.draw_editable_ui(classification)
for classification in ClassificationsData.data["classifications"]:
if self.props.active_classification_id == classification["id"]:
self.draw_editable_ui()
else:
self.draw_ui(classification_id, classification)
self.draw_ui(classification)
def draw_editable_ui(self, classification):
def draw_editable_ui(self):
row = self.layout.row(align=True)
row.prop(self.props.classification_attributes.get("Name"), "string_value", text="", icon="ASSET_MANAGER")
row.operator("bim.edit_classification", text="", icon="CHECKMARK")
row.operator("bim.edit_classification", text="Save changes", icon="CHECKMARK")
row.operator("bim.disable_editing_classification", text="", icon="CANCEL")
blenderbim.bim.helper.draw_attributes(self.props.classification_attributes, self.layout)
for attribute in self.props.classification_attributes:
if attribute.name == "Name":
continue
row = self.layout.row(align=True)
row.prop(attribute, "string_value", text=attribute.name)
if attribute.is_optional:
row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
def draw_ui(self, classification_id, classification):
def draw_ui(self, classification):
row = self.layout.row(align=True)
row.label(text=classification["Name"], icon="ASSET_MANAGER")
if not self.props.active_classification_id:
op = row.operator("bim.enable_editing_classification", text="", icon="GREASEPENCIL")
op.classification = classification_id
row.operator("bim.remove_classification", text="", icon="X").classification = classification_id
op.classification = classification["id"]
row.operator("bim.remove_classification", text="", icon="X").classification = classification["id"]
class BIM_PT_classification_references(Panel):
class ReferenceUI:
def draw_ui(self, context):
obj = context.active_object
self.oprops = obj.BIMObjectProperties
self.sprops = context.scene.BIMClassificationProperties
self.props = obj.BIMClassificationReferenceProperties
self.file = IfcStore.get_file()
self.draw_add_ui(context)
if not self.data.data["references"]:
row = self.layout.row(align=True)
row.label(text="No References")
for reference in self.data.data["references"]:
if self.props.active_reference_id == reference["id"]:
self.draw_editable_ui()
else:
self.draw_reference_ui(reference)
def draw_add_ui(self, context):
if not self.data.data["is_available_classification_added"]:
return
row = self.layout.row(align=True)
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.parent_id = int(self.sprops.available_classifications)
return
if self.sprops.active_library_referenced_source:
op = row.operator("bim.change_classification_level", text="", icon="FRAME_PREV")
op.parent_id = self.sprops.active_library_referenced_source
if self.sprops.active_library_reference_index < len(self.sprops.available_library_references):
op = row.operator("bim.add_classification_reference", text="", icon="ADD")
op.obj = self.obj
op.obj_type = self.obj_type
op.reference = self.sprops.available_library_references[
self.sprops.active_library_reference_index
].ifc_definition_id
row.operator("bim.disable_editing_classification_references", text="", icon="CANCEL")
self.layout.template_list(
"BIM_UL_classifications",
"",
self.sprops,
"available_library_references",
self.sprops,
"active_library_reference_index",
)
def draw_editable_ui(self):
row = self.layout.row(align=True)
row.operator("bim.edit_classification_reference", text="Save changes", icon="CHECKMARK")
row.operator("bim.disable_editing_classification_reference", text="", icon="CANCEL")
blenderbim.bim.helper.draw_attributes(self.props.reference_attributes, self.layout)
def draw_reference_ui(self, reference):
row = self.layout.row(align=True)
if self.file.schema == "IFC2X3":
name = reference["ItemReference"] or "No Identification"
else:
name = reference["Identification"] or "No Identification"
row.label(text=name, icon="ASSET_MANAGER")
row.label(text=reference["Name"] or "")
if not self.props.active_reference_id:
op = row.operator("bim.enable_editing_classification_reference", text="", icon="GREASEPENCIL")
op.reference = reference["id"]
op = row.operator("bim.remove_classification_reference", text="", icon="X")
op.reference = reference["id"]
op.obj = self.obj
op.obj_type = self.obj_type
class BIM_PT_classification_references(Panel, ReferenceUI):
bl_label = "IFC Classification References"
bl_idname = "BIM_PT_classification_references"
bl_options = {"DEFAULT_CLOSED"}
@@ -93,85 +164,65 @@ class BIM_PT_classification_references(Panel):
def poll(cls, context):
if not context.active_object:
return False
if not IfcStore.get_element(context.active_object.BIMObjectProperties.ifc_definition_id):
return False
return bool(context.active_object.BIMObjectProperties.ifc_definition_id)
return bool(tool.Ifc.get_entity(context.active_object))
def draw(self, context):
obj = context.active_object
self.oprops = obj.BIMObjectProperties
self.sprops = context.scene.BIMClassificationProperties
self.props = obj.BIMClassificationReferenceProperties
self.file = IfcStore.get_file()
if not Data.is_loaded:
Data.load(IfcStore.get_file())
if self.oprops.ifc_definition_id not in Data.products:
Data.load(IfcStore.get_file(), self.oprops.ifc_definition_id)
if not ClassificationReferencesData.is_loaded:
ClassificationReferencesData.load()
self.data = ClassificationReferencesData
self.obj = context.active_object.name
self.obj_type = "Object"
self.draw_ui(context)
self.draw_add_ui(context)
reference_ids = Data.products[self.oprops.ifc_definition_id]
if not reference_ids:
row = self.layout.row(align=True)
row.label(text="No References")
class BIM_PT_material_classifications(Panel, ReferenceUI):
bl_label = "IFC Material Classifications"
bl_idname = "BIM_PT_material_classifications"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "material"
for reference_id in reference_ids:
reference = Data.references[reference_id]
if self.props.active_reference_id == reference_id:
self.draw_editable_ui(reference)
else:
self.draw_ui(reference_id, reference)
@classmethod
def poll(cls, context):
if not tool.Ifc.get():
return False
try:
return bool(context.active_object.active_material.BIMObjectProperties.ifc_definition_id)
except:
return False
def draw_add_ui(self, context):
if not classification_prop.getClassifications(self.sprops, context):
return
def draw(self, context):
if not MaterialClassificationsData.is_loaded:
MaterialClassificationsData.load()
self.data = MaterialClassificationsData
self.obj = context.active_object.active_material.name
self.obj_type = "Material"
self.draw_ui(context)
name = Data.library_classifications[int(self.sprops.available_classifications)]
if name in [c["Name"] for c in Data.classifications.values()]:
row = self.layout.row(align=True)
row.prop(self.sprops, "available_classifications", text="")
if self.sprops.active_library_referenced_source:
op = row.operator("bim.change_classification_level", text="", icon="FRAME_PREV")
op.parent_id = self.sprops.active_library_referenced_source
op = row.operator("bim.add_classification_reference", text="", icon="ADD")
op.reference = self.sprops.available_library_references[
self.sprops.active_library_reference_index
].ifc_definition_id
self.layout.template_list(
"BIM_UL_classifications",
"",
self.sprops,
"available_library_references",
self.sprops,
"active_library_reference_index",
)
def draw_editable_ui(self, reference):
row = self.layout.row(align=True)
row.prop(self.props.reference_attributes.get("Name"), "string_value", text="", icon="ASSET_MANAGER")
row.operator("bim.edit_classification_reference", text="", icon="CHECKMARK")
row.operator("bim.disable_editing_classification_reference", text="", icon="CANCEL")
class BIM_PT_cost_classifications(Panel, ReferenceUI):
bl_label = "IFC Cost Classifications"
bl_idname = "BIM_PT_cost_classifications"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_cost_schedules"
for attribute in self.props.reference_attributes:
if attribute.name == "Name":
continue
row = self.layout.row(align=True)
row.prop(attribute, "string_value", text=attribute.name)
if attribute.is_optional:
row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
@classmethod
def poll(cls, context):
if not tool.Ifc.get():
return False
return bool(context.scene.BIMCostProperties.cost_items)
def draw_ui(self, reference_id, reference):
row = self.layout.row(align=True)
if self.file.schema == "IFC2X3":
name = reference["ItemReference"] or "No Identification"
else:
name = reference["Identification"] or "No Identification"
row.label(text=name, icon="ASSET_MANAGER")
row.label(text=reference["Name"] or "")
if not self.props.active_reference_id:
op = row.operator("bim.enable_editing_classification_reference", text="", icon="GREASEPENCIL")
op.reference = reference_id
row.operator("bim.remove_classification_reference", text="", icon="X").reference = reference_id
def draw(self, context):
if not CostClassificationsData.is_loaded:
CostClassificationsData.load()
self.data = CostClassificationsData
self.obj = ""
self.obj_type = "Cost"
self.draw_ui(context)
class BIM_UL_classifications(UIList):
@@ -17,7 +17,9 @@
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
from blenderbim.bim.prop import StrProperty, Attribute
from ifcopenshell.util.doc import get_entity_doc
import blenderbim.tool as tool
from blenderbim.bim.prop import Attribute
from bpy.types import PropertyGroup
from bpy.props import (
PointerProperty,
@@ -31,6 +33,11 @@ from bpy.props import (
)
def get_available_constraint_types(self, context):
version = tool.Ifc.get_schema()
return [(c, c, get_entity_doc(version, c).get("description", "")) for c in ["IfcObjective"]]
class Constraint(PropertyGroup):
name: StringProperty(name="Name")
ifc_definition_id: IntProperty(name="IFC Definition ID")
@@ -46,6 +53,4 @@ class BIMConstraintProperties(PropertyGroup):
class BIMObjectConstraintProperties(PropertyGroup):
is_adding: StringProperty(name="Is Adding")
available_constraint_types: EnumProperty(
items=[(c, c, "") for c in ["IfcObjective"]], name="Available Constraint Types"
)
available_constraint_types: EnumProperty(items=get_available_constraint_types, name="Available Constraint Types")
@@ -64,8 +64,8 @@ class BIM_PT_context(bpy.types.Panel):
row.operator("bim.remove_context", icon="X", text="").context = ifc_context["id"]
row = box.row(align=True)
row.prop(props, "subcontexts", text="")
row.prop(props, "target_views", text="")
blenderbim.bim.helper.prop_with_search(row, props, "subcontexts", text="")
blenderbim.bim.helper.prop_with_search(row, props, "target_views", text="")
op = row.operator("bim.add_context", icon="ADD", text="")
op.context_type = ifc_context["context_type"]
op.context_identifier = props.subcontexts
@@ -27,7 +27,7 @@ from blenderbim.bim.ifc import IfcStore
from bpy_extras.io_utils import ImportHelper
from ifcopenshell.api.cost.data import Data
from ifcopenshell.api.unit.data import Data as UnitData
from ifcopenshell.api.resource.data import Data as ResourceData
from blenderbim.bim.module.resource.data import ResourceData
class AddCostSchedule(bpy.types.Operator):
@@ -604,7 +604,7 @@ class AddCostValue(bpy.types.Operator):
value = ifcopenshell.api.run("cost.add_cost_value", self.file, parent=parent)
ifcopenshell.api.run("cost.edit_cost_value", self.file, cost_value=value, attributes=attributes)
if parent.is_a("IfcConstructionResource"):
ResourceData.load(self.file)
ResourceData.load()
else:
Data.load(self.file)
return {"FINISHED"}
@@ -630,7 +630,7 @@ class RemoveCostItemValue(bpy.types.Operator):
cost_value=self.file.by_id(self.cost_value),
)
if parent.is_a("IfcConstructionResource"):
ResourceData.load(self.file)
ResourceData.load()
else:
Data.load(self.file)
return {"FINISHED"}
@@ -923,24 +923,26 @@ class LoadCostItemQuantities(bpy.types.Operator):
def execute(self, context):
self.props = context.scene.BIMCostProperties
self.file = IfcStore.get_file()
Data.load(self.file)
self.props.cost_item_products.clear()
self.props.cost_item_processes.clear()
self.props.cost_item_resources.clear()
ifc_definition_id = self.props.cost_items[self.props.active_cost_item_index].ifc_definition_id
for control_id, quantity_ids in Data.cost_items[ifc_definition_id]["Controls"].items():
related_object = self.file.by_id(control_id)
if related_object.is_a("IfcProduct"):
new = self.props.cost_item_products.add()
elif related_object.is_a("IfcProcess"):
new = self.props.cost_item_processes.add()
elif related_object.is_a("IfcResource"):
new = self.props.cost_item_resources.add()
new.ifc_definition_id = control_id
new.name = related_object.Name or "Unnamed"
total_quantity = 0
for quantity_id in quantity_ids:
total_quantity += self.file.by_id(quantity_id)[3]
new.total_quantity = total_quantity
if self.props.active_cost_item_index < len(self.props.cost_items):
ifc_definition_id = self.props.cost_items[self.props.active_cost_item_index].ifc_definition_id
for control_id, quantity_ids in Data.cost_items[ifc_definition_id]["Controls"].items() or {}:
related_object = self.file.by_id(control_id)
if related_object.is_a("IfcProduct"):
new = self.props.cost_item_products.add()
elif related_object.is_a("IfcProcess"):
new = self.props.cost_item_processes.add()
elif related_object.is_a("IfcResource"):
new = self.props.cost_item_resources.add()
new.ifc_definition_id = control_id
new.name = related_object.Name or "Unnamed"
total_quantity = 0
for quantity_id in quantity_ids:
total_quantity += self.file.by_id(quantity_id)[3]
new.total_quantity = total_quantity
return {"FINISHED"}
@@ -19,6 +19,7 @@
import bpy
import ifcopenshell.api
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.module.classification.data import CostClassificationsData
from ifcopenshell.api.cost.data import Data
from ifcopenshell.api.pset.data import Data as PsetData
from blenderbim.bim.prop import StrProperty, Attribute
@@ -164,6 +165,7 @@ def update_active_cost_item_index(self, context):
bpy.ops.bim.load_cost_item_types()
else:
bpy.ops.bim.load_cost_item_quantities()
CostClassificationsData.load()
def update_cost_item_identification(self, context):
@@ -343,6 +343,11 @@ class BIM_PT_cost_item_quantities(Panel):
return False
def draw(self, context):
if not Data.is_loaded:
Data.load(IfcStore.get_file())
if not UnitData.is_loaded:
UnitData.load(IfcStore.get_file())
self.props = context.scene.BIMCostProperties
cost_item = self.props.cost_items[self.props.active_cost_item_index]
@@ -112,6 +112,7 @@ class CreateAllShapes(bpy.types.Operator):
def execute(self, context):
self.file = IfcStore.get_file()
elements = self.file.by_type("IfcElement") + self.file.by_type("IfcSpace")
total = len(elements)
settings = ifcopenshell.geom.settings()
failures = []
@@ -94,7 +94,7 @@ class BIM_PT_debug(Panel):
op = row.operator("bim.select_global_id", icon="RESTRICT_SELECT_OFF", text="")
op.global_id = attribute.string_value
if attribute.name == "ObjectPlacement":
op = row.operator("bim.print_object_placement", icon="TRACKER", text="")
op = row.operator("bim.print_object_placement", icon="OBJECT_ORIGIN", text="")
op.step_id = attribute.int_value
if attribute.int_value:
row.operator(
@@ -25,6 +25,7 @@ classes = (
operator.SelectDiffOldFile,
operator.SelectDiffNewFile,
operator.ExecuteIfcDiff,
prop.Relationships,
prop.DiffProperties,
ui.BIM_PT_diff,
)
@@ -0,0 +1,86 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2022 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 json
import blenderbim.tool as tool
def refresh():
DiffData.is_loaded = False
class DiffData:
data = {}
is_loaded = False
diff_json_file = None
diff = None
@classmethod
def load(cls):
cls.data["diff_json"] = cls.diff_json()
cls.data["total_added"] = cls.total_added()
cls.data["total_deleted"] = cls.total_deleted()
cls.data["total_changed"] = cls.total_changed()
cls.data["changes"] = cls.changes()
cls.is_loaded = True
@classmethod
def diff_json(cls):
props = bpy.context.scene.DiffProperties
if not props.diff_json_file:
cls.diff = None
return
if props.diff_json_file != cls.diff_json_file:
cls.diff_json_file = props.diff_json_file
with open(props.diff_json_file, "r") as file:
cls.diff = json.load(file)
return cls.diff
@classmethod
def total_added(cls):
diff = cls.diff_json()
if diff:
return len(diff["added"])
@classmethod
def total_deleted(cls):
diff = cls.diff_json()
if diff:
return len(diff["deleted"])
@classmethod
def total_changed(cls):
diff = cls.diff_json()
if diff:
return len(diff["changed"].keys())
@classmethod
def changes(cls):
diff = cls.diff_json()
if not diff:
return {}
element = tool.Ifc.get_entity(bpy.context.active_object)
if not element or not hasattr(element, "GlobalId"):
return {}
changes = {k.upper().replace("_", " "): str(v) for k, v in diff["changed"].get(element.GlobalId, {}).items()}
if element.GlobalId in diff["added"]:
changes["Added"] = True
elif element.GlobalId in diff["deleted"]:
changes["Deleted"] = True
return changes
@@ -17,10 +17,11 @@
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
import json
import ifccsv
import ifcopenshell
import json
from blenderbim.bim.ifc import IfcStore
import blenderbim.bim.handler
import blenderbim.tool as tool
class SelectDiffJsonFile(bpy.types.Operator):
@@ -28,6 +29,7 @@ class SelectDiffJsonFile(bpy.types.Operator):
bl_label = "Select Diff JSON File"
bl_options = {"REGISTER", "UNDO"}
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
filter_glob: bpy.props.StringProperty(default="*.json", options={"HIDDEN"})
def execute(self, context):
context.scene.DiffProperties.diff_json_file = self.filepath
@@ -44,21 +46,23 @@ class VisualiseDiff(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
# ifc_file = IfcStore.get_file() # In case we get from Store
ifc_file = ifcopenshell.open(context.scene.DiffProperties.diff_new_file) # for Now refer to the new file
ifc_file = tool.Ifc.get()
with open(context.scene.DiffProperties.diff_json_file, "r") as file:
diff = json.load(file)
for obj in context.visible_objects:
obj.color = (1.0, 1.0, 1.0, 0.2)
global_id = ifc_file.by_id(obj.BIMObjectProperties.ifc_definition_id).GlobalId
obj.color = (1.0, 1.0, 1.0, 1.0)
element = tool.Ifc.get_entity(obj)
if not element:
continue
global_id = getattr(element, "GlobalId", None)
if not global_id:
continue
if global_id in diff["deleted"]:
obj.color = (1.0, 0.0, 0.0, 0.2)
obj.color = (1.0, 0.0, 0.0, 1.0)
elif global_id in diff["added"]:
obj.color = (0.0, 1.0, 0.0, 0.2)
obj.color = (0.0, 1.0, 0.0, 1.0)
elif global_id in diff["changed"]:
obj.color = (0.0, 0.0, 1.0, 0.2)
obj.color = (0.0, 0.0, 1.0, 1.0)
area = next(area for area in context.screen.areas if area.type == "VIEW_3D")
area.spaces[0].shading.color_type = "OBJECT"
return {"FINISHED"}
@@ -69,9 +73,10 @@ class SelectDiffOldFile(bpy.types.Operator):
bl_label = "Select Diff Old File"
bl_options = {"REGISTER", "UNDO"}
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
filter_glob: bpy.props.StringProperty(default="*.ifc", options={"HIDDEN"})
def execute(self, context):
context.scene.DiffProperties.diff_old_file = self.filepath
context.scene.DiffProperties.old_file = self.filepath
return {"FINISHED"}
def invoke(self, context, event):
@@ -84,9 +89,10 @@ class SelectDiffNewFile(bpy.types.Operator):
bl_label = "Select Diff New File"
bl_options = {"REGISTER", "UNDO"}
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
filter_glob: bpy.props.StringProperty(default="*.ifc", options={"HIDDEN"})
def execute(self, context):
context.scene.DiffProperties.diff_new_file = self.filepath
context.scene.DiffProperties.new_file = self.filepath
return {"FINISHED"}
def invoke(self, context, event):
@@ -109,13 +115,14 @@ class ExecuteIfcDiff(bpy.types.Operator):
def execute(self, context):
import ifcdiff
ifc_diff = ifcdiff.IfcDiff(
context.scene.DiffProperties.diff_old_file,
context.scene.DiffProperties.diff_new_file,
self.filepath,
context.scene.DiffProperties.diff_relationships.split(),
)
old = ifcopenshell.open(context.scene.DiffProperties.old_file)
new = ifcopenshell.open(context.scene.DiffProperties.new_file)
relationships = [r.relationship for r in context.scene.DiffProperties.diff_relationships]
query = context.scene.DiffProperties.diff_filter_elements
ifc_diff = ifcdiff.IfcDiff(old, new, relationships=relationships, filter_elements=query)
ifc_diff.diff()
ifc_diff.export()
ifc_diff.export(self.filepath)
context.scene.DiffProperties.diff_json_file = self.filepath
blenderbim.bim.handler.refresh_ui_data()
return {"FINISHED"}
@@ -18,6 +18,7 @@
import bpy
from blenderbim.bim.prop import StrProperty
from blenderbim.bim.module.diff.data import DiffData
from bpy.types import PropertyGroup
from bpy.props import (
PointerProperty,
@@ -31,8 +32,20 @@ from bpy.props import (
)
def update_diff_json_file(self, context):
DiffData.data["diff_json"] = DiffData.diff_json()
class Relationships(PropertyGroup):
relationship: EnumProperty(
name="Relationship",
items=[(r, r.capitalize(), r) for r in ["type", "property", "container", "aggregate", "classification"]],
)
class DiffProperties(PropertyGroup):
diff_json_file: StringProperty(default="", name="Diff JSON File")
diff_old_file: StringProperty(default="", name="Diff Old IFC File")
diff_new_file: StringProperty(default="", name="Diff New IFC File")
diff_relationships: StringProperty(default="", name="Diff Relationships")
diff_json_file: StringProperty(default="", name="JSON Output", update=update_diff_json_file)
old_file: StringProperty(default="", name="Old IFC File")
new_file: StringProperty(default="", name="New IFC File")
diff_relationships: CollectionProperty(type=Relationships, name="Relationships")
diff_filter_elements: StringProperty(default="", name="Filter")
@@ -18,6 +18,9 @@
from bpy.types import Panel
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.module.diff.data import DiffData
import blenderbim.tool as tool
import json
class BIM_PT_diff(Panel):
@@ -30,30 +33,70 @@ class BIM_PT_diff(Panel):
bl_parent_id = "BIM_PT_quality_control"
def draw(self, context):
if not DiffData.is_loaded:
DiffData.load()
layout = self.layout
layout.use_property_split = True
scene = context.scene
bim_properties = scene.DiffProperties
props = scene.DiffProperties
layout.label(text="IFC Diff Setup:")
row = layout.row(align=True)
row.prop(bim_properties, "diff_old_file")
row.prop(props, "old_file")
row.operator("bim.select_diff_old_file", icon="FILE_FOLDER", text="")
row = layout.row(align=True)
row.prop(bim_properties, "diff_new_file")
row.prop(props, "new_file")
row.operator("bim.select_diff_new_file", icon="FILE_FOLDER", text="")
row = layout.row(align=True)
row.prop(bim_properties, "diff_relationships")
row.prop(props, "diff_relationships")
row.context_pointer_set("bim_prop_group", props)
add = row.operator("bim.edit_blender_collection", icon="ADD", text="")
add.option = "add"
add.collection = "diff_relationships"
for index, r in enumerate(props.diff_relationships):
row = layout.row(align=True)
row.context_pointer_set("bim_prop_group", props)
row.prop(r, "relationship", text=" ")
remove = row.operator("bim.edit_blender_collection", icon="REMOVE", text="")
remove.option = "remove"
remove.collection = "diff_relationships"
remove.index = index
row = layout.row(align=True)
row.prop(props, "diff_filter_elements")
row.operator("bim.ifc_selector", icon="FILTER", text="")
row = layout.row()
row.operator("bim.execute_ifc_diff")
# TODO: show if there ifc diff operation is sucessful
row = layout.row(align=True)
row.prop(bim_properties, "diff_json_file")
row.prop(props, "diff_json_file")
row.operator("bim.select_diff_json_file", icon="FILE_FOLDER", text="")
row.operator("bim.visualise_diff", icon="HIDE_OFF", text="")
if DiffData.data["diff_json"]:
row = layout.row()
row.alignment = "CENTER"
row.label(text=f"{DiffData.data['total_added']} added")
row.label(text=f"{DiffData.data['total_deleted']} deleted")
row.label(text=f"{DiffData.data['total_changed']} changed")
if DiffData.data["changes"]:
box = layout.box()
row = box.row()
row.label(text="Active Object Changes:")
for key, value in DiffData.data["changes"].items():
row = box.row()
if key == "Added":
icon = "ADD"
elif key == "Deleted":
icon = "X"
else:
icon = "GREASEPENCIL"
row.label(text=key, icon=icon)
@@ -84,21 +84,28 @@ class ObjectDocumentData:
continue
name = rel.RelatingDocument.Name
if not name and rel.RelatingDocument.ReferencedDocument:
name = rel.RelatingDocument.ReferencedDocument.Name
if tool.Ifc.get_schema() == "IFC2X3":
if not name and rel.RelatingDocument.ReferenceToDocument:
name = rel.RelatingDocument.ReferenceToDocument[0].Name
identification = rel.RelatingDocument.ItemReference
if not identification and rel.RelatingDocument.ReferencedDocument:
identification = rel.RelatingDocument.ReferencedDocument.DocumentId
if not identification and rel.RelatingDocument.ReferenceToDocument:
identification = rel.RelatingDocument.ReferenceToDocument[0].DocumentId
location = rel.RelatingDocument.Location
else:
if not name and rel.RelatingDocument.ReferencedDocument:
name = rel.RelatingDocument.ReferencedDocument.Name
identification = rel.RelatingDocument.Identification
if not identification and rel.RelatingDocument.ReferencedDocument:
identification = rel.RelatingDocument.ReferencedDocument.Identification
location = rel.RelatingDocument.Location
if location is None and rel.RelatingDocument.ReferencedDocument:
location = rel.RelatingDocument.ReferencedDocument.Location
location = rel.RelatingDocument.Location
if location is None and rel.RelatingDocument.ReferencedDocument:
location = rel.RelatingDocument.ReferencedDocument.Location
if location:
if not "://" in location:
if not os.path.isabs(location):
@@ -34,20 +34,19 @@ classes = (
operator.BuildSchedule,
operator.CleanWireframes,
operator.ContractSheet,
operator.ConvertEdgeToArc,
operator.CopyGrid,
operator.CreateDrawing,
operator.CreateSheets,
operator.DisableEditingAssignedProduct,
operator.DisableEditingDrawings,
operator.DisableEditingSchedules,
operator.DisableEditingSheets,
operator.DisableEditingText,
operator.DisableEditingTextProduct,
operator.EditAssignedProduct,
operator.EditText,
operator.EditTextProduct,
operator.EditVectorStyle,
operator.EnableEditingAssignedProduct,
operator.EnableEditingText,
operator.EnableEditingTextProduct,
operator.ExpandSheet,
operator.LoadDrawings,
operator.LoadSchedules,
@@ -72,12 +71,14 @@ classes = (
prop.DocProperties,
prop.BIMCameraProperties,
prop.BIMTextProperties,
prop.BIMAssignedProductProperties,
ui.BIM_PT_camera,
ui.BIM_PT_drawing_underlay,
ui.BIM_PT_annotation_utilities,
ui.BIM_PT_sheets,
ui.BIM_PT_drawings,
ui.BIM_PT_schedules,
ui.BIM_PT_product_assignments,
ui.BIM_PT_text,
ui.BIM_UL_drawinglist,
ui.BIM_UL_sheets,
@@ -92,6 +93,7 @@ classes = (
def register():
bpy.types.Scene.DocProperties = bpy.props.PointerProperty(type=prop.DocProperties)
bpy.types.Camera.BIMCameraProperties = bpy.props.PointerProperty(type=prop.BIMCameraProperties)
bpy.types.Object.BIMAssignedProductProperties = bpy.props.PointerProperty(type=prop.BIMAssignedProductProperties)
bpy.types.Object.BIMTextProperties = bpy.props.PointerProperty(type=prop.BIMTextProperties)
bpy.types.TextCurve.BIMTextProperties = bpy.props.PointerProperty(type=prop.BIMTextProperties)
bpy.app.handlers.load_post.append(handler.toggleDecorationsOnLoad)
@@ -101,6 +103,8 @@ def register():
def unregister():
del bpy.types.Scene.DocProperties
del bpy.types.Camera.BIMCameraProperties
del bpy.types.Object.BIMAssignedProductProperties
del bpy.types.Object.BIMTextProperties
del bpy.types.TextCurve.BIMTextProperties
bpy.app.handlers.load_post.remove(handler.toggleDecorationsOnLoad)
bpy.app.handlers.depsgraph_update_pre.remove(handler.depsgraph_update_pre_handler)
@@ -151,10 +151,17 @@ class Annotator:
return obj
@staticmethod
def get_annotation_obj(object_type, data_type):
collection = bpy.context.scene.camera.users_collection[0]
def get_annotation_obj(drawing, object_type, data_type):
camera = tool.Ifc.get_object(drawing)
co1, _, _, _ = Annotator.get_placeholder_coords(camera)
matrix_world = camera.matrix_world.copy()
matrix_world[0][3] = co1.x
matrix_world[1][3] = co1.y
matrix_world[2][3] = co1.z
collection = camera.users_collection[0]
if object_type == "TEXT":
obj = bpy.data.objects.new(object_type, None)
obj.matrix_world = matrix_world
collection.objects.link(obj)
return obj
elif object_type in ("TEXT_LEADER", "SECTION_LEVEL"):
@@ -162,12 +169,14 @@ class Annotator:
data.dimensions = "3D"
data.resolution_u = 2
obj = bpy.data.objects.new(object_type, data)
obj.matrix_world = matrix_world
collection.objects.link(obj)
return obj
for obj in collection.objects:
element = tool.Ifc.get_entity(obj)
if element and element.ObjectType == object_type:
return obj
if object_type != "ANGLE":
for obj in collection.objects:
element = tool.Ifc.get_entity(obj)
if element and element.ObjectType == object_type:
return obj
if data_type == "mesh":
data = bpy.data.meshes.new(object_type)
elif data_type == "curve":
@@ -175,12 +184,14 @@ class Annotator:
data.dimensions = "3D"
data.resolution_u = 2
obj = bpy.data.objects.new(object_type, data)
obj.matrix_world = matrix_world
collection.objects.link(obj)
return obj
@staticmethod
def get_placeholder_coords():
camera = bpy.context.scene.camera
def get_placeholder_coords(camera=None):
if not camera:
camera = bpy.context.scene.camera
z_offset = camera.matrix_world.to_quaternion() @ Vector((0, 0, -1))
if bpy.context.scene.render.resolution_x > bpy.context.scene.render.resolution_y:
y = (
@@ -22,19 +22,40 @@ import blenderbim.tool as tool
def refresh():
ProductAssignmentsData.is_loaded = False
TextData.is_loaded = False
SheetsData.is_loaded = False
SchedulesData.is_loaded = False
DrawingsData.is_loaded = False
class ProductAssignmentsData:
data = {}
is_loaded = False
@classmethod
def load(cls):
cls.data = {"relating_product": cls.relating_product()}
cls.is_loaded = True
@classmethod
def relating_product(cls):
element = tool.Ifc.get_entity(bpy.context.active_object)
if not element or not element.is_a("IfcAnnotation"):
return
for rel in element.HasAssignments:
if rel.is_a("IfcRelAssignsToProduct"):
name = rel.RelatingProduct.Name or "Unnamed"
return f"{rel.RelatingProduct.is_a()}/{name}"
class TextData:
data = {}
is_loaded = False
@classmethod
def load(cls):
cls.data = {"attributes": cls.attributes(), "relating_product": cls.relating_product()}
cls.data = {"attributes": cls.attributes()}
cls.is_loaded = True
@classmethod
@@ -49,15 +70,6 @@ class TextData:
{"name": "BoxAlignment", "value": text_literal.BoxAlignment},
]
@classmethod
def relating_product(cls):
element = tool.Ifc.get_entity(bpy.context.active_object)
if not element or not element.is_a("IfcAnnotation") or element.ObjectType not in ["TEXT", "TEXT_LEADER"]:
return
for rel in element.HasAssignments:
if rel.is_a("IfcRelAssignsToProduct"):
return rel.RelatingProduct.Name or "Unnamed"
class SheetsData:
data = {}
@@ -317,7 +317,7 @@ class OffsetHandle:
base = Vector((0, 0, 0))
axis = Vector((0, 0, 1))
# projecttion of the arrow to a plane, perpendicular to view ray
# projection of the arrow to a plane, perpendicular to view ray
axis_proj = axis - ray_norm * axis.dot(ray_norm)
# intersection of the axis with the plane through view origin perpendicular to the arrow projection
@@ -88,17 +88,17 @@ class BoundingBox:
def intersect(self, bb):
retVal = False
if self.vertex != None and bb.vertex != None:
# check all the faces of this object for a seperation axis
# check all the faces of this object for a separation axis
for i, f in enumerate(self.face):
d = f.normal
if self.whichSide(bb.vertex, d, f.vertex[0]) > 0:
return False # all the vertexes are on the +ve side of the face
return False # all the vertices are on the +ve side of the face
# now do it again for the other objects faces
for i, f in enumerate(bb.face):
d = f.normal
if self.whichSide(self.vertex, d, f.vertex[0]) > 0:
return False # all the vertexes are on the +ve side of the face
return False # all the vertices are on the +ve side of the face
# do edge checks
for e1 in self.edge:
@@ -139,7 +139,7 @@ def format_distance(value, isArea=False, hide_units=True):
value *= scaleFactor
# Imperial Formating
# Imperial Formatting
if unit_system == "IMPERIAL":
precision = bpy.context.scene.BIMProperties.imperial_precision
if precision == "NONE":
@@ -152,7 +152,7 @@ def format_distance(value, isArea=False, hide_units=True):
base = int(precision)
decInches = value * toInches
# Seperate ft and inches
# Separate ft and inches
# Unless Inches are the specified Length Unit
if unit_length != "INCHES":
feet = math.floor(decInches / inPerFoot)
@@ -160,7 +160,7 @@ def format_distance(value, isArea=False, hide_units=True):
else:
feet = 0
# Seperate Fractional Inches
# Separate Fractional Inches
inches = math.floor(decInches)
if inches != 0:
frac = round(base * (decInches - inches))
@@ -202,7 +202,7 @@ def format_distance(value, isArea=False, hide_units=True):
else:
tx_dist = str("%1.3f" % (value * toInches / inPerFoot)) + " sq. ft."
# METRIC FORMATING
# METRIC FORMATTING
elif unit_system == "METRIC":
precision = bpy.context.scene.BIMProperties.metric_precision
if precision != 0:
@@ -40,7 +40,8 @@ import blenderbim.bim.module.drawing.scheduler as scheduler
import blenderbim.bim.module.drawing.helper as helper
import blenderbim.bim.export_ifc
from lxml import etree
from mathutils import Vector, Matrix, Euler, geometry
from mathutils import Vector
from timeit import default_timer as timer
from blenderbim.bim.module.drawing.prop import RasterStyleProperty
from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.group.data import Data as GroupData
@@ -49,6 +50,21 @@ from ifcopenshell.api.pset.data import Data as PsetData
cwd = os.path.dirname(os.path.realpath(__file__))
class profile:
"""
A python context manager timing utility
"""
def __init__(self, task):
self.task = task
def __enter__(self):
self.start = timer()
def __exit__(self, *args):
print(self.task, timer() - self.start)
def open_with_user_command(user_command, path):
if user_command:
commands = eval(user_command)
@@ -115,53 +131,75 @@ class CreateDrawing(bpy.types.Operator):
self.camera = context.scene.camera
self.camera_element = tool.Ifc.get_entity(self.camera)
self.file = IfcStore.get_file()
self.time = None
start = time.time()
self.profile_code("Start drawing generation process")
self.props = context.scene.DocProperties
self.cprops = self.camera.data.BIMCameraProperties
self.drawing_name = self.file.by_id(self.camera.BIMObjectProperties.ifc_definition_id).Name
self.get_scale()
if self.cprops.update_representation(self.camera):
bpy.ops.bim.update_representation(obj=self.camera.name, ifc_representation_class="")
underlay_svg = self.generate_underlay(context)
self.profile_code("Generate underlay")
linework_svg = self.generate_linework(context)
self.profile_code("Generate linework")
annotation_svg = self.generate_annotation(context)
self.profile_code("Generate annotation")
svg_path = self.combine_svgs(context, underlay_svg, linework_svg, annotation_svg)
self.profile_code("Combine SVG layers")
with profile("Drawing generation process"):
with profile("Initialize drawing generation process"):
self.props = context.scene.DocProperties
self.cprops = self.camera.data.BIMCameraProperties
self.drawing_name = self.file.by_id(self.camera.BIMObjectProperties.ifc_definition_id).Name
self.get_scale()
if self.cprops.update_representation(self.camera):
bpy.ops.bim.update_representation(obj=self.camera.name, ifc_representation_class="")
self.svg_writer = svgwriter.SvgWriter()
self.svg_writer.human_scale = self.human_scale
self.svg_writer.scale = self.scale
self.svg_writer.data_dir = context.scene.BIMProperties.data_dir
self.svg_writer.camera = self.camera
self.svg_writer.camera_width, self.svg_writer.camera_height = self.get_camera_dimensions()
self.svg_writer.camera_projection = tuple(self.camera.matrix_world.to_quaternion() @ Vector((0, 0, -1)))
with profile("Generate underlay"):
underlay_svg = self.generate_underlay(context)
with profile("Generate linework"):
linework_svg = self.generate_linework(context)
with profile("Generate annotation"):
annotation_svg = self.generate_annotation(context)
with profile("Combine SVG layers"):
svg_path = self.combine_svgs(context, underlay_svg, linework_svg, annotation_svg)
open_with_user_command(context.preferences.addons["blenderbim"].preferences.svg_command, svg_path)
print("Total Time: {:.2f}".format(time.time() - start))
return {"FINISHED"}
def profile_code(self, message):
if not self.time:
self.time = time.time()
print("{} :: {:.2f}".format(message, time.time() - self.time))
self.time = time.time()
def get_camera_dimensions(self):
render = bpy.context.scene.render
if self.is_landscape(render):
width = self.camera.data.ortho_scale
height = width / render.resolution_x * render.resolution_y
else:
height = self.camera.data.ortho_scale
width = height / render.resolution_y * render.resolution_x
return width, height
def combine_svgs(self, context, underlay, linework, annotation):
# Hacky :)
svg_path = os.path.join(context.scene.BIMProperties.data_dir, "diagrams", self.drawing_name + ".svg")
with open(svg_path, "w") as outfile:
has_boilerplate = False
pset = ifcopenshell.util.element.get_psets(self.camera_element)["EPset_Drawing"]
self.svg_writer.create_blank_svg(svg_path).define_boilerplate(
pset.get("Stylesheet"), pset.get("Markers"), pset.get("Symbols"), pset.get("Patterns")
)
boilerplate = self.svg_writer.svg.tostring()
outfile.write(boilerplate.replace("</svg>", ""))
if underlay:
with open(underlay) as infile:
for line in infile:
if "<svg " in line:
line = line.replace('">', '" xmlns:ifc="http://www.ifcopenshell.org/ns">')
if "</svg>" in line:
for i, line in enumerate(infile):
if i < 2:
continue
elif "</svg>" in line:
continue
outfile.write(line)
shutil.copyfile(underlay[0:-4] + ".png", svg_path[0:-4] + "-underlay.png")
has_boilerplate = True
if linework:
with open(linework) as infile:
should_skip = False
for i, line in enumerate(infile):
if has_boilerplate and i == 0:
if i == 0:
continue
if "</svg>" in line:
continue
@@ -174,11 +212,10 @@ class CreateDrawing(bpy.types.Operator):
elif should_skip:
continue
outfile.write(line)
has_boilerplate = True
if annotation:
with open(annotation) as infile:
for i, line in enumerate(infile):
if has_boilerplate and i in [0, 1]:
if i < 2:
continue
if "</svg>" in line:
continue
@@ -223,25 +260,7 @@ class CreateDrawing(bpy.types.Operator):
for name, value in previous_visibility.items():
bpy.data.objects[name].hide_set(value)
svg_writer = svgwriter.SvgWriter()
render = context.scene.render
if self.is_landscape(render):
width = self.camera.data.ortho_scale
height = width / render.resolution_x * render.resolution_y
else:
height = self.camera.data.ortho_scale
width = height / render.resolution_y * render.resolution_x
svg_writer.human_scale = self.human_scale
svg_writer.scale = self.scale
svg_writer.output = svg_path
svg_writer.data_dir = context.scene.BIMProperties.data_dir
svg_writer.vector_style = drawing_style.vector_style
svg_writer.camera = self.camera
svg_writer.camera_width = width
svg_writer.camera_height = height
svg_writer.camera_projection = tuple(self.camera.matrix_world.to_quaternion() @ Vector((0, 0, -1)))
svg_writer.background_image = context.scene.render.filepath
svg_writer.write("underlay")
self.svg_writer.create_blank_svg(svg_path).draw_underlay(context.scene.render.filepath).save()
return svg_path
def generate_linework(self, context):
@@ -251,13 +270,14 @@ class CreateDrawing(bpy.types.Operator):
if os.path.isfile(svg_path) and self.props.should_use_linework_cache:
return svg_path
# All very hackish whilst prototyping
exporter = blenderbim.bim.export_ifc.IfcExporter(None)
exporter.file = tool.Ifc.get()
invalidated_guids = exporter.sync_deletions()
invalidated_elements = exporter.sync_all_objects()
invalidated_elements += exporter.sync_edited_objects()
[invalidated_guids.append(e.GlobalId) for e in invalidated_elements if hasattr(e, "GlobalId")]
with profile("sync"):
# All very hackish whilst prototyping
exporter = blenderbim.bim.export_ifc.IfcExporter(None)
exporter.file = tool.Ifc.get()
invalidated_guids = exporter.sync_deletions()
invalidated_elements = exporter.sync_all_objects()
invalidated_elements += exporter.sync_edited_objects()
[invalidated_guids.append(e.GlobalId) for e in invalidated_elements if hasattr(e, "GlobalId")]
# If we have already calculated it in the SVG in the past, don't recalculate
edited_guids = set()
@@ -308,40 +328,46 @@ class CreateDrawing(bpy.types.Operator):
cache = IfcStore.get_cache()
[cache.remove(guid) for guid in invalidated_guids]
if target_view_contexts and elements:
geom_settings = ifcopenshell.geom.settings(
DISABLE_TRIANGULATION=True, STRICT_TOLERANCE=True, INCLUDE_CURVES=True
)
geom_settings.set_context_ids(target_view_contexts)
it = ifcopenshell.geom.iterator(geom_settings, ifc, multiprocessing.cpu_count(), include=elements)
it.set_cache(cache)
processed = set()
for elem in self.yield_from_iterator(it):
processed.add(ifc.by_id(elem.id))
self.serialiser.write(elem)
elements -= processed
with profile("Processing target view context"):
if target_view_contexts and elements:
geom_settings = ifcopenshell.geom.settings(
DISABLE_TRIANGULATION=True, STRICT_TOLERANCE=True, INCLUDE_CURVES=True
)
geom_settings.set_context_ids(target_view_contexts)
it = ifcopenshell.geom.iterator(geom_settings, ifc, multiprocessing.cpu_count(), include=elements)
it.set_cache(cache)
processed = set()
for elem in self.yield_from_iterator(it):
processed.add(ifc.by_id(elem.id))
self.serialiser.write(elem)
elements -= processed
if body_contexts and elements:
with profile("Processing body context"):
if body_contexts and elements:
geom_settings = ifcopenshell.geom.settings(DISABLE_TRIANGULATION=True, STRICT_TOLERANCE=True)
geom_settings.set_context_ids(body_contexts)
it = ifcopenshell.geom.iterator(geom_settings, ifc, multiprocessing.cpu_count(), include=elements)
it.set_cache(cache)
for elem in self.yield_from_iterator(it):
self.serialiser.write(elem)
with profile("Camera element"):
# @todo tfk: is this needed?
geom_settings = ifcopenshell.geom.settings(DISABLE_TRIANGULATION=True, STRICT_TOLERANCE=True)
geom_settings.set_context_ids(body_contexts)
it = ifcopenshell.geom.iterator(geom_settings, ifc, multiprocessing.cpu_count(), include=elements)
it.set_cache(cache)
it = ifcopenshell.geom.iterator(geom_settings, ifc, include=[self.camera_element])
for elem in self.yield_from_iterator(it):
self.serialiser.write(elem)
geom_settings = ifcopenshell.geom.settings(DISABLE_TRIANGULATION=True, STRICT_TOLERANCE=True)
it = ifcopenshell.geom.iterator(geom_settings, ifc, include=[self.camera_element])
for elem in self.yield_from_iterator(it):
self.serialiser.write(elem)
self.serialiser.finalize()
with profile("Finalizing"):
self.serialiser.finalize()
results = self.svg_buffer.get_value()
root = etree.fromstring(results)
self.enrich_linework_with_metadata(root)
self.move_projection_to_bottom(root)
with open(svg_path, "wb") as svg:
svg.write(etree.tostring(root))
with profile("Post processing"):
root = etree.fromstring(results)
self.enrich_linework_with_metadata(root)
self.move_projection_to_bottom(root)
with open(svg_path, "wb") as svg:
svg.write(etree.tostring(root))
return svg_path
@@ -415,35 +441,13 @@ class CreateDrawing(bpy.types.Operator):
if os.path.isfile(svg_path) and self.props.should_use_annotation_cache:
return svg_path
camera = self.camera
svg_writer = svgwriter.SvgWriter()
drawing_style = context.scene.DocProperties.drawing_styles[self.cprops.active_drawing_style_index]
render = context.scene.render
if self.is_landscape(render):
width = camera.data.ortho_scale
height = width / render.resolution_x * render.resolution_y
else:
height = camera.data.ortho_scale
width = height / render.resolution_y * render.resolution_x
svg_writer.human_scale = self.human_scale
svg_writer.scale = self.scale
svg_writer.output = svg_path
svg_writer.data_dir = context.scene.BIMProperties.data_dir
svg_writer.vector_style = drawing_style.vector_style
svg_writer.camera = camera
svg_writer.camera_width = width
svg_writer.camera_height = height
svg_writer.camera_projection = tuple(camera.matrix_world.to_quaternion() @ Vector((0, 0, -1)))
elements = tool.Drawing.get_group_elements(tool.Drawing.get_drawing_group(self.camera_element))
svg_writer.annotations = sorted(elements, key=lambda a: tool.Drawing.get_annotation_z_index(a))
svg_writer.metadata = tool.Drawing.get_drawing_metadata(self.camera_element)
annotations = sorted(elements, key=lambda a: tool.Drawing.get_annotation_z_index(a))
svg_writer.write("annotation")
return svg_writer.output
self.svg_writer.metadata = tool.Drawing.get_drawing_metadata(self.camera_element)
self.svg_writer.create_blank_svg(svg_path).draw_annotations(annotations).save()
return svg_path
def get_scale(self):
if self.camera.data.BIMCameraProperties.diagram_scale == "CUSTOM":
@@ -505,8 +509,11 @@ class AddAnnotation(bpy.types.Operator, Operator):
def _execute(self, context):
drawing = tool.Ifc.get_entity(context.scene.camera)
if not drawing:
self.report({"WARNING"}, "Not a BIM camera")
return
core.add_annotation(tool.Ifc, tool.Collector, tool.Drawing, drawing=drawing, object_type=self.object_type)
r = core.add_annotation(tool.Ifc, tool.Collector, tool.Drawing, drawing=drawing, object_type=self.object_type)
if isinstance(r, str):
self.report({"WARNING"}, r)
class AddSheet(bpy.types.Operator, Operator):
@@ -1188,7 +1195,7 @@ class AddSectionsAnnotations(bpy.types.Operator):
]
def sideview(cam):
# leftmost and righmost points of camera view area, local coords
# leftmost and rightmost points of camera view area, local coords
xmin, xmax, _, _, _, _ = helper.ortho_view_frame(cam.data, margin=0)
proj = camera.matrix_world.inverted() @ cam.matrix_world
p_l = proj @ Vector((xmin, 0, 0))
@@ -1246,34 +1253,34 @@ class DisableEditingText(bpy.types.Operator, Operator):
core.disable_editing_text(tool.Drawing, obj=context.active_object)
class EditTextProduct(bpy.types.Operator, Operator):
bl_idname = "bim.edit_text_product"
class EditAssignedProduct(bpy.types.Operator, Operator):
bl_idname = "bim.edit_assigned_product"
bl_label = "Edit Text Product"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
product = None
if context.active_object.BIMTextProperties.relating_product:
product = tool.Ifc.get_entity(context.active_object.BIMTextProperties.relating_product)
core.edit_text_product(tool.Ifc, tool.Drawing, obj=context.active_object, product=product)
if context.active_object.BIMAssignedProductProperties.relating_product:
product = tool.Ifc.get_entity(context.active_object.BIMAssignedProductProperties.relating_product)
core.edit_assigned_product(tool.Ifc, tool.Drawing, obj=context.active_object, product=product)
class EnableEditingTextProduct(bpy.types.Operator, Operator):
bl_idname = "bim.enable_editing_text_product"
class EnableEditingAssignedProduct(bpy.types.Operator, Operator):
bl_idname = "bim.enable_editing_assigned_product"
bl_label = "Enable Editing Text Product"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
core.enable_editing_text_product(tool.Drawing, obj=context.active_object)
core.enable_editing_assigned_product(tool.Drawing, obj=context.active_object)
class DisableEditingTextProduct(bpy.types.Operator, Operator):
bl_idname = "bim.disable_editing_text_product"
class DisableEditingAssignedProduct(bpy.types.Operator, Operator):
bl_idname = "bim.disable_editing_assigned_product"
bl_label = "Disable Editing Text Product"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
core.disable_editing_text_product(tool.Drawing, obj=context.active_object)
core.disable_editing_assigned_product(tool.Drawing, obj=context.active_object)
class LoadSheets(bpy.types.Operator, Operator):
@@ -1356,44 +1363,3 @@ class ContractSheet(bpy.types.Operator):
sheet.is_expanded = False
core.load_sheets(tool.Drawing)
return {"FINISHED"}
class ConvertEdgeToArc(bpy.types.Operator):
bl_idname = "bim.convert_edge_to_arc"
bl_label = "Convert Edge to Arc"
bl_options = {"REGISTER", "UNDO"}
resolution: bpy.props.IntProperty(name="Arc Resolution", min=1, default=1)
should_flip: bpy.props.BoolProperty(name="Flip", description="Flip arc", default=False)
def draw(self, context):
layout = self.layout
for prop in self.__class__.__annotations__.keys():
layout.prop(self, prop)
def execute(self, context):
if bpy.context.mode != "EDIT_MESH":
return {"CANCELLED"}
obj = bpy.context.active_object
cursor = obj.matrix_world.inverted() @ bpy.context.scene.cursor.location
mesh = obj.data
bm = bmesh.from_edit_mesh(mesh)
selected_verts = [v for v in bm.verts if v.select]
if len(selected_verts) != 2:
return {"CANCELLED"}
l1 = selected_verts[0].co - cursor
l2 = selected_verts[1].co - cursor
angle = l1.xy.angle_signed(l2.xy)
if self.should_flip:
if angle > 0:
angle = -((pi * 2) - angle)
else:
angle = (pi * 2) + angle
v = selected_verts[0]
bm.verts.remove(selected_verts[1])
bmesh.ops.spin(bm, geom=[v], axis=(0, 0, 1), cent=cursor, steps=self.resolution * 4, angle=-angle)
bmesh.update_edit_mesh(mesh)
mesh.update()
return {"FINISHED"}
@@ -377,7 +377,6 @@ class BIMCameraProperties(PropertyGroup):
class BIMTextProperties(PropertyGroup):
is_editing: BoolProperty(name="Is Editing", default=False)
is_editing_product: BoolProperty(name="Is Editing Product", default=False)
attributes: CollectionProperty(name="Attributes", type=Attribute)
value: StringProperty(name="Value", default="TEXT")
font_size: EnumProperty(
@@ -392,4 +391,8 @@ class BIMTextProperties(PropertyGroup):
update=refreshFontSize,
name="Font Size",
)
class BIMAssignedProductProperties(PropertyGroup):
is_editing_product: BoolProperty(name="Is Editing Product", default=False)
relating_product: PointerProperty(name="Relating Product", type=bpy.types.Object)
@@ -118,7 +118,7 @@ class SheetBuilder:
sheet_tree = ET.parse(sheet_path)
sheet_root = sheet_tree.getroot()
for g in sheet_root.findall('{http://www.w3.org/2000/svg}g'):
for g in sheet_root.findall("{http://www.w3.org/2000/svg}g"):
if g.attrib.get("data-id") == str(reference.id()):
sheet_root.remove(g)
break
@@ -20,7 +20,9 @@ import os
import re
import bpy
import math
import bmesh
import pystache
import mathutils
import xml.etree.ElementTree as ET
import svgwrite
import ifcopenshell
@@ -34,14 +36,6 @@ from mathutils import geometry
from blenderbim.bim.ifc import IfcStore
def load_occ():
# Don't import until we really need to, as a temporary step before we can purge OCC
try:
from OCC.Core import BRep, BRepTools, TopExp, TopAbs
except ImportError:
from OCC import BRep, BRepTools, TopExp, TopAbs
class External(svgwrite.container.Group):
def __init__(self, xml, **extra):
self.xml = xml
@@ -61,87 +55,78 @@ class External(svgwrite.container.Group):
class SvgWriter:
def __init__(self):
self.output = "out.svg"
self.data_dir = None
self.vector_style = None
self.human_scale = "NTS"
self.annotations = []
self.metadata = []
self.background_image = None
self.scale = 1 / 100 # 1:100
self.camera_width = None
self.camera_height = None
def write(self, layer):
def create_blank_svg(self, output_path):
self.calculate_scale()
self.svg = svgwrite.Drawing(
self.output,
debug=False,
output_path,
size=("{}mm".format(self.width), "{}mm".format(self.height)),
viewBox=("0 0 {} {}".format(self.width, self.height)),
id="root",
data_scale=self.human_scale,
debug=False, # Disable validation so that we can insert the IFC namespace
)
self.svg.attribs["xmlns:ifc"] = "http://www.ifcopenshell.org/ns"
return self
if layer == "underlay":
self.draw_background_image()
elif layer == "annotation":
self.add_stylesheet()
self.add_markers()
self.add_symbols()
self.add_patterns()
# self.draw_background_elements()
# self.draw_cut_polygons()
self.draw_annotations()
def save(self):
self.svg.save(pretty=True)
def calculate_scale(self):
self.scale *= 1000 # IFC is in meters, SVG is in mm
self.raw_width = self.camera_width
self.raw_height = self.camera_height
self.width = self.raw_width * self.scale
self.height = self.raw_height * self.scale
def add_stylesheet(self):
with open(os.path.join(self.data_dir, "styles", f"{self.vector_style}.css"), "r") as stylesheet:
self.svg.defs.add(self.svg.style(stylesheet.read()))
def add_markers(self):
tree = ET.parse(os.path.join(self.data_dir, "templates", "markers.svg"))
root = tree.getroot()
for child in root:
self.svg.defs.add(External(child))
def add_symbols(self):
tree = ET.parse(os.path.join(self.data_dir, "templates", "symbols.svg"))
root = tree.getroot()
for child in root:
self.svg.defs.add(External(child))
def add_patterns(self):
tree = ET.parse(os.path.join(self.data_dir, "templates", "patterns.svg"))
root = tree.getroot()
for child in root:
self.svg.defs.add(External(child))
def draw_background_image(self):
def draw_underlay(self, image):
self.svg.add(
self.svg.image(
os.path.join("..", "diagrams", os.path.basename(self.background_image)),
**{"width": self.width, "height": self.height},
os.path.join("..", "diagrams", os.path.basename(image)),
width=self.width,
height=self.height,
)
)
return self
def draw_background_elements(self):
return # TODO purge?
for element in self.ifc_cutter.background_elements:
if element["type"] == "polygon":
self.draw_polygon(element, "background")
elif element["type"] == "polyline":
self.draw_polyline(element, "background")
elif element["type"] == "line":
self.draw_line(element, "background")
def define_boilerplate(self, stylesheet=None, markers=None, symbols=None, patterns=None):
self.add_stylesheet(stylesheet)
self.add_markers(markers)
self.add_symbols(symbols)
self.add_patterns(patterns)
return self
def draw_annotations(self):
for element in self.annotations:
def calculate_scale(self):
self.svg_scale = self.scale * 1000 # IFC is in meters, SVG is in mm
self.raw_width = self.camera_width
self.raw_height = self.camera_height
self.width = self.raw_width * self.svg_scale
self.height = self.raw_height * self.svg_scale
def add_stylesheet(self, uri):
default_stylesheet = os.path.join(self.data_dir, "styles", f"default.css")
with open(tool.Ifc.resolve_uri(uri) or default_stylesheet, "r") as stylesheet:
self.svg.defs.add(self.svg.style(stylesheet.read()))
def add_markers(self, uri):
tree = ET.parse(tool.Ifc.resolve_uri(uri) or os.path.join(self.data_dir, "templates", "markers.svg"))
root = tree.getroot()
for child in root:
self.svg.defs.add(External(child))
def add_symbols(self, uri):
tree = ET.parse(tool.Ifc.resolve_uri(uri) or os.path.join(self.data_dir, "templates", "symbols.svg"))
root = tree.getroot()
for child in root:
self.svg.defs.add(External(child))
def add_patterns(self, uri):
tree = ET.parse(tool.Ifc.resolve_uri(uri) or os.path.join(self.data_dir, "templates", "patterns.svg"))
root = tree.getroot()
for child in root:
self.svg.defs.add(External(child))
def draw_annotations(self, annotations):
for element in annotations:
obj = tool.Ifc.get_object(element)
if not obj or element.ObjectType == "DRAWING":
continue
@@ -153,6 +138,8 @@ class SvgWriter:
self.draw_stair_annotation(obj)
elif element.ObjectType == "DIMENSION":
self.draw_dimension_annotations(obj)
elif element.ObjectType == "ANGLE":
self.draw_angle_annotations(obj)
elif element.ObjectType == "RADIUS":
self.draw_radius_annotations(obj)
elif element.ObjectType == "DIAMETER":
@@ -176,6 +163,7 @@ class SvgWriter:
# Experimental integration with the MeasureIt-ARCH Add-on
self.draw_measureit_arch_dimension_annotations()
return self
def draw_section_level_annotation(self, obj):
x_offset = self.raw_width / 2
@@ -190,7 +178,7 @@ class SvgWriter:
projected_points = [self.project_point_onto_camera(matrix_world @ p.co.xyz) for p in points]
d = " ".join(
[
"L {} {}".format((x_offset + p.x) * self.scale, (y_offset - p.y) * self.scale)
"L {} {}".format((x_offset + p.x) * self.svg_scale, (y_offset - p.y) * self.svg_scale)
for p in projected_points
]
)
@@ -198,8 +186,8 @@ class SvgWriter:
path = self.svg.add(self.svg.path(d=d, class_=" ".join(classes)))
text_position = Vector(
(
(x_offset + projected_points[0].x) * self.scale,
((y_offset - projected_points[0].y) * self.scale) - 3.5,
(x_offset + projected_points[0].x) * self.svg_scale,
((y_offset - projected_points[0].y) * self.svg_scale) - 3.5,
)
)
# TODO: allow metric to be configurable
@@ -229,14 +217,14 @@ class SvgWriter:
projected_points = [self.project_point_onto_camera(matrix_world @ p.co.xyz) for p in points]
d = " ".join(
[
"L {} {}".format((x_offset + p.x) * self.scale, (y_offset - p.y) * self.scale)
"L {} {}".format((x_offset + p.x) * self.svg_scale, (y_offset - p.y) * self.svg_scale)
for p in projected_points
]
)
d = "M{}".format(d[1:])
start = Vector(((x_offset + projected_points[0].x), (y_offset - projected_points[0].y)))
next_point = Vector(((x_offset + projected_points[1].x), (y_offset - projected_points[1].y)))
text_position = (start * self.scale) - ((next_point - start).normalized() * 5)
text_position = (start * self.svg_scale) - ((next_point - start).normalized() * 5)
path = self.svg.add(self.svg.path(d=d, class_=" ".join(classes)))
self.svg.add(
self.svg.text(
@@ -266,14 +254,16 @@ class SvgWriter:
end = Vector(((x_offset + v1.x), (y_offset - v1.y)))
vector = end - start
line = self.svg.add(
self.svg.line(start=tuple(start * self.scale), end=tuple(end * self.scale), class_=" ".join(classes))
self.svg.line(
start=tuple(start * self.svg_scale), end=tuple(end * self.svg_scale), class_=" ".join(classes)
)
)
line["stroke-dasharray"] = "12.5, 3, 3, 3"
axis_tag = tool.Ifc.get_entity(obj).Name
self.svg.add(
self.svg.text(
axis_tag,
insert=tuple(start * self.scale),
insert=tuple(start * self.svg_scale),
**{
"font-size": annotation.Annotator.get_svg_text_size(5.0),
"font-family": "OpenGost Type B TT",
@@ -286,7 +276,7 @@ class SvgWriter:
self.svg.add(
self.svg.text(
axis_tag,
insert=tuple(end * self.scale),
insert=tuple(end * self.svg_scale),
**{
"font-size": annotation.Annotator.get_svg_text_size(5.0),
"font-family": "OpenGost Type B TT",
@@ -316,7 +306,7 @@ class SvgWriter:
projected_points.append(projected_points[0])
d = " ".join(
[
"L {} {}".format((x_offset + p.x) * self.scale, (y_offset - p.y) * self.scale)
"L {} {}".format((x_offset + p.x) * self.svg_scale, (y_offset - p.y) * self.svg_scale)
for p in projected_points
]
)
@@ -353,7 +343,7 @@ class SvgWriter:
projected_points = [self.project_point_onto_camera(matrix_world @ p.co.xyz) for p in points]
d = " ".join(
[
"L {} {}".format((x_offset + p.x) * self.scale, (y_offset - p.y) * self.scale)
"L {} {}".format((x_offset + p.x) * self.svg_scale, (y_offset - p.y) * self.svg_scale)
for p in projected_points
]
)
@@ -375,7 +365,9 @@ class SvgWriter:
end = Vector(((x_offset + v1.x), (y_offset - v1.y)))
vector = end - start
line = self.svg.add(
self.svg.line(start=tuple(start * self.scale), end=tuple(end * self.scale), class_=" ".join(classes))
self.svg.line(
start=tuple(start * self.svg_scale), end=tuple(end * self.svg_scale), class_=" ".join(classes)
)
)
def draw_leader_annotation(self, obj):
@@ -404,17 +396,17 @@ class SvgWriter:
symbol_position = Vector(((x_offset + symbol_position.x), (y_offset - symbol_position.y)))
transform = "rotate({}, {}, {})".format(
angle,
(symbol_position * self.scale)[0],
(symbol_position * self.scale)[1],
(symbol_position * self.svg_scale)[0],
(symbol_position * self.svg_scale)[1],
)
self.svg.add(
self.svg.use("#section-arrow", insert=tuple(symbol_position * self.scale), transform=transform)
self.svg.use("#section-arrow", insert=tuple(symbol_position * self.svg_scale), transform=transform)
)
self.svg.add(self.svg.use("#section-tag", insert=tuple(symbol_position * self.scale)))
self.svg.add(self.svg.use("#section-tag", insert=tuple(symbol_position * self.svg_scale)))
reference_id, sheet_id = self.get_reference_and_sheet_id_from_annotation(tool.Ifc.get_entity(obj))
text_position = list(symbol_position * self.scale)
text_position = list(symbol_position * self.svg_scale)
text_style = {
"font-size": annotation.Annotator.get_svg_text_size(2.5),
"font-family": "OpenGost Type B TT",
@@ -437,15 +429,17 @@ class SvgWriter:
transform = "rotate({}, {}, {})".format(
angle,
(symbol_position * self.scale)[0],
(symbol_position * self.scale)[1],
(symbol_position * self.svg_scale)[0],
(symbol_position * self.svg_scale)[1],
)
self.svg.add(self.svg.use("#elevation-arrow", insert=tuple(symbol_position * self.scale), transform=transform))
self.svg.add(self.svg.use("#elevation-tag", insert=tuple(symbol_position * self.scale)))
self.svg.add(
self.svg.use("#elevation-arrow", insert=tuple(symbol_position * self.svg_scale), transform=transform)
)
self.svg.add(self.svg.use("#elevation-tag", insert=tuple(symbol_position * self.svg_scale)))
reference_id, sheet_id = self.get_reference_and_sheet_id_from_annotation(tool.Ifc.get_entity(obj))
text_position = list(symbol_position * self.scale)
text_position = list(symbol_position * self.svg_scale)
text_style = {
"font-size": annotation.Annotator.get_svg_text_size(2.5),
"font-family": "OpenGost Type B TT",
@@ -462,7 +456,7 @@ class SvgWriter:
drawing = tool.Drawing.get_annotation_element(element)
reference = tool.Drawing.get_drawing_reference(drawing)
if reference:
sheet = tool.Drawing.get_reference_sheet(reference)
sheet = tool.Drawing.get_reference_document(reference)
if sheet:
if tool.Ifc.get_schema() == "IFC2X3":
reference_id = reference.ItemReference or "-"
@@ -493,13 +487,13 @@ class SvgWriter:
transform = "rotate({}, {}, {})".format(
angle,
(text_position * self.scale)[0],
(text_position * self.scale)[1],
(text_position * self.svg_scale)[0],
(text_position * self.svg_scale)[1],
)
symbol = tool.Drawing.get_annotation_symbol(element)
if symbol:
self.svg.add(self.svg.use(f"#{symbol}", insert=tuple(text_position * self.scale)))
self.svg.add(self.svg.use(f"#{symbol}", insert=tuple(text_position * self.svg_scale)))
if text_literal.BoxAlignment == "top-left":
alignment_baseline = "hanging"
@@ -531,17 +525,18 @@ class SvgWriter:
literal = text_literal.Literal
product = tool.Drawing.get_text_product(element)
product = tool.Drawing.get_assigned_product(element)
selector = ifcopenshell.util.selector.Selector
variables = {}
for variable in re.findall("{{.*?}}", literal):
literal = literal.replace(variable, selector.get_element_value(product, variable[2:-2]) or "")
if product != None:
for variable in re.findall("{{.*?}}", literal):
literal = literal.replace(variable, str(selector.get_element_value(product, variable[2:-2]) or ""))
for line_number, text_line in enumerate(literal.replace("\\n", "\n").split("\n")):
self.svg.add(
self.svg.text(
text_line,
insert=tuple((text_position * self.scale) + Vector((0, 3.5 * line_number))),
insert=tuple((text_position * self.svg_scale) + Vector((0, 3.5 * line_number))),
class_=" ".join(self.get_attribute_classes(text_obj)),
**{
"font-size": annotation.Annotator.get_svg_text_size(text_obj.BIMTextProperties.font_size),
@@ -570,7 +565,7 @@ class SvgWriter:
]
d = " ".join(
[
"L {} {}".format((x_offset + p.x) * self.scale, (y_offset - p.y) * self.scale)
"L {} {}".format((x_offset + p.x) * self.svg_scale, (y_offset - p.y) * self.svg_scale)
for p in projected_points
]
)
@@ -587,7 +582,7 @@ class SvgWriter:
projected_points = [self.project_point_onto_camera(matrix_world @ p.co.xyz) for p in points]
d = " ".join(
[
"L {} {}".format((x_offset + p.x) * self.scale, (y_offset - p.y) * self.scale)
"L {} {}".format((x_offset + p.x) * self.svg_scale, (y_offset - p.y) * self.svg_scale)
for p in projected_points
]
)
@@ -595,8 +590,8 @@ class SvgWriter:
path = self.svg.add(self.svg.path(d=d, class_=" ".join(classes)))
text_position = Vector(
(
(x_offset + projected_points[0].x) * self.scale,
((y_offset - projected_points[0].y) * self.scale) - 2.5,
(x_offset + projected_points[0].x) * self.svg_scale,
((y_offset - projected_points[0].y) * self.svg_scale) - 2.5,
)
)
# TODO: allow metric to be configurable
@@ -623,6 +618,119 @@ class SvgWriter:
)
)
def draw_angle_annotations(self, obj):
# This implementation uses an SVG arc, which means that it can only draw
# arcs that are orthogonal to the view (e.g. not arcs in 3D).
# Gosh this is bad code :(
x_offset = self.raw_width / 2
y_offset = self.raw_height / 2
classes = self.get_attribute_classes(obj)
matrix_world = obj.matrix_world
points = [v.co for v in obj.data.vertices][:3]
center = tool.Cad.get_center_of_arc(points, obj)
bm = bmesh.new()
bm.from_mesh(obj.data)
bm.verts.ensure_lookup_table()
arc_end_verts = [v for v in bm.verts if len(v.link_edges) == 1]
arc_end_pts = [matrix_world @ v.co for v in arc_end_verts]
# Probably need this when rewriting to use an SVG polyline instead of an arc
# arc_path = [arc_end_verts[0]]
# while True:
# last_point = arc_end_verts[0]
# found_another_point = False
# for edge in last_point.link_edges:
# v = edge.other_vert(last_point)
# if v not in arc_path:
# found_another_point = True
# arc_path.append(v)
# if not found_another_point:
# break
distance_between_end_verts = (arc_end_verts[0].co - arc_end_verts[1].co).length
arc_mid_vert = arc_end_verts[0].link_edges[0].other_vert(arc_end_verts[0])
is_reflex = 0 if (arc_mid_vert.co - arc_end_verts[1].co).length < distance_between_end_verts else 1
bm.free()
# Calculate the angle
# This is the true normal in 3D, whereas the camera projection we use is the drawing direction.
# This assumes (because we use SVG arcs) that the radius is always orthogonal to our view.
# When rewriting to use polylines, we should use this normal instead.
# normal = mathutils.geometry.normal([arc_end_pts[0], arc_end_pts[1], center])
normal = Vector(self.camera_projection)
dir1 = (arc_end_pts[0] - center).normalized()
dir2 = (arc_end_pts[1] - center).normalized()
# Let's get the matrix that represents the coordinate system of the arc.
# This matrix allows us to get 2D vectors for calculating the signed arc angle.
z = normal
x = (arc_end_pts[0] - center).normalized()
y = z.cross(x)
arc_matrix = mathutils.Matrix([x, y, z]).transposed().to_4x4()
dir1 = ((arc_matrix.inverted() @ arc_end_pts[0]) - (arc_matrix.inverted() @ center)).normalized()
dir2 = ((arc_matrix.inverted() @ arc_end_pts[1]) - (arc_matrix.inverted() @ center)).normalized()
angle = -dir1.xy.angle_signed(dir2.xy)
# if is_reflex:
# angle = angle % (math.pi * 2)
# Center of gravity of all vertices, used to help position the text
cog = Vector((0, 0, 0))
for vert in obj.data.vertices:
cog += vert.co
cog = matrix_world @ (cog / len(obj.data.vertices))
radius = ((matrix_world @ obj.data.vertices[0].co) - center).length
arc_midpoint = center + ((cog - center).normalized() * radius)
text_position = self.project_point_onto_camera(arc_midpoint)
text_position = Vector(
((x_offset + text_position.x) * self.svg_scale, (y_offset - text_position.y) * self.svg_scale)
)
center_projected = self.project_point_onto_camera(center)
center_position = Vector(
((x_offset + center_projected.x) * self.svg_scale, (y_offset - center_projected.y) * self.svg_scale)
)
text_offset = (text_position - center_position).xy.normalized() * 5
text_position += text_offset
text_style = {
"font-size": annotation.Annotator.get_svg_text_size(2.5),
"font-family": "OpenGost Type B TT",
"text-anchor": "middle",
"alignment-baseline": "middle",
"dominant-baseline": "middle",
}
angle_text = abs(round(math.degrees(angle), 3))
if is_reflex:
angle_text = 360 - angle_text
self.svg.add(self.svg.text(f"{angle_text}deg", insert=tuple(text_position), **text_style))
# Draw SVG arc, see for details: http://xahlee.info/js/svg_circle_arc.html
arc_proj_end_pts = [self.project_point_onto_camera(v) for v in arc_end_pts]
p1 = Vector(
((x_offset + arc_proj_end_pts[0].x) * self.svg_scale, (y_offset - arc_proj_end_pts[0].y) * self.svg_scale)
)
p2 = Vector(
((x_offset + arc_proj_end_pts[1].x) * self.svg_scale, (y_offset - arc_proj_end_pts[1].y) * self.svg_scale)
)
r = radius * self.svg_scale
# reflex = 1 if angle > math.pi else 0
reflex = is_reflex
if reflex:
sense = 0 if angle > 0 else 1
else:
sense = 1 if angle > 0 else 0
d = f"M {p1.x} {p1.y} A {r} {r} 0 {reflex} {sense} {p2.x} {p2.y}"
path = self.svg.add(self.svg.path(d=d, class_=" ".join(classes)))
def draw_radius_annotations(self, obj):
x_offset = self.raw_width / 2
y_offset = self.raw_height / 2
@@ -634,7 +742,7 @@ class SvgWriter:
projected_points = [self.project_point_onto_camera(matrix_world @ p.co.xyz) for p in points]
d = " ".join(
[
"L {} {}".format((x_offset + p.x) * self.scale, (y_offset - p.y) * self.scale)
"L {} {}".format((x_offset + p.x) * self.svg_scale, (y_offset - p.y) * self.svg_scale)
for p in projected_points
]
)
@@ -642,15 +750,21 @@ class SvgWriter:
path = self.svg.add(self.svg.path(d=d, class_=" ".join(classes)))
p0 = Vector(
((x_offset + projected_points[0].x) * self.scale, (y_offset - projected_points[0].y) * self.scale)
(
(x_offset + projected_points[0].x) * self.svg_scale,
(y_offset - projected_points[0].y) * self.svg_scale,
)
)
p1 = Vector(
((x_offset + projected_points[1].x) * self.scale, (y_offset - projected_points[1].y) * self.scale)
(
(x_offset + projected_points[1].x) * self.svg_scale,
(y_offset - projected_points[1].y) * self.svg_scale,
)
)
text_offset = (p0 - p1).xy.normalized() * 5
text_position = projected_points[0]
text_position = Vector(
((x_offset + text_position.x) * self.scale, (y_offset - text_position.y) * self.scale)
((x_offset + text_position.x) * self.svg_scale, (y_offset - text_position.y) * self.svg_scale)
)
text_position += text_offset
@@ -722,15 +836,17 @@ class SvgWriter:
perpendicular = Vector((vector.y, -vector.x)).normalized()
dimension = (v1_global - v0_global).length
dimension = helper.format_distance(dimension)
sheet_dimension = ((end * self.scale) - (start * self.scale)).length
sheet_dimension = ((end * self.svg_scale) - (start * self.svg_scale)).length
if sheet_dimension < 5: # annotation can't fit
# offset text to right of marker
text_position = (end * self.scale) + perpendicular + (3 * vector.normalized())
text_position = (end * self.svg_scale) + perpendicular + (3 * vector.normalized())
else:
text_position = (mid * self.scale) + perpendicular
text_position = (mid * self.svg_scale) + perpendicular
rotation = math.degrees(vector.angle_signed(Vector((1, 0))))
line = self.svg.add(
self.svg.line(start=tuple(start * self.scale), end=tuple(end * self.scale), class_=" ".join(classes))
self.svg.line(
start=tuple(start * self.svg_scale), end=tuple(end * self.svg_scale), class_=" ".join(classes)
)
)
if text_override is not None:
text = text_override
@@ -762,35 +878,8 @@ class SvgWriter:
def get_spline_points(self, spline):
return spline.bezier_points if spline.bezier_points else spline.points
def draw_cut_polygons(self):
return # deprecate?
for polygon in self.ifc_cutter.cut_polygons:
self.draw_polygon(polygon, "cut")
def draw_polyline(self, element, position):
load_occ()
classes = self.get_classes(element["raw"], position)
exp = BRepTools.BRepTools_WireExplorer(element["geometry"])
points = []
while exp.More():
point = BRep.BRep_Tool.Pnt(exp.CurrentVertex())
points.append((point.X() * self.scale, -point.Y() * self.scale))
exp.Next()
self.svg.add(self.svg.polyline(points=points, class_=" ".join(classes)))
def draw_line(self, element, position):
load_occ()
classes = self.get_classes(element["raw"], position)
exp = TopExp.TopExp_Explorer(element["geometry"], TopAbs.TopAbs_VERTEX)
points = []
while exp.More():
point = BRep.BRep_Tool.Pnt(topods.Vertex(exp.Current()))
points.append((point.X() * self.scale, -point.Y() * self.scale))
exp.Next()
self.svg.add(self.svg.line(start=points[0], end=points[1], class_=" ".join(classes)))
def draw_polygon(self, polygon, position):
points = [(p[0] * self.scale, p[1] * self.scale) for p in polygon["points"]]
points = [(p[0] * self.svg_scale, p[1] * self.svg_scale) for p in polygon["points"]]
if "classes" in polygon["metadata"]:
classes = " ".join(polygon["metadata"]["classes"])
else:
@@ -20,7 +20,7 @@ import bpy
import blenderbim.bim.helper
import blenderbim.tool as tool
from bpy.types import Panel
from blenderbim.bim.module.drawing.data import TextData, SheetsData, SchedulesData, DrawingsData
from blenderbim.bim.module.drawing.data import ProductAssignmentsData, TextData, SheetsData, SchedulesData, DrawingsData
class BIM_PT_camera(Panel):
@@ -144,6 +144,10 @@ class BIM_PT_drawings(Panel):
bl_region_type = "UI"
bl_category = "BIM Documentation"
@classmethod
def poll(cls, context):
return tool.Ifc.get()
def draw(self, context):
if not DrawingsData.is_loaded:
DrawingsData.load()
@@ -166,13 +170,16 @@ class BIM_PT_drawings(Panel):
if self.props.active_drawing_index < len(self.props.drawings):
active_drawing = self.props.drawings[self.props.active_drawing_index]
row = self.layout.row(align=True)
row.alignment = "RIGHT"
col = row.column()
col.alignment = 'LEFT'
col.operator("bim.remove_drawing", icon="X", text="").drawing = active_drawing.ifc_definition_id
col = row.column()
col.alignment = 'RIGHT'
op = row.operator("bim.open_view", icon="URL", text="")
op.view = active_drawing.name
op = row.operator("bim.activate_view", icon="OUTLINER_OB_CAMERA", text="")
op.drawing = active_drawing.ifc_definition_id
row.operator("bim.create_drawing", text="", icon="OUTPUT")
row.operator("bim.remove_drawing", icon="X", text="").drawing = active_drawing.ifc_definition_id
self.layout.template_list(
"BIM_UL_drawinglist", "", self.props, "drawings", self.props, "active_drawing_index"
)
@@ -195,6 +202,10 @@ class BIM_PT_schedules(Panel):
bl_region_type = "UI"
bl_category = "BIM Documentation"
@classmethod
def poll(cls, context):
return tool.Ifc.get()
def draw(self, context):
if not SchedulesData.is_loaded:
SchedulesData.load()
@@ -217,10 +228,14 @@ class BIM_PT_schedules(Panel):
row = self.layout.row(align=True)
row.alignment = "RIGHT"
row.operator("bim.open_schedule", icon="URL", text="").schedule = active_schedule.ifc_definition_id
row.operator("bim.build_schedule", icon="LINENUMBERS_ON", text="").schedule = active_schedule.ifc_definition_id
row.operator(
"bim.build_schedule", icon="LINENUMBERS_ON", text=""
).schedule = active_schedule.ifc_definition_id
row.operator("bim.remove_schedule", icon="X", text="").schedule = active_schedule.ifc_definition_id
self.layout.template_list("BIM_UL_generic", "", self.props, "schedules", self.props, "active_schedule_index")
self.layout.template_list(
"BIM_UL_generic", "", self.props, "schedules", self.props, "active_schedule_index"
)
class BIM_PT_sheets(Panel):
@@ -230,6 +245,10 @@ class BIM_PT_sheets(Panel):
bl_region_type = "UI"
bl_category = "BIM Documentation"
@classmethod
def poll(cls, context):
return tool.Ifc.get()
def draw(self, context):
if not SheetsData.is_loaded:
SheetsData.load()
@@ -264,6 +283,39 @@ class BIM_PT_sheets(Panel):
self.layout.template_list("BIM_UL_sheets", "", self.props, "sheets", self.props, "active_sheet_index")
class BIM_PT_product_assignments(Panel):
bl_label = "IFC Product Assignments"
bl_idname = "BIM_PT_product_assignments"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
@classmethod
def poll(cls, context):
if not tool.Ifc.get() or not context.active_object:
return
element = tool.Ifc.get_entity(context.active_object)
if not element:
return
return element.is_a("IfcAnnotation")
def draw(self, context):
if not ProductAssignmentsData.is_loaded:
ProductAssignmentsData.load()
props = context.active_object.BIMAssignedProductProperties
if props.is_editing_product:
row = self.layout.row(align=True)
row.prop(props, "relating_product", text="")
row.operator("bim.edit_assigned_product", icon="CHECKMARK", text="")
row.operator("bim.disable_editing_assigned_product", icon="CANCEL", text="")
else:
row = self.layout.row(align=True)
row.label(text=ProductAssignmentsData.data["relating_product"] or "No Relating Product", icon="OBJECT_DATA")
row.operator("bim.enable_editing_assigned_product", icon="GREASEPENCIL", text="")
class BIM_PT_text(Panel):
bl_label = "IFC Text"
bl_idname = "BIM_PT_text"
@@ -300,16 +352,6 @@ class BIM_PT_text(Panel):
row.label(text=attribute["name"])
row.label(text=attribute["value"])
if props.is_editing_product:
row = self.layout.row(align=True)
row.prop(props, "relating_product", text="")
row.operator("bim.edit_text_product", icon="CHECKMARK", text="")
row.operator("bim.disable_editing_text_product", icon="CANCEL", text="")
else:
row = self.layout.row(align=True)
row.label(text=TextData.data["relating_product"] or "No Relating Product", icon="OBJECT_DATA")
row.operator("bim.enable_editing_text_product", icon="GREASEPENCIL", text="")
row = self.layout.row()
row.prop(props, "font_size")
row = self.layout.row()
@@ -382,6 +424,7 @@ class BIM_PT_annotation_utilities(Panel):
row = layout.row(align=True)
row.prop(self.props, "should_draw_decorations", text="Viewport Annotations")
row.enabled = context.scene.camera is not None
class BIM_UL_drawinglist(bpy.types.UIList):
@@ -25,17 +25,20 @@ classes = (
operator.EditObjectPlacement,
operator.GetRepresentationIfcParameters,
operator.OverrideDelete,
operator.OverrideOutlinerDelete,
operator.OverrideDuplicateMove,
operator.OverrideDuplicateMoveLinked,
operator.OverrideOutlinerDelete,
operator.OverridePasteBuffer,
operator.RemoveConnection,
operator.RemoveRepresentation,
operator.SelectConnection,
operator.SwitchRepresentation,
operator.UpdateParametricRepresentation,
operator.UpdateRepresentation,
prop.BIMGeometryProperties,
ui.BIM_PT_derived_placements,
ui.BIM_PT_representations,
ui.BIM_PT_connections,
ui.BIM_PT_mesh,
ui.BIM_PT_workarounds,
)
@@ -25,6 +25,7 @@ from mathutils import Vector
def refresh():
DerivedPlacementsData.is_loaded = False
RepresentationsData.is_loaded = False
ConnectionsData.is_loaded = False
class RepresentationsData:
@@ -64,6 +65,40 @@ class RepresentationsData:
return results
class ConnectionsData:
data = {}
is_loaded = False
@classmethod
def load(cls):
cls.data = {"connections": cls.connections()}
cls.is_loaded = True
@classmethod
def connections(cls):
results = []
element = tool.Ifc.get_entity(bpy.context.active_object)
for rel in getattr(element, "ConnectedTo", []):
results.append(
{
"id": rel.id(),
"is_relating": True,
"Name": rel.RelatedElement.Name or "Unnamed",
"ConnectionType": rel.RelatingConnectionType,
}
)
for rel in getattr(element, "ConnectedFrom", []):
results.append(
{
"id": rel.id(),
"is_relating": False,
"Name": rel.RelatingElement.Name or "Unnamed",
"ConnectionType": rel.RelatedConnectionType,
}
)
return results
class DerivedPlacementsData:
data = {}
is_loaded = False
@@ -21,7 +21,7 @@ import bmesh
import mathutils
import ifcopenshell
import ifcopenshell.util.unit
from math import pi
from math import pi, pow
from mathutils import Vector, Matrix, geometry
@@ -112,6 +112,167 @@ class Helper:
return {"profile": profile, "extrusion": extrusion}
def auto_detect_arbitrary_profile_with_voids(self, obj, mesh):
groups = {"IFCARCINDEX": [], "IFCCIRCLE": []}
for i, group in enumerate(obj.vertex_groups):
if "IFCARCINDEX" in group.name:
groups["IFCARCINDEX"].append(i)
elif "IFCCIRCLE" in group.name:
groups["IFCCIRCLE"].append(i)
bm = bmesh.new()
bm.from_mesh(mesh)
bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=1e-5)
bmesh.ops.delete(bm, geom=bm.faces, context="FACES_ONLY")
# https://docs.blender.org/api/blender_python_api_2_63_8/bmesh.html#CustomDataAccess
# This is how we access vertex groups via bmesh, apparently, it's not very intuitive
deform_layer = bm.verts.layers.deform.active
# Sanity check
group_verts = {"IFCARCINDEX": {}, "IFCCIRCLE": {}}
for vert in bm.verts:
total_groups = 0
is_circle = False
for group_type, group_indices in groups.items():
for group_index in group_indices:
if group_index in vert[deform_layer]:
if group_type == "IFCCIRCLE":
is_circle = True
group_verts[group_type].setdefault(group_index, 0)
group_verts[group_type][group_index] += 1
total_groups += 0
if total_groups > 1: # A vert can only belong to one group
return (False, "AMBIGUOUS_SPECIAL_VERTEX")
elif is_circle:
pass # Circles are allowed to be unclosed
elif total_groups == 0 and len(vert.link_edges) != 2: # Unclosed loop or forked loop
return (False, "UNCLOSED_LOOP")
for group_type, group_counts in group_verts.items():
if group_type == "IFCARCINDEX":
for group_count in group_counts.values():
if group_count != 3: # Each arc needs 3 verts
return (False, "3POINT_ARC")
elif group_type == "IFCCIRCLE":
for group_count in group_counts.values():
if group_count != 2: # Each circle needs 2 verts
return (False, "CIRCLE")
loop_edges = set(bm.edges)
# Create loops from edges
loops = []
while loop_edges:
edge = loop_edges.pop()
loop = [edge]
has_found_connected_edge = True
while has_found_connected_edge:
has_found_connected_edge = False
for edge in loop_edges.copy():
edge_verts = set(edge.verts)
if edge_verts & set(loop[0].verts):
loop.insert(0, edge)
loop_edges.remove(edge)
has_found_connected_edge = True
elif edge_verts & set(loop[-1].verts):
loop.append(edge)
loop_edges.remove(edge)
has_found_connected_edge = True
loops.append(loop)
# Determine outer loop
max_area = 0
outer_loop = None
inner_loops = []
for loop in loops:
loop_vertices = []
total_edges = len(loop)
if total_edges == 1:
loop_vertices = [loop[0].verts[0], loop[0].verts[1]]
else:
for i, edge in enumerate(loop):
if i + 1 == total_edges and edge.verts[0] in loop[i - 1].verts:
loop_vertices.append(edge.verts[0])
elif i + 1 == total_edges and edge.verts[1] in loop[i - 1].verts:
loop_vertices.append(edge.verts[1])
elif edge.verts[0] in loop[i + 1].verts:
loop_vertices.append(edge.verts[1])
elif edge.verts[1] in loop[i + 1].verts:
loop_vertices.append(edge.verts[0])
if len(loop_vertices) == 2:
# Unofficial convention right now that a loop of 2 verts always is a circle
radius = (loop_vertices[0].co - loop_vertices[1].co).length / 2
face_area = pi * pow(radius, 2)
else:
loop_bm = bmesh.new()
for vert in loop_vertices:
loop_bm.verts.new(vert.co)
face = loop_bm.faces.new(loop_bm.verts)
face_area = face.calc_area()
loop_bm.free()
loop_vertex_indices = []
active_arc_id = None
arc_stack = []
last_index = len(loop_vertices) - 1
# It is possible to loop through loop_vertices halfway through an arc.
# If that is the case, we store it in incomplete_arc
incomplete_arc = []
for i, v in enumerate(loop_vertices):
if len(arc_stack) == 3:
loop_vertex_indices.append(arc_stack)
loop_vertex_indices.append((arc_stack[-1], v.index))
arc_stack = []
active_arc_id = None
is_arc = False
for group_index in groups["IFCARCINDEX"]:
if group_index in v[deform_layer]:
if active_arc_id is not None and active_arc_id != group_index:
incomplete_arc = arc_stack
arc_stack = []
active_arc_id = group_index
is_arc = True
break
if i == last_index and not is_arc:
continue
if is_arc:
arc_stack.append(v.index)
else:
if active_arc_id is not None:
incomplete_arc = arc_stack
active_arc_id = None
arc_stack = []
loop_vertex_indices.append((v.index, loop_vertices[i + 1].index))
if active_arc_id is not None:
arc_stack.extend(incomplete_arc)
loop_vertex_indices.append(arc_stack)
loop_vertex_indices.append((loop_vertex_indices[-1][-1], loop_vertex_indices[0][0]))
if face_area > max_area:
max_area = face_area
outer_loop = loop_vertex_indices
inner_loops.append(loop_vertex_indices)
inner_loops.remove(outer_loop)
points = [v.co for v in bm.verts]
bm.to_mesh(mesh)
mesh.update()
bm.free()
return {"points": points, "profile": outer_loop, "inner_curves": inner_loops}
# An arbitrary closed profile with voids is similar to one without voids.
# We start the same way with any ngon (no tri), but instead of being the entire
# profile, it is only one of the possible faces that make up the end of our
@@ -86,6 +86,26 @@ class AddRepresentation(bpy.types.Operator, Operator):
)
class SelectConnection(bpy.types.Operator, Operator):
bl_idname = "bim.select_connection"
bl_label = "Select Connection"
bl_options = {"REGISTER", "UNDO"}
connection: bpy.props.IntProperty()
def _execute(self, context):
core.select_connection(tool.Geometry, connection=tool.Ifc.get().by_id(self.connection))
class RemoveConnection(bpy.types.Operator, Operator):
bl_idname = "bim.remove_connection"
bl_label = "Remove Connection"
bl_options = {"REGISTER", "UNDO"}
connection: bpy.props.IntProperty()
def _execute(self, context):
core.remove_connection(tool.Geometry, connection=tool.Ifc.get().by_id(self.connection))
class SwitchRepresentation(bpy.types.Operator, Operator):
bl_idname = "bim.switch_representation"
bl_label = "Switch Representation"
@@ -97,15 +117,28 @@ class SwitchRepresentation(bpy.types.Operator, Operator):
should_switch_all_meshes: bpy.props.BoolProperty()
def _execute(self, context):
core.switch_representation(
tool.Geometry,
obj=bpy.data.objects.get(self.obj) if self.obj else context.active_object,
representation=tool.Ifc.get().by_id(self.ifc_definition_id),
should_reload=self.should_reload,
enable_dynamic_voids=self.disable_opening_subtractions,
is_global=self.should_switch_all_meshes,
should_sync_changes_first=True,
)
target = tool.Ifc.get().by_id(self.ifc_definition_id).ContextOfItems
is_subcontext = target.is_a("IfcGeometricRepresentationSubContext")
for obj in context.selected_objects:
element = tool.Ifc.get_entity(obj)
if not element:
continue
if is_subcontext:
representation = ifcopenshell.util.representation.get_representation(
element, target.ContextType, target.ContextIdentifier, target.TargetView
)
else:
representation = ifcopenshell.util.representation.get_representation(element, target.ContextType)
if not representation:
continue
core.switch_representation(
tool.Geometry,
obj=obj,
representation=representation,
should_reload=self.should_reload,
is_global=self.should_switch_all_meshes,
should_sync_changes_first=True,
)
class RemoveRepresentation(bpy.types.Operator, Operator):
@@ -130,10 +163,6 @@ class UpdateRepresentation(bpy.types.Operator):
obj: bpy.props.StringProperty()
ifc_representation_class: bpy.props.StringProperty()
@classmethod
def poll(cls, context):
return context.active_object.mode == "OBJECT"
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
@@ -141,6 +170,10 @@ class UpdateRepresentation(bpy.types.Operator):
if not ContextData.is_loaded:
ContextData.load(IfcStore.get_file())
if context.active_object and context.active_object.mode != "OBJECT":
# Ensure mode is object to prevent invalid mesh data causing CTD
bpy.ops.object.mode_set(mode="OBJECT", toggle=False)
objs = [bpy.data.objects.get(self.obj)] if self.obj else context.selected_objects
self.file = IfcStore.get_file()
@@ -156,18 +189,23 @@ class UpdateRepresentation(bpy.types.Operator):
def update_obj_mesh_representation(self, context, obj):
product = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
material = ifcopenshell.util.element.get_material(product, should_skip_usage=True)
if not product.is_a("IfcGridAxis"):
tool.Geometry.clear_cache(product)
if product.is_a("IfcGridAxis"):
# Grid geometry does not follow the "representation" paradigm and needs to be treated specially
ifcopenshell.api.run("grid.create_axis_curve", self.file, **{"axis_curve": obj, "grid_axis": product})
return
if product.is_a("IfcRelSpaceBoundary"):
elif product.is_a("IfcRelSpaceBoundary"):
# TODO refactor
settings = tool.Boundary.get_assign_connection_geometry_settings(obj)
ifcopenshell.api.run("boundary.assign_connection_geometry", tool.Ifc.get(), **settings)
return
elif material and material.is_a() in ["IfcMaterialProfileSet", "IfcMaterialLayerSet"]:
# These objects are parametrically based on an axis and should not be modified as a mesh
return
core.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj)
@@ -207,7 +245,10 @@ class UpdateRepresentation(bpy.types.Operator):
new_representation = ifcopenshell.api.run("geometry.add_representation", self.file, **representation_data)
if tool.Geometry.is_body_representation(new_representation):
[tool.Geometry.run_style_add_style(obj=mat) for mat in tool.Geometry.get_object_materials_without_styles(obj)]
[
tool.Geometry.run_style_add_style(obj=mat)
for mat in tool.Geometry.get_object_materials_without_styles(obj)
]
ifcopenshell.api.run(
"style.assign_representation_styles",
self.file,
@@ -237,7 +278,7 @@ class UpdateParametricRepresentation(bpy.types.Operator):
@classmethod
def poll(cls, context):
return context.active_object.mode == "OBJECT"
return context.active_object and context.active_object.mode == "OBJECT"
def execute(self, context):
self.file = IfcStore.get_file()
@@ -251,7 +292,6 @@ class UpdateParametricRepresentation(bpy.types.Operator):
obj=obj,
representation=tool.Ifc.get().by_id(props.ifc_definition_id),
should_reload=True,
enable_dynamic_voids=False,
is_global=True,
should_sync_changes_first=False,
)
@@ -327,21 +367,27 @@ class OverrideDeleteTrait:
if getattr(element, "FillsVoids", None):
self.remove_filling(element)
if element.is_a("IfcOpeningElement"):
for rel in element.HasFillings:
self.remove_filling(rel.RelatedBuildingElement)
if element.VoidsElements:
self.delete_opening_element(element)
elif getattr(element, "HasOpenings", None):
for rel in element.HasOpenings:
self.delete_opening_element(rel.RelatedOpeningElement)
if element.HasFillings:
for rel in element.HasFillings:
self.remove_filling(rel.RelatedBuildingElement)
else:
if element.VoidsElements:
self.delete_opening_element(element)
else:
if getattr(element, "HasOpenings", None):
for rel in element.HasOpenings:
self.delete_opening_element(rel.RelatedOpeningElement)
for port in ifcopenshell.util.system.get_ports(element):
self.remove_port(port)
def delete_opening_element(self, element):
obj = IfcStore.get_element(element.VoidsElements[0].RelatingBuildingElement.id())
bpy.ops.bim.remove_opening(opening_id=element.id(), obj=obj.name)
bpy.ops.bim.remove_opening(opening_id=element.id())
def remove_filling(self, element):
obj = IfcStore.get_element(element.id())
bpy.ops.bim.remove_filling(obj=obj.name)
bpy.ops.bim.remove_filling(filling=element.id())
def remove_port(self, port):
blenderbim.core.system.remove_port(tool.Ifc, tool.System, port=port)
class OverrideDelete(bpy.types.Operator, OverrideDeleteTrait):
@@ -485,6 +531,9 @@ class OverrideDuplicateMove(bpy.types.Operator):
def _execute(self, context):
self.new_active_obj = None
# Track decompositions so they can be recreated after the operation
relationships = tool.Root.get_decomposition_relationships(context.selected_objects)
old_to_new = {}
for obj in context.selected_objects:
new_obj = obj.copy()
if obj.data:
@@ -495,8 +544,12 @@ class OverrideDuplicateMove(bpy.types.Operator):
collection.objects.link(new_obj)
obj.select_set(False)
new_obj.select_set(True)
# This is the only difference
blenderbim.core.root.copy_class(tool.Ifc, tool.Collector, tool.Geometry, tool.Root, obj=new_obj)
# Copy the actual class
new = blenderbim.core.root.copy_class(tool.Ifc, tool.Collector, tool.Geometry, tool.Root, obj=new_obj)
if new:
old_to_new[tool.Ifc.get_entity(obj)] = [new]
# Recreate decompositions
tool.Root.recreate_decompositions(relationships, old_to_new)
bpy.ops.transform.translate("INVOKE_DEFAULT")
blenderbim.bim.handler.purge_module_data()
@@ -533,6 +586,9 @@ class OverrideDuplicateMoveLinked(bpy.types.Operator):
def _execute(self, context):
self.new_active_obj = None
# Track decompositions so they can be recreated after the operation
relationships = tool.Root.get_decomposition_relationships(context.selected_objects)
old_to_new = {}
for obj in context.selected_objects:
new_obj = obj.copy()
if obj == context.active_object:
@@ -541,8 +597,12 @@ class OverrideDuplicateMoveLinked(bpy.types.Operator):
collection.objects.link(new_obj)
obj.select_set(False)
new_obj.select_set(True)
# This is the only difference
blenderbim.core.root.copy_class(tool.Ifc, tool.Collector, tool.Geometry, tool.Root, obj=new_obj)
# Copy the actual class
new = blenderbim.core.root.copy_class(tool.Ifc, tool.Collector, tool.Geometry, tool.Root, obj=new_obj)
if new:
old_to_new[tool.Ifc.get_entity(obj)] = new
# Recreate decompositions
tool.Root.recreate_decompositions(relationships, old_to_new)
bpy.ops.transform.translate("INVOKE_DEFAULT")
blenderbim.bim.handler.purge_module_data()
return {"FINISHED"}
@@ -19,7 +19,8 @@
import bpy
from bpy.types import Panel
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.module.geometry.data import RepresentationsData, DerivedPlacementsData
from blenderbim.bim.helper import prop_with_search
from blenderbim.bim.module.geometry.data import RepresentationsData, ConnectionsData, DerivedPlacementsData
class BIM_PT_representations(Panel):
@@ -50,7 +51,7 @@ class BIM_PT_representations(Panel):
layout.label(text="No representations found")
row = layout.row(align=True)
row.prop(context.scene.BIMRootProperties, "contexts", text="")
prop_with_search(row, context.scene.BIMRootProperties, "contexts", text="")
row.operator("bim.add_representation", icon="ADD", text="")
for representation in RepresentationsData.data["representations"]:
@@ -67,6 +68,43 @@ class BIM_PT_representations(Panel):
row.operator("bim.remove_representation", icon="X", text="").representation_id = representation["id"]
class BIM_PT_connections(Panel):
bl_label = "IFC Connections"
bl_idname = "BIM_PT_connections"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
bl_parent_id = "BIM_PT_geometry_object"
@classmethod
def poll(cls, context):
if not context.active_object:
return False
if not IfcStore.get_element(context.active_object.BIMObjectProperties.ifc_definition_id):
return False
return IfcStore.get_file()
def draw(self, context):
if not ConnectionsData.is_loaded:
ConnectionsData.load()
layout = self.layout
props = context.active_object.BIMObjectProperties
if not ConnectionsData.data["connections"]:
layout.label(text="No connections found")
for connection in ConnectionsData.data["connections"]:
row = self.layout.row(align=True)
row.label(text=connection["Name"], icon="SNAP_ON" if connection["is_relating"] else "SNAP_OFF")
row.label(text=connection["ConnectionType"])
op = row.operator("bim.select_connection", icon="RESTRICT_SELECT_OFF", text="")
op.connection = connection["id"]
op = row.operator("bim.remove_connection", icon="X", text="")
op.connection = connection["id"]
class BIM_PT_mesh(Panel):
bl_label = "IFC Representation"
bl_idname = "BIM_PT_mesh"
@@ -89,18 +127,6 @@ class BIM_PT_mesh(Panel):
layout = self.layout
props = context.active_object.data.BIMMeshProperties
row = layout.row(align=True)
op = row.operator("bim.switch_representation", text="Bake Voids", icon="SELECT_SUBTRACT")
op.should_switch_all_meshes = True
op.should_reload = True
op.ifc_definition_id = props.ifc_definition_id
op.disable_opening_subtractions = False
op = row.operator("bim.switch_representation", text="Dynamic Voids", icon="SELECT_INTERSECT")
op.should_switch_all_meshes = True
op.should_reload = True
op.ifc_definition_id = props.ifc_definition_id
op.disable_opening_subtractions = True
row = layout.row()
row.operator("bim.copy_representation")
@@ -0,0 +1,123 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2022 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 ifcopenshell.util.geolocation
import blenderbim.tool as tool
def refresh():
GeoreferenceData.is_loaded = False
class GeoreferenceData:
data = {}
is_loaded = False
@classmethod
def load(cls):
cls.data["blender_derived_angle"] = cls.blender_derived_angle()
cls.data["map_conversion"] = cls.map_conversion()
cls.data["map_derived_angle"] = cls.map_derived_angle()
cls.data["projected_crs"] = cls.projected_crs()
cls.data["true_north"] = cls.true_north()
cls.data["true_derived_angle"] = cls.true_derived_angle()
cls.is_loaded = True
@classmethod
def blender_derived_angle(cls):
props = bpy.context.scene.BIMGeoreferenceProperties
if props.has_blender_offset:
return str(
round(
ifcopenshell.util.geolocation.xaxis2angle(
float(props.blender_x_axis_abscissa), float(props.blender_x_axis_ordinate)
),
3,
)
)
@classmethod
def map_conversion(cls):
if tool.Ifc.get_schema() == "IFC2X3":
return {}
for context in tool.Ifc.get().by_type("IfcGeometricRepresentationContext", include_subtypes=False):
if context.HasCoordinateOperation:
map_conversion = context.HasCoordinateOperation[0].get_info()
del map_conversion["id"]
del map_conversion["type"]
del map_conversion["SourceCRS"]
del map_conversion["TargetCRS"]
return map_conversion
return {}
@classmethod
def map_derived_angle(cls):
if (
cls.data["map_conversion"]
and cls.data["map_conversion"]["XAxisAbscissa"] is not None
and cls.data["map_conversion"]["XAxisOrdinate"] is not None
):
return str(
round(
ifcopenshell.util.geolocation.xaxis2angle(
cls.data["map_conversion"]["XAxisAbscissa"], cls.data["map_conversion"]["XAxisOrdinate"]
),
3,
)
)
return ""
@classmethod
def projected_crs(cls):
if tool.Ifc.get_schema() == "IFC2X3":
return {}
for context in tool.Ifc.get().by_type("IfcGeometricRepresentationContext", include_subtypes=False):
if context.HasCoordinateOperation:
projected_crs = context.HasCoordinateOperation[0].TargetCRS.get_info()
del projected_crs["id"]
del projected_crs["type"]
if projected_crs["MapUnit"]:
unit = projected_crs["MapUnit"]
projected_crs["MapUnit"] = (getattr(unit, "Prefix", "") or "") + unit.Name
return projected_crs
return {}
@classmethod
def true_north(cls):
ifc = tool.Ifc.get()
true_north = {}
if ifc.schema == "IFC2X3":
return
for context in ifc.by_type("IfcGeometricRepresentationContext", include_subtypes=False):
if not context.TrueNorth:
continue
true_north = context.TrueNorth.DirectionRatios
break
return true_north
@classmethod
def true_derived_angle(cls):
if cls.data["true_north"]:
return str(round(ifcopenshell.util.geolocation.yaxis2angle(*cls.data["true_north"][0:2]), 3))
@@ -17,345 +17,157 @@
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
import json
import ifcopenshell
import ifcopenshell.util.unit
import ifcopenshell.util.attribute
import ifcopenshell.api
import blenderbim.bim.helper
from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.georeference.data import Data
from ifcopenshell.api.unit.data import Data as UnitData
from math import radians, degrees, atan, tan, cos, sin
import blenderbim.tool as tool
import blenderbim.core.georeference as core
class EnableEditingGeoreferencing(bpy.types.Operator):
bl_idname = "bim.enable_editing_georeferencing"
bl_label = "Enable Editing Georeferencing"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
self.file = IfcStore.get_file()
props = context.scene.BIMGeoreferenceProperties
self.props = props
props.projected_crs.clear()
blenderbim.bim.helper.import_attributes(
"IfcProjectedCRS", props.projected_crs, Data.projected_crs, self.import_projected_crs_attributes
)
props.map_conversion.clear()
blenderbim.bim.helper.import_attributes(
"IfcMapConversion", props.map_conversion, Data.map_conversion, self.import_map_conversion_attributes
)
props.has_true_north = bool(Data.true_north)
if Data.true_north:
props.true_north_abscissa = str(Data.true_north[0])
props.true_north_ordinate = str(Data.true_north[1])
props.is_editing = True
return {"FINISHED"}
def import_projected_crs_attributes(self, name, prop, data):
if name == "MapUnit":
new = self.props.projected_crs.add()
new.name = name
new.data_type = "enum"
new.is_null = data[name] is None
new.is_optional = True
new.enum_items = json.dumps(
{u["id"]: u["Name"] for u in UnitData.units.values() if u["UnitType"] == "LENGTHUNIT"}
)
if data["MapUnit"]:
new.enum_value = str(data["MapUnit"]["id"])
return True
def import_map_conversion_attributes(self, name, prop, data):
if name not in ["SourceCRS", "TargetCRS"]:
# Enforce a string data type to prevent data loss in single-precision Blender props
prop.data_type = "string"
prop.string_value = "" if prop.is_null else str(data[name])
return True
class DisableEditingGeoreferencing(bpy.types.Operator):
bl_idname = "bim.disable_editing_georeferencing"
bl_label = "Disable Editing Georeferencing"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
props = context.scene.BIMGeoreferenceProperties
props.is_editing = False
return {"FINISHED"}
class EditGeoreferencing(bpy.types.Operator):
bl_idname = "bim.edit_georeferencing"
bl_label = "Edit Georeferencing"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
self.file = IfcStore.get_file()
props = context.scene.BIMGeoreferenceProperties
projected_crs = blenderbim.bim.helper.export_attributes(props.projected_crs, self.export_crs_attributes)
map_conversion = blenderbim.bim.helper.export_attributes(props.map_conversion, self.export_map_attributes)
true_north = None
if props.has_true_north:
try:
true_north = [float(props.true_north_abscissa), float(props.true_north_ordinate)]
except ValueError:
self.report({"ERROR"}, "True North Abscissa and Ordinate expect a number")
ifcopenshell.api.run(
"georeference.edit_georeferencing",
self.file,
**{
"map_conversion": map_conversion,
"projected_crs": projected_crs,
"true_north": true_north,
}
)
Data.load(self.file)
bpy.ops.bim.disable_editing_georeferencing()
return {"FINISHED"}
def export_map_attributes(self, attributes, prop):
if not prop.is_null and prop.data_type == "string":
# We store our floats as string to prevent single precision data loss
attributes[prop.name] = float(prop.string_value)
return True
def export_crs_attributes(self, attributes, prop):
if not prop.is_null and prop.name == "MapUnit":
attributes[prop.name] = self.file.by_id(int(prop.enum_value))
return True
class SetBlenderGridNorth(bpy.types.Operator):
bl_idname = "bim.set_blender_grid_north"
bl_label = "Set Blender Grid North"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
context.scene.sun_pos_properties.north_offset = -radians(
ifcopenshell.util.geolocation.xaxis2angle(
float(context.scene.BIMGeoreferenceProperties.map_conversion.get("XAxisAbscissa").string_value),
float(context.scene.BIMGeoreferenceProperties.map_conversion.get("XAxisOrdinate").string_value),
)
)
return {"FINISHED"}
class SetIfcGridNorth(bpy.types.Operator):
bl_idname = "bim.set_ifc_grid_north"
bl_label = "Set IFC Grid North"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
x_angle = -context.scene.sun_pos_properties.north_offset
context.scene.BIMGeoreferenceProperties.map_conversion.get("XAxisAbscissa").string_value = str(cos(x_angle))
context.scene.BIMGeoreferenceProperties.map_conversion.get("XAxisOrdinate").string_value = str(sin(x_angle))
return {"FINISHED"}
class SetBlenderTrueNorth(bpy.types.Operator):
bl_idname = "bim.set_blender_true_north"
bl_label = "Set Blender True North"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
context.scene.sun_pos_properties.north_offset = -radians(
ifcopenshell.util.geolocation.yaxis2angle(
float(context.scene.BIMGeoreferenceProperties.true_north_abscissa),
float(context.scene.BIMGeoreferenceProperties.true_north_ordinate),
)
)
return {"FINISHED"}
class SetIfcTrueNorth(bpy.types.Operator):
bl_idname = "bim.set_ifc_true_north"
bl_label = "Set IFC True North"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
y_angle = -context.scene.sun_pos_properties.north_offset + radians(90)
context.scene.BIMGeoreferenceProperties.true_north_abscissa = str(cos(y_angle))
context.scene.BIMGeoreferenceProperties.true_north_ordinate = str(sin(y_angle))
return {"FINISHED"}
class RemoveGeoreferencing(bpy.types.Operator):
bl_idname = "bim.remove_georeferencing"
bl_label = "Remove Georeferencing"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
ifcopenshell.api.run("georeference.remove_georeferencing", IfcStore.get_file())
Data.load(IfcStore.get_file())
return {"FINISHED"}
class AddGeoreferencing(bpy.types.Operator):
class AddGeoreferencing(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.add_georeferencing"
bl_label = "Add Georeferencing"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
bl_description = "Add a new georeference"
def _execute(self, context):
ifcopenshell.api.run("georeference.add_georeferencing", IfcStore.get_file())
Data.load(IfcStore.get_file())
return {"FINISHED"}
core.add_georeferencing(tool.Ifc)
class ConvertLocalToGlobal(bpy.types.Operator):
bl_idname = "bim.convert_local_to_global"
bl_label = "Convert Local To Global"
class EnableEditingGeoreferencing(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.enable_editing_georeferencing"
bl_label = "Enable Editing Georeferencing"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Enable editing georeferencing"
@classmethod
def poll(cls, context):
file = IfcStore.get_file()
props = context.scene.BIMGeoreferenceProperties
return file and props.coordinate_input.count(",") == 2
def execute(self, context):
if not Data.is_loaded:
Data.load(IfcStore.get_file())
props = context.scene.BIMGeoreferenceProperties
x, y, z = [float(co) for co in props.coordinate_input.split(",")]
if props.has_blender_offset:
results = ifcopenshell.util.geolocation.xyz2enh(
x,
y,
z,
float(props.blender_eastings),
float(props.blender_northings),
float(props.blender_orthogonal_height),
float(props.blender_x_axis_abscissa),
float(props.blender_x_axis_ordinate),
1.0,
)
x, y, z = results
# TODO: what if the project CRS units and the project units are different?
if Data.map_conversion:
results = ifcopenshell.util.geolocation.xyz2enh(
x,
y,
z,
Data.map_conversion["Eastings"],
Data.map_conversion["Northings"],
Data.map_conversion["OrthogonalHeight"],
Data.map_conversion.get("XAxisAbscissa", 1.0),
Data.map_conversion.get("XAxisOrdinate", 0.0),
Data.map_conversion.get("Scale", 1.0),
)
else:
results = (x, y, z)
props.coordinate_output = ",".join([str(r) for r in results])
context.scene.cursor.location = results
return {"FINISHED"}
def _execute(self, context):
core.enable_editing_georeferencing(tool.Georeference)
class ConvertGlobalToLocal(bpy.types.Operator):
bl_idname = "bim.convert_global_to_local"
bl_label = "Convert Global To Local"
class RemoveGeoreferencing(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.remove_georeferencing"
bl_label = "Remove Georeferencing"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Remove the georeferencing"
@classmethod
def poll(cls, context):
file = IfcStore.get_file()
props = context.scene.BIMGeoreferenceProperties
return file and file.by_type("IfcUnitAssignment") and props.coordinate_input.count(",") == 2
def execute(self, context):
if not Data.is_loaded:
Data.load(IfcStore.get_file())
props = context.scene.BIMGeoreferenceProperties
x, y, z = [float(co) for co in props.coordinate_input.split(",")]
if Data.map_conversion:
results = ifcopenshell.util.geolocation.enh2xyz(
x,
y,
z,
Data.map_conversion["Eastings"],
Data.map_conversion["Northings"],
Data.map_conversion["OrthogonalHeight"],
Data.map_conversion.get("XAxisAbscissa", 1.0),
Data.map_conversion.get("XAxisOrdinate", 0.0),
Data.map_conversion.get("Scale", 1.0),
)
else:
results = (x, y, z)
if props.has_blender_offset:
results = ifcopenshell.util.geolocation.enh2xyz(
results[0],
results[1],
results[2],
float(props.blender_eastings),
float(props.blender_northings),
float(props.blender_orthogonal_height),
float(props.blender_x_axis_abscissa),
float(props.blender_x_axis_ordinate),
1.0,
)
props.coordinate_output = ",".join([str(r) for r in results])
scale = ifcopenshell.util.unit.calculate_unit_scale(IfcStore.get_file())
context.scene.cursor.location = [o * scale for o in results]
return {"FINISHED"}
def _execute(self, context):
core.remove_georeferencing(tool.Ifc)
class GetCursorLocation(bpy.types.Operator):
class EditGeoreferencing(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.edit_georeferencing"
bl_label = "Edit Georeferencing"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Edit the georeferencing"
def _execute(self, context):
core.edit_georeferencing(tool.Ifc, tool.Georeference)
class DisableEditingGeoreferencing(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.disable_editing_georeferencing"
bl_label = "Disable Editing Georeferencing"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Close editing panel"
def _execute(self, context):
core.disable_editing_georeferencing(tool.Georeference)
class SetIfcGridNorth(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.set_ifc_grid_north"
bl_label = "Set IFC Grid North"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Set IFC grid north"
def _execute(self, context):
core.set_ifc_grid_north(tool.Georeference)
class SetBlenderGridNorth(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.set_blender_grid_north"
bl_label = "Set Blender Grid North"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Set Blender grid north"
def _execute(self, context):
core.set_blender_grid_north(tool.Georeference)
class GetCursorLocation(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.get_cursor_location"
bl_label = "Get Cursor Location"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Insert the current cursor coordinates"
@classmethod
def poll(cls, context):
file = IfcStore.get_file()
return file and file.by_type("IfcUnitAssignment")
return tool.Ifc.get()
def execute(self, context):
props = context.scene.BIMGeoreferenceProperties
scale = ifcopenshell.util.unit.calculate_unit_scale(IfcStore.get_file())
project_coordinates = [o / scale for o in context.scene.cursor.location]
props.coordinate_input = ",".join([str(o) for o in project_coordinates])
return {"FINISHED"}
def _execute(self, context):
core.get_cursor_location(tool.Georeference)
class SetCursorLocation(bpy.types.Operator):
class SetCursorLocation(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.set_cursor_location"
bl_label = "Set Cursor Location"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Move curson location to the specified coordinates"
@classmethod
def poll(cls, context):
file = IfcStore.get_file()
props = context.scene.BIMGeoreferenceProperties
return file and file.by_type("IfcUnitAssignment") and props.coordinate_output.count(",") == 2
return tool.Ifc.get() and props.coordinate_output.count(",") == 2
def execute(self, context):
def _execute(self, context):
core.set_cursor_location(tool.Georeference)
class SetIfcTrueNorth(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.set_ifc_true_north"
bl_label = "Set IFC True North"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Set IFC True north"
def _execute(self, context):
core.set_ifc_true_north(tool.Georeference)
class SetBlenderTrueNorth(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.set_blender_true_north"
bl_label = "Set Blender True North"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Set Blender true north"
def _execute(self, context):
core.set_blender_true_north(tool.Georeference)
class ConvertLocalToGlobal(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.convert_local_to_global"
bl_label = "Convert Local To Global"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Convert local coordinate to global coordinate"
@classmethod
def poll(cls, context):
file = tool.Ifc.get()
props = context.scene.BIMGeoreferenceProperties
scale = ifcopenshell.util.unit.calculate_unit_scale(IfcStore.get_file())
context.scene.cursor.location = [float(co) * scale for co in props.coordinate_output.split(",")]
return {"FINISHED"}
return file and props.coordinate_input.count(",") == 2
def _execute(self, context):
core.convert_local_to_global(tool.Georeference)
class ConvertGlobalToLocal(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.convert_global_to_local"
bl_label = "Convert Global To Local"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Convert global coordinate to local coordinate"
@classmethod
def poll(cls, context):
file = tool.Ifc.get()
props = context.scene.BIMGeoreferenceProperties
return file and file.by_type("IfcUnitAssignment") and props.coordinate_input.count(",") == 2
def _execute(self, context):
core.convert_global_to_local(tool.Georeference)
@@ -16,11 +16,10 @@
# 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 ifcopenshell.util.geolocation
import blenderbim.tool as tool
from bpy.types import Panel
from ifcopenshell.api.georeference.data import Data
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.helper import draw_attributes, draw_attribute
from blenderbim.bim.module.georeference.data import GeoreferenceData
class BIM_PT_gis(Panel):
@@ -32,16 +31,13 @@ class BIM_PT_gis(Panel):
bl_context = "scene"
bl_parent_id = "BIM_PT_geometry"
@classmethod
def poll(cls, context):
return IfcStore.get_file()
def draw(self, context):
self.layout.use_property_split = True
self.layout.use_property_decorate = False
props = context.scene.BIMGeoreferenceProperties
if not Data.is_loaded:
Data.load(IfcStore.get_file())
if not GeoreferenceData.is_loaded:
GeoreferenceData.load()
if props.is_editing:
return self.draw_editable_ui(context)
@@ -82,10 +78,10 @@ class BIM_PT_gis(Panel):
def draw_ui(self, context):
props = context.scene.BIMGeoreferenceProperties
if not Data.projected_crs:
if not GeoreferenceData.data["projected_crs"]:
row = self.layout.row(align=True)
row.label(text="Not Georeferenced")
if IfcStore.get_file().schema != "IFC2X3":
if tool.Ifc.get_schema != "IFC2X3":
row.operator("bim.add_georeferencing", icon="ADD", text="")
if props.has_blender_offset:
@@ -109,40 +105,27 @@ class BIM_PT_gis(Panel):
row.label(text=props.blender_x_axis_ordinate)
row = self.layout.row(align=True)
row.label(text="Derived Grid North")
row.label(
text=str(
round(
ifcopenshell.util.geolocation.xaxis2angle(
float(props.blender_x_axis_abscissa), float(props.blender_x_axis_ordinate)
),
3,
)
)
)
row.label(text=GeoreferenceData.data["blender_derived_angle"])
if Data.projected_crs:
if GeoreferenceData.data["projected_crs"]:
row = self.layout.row(align=True)
row.label(text="Projected CRS", icon="WORLD")
row.operator("bim.enable_editing_georeferencing", icon="GREASEPENCIL", text="")
row.operator("bim.remove_georeferencing", icon="X", text="")
for key, value in Data.projected_crs.items():
if key == "id" or key == "type" or not value:
for key, value in GeoreferenceData.data["projected_crs"].items():
if not value:
continue
if key == "MapUnit":
unit_value = value.get("Prefix", "") or ""
unit_value += value["Name"]
value = unit_value
row = self.layout.row(align=True)
row.label(text=key)
row.label(text=str(value))
if Data.map_conversion:
if GeoreferenceData.data["map_conversion"]:
row = self.layout.row(align=True)
row.label(text="Map Conversion", icon="GRID")
for key, value in Data.map_conversion.items():
if key == "id" or key == "type" or key == "SourceCRS" or key == "TargetCRS" or value is None:
for key, value in GeoreferenceData.data["map_conversion"].items():
if value is None:
continue
row = self.layout.row(align=True)
row.label(text=key)
@@ -150,26 +133,17 @@ class BIM_PT_gis(Panel):
if key == "XAxisOrdinate":
row = self.layout.row(align=True)
row.label(text="Derived Angle")
row.label(
text=str(
round(
ifcopenshell.util.geolocation.xaxis2angle(
Data.map_conversion["XAxisAbscissa"], Data.map_conversion["XAxisOrdinate"]
),
3,
)
)
)
row.label(text=GeoreferenceData.data["map_derived_angle"])
if Data.true_north:
if GeoreferenceData.data["true_north"]:
row = self.layout.row()
row.label(text="True North", icon="LIGHT_SUN")
row = self.layout.row(align=True)
row.label(text="Vector")
row.label(text=str(Data.true_north[0:2])[1:-1])
row.label(text=str(GeoreferenceData.data["true_north"][0:2])[1:-1])
row = self.layout.row(align=True)
row.label(text="Derived Angle")
row.label(text=str(round(ifcopenshell.util.geolocation.yaxis2angle(*Data.true_north[0:2]), 3)))
row.label(text=GeoreferenceData.data["true_derived_angle"])
class BIM_PT_gis_utilities(Panel):
@@ -20,17 +20,20 @@ import bpy
from . import ui, prop, operator
classes = (
operator.LoadGroups,
operator.DisableGroupEditingUI,
operator.AddGroup,
operator.EditGroup,
operator.RemoveGroup,
operator.ToggleAssigningGroup,
operator.AssignGroup,
operator.UnassignGroup,
operator.EnableEditingGroup,
operator.DisableEditingGroup,
operator.DisableGroupEditingUI,
operator.EditGroup,
operator.EnableEditingGroup,
operator.LoadGroups,
operator.RemoveGroup,
operator.SelectGroupProducts,
operator.ToggleAssigningGroup,
operator.ToggleGroup,
operator.UnassignGroup,
operator.UpdateGroup,
prop.ExpandedGroups,
prop.Group,
prop.BIMGroupProperties,
ui.BIM_PT_groups,
@@ -42,7 +45,9 @@ classes = (
def register():
bpy.types.Scene.BIMGroupProperties = bpy.props.PointerProperty(type=prop.BIMGroupProperties)
bpy.types.Scene.ExpandedGroups = bpy.props.PointerProperty(type=prop.ExpandedGroups)
def unregister():
del bpy.types.Scene.BIMGroupProperties
del bpy.types.Scene.ExpandedGroups
@@ -20,24 +20,89 @@ import bpy
import ifcopenshell.util.attribute
import ifcopenshell.api
import blenderbim.bim.helper
import blenderbim.tool as tool
from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.group.data import Data
from ifcopenshell.util.selector import Selector
import json
class LoadGroups(bpy.types.Operator):
bl_idname = "bim.load_groups"
bl_label = "Load Groups"
bl_options = {"REGISTER", "UNDO"}
is_refresh: bpy.props.BoolProperty(default=False)
def execute(self, context):
props = context.scene.BIMGroupProperties
props.groups.clear()
self.props = context.scene.BIMGroupProperties
self.expanded_groups = json.loads(context.scene.ExpandedGroups.json_string)
self.props.groups.clear()
self.ifc = IfcStore.get_file()
if not self.is_refresh:
context.scene.ExpandedGroups.json_string = "{}"
for ifc_definition_id, group in Data.groups.items():
new = props.groups.add()
new.ifc_definition_id = ifc_definition_id
new.name = group["Name"]
props.is_editing = True
if not group["HasAssignments"]:
new = self.props.groups.add()
new.ifc_definition_id = ifc_definition_id
new.name = group["Name"]
new.selection_query = group["Description"].split("*selector*")[1] if group["Description"] else ""
new.tree_depth = 0
if group["IsGroupedBy"]:
# assumes 1:1 cardinality, will need to be updated to reflect IFC4 changes
# where the cardinality is 0:? - vulevukusej
sub_groups = [g for g in group["IsGroupedBy"][0].RelatedObjects if g.is_a("IfcGroup")]
new.has_children = True if len(sub_groups) != 0 else False
if self.is_refresh:
self.recursively_load_sub_groups(ifc_definition_id, new)
self.props.is_editing = True
bpy.ops.bim.disable_editing_group()
Data.load(IfcStore.get_file())
return {"FINISHED"}
def recursively_load_sub_groups(self, ifc_definition_id, parent):
sub_groups = [
k for k, v in Data.products.items() if self.ifc.by_id(k).is_a("IfcGroup") and ifc_definition_id in v
]
if str(ifc_definition_id) not in self.expanded_groups or sub_groups == []:
return
else:
parent.is_expanded = True
parent.has_children = True
for group in sub_groups:
new = self.props.groups.add()
new.ifc_definition_id = group
new.name = Data.groups[group]["Name"]
new.selection_query = (
Data.groups[group]["Description"].split("*selector*")[1]
if Data.groups[group]["Description"]
else ""
)
new.has_children = True if len(Data.groups[group]["IsGroupedBy"]) != 0 else False
new.tree_depth = parent.tree_depth + 1
self.recursively_load_sub_groups(new.ifc_definition_id, new)
class ToggleGroup(bpy.types.Operator):
bl_idname = "bim.toggle_group"
bl_label = "Toggle Group"
bl_options = {"REGISTER", "UNDO"}
ifc_definition_id: bpy.props.IntProperty()
index: bpy.props.IntProperty()
option: bpy.props.StringProperty(name="Expand or Collapse")
def execute(self, context):
context.scene.BIMGroupProperties.groups[self.index].is_expanded = True if self.option == "Expand" else False
json_string = json.loads(context.scene.ExpandedGroups.json_string)
if self.option == "Expand":
json_string.setdefault(str(self.ifc_definition_id), None)
else:
json_string.pop(str(self.ifc_definition_id), None)
context.scene.ExpandedGroups.json_string = json.dumps(json_string)
bpy.ops.bim.load_groups(is_refresh=True)
return {"FINISHED"}
@@ -54,17 +119,21 @@ class DisableGroupEditingUI(bpy.types.Operator):
class AddGroup(bpy.types.Operator):
bl_idname = "bim.add_group"
bl_label = "Add Group"
bl_label = "Add New Group"
bl_options = {"REGISTER", "UNDO"}
group: bpy.props.IntProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
result = ifcopenshell.api.run("group.add_group", IfcStore.get_file())
Data.load(IfcStore.get_file())
bpy.ops.bim.load_groups()
bpy.ops.bim.enable_editing_group(group=result.id())
result = ifcopenshell.api.run("group.add_group", tool.Ifc.get())
if self.group:
ifcopenshell.api.run(
"group.assign_group", tool.Ifc.get(), products=[result], group=tool.Ifc.get().by_id(self.group)
)
Data.load(tool.Ifc.get())
bpy.ops.bim.load_groups(is_refresh=True)
return {"FINISHED"}
@@ -72,6 +141,7 @@ class EditGroup(bpy.types.Operator):
bl_idname = "bim.edit_group"
bl_label = "Edit Group"
bl_options = {"REGISTER", "UNDO"}
copy_from_selector: bpy.props.BoolProperty(name="Copy from Selector", default=False)
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
@@ -84,12 +154,16 @@ class EditGroup(bpy.types.Operator):
attributes[attribute.name] = None
else:
attributes[attribute.name] = attribute.string_value
if self.copy_from_selector:
attributes["Description"] = context.scene.IFCSelector.selection_query
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"group.edit_group", self.file, **{"group": self.file.by_id(props.active_group_id), "attributes": attributes}
)
Data.load(IfcStore.get_file())
bpy.ops.bim.load_groups()
bpy.ops.bim.load_groups(is_refresh=True)
return {"FINISHED"}
@@ -107,7 +181,7 @@ class RemoveGroup(bpy.types.Operator):
self.file = IfcStore.get_file()
ifcopenshell.api.run("group.remove_group", self.file, **{"group": self.file.by_id(self.group)})
Data.load(IfcStore.get_file())
bpy.ops.bim.load_groups()
bpy.ops.bim.load_groups(is_refresh=True)
return {"FINISHED"}
@@ -120,9 +194,7 @@ class EnableEditingGroup(bpy.types.Operator):
def execute(self, context):
props = context.scene.BIMGroupProperties
props.group_attributes.clear()
blenderbim.bim.helper.import_attributes("IfcGroup", props.group_attributes, Data.groups[self.group])
props.active_group_id = self.group
return {"FINISHED"}
@@ -166,10 +238,8 @@ class AssignGroup(bpy.types.Operator):
ifcopenshell.api.run(
"group.assign_group",
self.file,
**{
"product": self.file.by_id(product.BIMObjectProperties.ifc_definition_id),
"group": self.file.by_id(self.group),
}
products=[self.file.by_id(product.BIMObjectProperties.ifc_definition_id)],
group=self.file.by_id(self.group),
)
Data.load(self.file)
return {"FINISHED"}
@@ -219,3 +289,32 @@ class SelectGroupProducts(bpy.types.Operator):
if self.group in product_groups:
obj.select_set(True)
return {"FINISHED"}
class UpdateGroup(bpy.types.Operator):
bl_idname = "bim.update_group"
bl_label = "Update Group"
bl_options = {"REGISTER", "UNDO"}
query: bpy.props.StringProperty()
group_id: bpy.props.IntProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
self.file = IfcStore.get_file()
group = self.file.by_id(self.group_id)
query = self.query
new_products = Selector.parse(self.file, query)
ifcopenshell.api.run(
"group.update_group_products",
self.file,
**{
"group": group,
"products": new_products,
}
)
Data.load(IfcStore.get_file())
bpy.ops.bim.load_groups(is_refresh=True)
return {"FINISHED"}
@@ -18,7 +18,10 @@
import bpy
from blenderbim.bim.prop import StrProperty, Attribute
from blenderbim.bim.helper import import_attributes
from ifcopenshell.api.group.data import Data
from bpy.types import PropertyGroup
import json
from bpy.props import (
PointerProperty,
StringProperty,
@@ -31,9 +34,17 @@ from bpy.props import (
)
class ExpandedGroups(StrProperty):
json_string: StringProperty(name="JSON String", default="{}")
class Group(PropertyGroup):
name: StringProperty(name="Name")
ifc_definition_id: IntProperty(name="IFC Definition ID")
selection_query: StringProperty(name="Selection Query")
is_expanded: BoolProperty(name="Is Expanded", default=False)
has_children: BoolProperty(name="Has Children", default=False)
tree_depth: IntProperty(name="Tree Depth")
class BIMGroupProperties(PropertyGroup):
@@ -40,9 +40,9 @@ class BIM_PT_groups(Panel):
self.props = context.scene.BIMGroupProperties
row = self.layout.row(align=True)
row.label(text="{} Groups Found".format(len(Data.groups)), icon="OUTLINER")
row.label(text=f"{len(Data.groups)} Groups Found", icon="OUTLINER")
if self.props.is_editing:
row.operator("bim.add_group", text="", icon="ADD")
row.operator("bim.add_group", text="", icon="ADD").group = 0
row.operator("bim.disable_group_editing_ui", text="", icon="CANCEL")
else:
row.operator("bim.load_groups", text="", icon="GREASEPENCIL")
@@ -115,10 +115,22 @@ class BIM_PT_object_groups(Panel):
class BIM_UL_groups(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
if item:
row = layout.row(align=True)
row.label(text=item.name)
for i in range(0, item.tree_depth):
row.label(text="", icon="BLANK1")
if item.has_children:
op = row.operator(
"bim.toggle_group", icon="TRIA_DOWN" if item.is_expanded else "TRIA_RIGHT", text="", emboss=False
)
op.ifc_definition_id = item.ifc_definition_id
op.index = index
op.option = "Collapse" if item.is_expanded else "Expand"
else:
row.label(text="", icon="BLANK1")
row.label(text=f"*{item.name}") if item.selection_query != "" else row.label(text=item.name)
group_id = item.ifc_definition_id
if context.scene.BIMGroupProperties.active_group_id == group_id:
op = row.operator("bim.select_group_products", text="", icon="RESTRICT_SELECT_OFF")
@@ -128,21 +140,46 @@ class BIM_UL_groups(UIList):
elif context.scene.BIMGroupProperties.active_group_id:
op = row.operator("bim.select_group_products", text="", icon="RESTRICT_SELECT_OFF")
op.group = group_id
op = row.operator("bim.add_group", text="", icon="ADD")
op.group = group_id
op = row.operator("bim.remove_group", text="", icon="X")
op.group = group_id
if item.selection_query != "":
op = row.operator("bim.update_group", text="", icon="FILE_REFRESH")
op.group_id = item.ifc_definition_id
op.query = item.selection_query
else:
op = row.operator("bim.select_group_products", text="", icon="RESTRICT_SELECT_OFF")
op.group = group_id
op = row.operator("bim.enable_editing_group", text="", icon="GREASEPENCIL")
op.group = group_id
op = row.operator("bim.add_group", text="", icon="ADD")
op.group = group_id
op = row.operator("bim.remove_group", text="", icon="X")
op.group = group_id
if item.selection_query != "":
op = row.operator("bim.update_group", text="", icon="FILE_REFRESH")
op.group_id = item.ifc_definition_id
op.query = item.selection_query
class BIM_UL_object_groups(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
if item:
row = layout.row(align=True)
for i in range(0, item.tree_depth):
row.label(text="", icon="BLANK1")
if item.has_children:
op = row.operator(
"bim.toggle_group", icon="TRIA_DOWN" if item.is_expanded else "TRIA_RIGHT", text="", emboss=False
)
op.ifc_definition_id = item.ifc_definition_id
op.index = index
op.option = "Collapse" if item.is_expanded else "Expand"
else:
row.label(text="", icon="BLANK1")
row.label(text=item.name)
op = row.operator("bim.remove_group", text="", icon="X")
op.group = item.ifc_definition_id
op = row.operator("bim.assign_group", text="", icon="ADD")
op.group = item.ifc_definition_id
@@ -28,6 +28,7 @@ classes = (
operator.AddProfile,
operator.AssignMaterial,
operator.AssignParameterizedProfile,
operator.ContractMaterialCategory,
operator.CopyMaterial,
operator.DisableEditingAssignedMaterial,
operator.DisableEditingMaterialSetItem,
@@ -36,6 +37,7 @@ classes = (
operator.EditMaterialSetItem,
operator.EnableEditingAssignedMaterial,
operator.EnableEditingMaterialSetItem,
operator.ExpandMaterialCategory,
operator.LoadMaterials,
operator.RemoveConstituent,
operator.RemoveLayer,
@@ -37,6 +37,7 @@ class MaterialsData:
cls.data = {
"total_materials": cls.total_materials(),
"material_types": cls.material_types(),
"profiles": cls.profiles(),
}
cls.is_loaded = True
@@ -65,9 +66,16 @@ class MaterialsData:
"IfcMaterialProfileSet",
"IfcMaterialList",
]
if tool.Ifc.get_schema() == "IFC2X3":
version = tool.Ifc.get_schema()
if version == "IFC2X3":
material_types = ["IfcMaterial", "IfcMaterialLayerSet", "IfcMaterialList"]
return [(m, m, "") for m in material_types]
return [
(m, m, ifcopenshell.util.doc.get_entity_doc(version, m).get("description", "")) for m in material_types
]
@classmethod
def profiles(cls):
return [(str(p.id()), p.ProfileName or "Unnamed", "") for p in tool.Ifc.get().by_type("IfcProfileDef")]
class ObjectMaterialData:
@@ -78,9 +86,26 @@ class ObjectMaterialData:
def load(cls):
cls.data = {
"materials": cls.materials(),
"type_material": cls.type_material(),
}
cls.is_loaded = True
@classmethod
def materials(cls):
return sorted([(str(m.id()), m.Name or "Unnamed", "") for m in tool.Ifc.get().by_type("IfcMaterial")], key=lambda x: x[1])
return sorted(
[(str(m.id()), m.Name or "Unnamed", "") for m in tool.Ifc.get().by_type("IfcMaterial")], key=lambda x: x[1]
)
@classmethod
def type_material(cls):
element = tool.Ifc.get_entity(bpy.context.active_object)
element_type = ifcopenshell.util.element.get_type(element)
if element_type and element_type != element:
material = ifcopenshell.util.element.get_material(element_type)
if not material:
return
if material.is_a() in ("IfcMaterialLayerSetUsage", "IfcMaterialLayerSet"):
name_attr = "LayerSetName"
else:
name_attr = "Name"
return getattr(material, name_attr, "Unnamed") or "Unnamed"
@@ -28,7 +28,6 @@ import blenderbim.core.material as core
from blenderbim.bim.module.material.prop import purge as material_prop_purge
from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.material.data import Data
from ifcopenshell.api.profile.data import Data as ProfileData
class LoadMaterials(bpy.types.Operator, tool.Ifc.Operator):
@@ -84,7 +83,6 @@ class AssignParameterizedProfile(bpy.types.Operator):
**{"material_profile": self.file.by_id(self.material_profile), "profile": profile},
)
Data.load_profiles()
ProfileData.load(self.file)
bpy.ops.bim.enable_editing_material_set_item(obj=obj.name, material_set_item=self.material_profile)
return {"FINISHED"}
@@ -256,13 +254,11 @@ class AddProfile(bpy.types.Operator):
ifcopenshell.api.run(
"material.add_profile",
self.file,
**{
"profile_set": self.file.by_id(self.profile_set),
"material": self.file.by_id(int(obj.BIMObjectMaterialProperties.material)),
},
profile_set=self.file.by_id(self.profile_set),
material=self.file.by_id(int(obj.BIMObjectMaterialProperties.material)),
profile=self.file.by_id(int(context.scene.BIMMaterialProperties.profiles)),
)
Data.load_profiles()
ProfileData.load(self.file)
return {"FINISHED"}
@@ -281,7 +277,6 @@ class RemoveProfile(bpy.types.Operator):
self.file = IfcStore.get_file()
ifcopenshell.api.run("material.remove_profile", self.file, **{"profile": self.file.by_id(self.profile)})
Data.load_profiles()
ProfileData.load(self.file)
return {"FINISHED"}
@@ -341,7 +336,6 @@ class ReorderMaterialSetItem(bpy.types.Operator):
Data.load_layers()
elif material_set.is_a("IfcMaterialProfileSet"):
Data.load_profiles()
ProfileData.load(self.file)
elif material_set.is_a("IfcMaterialList"):
Data.load_lists()
return {"FINISHED"}
@@ -503,6 +497,7 @@ class DisableEditingAssignedMaterial(bpy.types.Operator):
obj: bpy.props.StringProperty()
def execute(self, context):
bpy.ops.bim.disable_editing_material_set_item()
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
props = obj.BIMObjectMaterialProperties
props.is_editing = False
@@ -526,6 +521,9 @@ class EditAssignedMaterial(bpy.types.Operator):
props = obj.BIMObjectMaterialProperties
product_data = Data.products[obj.BIMObjectProperties.ifc_definition_id]
if props.active_material_set_item_id != 0: # We were editing a material layer set item
bpy.ops.bim.edit_material_set_item(material_set_item=props.active_material_set_item_id)
if product_data["type"] == "IfcMaterial":
bpy.ops.bim.unassign_material(obj=obj.name)
bpy.ops.bim.assign_material(obj=obj.name, material_type="IfcMaterial")
@@ -582,6 +580,7 @@ class EnableEditingMaterialSetItem(bpy.types.Operator):
def execute(self, context):
self.file = IfcStore.get_file()
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
self.mprops = context.scene.BIMMaterialProperties
self.props = obj.BIMObjectMaterialProperties
self.props.active_material_set_item_id = self.material_set_item
product_data = Data.products[obj.BIMObjectProperties.ifc_definition_id]
@@ -600,7 +599,8 @@ class EnableEditingMaterialSetItem(bpy.types.Operator):
self.load_set_item_attributes(material_set_item, material_set_item_data)
if material_set_item.is_a("IfcMaterialProfile"):
self.load_profile_attributes(material_set_item, material_set_item_data)
if material_set_item.Profile:
self.mprops.profiles = str(material_set_item.Profile.id())
return {"FINISHED"}
@@ -613,6 +613,7 @@ class EnableEditingMaterialSetItem(bpy.types.Operator):
continue
if attribute.name() in material_set_item_data:
new = self.props.material_set_item_attributes.add()
new.ifc_class = material_set_item.is_a()
new.name = attribute.name()
new.is_null = material_set_item_data[attribute.name()] is None
new.data_type = data_type
@@ -624,44 +625,8 @@ class EnableEditingMaterialSetItem(bpy.types.Operator):
new.int_value = 0 if new.is_null else material_set_item_data[attribute.name()]
elif data_type == "boolean":
new.bool_value = False if new.is_null else material_set_item_data[attribute.name()]
def load_profile_attributes(self, material_set_item, material_set_item_data):
self.props.material_set_item_profile_attributes.clear()
if not material_set_item_data["Profile"]:
return
profile = self.file.by_id(material_set_item_data["Profile"])
profile_data = ProfileData.profiles[material_set_item_data["Profile"]]
for attribute in IfcStore.get_schema().declaration_by_name(profile.is_a()).all_attributes():
data_type = ifcopenshell.util.attribute.get_primitive_type(attribute)
if data_type == "entity":
continue
if attribute.name() in profile_data:
new = self.props.material_set_item_profile_attributes.add()
new.name = attribute.name()
new.is_null = profile_data[attribute.name()] is None
new.is_optional = attribute.optional()
new.data_type = data_type
if data_type == "string":
new.string_value = "" if new.is_null else profile_data[attribute.name()]
elif data_type == "float":
new.float_value = 0.0 if new.is_null else profile_data[attribute.name()]
elif data_type == "integer":
new.int_value = 0 if new.is_null else profile_data[attribute.name()]
elif data_type == "boolean":
new.bool_value = False if new.is_null else profile_data[attribute.name()]
elif data_type == "enum":
new.enum_items = json.dumps(ifcopenshell.util.attribute.get_enum_items(attribute))
if profile_data[attribute.name()]:
new.enum_value = profile_data[attribute.name()]
# Force null to be false if the attribute is mandatory because when we first assign a profile, all of
# its fields are null (which is illegal).
# TODO: find a better solution.
if not new.is_optional:
new.is_null = False
blenderbim.bim.helper.add_attribute_description(new)
blenderbim.bim.helper.add_attribute_min_max(new)
class DisableEditingMaterialSetItem(bpy.types.Operator):
@@ -691,6 +656,7 @@ class EditMaterialSetItem(bpy.types.Operator):
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
self.file = IfcStore.get_file()
props = obj.BIMObjectMaterialProperties
mprops = context.scene.BIMMaterialProperties
product_data = Data.products[obj.BIMObjectProperties.ifc_definition_id]
attributes = blenderbim.bim.helper.export_attributes(props.material_set_item_attributes)
@@ -718,19 +684,19 @@ class EditMaterialSetItem(bpy.types.Operator):
)
Data.load_layers()
elif product_data["type"] == "IfcMaterialProfileSet" or product_data["type"] == "IfcMaterialProfileSetUsage":
profile_attributes = blenderbim.bim.helper.export_attributes(props.material_set_item_profile_attributes)
profile_def = None
if mprops.profiles:
profile_def = tool.Ifc.get().by_id(int(mprops.profiles))
ifcopenshell.api.run(
"material.edit_profile",
self.file,
**{
"profile": self.file.by_id(self.material_set_item),
"attributes": attributes,
"profile_attributes": profile_attributes,
"material": self.file.by_id(int(obj.BIMObjectMaterialProperties.material_set_item_material)),
},
profile=self.file.by_id(self.material_set_item),
attributes=attributes,
profile_def=profile_def,
material=self.file.by_id(int(obj.BIMObjectMaterialProperties.material_set_item_material)),
)
Data.load_profiles()
ProfileData.load(self.file)
else:
pass
@@ -778,3 +744,31 @@ class CopyMaterial(bpy.types.Operator):
if material.id() in object_material_ids:
return
obj.data.materials.append(IfcStore.get_element(material.id()))
class ExpandMaterialCategory(bpy.types.Operator):
bl_idname = "bim.expand_material_category"
bl_label = "Expand Material Category"
bl_options = {"REGISTER", "UNDO"}
category: bpy.props.StringProperty()
def execute(self, context):
props = context.scene.BIMMaterialProperties
for category in [c for c in props.materials if c.is_category and c.name == self.category]:
category.is_expanded = True
core.load_materials(tool.Material, props.material_type)
return {"FINISHED"}
class ContractMaterialCategory(bpy.types.Operator):
bl_idname = "bim.contract_material_category"
bl_label = "Contract Material Category"
bl_options = {"REGISTER", "UNDO"}
category: bpy.props.StringProperty()
def execute(self, context):
props = context.scene.BIMMaterialProperties
for category in [c for c in props.materials if c.is_category and c.name == self.category]:
category.is_expanded = False
core.load_materials(tool.Material, props.material_type)
return {"FINISHED"}
@@ -17,7 +17,9 @@
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
from ifcopenshell.util.doc import get_entity_doc
from ifcopenshell.api.material.data import Data
import blenderbim.tool as tool
from blenderbim.bim.module.material.data import MaterialsData, ObjectMaterialData
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.prop import StrProperty, Attribute
@@ -47,28 +49,31 @@ def purge():
parameterizedprofileclasses_enum = []
def getProfileClasses(self, context):
def get_profile_classes(self, context):
global profileclasses_enum
if len(profileclasses_enum) == 0 and IfcStore.get_schema():
version = tool.Ifc.get_schema()
profileclasses_enum.clear()
profileclasses_enum = [
(t.name(), t.name(), "") for t in IfcStore.get_schema().declaration_by_name("IfcProfileDef").subtypes()
(t.name(), t.name(), get_entity_doc(version, t.name()).get("description", ""))
for t in IfcStore.get_schema().declaration_by_name("IfcProfileDef").subtypes()
]
return profileclasses_enum
def getParameterizedProfileClasses(self, context):
def get_parameterized_profile_classes(self, context):
global parameterizedprofileclasses_enum
if len(parameterizedprofileclasses_enum) == 0 and IfcStore.get_schema():
version = tool.Ifc.get_schema()
parameterizedprofileclasses_enum.clear()
parameterizedprofileclasses_enum = [
(t.name(), t.name(), "")
(t.name(), t.name(), get_entity_doc(version, t.name()).get("description", ""))
for t in IfcStore.get_schema().declaration_by_name("IfcParameterizedProfileDef").subtypes()
]
for ifc_class in parameterizedprofileclasses_enum:
parameterizedprofileclasses_enum.extend(
[
(t.name(), t.name(), "")
(t.name(), t.name(), get_entity_doc(version, t.name()).get("description", ""))
for t in IfcStore.get_schema().declaration_by_name(ifc_class[0]).subtypes() or []
]
)
@@ -81,7 +86,7 @@ def get_materials(self, context):
return ObjectMaterialData.data["materials"]
def getMaterialTypes(self, context):
def get_object_material_types(self, context):
global materialtypes_enum
if len(materialtypes_enum) == 0 and IfcStore.get_file():
material_types = [
@@ -93,10 +98,11 @@ def getMaterialTypes(self, context):
"IfcMaterialProfileSetUsage",
"IfcMaterialList",
]
if IfcStore.get_file().schema == "IFC2X3":
version = tool.Ifc.get_schema()
if version == "IFC2X3":
material_types = ["IfcMaterial", "IfcMaterialLayerSet", "IfcMaterialLayerSetUsage", "IfcMaterialList"]
materialtypes_enum.clear()
materialtypes_enum = [(m, m, "") for m in material_types]
materialtypes_enum = [(m, m, get_entity_doc(version, m).get("description", "")) for m in material_types]
return materialtypes_enum
@@ -106,9 +112,17 @@ def get_material_types(self, context):
return MaterialsData.data["material_types"]
def get_profiles(self, context):
if not MaterialsData.is_loaded:
MaterialsData.load()
return MaterialsData.data["profiles"]
class Material(PropertyGroup):
name: StringProperty(name="Name")
ifc_definition_id: IntProperty(name="IFC Definition ID")
is_category: BoolProperty(name="Is Category", default=False)
is_expanded: BoolProperty(name="Is Expanded", default=True)
total_elements: IntProperty(name="Total Elements")
@@ -117,21 +131,19 @@ class BIMMaterialProperties(PropertyGroup):
material_type: EnumProperty(items=get_material_types, name="Material Type")
materials: CollectionProperty(name="Materials", type=Material)
active_material_index: IntProperty(name="Active Material Index")
profiles: EnumProperty(items=get_profiles, name="Profiles")
class BIMObjectMaterialProperties(PropertyGroup):
material_type: EnumProperty(items=getMaterialTypes, name="Material Type")
material_type: EnumProperty(items=get_object_material_types, name="Material Type")
material: EnumProperty(items=get_materials, name="Material")
is_editing: BoolProperty(name="Is Editing", default=False)
material_set_usage_attributes: CollectionProperty(name="Material Set Usage Attributes", type=Attribute)
material_set_attributes: CollectionProperty(name="Material Set Attributes", type=Attribute)
active_material_set_item_id: IntProperty(name="Active Material Set ID")
material_set_item_attributes: CollectionProperty(name="Material Set Item Attributes", type=Attribute)
material_set_item_profile_attributes: CollectionProperty(
name="Material Set Item Profile Attributes", type=Attribute
)
material_set_item_material: EnumProperty(items=get_materials, name="Material")
profile_classes: EnumProperty(items=getProfileClasses, name="Profile Classes")
profile_classes: EnumProperty(items=get_profile_classes, name="Profile Classes")
parameterized_profile_classes: EnumProperty(
items=getParameterizedProfileClasses, name="Parameterized Profile Classes"
items=get_parameterized_profile_classes, name="Parameterized Profile Classes"
)
@@ -19,9 +19,9 @@
import blenderbim.bim.helper
from bpy.types import Panel, UIList
from ifcopenshell.api.material.data import Data
from ifcopenshell.api.profile.data import Data as ProfileData
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.helper import draw_attributes
from blenderbim.bim.helper import prop_with_search
from blenderbim.bim.module.material.data import MaterialsData, ObjectMaterialData
@@ -50,7 +50,7 @@ class BIM_PT_materials(Panel):
row.operator("bim.disable_editing_materials", text="", icon="CANCEL")
else:
row = self.layout.row(align=True)
row.prop(self.props, "material_type", text="")
prop_with_search(row, self.props, "material_type", text="")
row.operator("bim.load_materials", text="", icon="IMPORT")
return
@@ -61,16 +61,18 @@ class BIM_PT_materials(Panel):
row.operator("bim.add_material", text="", icon="ADD")
if self.props.materials and self.props.active_material_index < len(self.props.materials):
material = self.props.materials[self.props.active_material_index]
op = row.operator("bim.select_by_material", text="", icon="RESTRICT_SELECT_OFF")
op.material = material.ifc_definition_id
row.operator("bim.remove_material", text="", icon="X").material = material.ifc_definition_id
if material.ifc_definition_id:
op = row.operator("bim.select_by_material", text="", icon="RESTRICT_SELECT_OFF")
op.material = material.ifc_definition_id
row.operator("bim.remove_material", text="", icon="X").material = material.ifc_definition_id
else:
row.operator("bim.add_material_set", text="", icon="ADD").set_type = self.props.material_type
if self.props.materials and self.props.active_material_index < len(self.props.materials):
material = self.props.materials[self.props.active_material_index]
op = row.operator("bim.select_by_material", text="", icon="RESTRICT_SELECT_OFF")
op.material = material.ifc_definition_id
row.operator("bim.remove_material_set", text="", icon="X").material = material.ifc_definition_id
if material.ifc_definition_id:
op = row.operator("bim.select_by_material", text="", icon="RESTRICT_SELECT_OFF")
op.material = material.ifc_definition_id
row.operator("bim.remove_material_set", text="", icon="X").material = material.ifc_definition_id
self.layout.template_list("BIM_UL_materials", "", self.props, "materials", self.props, "active_material_index")
@@ -125,12 +127,11 @@ class BIM_PT_object_material(Panel):
self.file = IfcStore.get_file()
self.oprops = context.active_object.BIMObjectProperties
self.props = context.active_object.BIMObjectMaterialProperties
self.mprops = context.scene.BIMMaterialProperties
if not Data.is_loaded:
Data.load(IfcStore.get_file())
if self.oprops.ifc_definition_id not in Data.products:
Data.load(IfcStore.get_file(), self.oprops.ifc_definition_id)
if not ProfileData.is_loaded:
ProfileData.load(self.file)
self.product_data = Data.products[self.oprops.ifc_definition_id]
if not ObjectMaterialData.data["materials"]:
@@ -139,6 +140,10 @@ class BIM_PT_object_material(Panel):
row.operator("bim.add_material", icon="ADD", text="").obj = ""
return
if ObjectMaterialData.data["type_material"]:
row = self.layout.row(align=True)
row.label(text="Inherited Material: " + ObjectMaterialData.data["type_material"], icon="FILE_PARENT")
if self.product_data:
if self.product_data["type"] == "IfcMaterialConstituentSet":
self.material_set_id = self.product_data["id"]
@@ -182,9 +187,9 @@ class BIM_PT_object_material(Panel):
return self.draw_material_ui()
row = self.layout.row(align=True)
row.prop(self.props, "material_type", text="")
prop_with_search(row, self.props, "material_type", text="")
if self.props.material_type == "IfcMaterial" or self.props.material_type == "IfcMaterialList":
row.prop(self.props, "material", text="")
prop_with_search(row, self.props, "material", text="")
row.operator("bim.assign_material", icon="ADD", text="")
def draw_material_ui(self):
@@ -214,8 +219,7 @@ class BIM_PT_object_material(Panel):
return self.draw_read_only_single_ui()
def draw_editable_single_ui(self):
row = self.layout.row(align=True)
row.prop(self.props, "material", text="")
prop_with_search(self.layout, self.props, "material", text="")
def draw_read_only_single_ui(self):
material = Data.materials[self.product_data["id"]]
@@ -229,15 +233,13 @@ class BIM_PT_object_material(Panel):
self.draw_read_only_set_ui()
def draw_editable_set_ui(self):
blenderbim.bim.helper.draw_attributes(self.props.material_set_attributes, self.layout)
blenderbim.bim.helper.draw_attributes(self.props.material_set_usage_attributes, self.layout)
for attribute in self.props.material_set_attributes:
row = self.layout.row(align=True)
row.prop(attribute, "string_value", text=attribute.name)
row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
row = self.layout.row(align=True)
row.prop(self.props, "material", text="")
if self.set_item_name == "profile":
row.prop(self.mprops, "profiles", icon="ITALIC", text="")
prop_with_search(row, self.props, "material", icon="MATERIAL", text="")
op = row.operator(f"bim.add_{self.set_item_name}", icon="ADD", text="")
setattr(op, f"{self.set_item_name}_set", self.material_set_id)
@@ -256,34 +258,18 @@ class BIM_PT_object_material(Panel):
box = self.layout.box()
row = box.row(align=True)
row.prop(self.props, "material_set_item_material", icon="MATERIAL")
op = row.operator("bim.edit_material_set_item", icon="CHECKMARK", text="")
op = row.operator("bim.edit_material_set_item", icon="CHECKMARK", text="Save Changes")
op.material_set_item = set_item_id
row.operator("bim.disable_editing_material_set_item", icon="CANCEL", text="")
draw_attributes(self.props.material_set_item_attributes, self.layout)
draw_attributes(self.props.material_set_item_attributes, box)
row = box.row()
row.prop(self.props, "material_set_item_material", icon="MATERIAL", text="Material")
if self.set_item_name == "profile":
self.draw_assign_profile_ui(box, item)
self.draw_editable_profile_ui(box, item)
def draw_assign_profile_ui(self, layout, item):
row = layout.row(align=True)
row.prop(self.props, "profile_classes", text="")
if self.props.profile_classes == "IfcParameterizedProfileDef":
row.prop(self.props, "parameterized_profile_classes", text="")
op = row.operator(
"bim.assign_parameterized_profile", icon="GREASEPENCIL" if item["Profile"] else "ADD", text=""
)
op.ifc_class = self.props.parameterized_profile_classes
op.material_profile = item["id"]
else:
# TODO: support non parametric profiles by showing a list of named profiles to select from, or an
# eyedropper to pick profile geometry from the scene
row.operator("bim.disable_editing_material_set_item", icon="CANCEL", text="")
def draw_editable_profile_ui(self, layout, item):
draw_attributes(self.props.material_set_item_profile_attributes, self.layout)
row = box.row()
row.prop(self.mprops, "profiles", icon="ITALIC", text="Profile")
def draw_read_only_set_item_ui(self, set_item_id, index, is_first=False, is_last=False):
if self.product_data["type"] == "IfcMaterialList":
@@ -395,7 +381,21 @@ class BIM_UL_materials(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
if item:
row = layout.row(align=True)
row.label(text=item.name)
row2 = row.row()
row2.alignment = "RIGHT"
row2.label(text=str(item.total_elements))
if item.is_category:
if item.is_expanded:
row.operator(
"bim.contract_material_category", text="", emboss=False, icon="DISCLOSURE_TRI_DOWN"
).category = item.name
else:
row.operator(
"bim.expand_material_category", text="", emboss=False, icon="DISCLOSURE_TRI_RIGHT"
).category = item.name
row.label(text=item.name or "Uncategorised")
else:
row.label(text="", icon="BLANK1")
row.label(text=item.name, icon="MATERIAL")
row2 = row.row()
row2.alignment = "RIGHT"
row2.label(text=str(item.total_elements))

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