Compare commits

..

118 Commits

Author SHA1 Message Date
Dion Moult 2f071bd809 Material total now shows the total of the select material class, not all material classes
This is more meaningful. Often the presence of sets will inflate the number artificially.
2024-05-25 14:11:48 +10:00
Andrej730 7a44f9e8a7 different columns for different cost schedules #4724
though columns are still stored in .blend file and not in .ifc

Example - https://imgur.com/a/N6epGZ4
2024-05-24 19:15:51 +05:00
Andrej730 59a19a1a70 disable adding cost schedule columns with an empty name
example - https://i.imgur.com/5lGdI2X.png
2024-05-24 19:15:51 +05:00
Andrej730 6a8b59f2cf remove unused code
1) core.enable_editing_task was outdated, tool.enable_editing_task doesn't exist
2) tool.get_task_outputs was duplicated
2024-05-24 19:15:51 +05:00
Andrej730 ec28d3d0af black format 2024-05-24 19:15:51 +05:00
Andrej730 f576aadee1 typing 2024-05-24 19:15:51 +05:00
Andrej730 fffd7360cb fix for d4ccadb
forgot to add it to global namespace
2024-05-24 19:15:43 +05:00
Andrej730 64ea455c01 move update_simple_openings to tool.Model 2024-05-24 16:52:33 +05:00
Andrej730 cf635986a6 same openings generator for non-/parametric doors/windows #4710 2024-05-24 16:51:37 +05:00
Andrej730 ac8040008a small refactor 2024-05-24 16:51:37 +05:00
Andrej730 df15a37127 fix core.root.assign_class error after 3884403 #4719 2024-05-24 15:22:55 +05:00
Andrej730 6d887d593e fix issues running core tests after 37c008487 2024-05-24 15:20:12 +05:00
Andrej730 fa87092fe0 Keep 1.2m minimum opening thickness instead of using wall thickness
That way there will be a workaround if the wall is 0.6m+ meters thick but won't have an issue when you first added a window to a thin wall and it's opening later won't work for the thicker walls.
Mentioned in #4710
2024-05-24 12:10:40 +05:00
Andrej730 1a4e4678d0 assign/unassign resource operators tooltips
also removed couple unused properties
2024-05-23 18:45:31 +05:00
Andrej730 7f2c7eb467 python 3.9 compatibility 2024-05-23 18:45:31 +05:00
Andrej730 c70745826a rename bim.export_ifc to bim.save_project (similar to bim.load_project)
note that old name "export_ifc.bim" is still available (cacb38a25) though deprecated
2024-05-23 18:45:30 +05:00
Andrej730 291bffca95 add tooltip for cost schedule columns
example - https://i.imgur.com/Uh8D7Vg.png
2024-05-23 18:45:30 +05:00
Andrej730 e5a316e509 fix api calls error handling
by accident replaced in f2696d5 TypeError with NotImplementedError
2024-05-23 18:45:30 +05:00
Andrej730 e4aa97091e fix bug removing cost schedule columns
it was using _execute though it's not an ifc operator
2024-05-23 18:45:30 +05:00
Andrej730 b4f0a346c1 fix couple broken properties definitions
There was no errors but Blender seems to ignore the provided name if it was provided as a positional argument instead of a keyword argument.
2024-05-23 18:45:30 +05:00
Andrej730 073e471305 typing 2024-05-23 18:45:30 +05:00
Andrej730 4d9f47b3a7 make it more clear that bim.export_cost_schedules is expecting a folder
1) add folder filter
2) remove filepath property - that way only directories will be selectable in the file dialog.
2024-05-23 18:31:59 +05:00
Andrej730 96088c1d08 fix ui error in 40e4949
bim.add_manual_classification_reference operator doesn't have a "type" property, only "obj_type"
2024-05-23 18:31:59 +05:00
Dion Moult c405ff1adf Fix unassign_representation to not incorrectly use remove_deep without first removing known inverses.
In theory, it would be possible for the representation map to be used elsewhere, then file.remove(representation_map) would cause problems.
2024-05-23 23:08:53 +10:00
Dion Moult 02cae9c7c2 Continue to revise optimisation of remove_deep to fix failing tests after a8729933 2024-05-23 22:54:19 +10:00
Dion Moult 3c42962635 Fix #4716. Fix bug where IfcTester fails to verify negative numbers. 2024-05-23 21:40:19 +10:00
Dion Moult 0336dbabf7 Fix #4722. Things like type elements are not allowed to have openings applied to them. 2024-05-23 20:41:15 +10:00
Dion Moult a8729933af Optimise remove_deep, which can make removing lots of elements 100x faster in scripts. 2024-05-23 20:15:09 +10:00
Andrej730 993ce46fcc improve ux for assigning cost item to product types
1) also consider active object as selected when assigning / unassigning cost item to product types. Typically types are hidden and if you select some type in outliner it will become active but still not selected. Now it will be possible to add this active object without unhiding the entire Types collection.

2) info messages to make UI more responsive
2024-05-23 12:13:19 +05:00
Dion Moult edfd446833 Allow custom qtos without a template 2024-05-23 15:17:08 +10:00
Dion Moult 87c184ef8a Easily add custom psets and props without needing to create a template first
This is not best practice but is useful for quick and dirty properties.
2024-05-23 13:28:59 +10:00
Andrej730 e9b9113df8 small fix for 1aaccba80 2024-05-22 21:41:07 +05:00
Thomas Krijnen f772a53dd5 Update ci.yml 2024-05-22 17:29:15 +02:00
Thomas Krijnen 85c6b6c4da Update ci.yml 2024-05-22 15:58:34 +02:00
Andrej730 1aaccba803 simpler way to check if enum property is valid
Well, it's simpler to use but not really simpler in implementation 😅
Thanks to @Gorgious56 for helping out
Hopefully, it's a farewell to those annoying warnings

see https://blenderartists.org/t/best-way-to-handle-dynamic-enum-items-without-pyrna-enum-to-py-current-value-matches-no-enum
2024-05-22 18:44:12 +05:00
Andrej730 f2eb08a066 typing 2024-05-22 17:43:05 +05:00
Andrej730 f460676e54 fix calculating cost summary #4704 (was confusing 0 qty and no qty)
similar to 295d0a677
2024-05-22 17:37:32 +05:00
Andrej730 382e028b90 csv to cost schedule - support more cases of 0 quantity #4704
Before - https://i.imgur.com/CYwSGxY.png
After - https://i.imgur.com/Wbe6Ij6.png
I'll attach example .csv and .ifc in #4704

What changed:

1) If query was provided but it didn't found any elements, then it will still autoassign quantity = 0 instead of cost item end up without quantities at all (which has a different meaning in ifc). Works both with Property provided and without it.

2) If provided quantity = 0, it will now load as quantity = 0 instead of not creating any quantities at all.

3) You can provide both Query and Quantity and they all will be added to the cost item. E.g. if Quantity = 15, Query = "IfcWall", Property="Prop" and there are 3 walls in the model each having Prop = 25 then final quantity will be 15+25*3=90. Previously Quantity would take the priority and the result would be just = 15.

Though this is still doesn't work with counting quantities due behaviour in cost.assign_cost_item_quantity.
E.g. if Quantity = 7, Query = "IfcWall", Property="" (to make sure it will just count them) and there are 3 walls in the model then final quantity will be not 7+3=10 but just = 3, as query will take the priority here.
2024-05-22 17:15:42 +05:00
Thomas Krijnen 53d02446bf Update ci.yml 2024-05-22 13:13:18 +02:00
Dion Moult 5a82d7a9d4 Refactor enabling pset editing operator into core/tool 2024-05-22 20:52:43 +10:00
Dion Moult 69c197c1a4 get_psets utility now defaults to merging duplicate psets instead of overriding
This behaviour is more comprehensive for broken data and now consistent for types and materials.
2024-05-22 08:12:38 +10:00
Ryan Schultz 4027f28c85 Can bim.select_similar from the pset or attribute panels (#4611) 2024-05-21 10:30:38 -05:00
Bruno Perdigão 3a1154c4ba Merge pull request #4597 from IfcOpenShell/linked_aggregate_operator_from_panel
when bpy.ops.bim.object_duplicate_move_linked_aggregate is called fro…
2024-05-21 10:45:49 -03:00
Dion Moult aeb1e4bf49 See #4696. Allow editing of properties that do not comply with the pset template definition.
Strictly speaking, the only properties that should exist in a pset should be those in a pset template. However, there are situations where this is not the case, such as when the pset template has since been modified, or if there is invalid data coming from other software, or migrating between schemas. In this case, we should still load the property, and give the user the option to edit it (or null it).
2024-05-21 23:06:15 +10:00
Andrej730 4fb34c0816 base autogenerated opening thickness on wall thickness #4710 2024-05-21 17:54:51 +05:00
Andrej730 9b562669d2 fix root.create_entity skipping setting up defaults on ifc4x3 2024-05-21 17:54:08 +05:00
Andrej730 295d0a677e cost schedule - prohibit adding multiple quantity types on 1 cost item
All IfcCostItem.CostQuantities supposed to use only 1 type of quantity, so they can be added up.

Changed UI to reflect that requirement - dropdown for selecting quantity type is now only visible if cost item has no quantities, otherwise it just reuses already used quantity type.

See https://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/IfcCostItem.htm

Before - https://i.imgur.com/gFlwHEk.png
After - https://i.imgur.com/pk3wXfe.png
2024-05-21 17:54:07 +05:00
Andrej730 9aacafd575 fix bug with cost schedules confusing 0 quantity with no quantities
a bit related to #4704

1) fixed util.cost.get_total_quantity
2) fixed similar issue in cost.data that calculates the final value that user will see in UI
3) changed UI, "-" is shown when there are no quantities and "0" is when quantities are there but they just equal to zero.

Before - https://i.imgur.com/EO53DhM.png
After - https://i.imgur.com/H6rK4sP.png

fyi @myoualid
2024-05-21 17:54:07 +05:00
Andrej730 ce6ce1b975 bim.assign_cost_item_type name change 2024-05-21 17:54:07 +05:00
Andrej730 d064ef1237 black format 2024-05-21 17:53:46 +05:00
Andrej730 2db9bd9c39 fix errors adding occurrences from type manager #4702
Errors occurred if occurrence type wasn't matching current tool's type. E.g. if you would add an IfcRoofType being in a Slab Tool.
2024-05-21 17:53:42 +05:00
Andrej730 7622504d68 avoid even more invalid enum warnings...
warnings like `pyrna_enum_to_py: current value '17' matches no enum in 'BIMModelProperties', '', 'relating_type_id'`
2024-05-21 17:53:42 +05:00
Andrej730 221dd9a7a2 typing 2024-05-21 17:53:42 +05:00
Andrej730 1b74a99667 use delete_ifc_object removing a type
It was acting a bit different - e.g. leaving mesh representation from type on occurrences leaving to lots of errors. Though there is still #3931
2024-05-21 17:07:32 +05:00
Dion Moult 9122f006c7 Deprecate material attributes panel in favour of new material manager. 2024-05-20 23:21:13 +10:00
Andrej730 678dbaa66a nest api to maintain order of related objects #4698
order of objects in .RelatedObjects is important (e.g. for cost items, it's the order of their appearance), so we should maintain it and cannot use sets for .RelatedObjects
2024-05-20 17:19:00 +05:00
Andrej730 fb88499197 ifc5d - check for mandatory fields in csv header 2024-05-20 17:19:00 +05:00
Andrej730 f84adf67cd typing 2024-05-20 17:19:00 +05:00
Andrej730 cacb38a251 maintain "bim." as operators bl_idname prefix for BlenderBIM
1) removed unused operator import_ifc.bim
2) marked export_ifc.bim as deprecated
2024-05-20 17:19:00 +05:00
Andrej730 7c6c01e6b9 fix python 3.10 loading issue after bb8e84e
The error is below.

It was failing because Callable was checking if `ifcopenshell.entity_instance` is a callable and at runtime actually it's not, it's a module.
In python 3.11 they've removed that check and therefore it's not throwing an error. Enabling forward annotations fixes it for python 3.10.
Not sure if there is a need to ensure `ifcopenshell.entity_instance` should be recgonized by python as a class at runtime rather than a module since we need it just for type checking and type checking seems to be clever enough to prioritize module classes over submodules.

  File "C:\Users\user_name\AppData\Roaming\Blender Foundation\Blender\4.0\scripts\addons\blenderbim\bim\__init__.py", line 25, in <module>
    from . import handler, ui, prop, operator, helper
  File "C:\Users\user_name\AppData\Roaming\Blender Foundation\Blender\4.0\scripts\addons\blenderbim\bim\handler.py", line 23, in <module>
    import ifcopenshell.api.owner.settings
  File "C:\Users\user_name\AppData\Roaming\Blender Foundation\Blender\4.0\scripts\addons\blenderbim\libs\site\packages\ifcopenshell\__init__.py", line 85, in <module>
    from .file import file
  File "C:\Users\user_name\AppData\Roaming\Blender Foundation\Blender\4.0\scripts\addons\blenderbim\libs\site\packages\ifcopenshell\file.py", line 179, in <module>
    class file:
  File "C:\Users\user_name\AppData\Roaming\Blender Foundation\Blender\4.0\scripts\addons\blenderbim\libs\site\packages\ifcopenshell\file.py", line 382, in file
    def __getattr__(self, attr) -> Union[Any, Callable[..., ifcopenshell.entity_instance]]:
  File "C:\Program Files\Blender Foundation\Blender 4.0\4.0\python\lib\typing.py", line 1206, in __getitem__
    return self.__getitem_inner__(params)
  File "C:\Program Files\Blender Foundation\Blender 4.0\4.0\python\lib\typing.py", line 312, in inner
    return func(*args, **kwds)
  File "C:\Program Files\Blender Foundation\Blender 4.0\4.0\python\lib\typing.py", line 1212, in __getitem_inner__
    result = _type_check(result, msg)
  File "C:\Program Files\Blender Foundation\Blender 4.0\4.0\python\lib\typing.py", line 176, in _type_check
    raise TypeError(f"{msg} Got {arg!r:.100}.")
TypeError: Callable[args, result]: result must be a type. Got <module 'ifcopenshell.entity_instance' from 'C:\\Users\\user_name\\AppData\\Roaming\\Blender F.
2024-05-20 13:17:17 +05:00
Andrej730 4e462a3cf7 use .alert in fatal loading error UI to draw more attention (3320acc)
https://i.imgur.com/5ZukO0b.png
we probably will need to use 1 method for both error UIs at some point 😅
2024-05-20 12:47:09 +05:00
Andrej730 e7f4c6c9cc update error UI similar to 7bf8c0bbf 2024-05-20 12:34:56 +05:00
Dion Moult 7bf8c0bbfb Fix #4686. Automatically derive correct download URI if add-on fails to install. 2024-05-20 16:32:42 +10:00
Dion Moult c4157673f5 Fix #4696. Null properties are now purged by default to prevent cruft build up. Add support for purging empty enum props. 2024-05-20 16:04:27 +10:00
E Shattow 16285e0625 Update installation.rst refering to System not Import-Export
Blender BIM addon is listed by Blender 4.1 as `System: BlenderBIM`
2024-05-20 10:57:38 +05:00
Dion Moult a07d3b9669 Fix #4633. Only update the text value of the object, not try to be clever with types and occurrences.
Previously, we were clever in detecting types and occurrences because if you edited a type, you wanted all occurrences to be updated. Now, we no longer need to be clever because like any other UI element, the annotations shown in the active view are updated after every IFC operation.
2024-05-20 14:12:03 +10:00
Dion Moult 2d0b356203 See #4690. User can now configure font and font-scaling in schedules. 2024-05-20 12:00:59 +10:00
Dion Moult f666053b63 Use pip instead of hardcoding bcf packages during build. See #4640. 2024-05-19 23:50:57 +10:00
Dion Moult 3bdc969efb Fix #4690. User can now configure the font used to render viewport text. 2024-05-19 23:36:38 +10:00
Bruno Postle 932817877c Fix CPack source tarball generation
This was broken, now a IfcOpenShell-0.7.0.tar.gz file can be created
like so:

  mkdir build
  cd build
  cmake ../cmake/ -DEXTRA_VERSION=
  make package_source
2024-05-19 13:29:26 +01:00
Andrej730 cb255a18b9 fix error editing pset templates when you just opened ifc file
It was failing with an error below if you'd try to edit any property since `primary_measure_type()` returned `[]` because it was executed before `pset_templates()` and `IfcStore.pset_template_file` wasn't yet set.

```python
Error: Python: Traceback (most recent call last):
  File "\blenderbim\bim\module\pset_template\operator.py", line 146, in execute
    props.active_prop_template.primary_measure_type = template.PrimaryMeasureType
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: bpy_struct: item.attr = val: enum "IfcLabel" not found in ()
```
2024-05-18 11:24:10 +05:00
Andrej730 183028570e bump xsdata in blenderbim after 87c9bbe2a #4640 2024-05-18 01:49:56 +05:00
Andrej730 341cdd4ef7 fix error updating curve representations after 3fa573e #4685 #4682 2024-05-17 17:40:13 +05:00
Andrej730 fe68e16ca3 bim.update_representation - error if add_representation didn't worked 2024-05-17 17:31:01 +05:00
Andrej730 a8f0c90d47 set_element_value - fix issues with classification, similar to 52ae759 2024-05-17 17:31:01 +05:00
Andrej730 39f550529b experimental - set_element_value to throw exception for invalid queries 2024-05-17 17:31:01 +05:00
Andrej730 25c1a14e64 set_element_value - pass attr value to the next part of the query #4495
E.g. previously set_element_value with query "material.item.Material.Name" would fail with error "Material property is expecting an IFC entity and not a string".

But now it will detect that "Material" property is not a last key in the query and will pass `.Material` value forward and try to set it's `.Name` attribute with value.

The main goal is to make sure get_element_value and set_element_value would have a same result for same queries.
2024-05-17 17:31:01 +05:00
Andrej730 9f7d710119 fix set_element_value bug #4495 2024-05-17 17:31:01 +05:00
Andrej730 ea592775e4 fix set_element_value setting bool attributes with "False" str value 2024-05-17 17:31:01 +05:00
Andrej730 42be4eb064 typing 2024-05-17 17:31:01 +05:00
Hilko 8662e20c58 remove unused include #4620 2024-05-17 10:26:04 +02:00
Andrej730 da4ce3773f bcf - fix xsdata warning about deprecated argument in SerializerConfig
`pretty_print` was deprecated in 24.2 (https://github.com/tefra/xsdata/commit/2b01dbf6cf586e7d64e93ec7045dc17d2585e0de) and replaced with `indent`
2024-05-17 11:57:46 +05:00
Andrej730 87c9bbe2a7 Fix bcf due xsdata update #4680
After https://github.com/tefra/xsdata/commit/93a8ca0548d1f7badb628ad9fd0327672abe140b

`parser.register_namespace("xs", "http://www.w3.org/2001/XMLSchema")` started failing with `TypeError: PushParser.register_namespace() missing 1 required positional argument: 'uri'` since xsdata added a new argument `ns_map` (previously it was always using `parse.ns_map` under the hood, now it allows to provide some external dictionary). We just restore the original behaviour with internal `ns_map` by providing it explicitly.

Specified xsdata version after that change in pyproject.toml.

Also `serialize()` is now using `parser.ns_map` as a fallback value (otherwise why we do `parser.register_namespace` if we never used the `parser.ns_map`?)
2024-05-17 11:46:53 +05:00
Andrej730 a66ee06e3d dev environment - add bcf 2024-05-17 11:42:06 +05:00
Andrej730 9969de58cb add a console toggle to error message section 2024-05-16 18:14:07 +05:00
Andrej730 6995cafe63 bim.split_along_edge fixes #4675
1) description to emphasize that active object will be the one that's cutting
2) skip non-ifc elements, previously they failed to process with error `AttributeError: 'NoneType' object has no attribute 'RepresentationType'`. Also skip objects without representations.
3) add an info message at the end

Here's a short gif in case if anyone doesn't about that feature: https://imgur.com/a/338w7jx
2024-05-16 18:14:06 +05:00
Andrej730 eaa80ad08a fix issue getting styles if object had empty mat slots
Occurred only with only_assigned_to_faces = True
A bit related to #4675

Error was
    styles = [style for style, usage in zip(styles, usage_count, strict=True) if usage > 0]

ValueError: zip() argument 2 is longer than argument 1
2024-05-16 18:14:06 +05:00
Andrej730 d001180082 remove unused code 2024-05-16 18:14:06 +05:00
Andrej730 3884403231 typing 2024-05-16 18:14:06 +05:00
Andrej730 8d4b5d83e1 same as e1250f217 2024-05-16 16:13:22 +05:00
Andrej730 e1250f2177 fix saving debug info to clipboard #4675
now we just rely on blender to make it crossplatform
2024-05-16 16:00:38 +05:00
Andrej730 a2ee920a5f fix group.update_group_products to work with multiple rels 2024-05-15 17:18:34 +05:00
Andrej730 fdbe74a432 maintain snake in api calls
there were only two methods using camel case for arguments
2024-05-15 17:18:34 +05:00
Andrej730 4e39fb3edd fix issue adding layers (name is not optional in ifc) 2024-05-15 17:18:34 +05:00
Andrej730 c2049246cd fix issue adding classification failing to use a None value for the date 2024-05-15 17:18:34 +05:00
Andrej730 335e2b7a78 ifc2x3 tests 2024-05-15 17:18:34 +05:00
Andrej730 bb8e84e5ec typing 2024-05-15 17:18:33 +05:00
Andrej730 2fa30be0d0 small optimization for da1bdc802
On large projects predict dense mesh stage can take 20s+ and reusing attribute value can save up to half of this time.
Using indices also helps but it's not that significant and sometimes it's the same time as using attribute names.
2024-05-14 18:19:13 +05:00
Bruno Perdigão c50149ad87 added 'product' parameter to a few usages of 'pset.remove_pset' after ebd03e9 2024-05-14 17:48:05 +05:00
Dion Moult da1bdc802d Accommodate invalid models coming from Cadwork 2024-05-14 16:16:18 +10:00
Andrej730 a9cba30da6 ifc2x3 tests
removed part of test_append_two_type_products_sharing_the_same_material_indirectly_via_a_material_set for ifc2x3 compatibility and removed part is already tested in test_append_two_type_products_sharing_the_same_material_with_properties
2024-05-13 18:01:18 +05:00
Andrej730 3665dda87c library.remove_library, remove_reference to support ifc2x3 2024-05-13 18:01:18 +05:00
Andrej730 f2696d5352 avoid confusing TypeErrors from api calls
After ab5ea4c85 it was always throwing wrong singature errors like below even if TypeError was caused by some internal issues inside API - it was adding couple extra steps to traceback making errors more noisy.

TypeError: Incorrect function arguments provided for library.edit_library
attribute 'VersionDate' for entity 'IFC2X3.IfcLibraryInformation' is expecting value of type 'ENTITY INSTANCE', got 'str'.. You specified args (<ifcopenshell.file.file object at 0x0000027EB8E6BCD0>,) and settings {'library': #1=IfcLibraryInformation('Name','Version',$,$,$), 'attributes': {'Name': 'Name', 'Version': 'Version', 'VersionDate': 'VersionDate', 'Location': 'Location', 'Description': 'Description'}}
E
Correct signature is (file: ifcopenshell.file.file, library: ifcopenshell.entity_instance.entity_instance, attributes: dict[str, typing.Any]) -> None
See help(ifcopenshell.api.library.edit_library) for documentation.
2024-05-13 18:01:18 +05:00
Andrej730 5e394e1576 library.edit_library - support datetime for VersionDate attribute 2024-05-13 18:01:18 +05:00
Andrej730 ebd03e9290 typing 2024-05-13 18:01:17 +05:00
Andrej730 c3bfd7354f pset.add_pset - throw an error if entity doesn't support adding a pset 2024-05-13 18:01:17 +05:00
Andrej730 1d046eaadf material.remove_material and copy_material to handle ifc2x3 props 2024-05-13 18:01:17 +05:00
Andrej730 2f554db54b owner.remove_person to remove IfcInventory in ifc2x3 2024-05-13 18:01:17 +05:00
Andrej730 54b4cef25e fix errors appending related materials in ifc2x3
it wasn't processing materials properties correctly because it was expecting material to have class IfcMaterialDefinition
2024-05-13 18:01:17 +05:00
Andrej730 c6106e6636 fix edit_pset error for editing material properties in ifc2x3 2024-05-13 18:01:17 +05:00
Andrej730 865dbab3ec fix error removing array pset after c50d1ea2f 2024-05-13 18:01:17 +05:00
Andrej730 76e6c63a01 ifc2x3 tests - prevent using removed user/application
When some test was creating an element and then removing it, it would also remove user and application as they wasn't used anywhere else.
`ifcopenshell.util.element.remove_deep2(file, history)` we use in every api for element deletion can possibly remove user and application which can be unsafe if `get_user` is returning some specific entity that then will become invalid.

Also fixed tests breaking due ifcownerhistory and user/application appearing in ifc2x3.
2024-05-13 18:01:16 +05:00
Gorgious56 3320accc7a The error box is now drawn in red to draw attention to it 2024-05-13 12:00:01 +02:00
Gorgious56 ac686db298 Prevent error when user deletes all spatial structure elements and the spatial manager panel is expanded 2024-05-13 11:57:48 +02:00
Dion Moult aca26c7597 Fix #4659. 2024-05-12 17:16:50 +10:00
Bruno Perdigão 1110f0cffb Merge branch 'v0.7.0' into linked_aggregate_operator_from_panel 2024-05-07 21:00:03 -03:00
Bruno Perdigão 66e829a8aa - Fix the basepoint to be the parent aggregate.
- Change the way that "location_to_cursor_3d" is called.
- Create a new operator to use in the ui.
2024-05-07 20:56:12 -03:00
Bruno Perdigão 04172a87cb when bpy.ops.bim.object_duplicate_move_linked_aggregate is called from the panel, the new objects are placed based on the 3d cursor position 2024-04-30 17:50:31 -03:00
288 changed files with 4266 additions and 2315 deletions
+2 -2
View File
@@ -46,7 +46,7 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install xmlschema xsdata numpy lxml pytest isodate lark networkx tabulate python-dateutil
pip install xmlschema xsdata numpy lxml pytest isodate lark networkx tabulate python-dateutil shapely
pip install src/bcf --no-deps
pip install https://github.com/Andrej730/aud/archive/refs/heads/master-reduced-size.zip
@@ -90,7 +90,7 @@ jobs:
-DPYTHON_INCLUDE_DIR:PATH=${{ env.pythonLocation }}/include/python3.11 \
-DPYTHON_LIBRARY:FILEPATH=${{ env.pythonLocation }}/lib/libpython3.11.so \
-DCOLLADA_SUPPORT=Off \
"-DSCHEMA_VERSIONS=2x3;4;4x3_add1" \
"-DSCHEMA_VERSIONS=2x3;4;4x3;4x3_add1;4x3_add2" \
-DGLTF_SUPPORT=On \
-DJSON_INCLUDE_DIR=/usr/include \
-DCGAL_INCLUDE_DIR=/usr/include \
+6 -3
View File
@@ -36,7 +36,9 @@ if(NOT CMAKE_BUILD_TYPE)
endif()
# use extra version to make pre-release using eg semver
set(EXTRA_VERSION "-alpha.3")
if(NOT DEFINED EXTRA_VERSION)
set(EXTRA_VERSION "-alpha.3")
endif()
option(MINIMAL_BUILD "The build is to make a minimal version of IFC converter from OCCT into IFC." OFF)
option(WASM_BUILD "Build a WebAssembly binary." OFF)
@@ -1147,9 +1149,10 @@ endif()
# Packaging
list(APPEND CPACK_SOURCE_IGNORE_FILES
.git
.gitignore
"/\\\\.git"
"/build/"
)
set(CPACK_SOURCE_INSTALLED_DIRECTORIES "${CMAKE_SOURCE_DIR}/..;/")
set(CPACK_PACKAGE_NAME "${PROJECT_NAME}-${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}${EXTRA_VERSION}")
set(CPACK_SOURCE_PACKAGE_FILE_NAME "${PROJECT_NAME}-${PROJECT_VERSION}${EXTRA_VERSION}")
SET(CPACK_PACKAGE_FILE_NAME "${PROJECT_NAME}-${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}${EXTRA_VERSION}-${CMAKE_SYSTEM_NAME}")
+1 -1
View File
@@ -13,7 +13,7 @@ readme = "README.md"
requires-python = ">=3.8"
keywords = ["IFC", "BCF", "BIM"]
dependencies = [
"xsdata",
"xsdata>=24.4",
"numpy",
"ifcopenshell",
]
+7 -5
View File
@@ -10,14 +10,14 @@ from xsdata.formats.dataclass.serializers.config import SerializerConfig
def build_xml_parser(context: Optional[XmlContext] = None) -> XmlParser:
"""Return a parser for an XML file."""
parser = XmlParser(context=context or XmlContext())
parser.register_namespace("xs", "http://www.w3.org/2001/XMLSchema")
parser.register_namespace(ns_map=parser.ns_map, prefix="xs", uri="http://www.w3.org/2001/XMLSchema")
return parser
def build_serializer(context: Optional[XmlContext] = None) -> XmlSerializer:
"""Return a serializer for an XML file."""
return XmlSerializer(
config=SerializerConfig(pretty_print=True),
config=SerializerConfig(indent=" "),
context=context or XmlContext(),
)
@@ -36,8 +36,9 @@ class AbstractXmlParserSerializer(Protocol):
xml: The XML file as bytes.
clazz: The class to parse to.
"""
...
def serialize(self, obj: T, ns_map: Optional[dict[str, str]] = None) -> str:
def serialize(self, obj: object, ns_map: Optional[dict[str, str]] = None) -> str:
"""
Serialize an object to XML.
@@ -48,6 +49,7 @@ class AbstractXmlParserSerializer(Protocol):
Returns:
The XML as string.
"""
...
class XmlParserSerializer:
@@ -68,7 +70,7 @@ class XmlParserSerializer:
"""
return self.parser.from_bytes(xml, clazz)
def serialize(self, obj: T, ns_map: Optional[dict[str, str]] = None) -> str:
def serialize(self, obj: object, ns_map: Optional[dict[Optional[str], str]] = None) -> str:
"""
Serialize an object to XML.
@@ -79,5 +81,5 @@ class XmlParserSerializer:
Returns:
The XML as string.
"""
ns_map = ns_map or {"xs": "http://www.w3.org/2001/XMLSchema"}
ns_map = ns_map or self.parser.ns_map or {"xs": "http://www.w3.org/2001/XMLSchema"}
return self.serializer.render(obj, ns_map)
+6 -29
View File
@@ -277,34 +277,6 @@ endif
cp -r dist/working/pyparsing-2.4.5/pyparsing.py dist/blenderbim/libs/site/packages/
rm -rf dist/working
# Required by bcf
mkdir dist/working
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-2.0.2/xmlschema dist/blenderbim/libs/site/packages/
rm -rf dist/working
# Required by bcf
mkdir dist/working
cd dist/working && wget https://files.pythonhosted.org/packages/ad/c7/17c9d16320d8e2cfdb27d2fd298b5e8f7a3f211025b0c1bc7a39a85bd690/xsdata-22.11.tar.gz
cd dist/working && tar -xzvf xsdata*
cp -r dist/working/xsdata-22.11/xsdata dist/blenderbim/libs/site/packages/
rm -rf dist/working
# Required by bcf
mkdir dist/working
cd dist/working && wget https://files.pythonhosted.org/packages/11/bc/5afb61dd5d863e5cf77cd952445c50c17e65953405986f19e97e4389692a/elementpath-3.0.2.tar.gz
cd dist/working && tar -xzvf elementpath*
cp -r dist/working/elementpath-3.0.2/elementpath dist/blenderbim/libs/site/packages/
rm -rf dist/working
# Required by bcf
mkdir dist/working
cd dist/working && wget https://files.pythonhosted.org/packages/21/9f/b251f7f8a76dec1d6651be194dfba8fb8d7781d10ab3987190de8391d08e/six-1.14.0.tar.gz
cd dist/working && tar -xzvf six*
cp -r dist/working/six-1.14.0/six.py dist/blenderbim/libs/site/packages/
rm -rf dist/working
# Required by IFCCSV and ifcopenshell.util.selector
mkdir dist/working
cd dist/working && wget https://files.pythonhosted.org/packages/18/4d/8d522136c37d9e1ea74062b41b8d5e1318ebf45063ae46ce72ed60af223b/lark-parser-0.8.5.tar.gz
@@ -366,7 +338,12 @@ endif
# Provides Brickschema functionality
cd dist/working && . env/bin/activate && $(PIP) install "brickschema[persistence]==0.7.6a2" --target=./site-packages
# Required for SVG to DXF conversion
cd dist/working && . env/bin/activate && $(PIP) install "ezdxf" --target=./site-packages
cd dist/working && . env/bin/activate && $(PIP) install ezdxf --target=./site-packages
# Required by bcf
cd dist/working && . env/bin/activate && $(PIP) install xsdata --target=./site-packages
cd dist/working && . env/bin/activate && $(PIP) install xmlschema --target=./site-packages
cd dist/working && . env/bin/activate && $(PIP) install elementpath --target=./site-packages
cd dist/working && . env/bin/activate && $(PIP) install six --target=./site-packages
cp -r dist/working/site-packages/* dist/blenderbim/libs/site/packages/
rm -rf dist/working
+35 -14
View File
@@ -18,7 +18,15 @@
import os
import sys
import bpy
# Ensure we don't try to import bpy or blenderbim.bim
# to support running core tests.
# We assume if bpy was never loaded in current python session
# then we're not in Blender. It's still possible to use
# bpy in core and core tests for annotations using TYPE_CHECKING.
IN_BLENDER = sys.modules.get("bpy", None)
if IN_BLENDER:
import bpy
import platform
import traceback
import subprocess
@@ -78,7 +86,7 @@ def format_debug_info(info: dict):
return text.strip()
if sys.modules.get("bpy", None):
if IN_BLENDER:
# Process *.pth in /libs/site/packages to setup globally importable modules
# This is 3 levels deep as required by the static RPATH of ../../ from dependencies taken from Anaconda
# site.addsitedir(os.path.join(os.path.dirname(os.path.realpath(__file__)), "libs", "site", "packages"))
@@ -120,11 +128,16 @@ if sys.modules.get("bpy", None):
bl_context = "scene"
def draw(self, context):
layout = self.layout
layout.label(text="BlenderBIM could not load.", icon="ERROR")
layout.label(text="View the console for full logs.", icon="CONSOLE")
box = layout.box()
info = get_debug_info()
layout = self.layout
layout.alert = True
layout.label(text="BlenderBIM could not load.", icon="ERROR")
if info["os"] == "Windows":
layout.operator("wm.console_toggle", text="View the console for full logs.", icon="CONSOLE")
else:
layout.label(text="View the console for full logs.", icon="CONSOLE")
box = layout.box()
py = ".".join(info["python_version"].split(".")[0:2])
b3d = ".".join(info["blender_version"].split(".")[0:2])
box.label(text=f"Blender {b3d} {info['os']} {info['machine']}", icon="BLENDER")
@@ -133,6 +146,21 @@ if sys.modules.get("bpy", None):
op = layout.operator("bim.open_uri", text="How Can I Fix This?")
op.uri = "https://docs.blenderbim.org/users/troubleshooting.html#installation-issues"
layout.label(text="Try Reinstalling:", icon="IMPORT")
op = layout.operator("bim.open_uri", text="Re-download Add-on")
bbim_date = info["blenderbim_version"].split(".")[-1]
py_tag = py.replace(".", "")
if "Linux" in info["os"]:
os = "linux"
elif "Darwin" in info["os"]:
if "arm64" in info["machine"]:
os = "macosm1"
else:
os = "macos"
else:
os = "win"
op.uri = f"https://github.com/IfcOpenShell/IfcOpenShell/releases/download/blenderbim-{bbim_date}/blenderbim-{bbim_date}-py{py_tag}-{os}.zip"
class OpenUri(bpy.types.Operator):
bl_idname = "bim.open_uri"
bl_label = "Open URI"
@@ -149,14 +177,7 @@ if sys.modules.get("bpy", None):
def execute(self, context):
info = format_debug_info(get_debug_info())
if platform.system() == "Windows":
command = "echo | set /p nul=" + info
elif platform.system() == "Darwin": # for MacOS
command = 'printf "' + info.replace("\n", "\\n").replace('"', "") + '" | pbcopy'
else: # Linux
command = 'printf "' + info.replace("\n", "\\n").replace('"', "") + '" | xclip -selection clipboard'
subprocess.run(command, shell=True, check=True)
context.window_manager.clipboard = info
return {"FINISHED"}
class HiddenPanel:
@@ -0,0 +1,45 @@
/*
* 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/>.
*/
/**
* You may copy this `schedule.css` template alongside your input schedule
* document with the same filename. For example if you have a schedule called
* `door_types.ods`, you can create a `door_types.css` in the same folder to
* style that schedule.
*/
/**
* If you specify a font size in CSS, such as text { font-size: 5; }, all fonts
* will be overriden to match that size.
*
* If your CSS, ODS, XLSX does not specify a font size, the variables below
* will specify the default font size.
*
* If your ODS, XLSX does specify a font size, they will scale linearly based
* on the variables below.
*/
:root {
--font-size-pt: 12;
--font-size-px: 4.13;
--font-width: 0.45;
}
text, tspan { /* 2.5mm */ fill: black; stroke: none; font-family: 'OpenGost Type B TT', 'DejaVu Sans Condensed', 'Liberation Sans', 'Arial Narrow', 'Arial'; }
+6 -2
View File
@@ -29,7 +29,7 @@ from mathutils import geometry
from mathutils import Vector
import blenderbim.tool as tool
from blenderbim.bim.ifc import IfcStore
from typing import Optional, Callable, Any
from typing import Optional, Callable, Any, Union
def draw_attributes(props, layout, copy_operator=None, popup_active_attribute=None):
@@ -91,7 +91,11 @@ def import_attributes(ifc_class, props, data, callback=None):
# A more elegant attribute importer signature, intended to supersede import_attributes
def import_attributes2(element, props, callback=None):
def import_attributes2(
element: Union[str, ifcopenshell.entity_instance],
props: bpy.types.PropertyGroup,
callback: Optional[Callable] = None,
) -> None:
if isinstance(element, str):
attributes = tool.Ifc.schema().declaration_by_name(element).as_entity().all_attributes()
info = {a.name(): None for a in attributes}
+21 -13
View File
@@ -26,6 +26,7 @@ import bmesh
import logging
import mathutils
import numpy as np
import numpy.typing as npt
import multiprocessing
import ifcopenshell
import ifcopenshell.geom
@@ -303,22 +304,22 @@ class IfcImporter:
self.update_progress(100)
bpy.context.window_manager.progress_end()
def is_element_far_away(self, element):
def is_element_far_away(self, element: ifcopenshell.entity_instance) -> bool:
try:
placement = ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement)
point = placement[:, 3][0:3]
return self.is_point_far_away(point, is_meters=False)
except:
pass
return False
def is_point_far_away(self, point, is_meters=True):
def is_point_far_away(
self, point: Union[ifcopenshell.entity_instance, npt.NDArray[np.float64]], is_meters: bool = True
) -> bool:
# Locations greater than 1km are not considered "small sites" according to the georeferencing guide
# 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
coords = getattr(point, "Coordinates", point)
return abs(coords[0]) > limit or abs(coords[1]) > limit or abs(coords[2]) > limit
def process_context_filter(self):
@@ -608,7 +609,9 @@ class IfcImporter:
threshold = 10000 # Just from experience.
faces = [len(e.CfsFaces) for e in self.file.by_type("IfcClosedShell")]
# The check for CfsFaces/Faces/CoordIndex accommodates invalid data from Cadwork
# 0 IfcClosedShell.CfsFaces
faces = [len(faces) for e in self.file.by_type("IfcClosedShell") if (faces := e[0])]
if faces and max(faces) > threshold:
self.ifc_import_settings.should_use_native_meshes = True
return
@@ -616,12 +619,14 @@ class IfcImporter:
if self.file.schema == "IFC2X3":
return
faces = [len(e.Faces) for e in self.file.by_type("IfcPolygonalFaceSet")]
# 2 IfcPolygonalFaceSet.Faces
faces = [len(faces) for e in self.file.by_type("IfcPolygonalFaceSet") if (faces := e[2])]
if faces and max(faces) > threshold:
self.ifc_import_settings.should_use_native_meshes = True
return
faces = [len(e.CoordIndex) for e in self.file.by_type("IfcTriangulatedFaceSet")]
# 3 IfcTriangulatedFaceSet.CoordIndex
faces = [len(index) for e in self.file.by_type("IfcTriangulatedFaceSet") if (index := e[3])]
if faces and max(faces) > threshold:
self.ifc_import_settings.should_use_native_meshes = True
@@ -679,7 +684,7 @@ class IfcImporter:
props.blender_orthogonal_height = str(offset_point[2])
props.has_blender_offset = True
def get_offset_point(self):
def get_offset_point(self) -> Union[npt.NDArray[np.float64], None]:
elements_checked = 0
# If more than these elements aren't far away, the file probably isn't absolutely positioned
element_checking_threshold = 10
@@ -714,7 +719,7 @@ class IfcImporter:
if self.is_point_far_away(point, is_meters=False):
return point
def does_element_likely_have_geometry_far_away(self, element):
def does_element_likely_have_geometry_far_away(self, element: ifcopenshell.entity_instance) -> bool:
for representation in element.Representation.Representations:
items = []
for item in representation.Items:
@@ -731,13 +736,14 @@ class IfcImporter:
if subelement.is_a("IfcCartesianPoint"):
if len(subelement.Coordinates) == 3 and self.is_point_far_away(subelement, is_meters=False):
return True
return False
def apply_blender_offset_to_matrix_world(self, obj: bpy.types.Object, matrix: np.ndarray) -> mathutils.Matrix:
props = bpy.context.scene.BIMGeoreferenceProperties
if props.has_blender_offset:
if obj.data and obj.data.get("has_cartesian_point_offset", None):
obj.BIMObjectProperties.blender_offset_type = "CARTESIAN_POINT"
elif self.is_point_far_away((matrix[0, 3], matrix[1, 3], matrix[2, 3])):
elif self.is_point_far_away((matrix[:3, 3])):
obj.BIMObjectProperties.blender_offset_type = "OBJECT_PLACEMENT"
matrix = ifcopenshell.util.geolocation.global2local(
matrix,
@@ -750,7 +756,9 @@ class IfcImporter:
return mathutils.Matrix(matrix.tolist())
def find_decomposed_ifc_class(self, element, ifc_class):
def find_decomposed_ifc_class(
self, element: ifcopenshell.entity_instance, ifc_class: str
) -> Union[ifcopenshell.entity_instance, None]:
if element.is_a(ifc_class):
return element
rel_aggregates = element.IsDecomposedBy
@@ -100,7 +100,7 @@ class BIM_OT_aggregate_unassign_object(bpy.types.Operator, Operator):
pset = ifcopenshell.util.element.get_pset(element, 'BBIM_Linked_Aggregate')
if pset:
pset = tool.Ifc.get().by_id(pset["id"])
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), pset=pset)
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), product=element, pset=pset)
class BIM_OT_enable_editing_aggregate(bpy.types.Operator, Operator):
@@ -144,7 +144,7 @@ class BIM_PT_linked_aggregate(Panel):
row.label(text="Not a Linked Aggregate")
else:
row.label(text=f"{Number_Linked_Aggregates} Linked Aggregates")
op = row.operator("bim.object_duplicate_move_linked_aggregate", text="", icon="DUPLICATE")
op = row.operator("bim.duplicate_linked_aggregate_to_3d_cursor", text="", icon="DUPLICATE")
if type(Number_Linked_Aggregates) is int:
if Number_Linked_Aggregates > 0:
op = row.operator("bim.select_linked_aggregates", text="", icon="OUTLINER_DATA_POINTCLOUD")
@@ -27,15 +27,12 @@ classes = (
operator.CopyAttributeToSelection,
prop.BIMAttributeProperties,
ui.BIM_PT_object_attributes,
ui.BIM_PT_material_attributes,
)
def register():
bpy.types.Object.BIMAttributeProperties = bpy.props.PointerProperty(type=prop.BIMAttributeProperties)
bpy.types.Material.BIMAttributeProperties = bpy.props.PointerProperty(type=prop.BIMAttributeProperties)
def unregister():
del bpy.types.Object.BIMAttributeProperties
del bpy.types.Material.BIMAttributeProperties
@@ -23,7 +23,6 @@ import blenderbim.tool as tool
def refresh():
AttributesData.is_loaded = False
MaterialAttributesData.is_loaded = False
class AttributesData:
@@ -51,28 +50,3 @@ class AttributesData:
key = "STEP ID"
results.append({"name": key, "value": str(value)})
return results
class MaterialAttributesData:
data = {}
is_loaded = False
@classmethod
def load(cls):
cls.data = {"ifc_definition_id": cls.ifc_definition_id(), "attributes": cls.attributes()}
cls.is_loaded = True
@classmethod
def ifc_definition_id(cls):
return bpy.context.active_object.active_material.BIMObjectProperties.ifc_definition_id
@classmethod
def attributes(cls):
results = []
element = tool.Ifc.get_entity(bpy.context.active_object.active_material)
data = element.get_info()
for key, value in data.items():
if value is None or isinstance(value, ifcopenshell.entity_instance) or key in ["id", "type"]:
continue
results.append({"name": key, "value": str(value)})
return results
@@ -40,14 +40,10 @@ class EnableEditingAttributes(bpy.types.Operator):
bl_label = "Enable Editing Attributes"
bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
obj_type: bpy.props.StringProperty()
def execute(self, context):
self.file = IfcStore.get_file()
if self.obj_type == "Object":
obj = bpy.data.objects.get(self.obj)
elif self.obj_type == "Material":
obj = bpy.data.materials.get(self.obj)
obj = bpy.data.objects.get(self.obj)
props = obj.BIMAttributeProperties
props.attributes.clear()
@@ -85,13 +81,9 @@ class DisableEditingAttributes(bpy.types.Operator):
bl_label = "Disable Editing Attributes"
bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
obj_type: bpy.props.StringProperty()
def execute(self, context):
if self.obj_type == "Object":
obj = bpy.data.objects.get(self.obj)
elif self.obj_type == "Material":
obj = bpy.data.materials.get(self.obj)
obj = bpy.data.objects.get(self.obj)
props = obj.BIMAttributeProperties
props.is_editing_attributes = False
return {"FINISHED"}
@@ -102,14 +94,10 @@ class EditAttributes(bpy.types.Operator, Operator):
bl_label = "Edit Attributes"
bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
obj_type: bpy.props.StringProperty()
def _execute(self, context):
self.file = IfcStore.get_file()
if self.obj_type == "Object":
obj = bpy.data.objects.get(self.obj)
elif self.obj_type == "Material":
obj = bpy.data.materials.get(self.obj)
obj = bpy.data.objects.get(self.obj)
props = obj.BIMAttributeProperties
product = tool.Ifc.get_entity(obj)
@@ -126,7 +114,7 @@ class EditAttributes(bpy.types.Operator, Operator):
attributes = blenderbim.bim.helper.export_attributes(props.attributes, callback=callback)
ifcopenshell.api.run("attribute.edit_attributes", self.file, product=product, attributes=attributes)
bpy.ops.bim.disable_editing_attributes(obj=obj.name, obj_type=self.obj_type)
bpy.ops.bim.disable_editing_attributes(obj=obj.name)
return {"FINISHED"}
@@ -19,34 +19,34 @@
import blenderbim.bim.helper
from bpy.types import Panel
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.module.attribute.data import AttributesData, MaterialAttributesData
from blenderbim.bim.module.attribute.data import AttributesData
def draw_ui(context, layout, obj_type, attributes):
obj = context.active_object if obj_type == "Object" else context.active_object.active_material
def draw_ui(context, layout, attributes):
obj = context.active_object
oprops = obj.BIMObjectProperties
props = obj.BIMAttributeProperties
if props.is_editing_attributes:
row = layout.row(align=True)
op = row.operator("bim.edit_attributes", icon="CHECKMARK", text="Save Attributes")
op.obj_type = obj_type
op.obj = obj.name
op = row.operator("bim.disable_editing_attributes", icon="CANCEL", text="")
op.obj_type = obj_type
op.obj = obj.name
blenderbim.bim.helper.draw_attributes(props.attributes, layout, copy_operator="bim.copy_attribute_to_selection")
else:
row = layout.row()
op = row.operator("bim.enable_editing_attributes", icon="GREASEPENCIL", text="Edit")
op.obj_type = obj_type
op.obj = obj.name
for attribute in attributes:
row = layout.row(align=True)
row.label(text=attribute["name"])
row.label(text=attribute["value"])
# row.label(text=attribute["value"])
op = row.operator("bim.select_similar", text=attribute["value"], icon="NONE", emboss=False)
op.key = attribute['name']
# TODO: reimplement, see #1222
# if "IfcSite/" in context.active_object.name or "IfcBuilding/" in context.active_object.name:
@@ -72,32 +72,4 @@ class BIM_PT_object_attributes(Panel):
def draw(self, context):
if not AttributesData.is_loaded:
AttributesData.load()
draw_ui(context, self.layout, "Object", AttributesData.data["attributes"])
class BIM_PT_material_attributes(Panel):
bl_label = "Material Attributes"
bl_idname = "BIM_PT_material_attributes"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "material"
@classmethod
def poll(cls, context):
if not IfcStore.get_file():
return False
try:
return bool(context.active_object.active_material.BIMObjectProperties.ifc_definition_id)
except:
return False
def draw(self, context):
if not MaterialAttributesData.is_loaded:
MaterialAttributesData.load()
elif (
context.active_object.active_material.BIMObjectProperties.ifc_definition_id
!= MaterialAttributesData.data["ifc_definition_id"]
):
MaterialAttributesData.load()
draw_ui(context, self.layout, "Material", MaterialAttributesData.data["attributes"])
draw_ui(context, self.layout, AttributesData.data["attributes"])
@@ -155,7 +155,7 @@ class ReferenceUI:
blenderbim.bim.helper.draw_attributes(self.props.reference_attributes, self.layout)
row = self.layout.row(align=True)
op = row.operator("bim.add_manual_classification_reference", text="Save", icon="CHECKMARK")
op.type = self.data.data["object_type"]
op.obj_type = self.data.data["object_type"]
row.operator("bim.disable_adding_manual_classification_reference", text="", icon="CANCEL")
else:
row = self.layout.row()
@@ -82,6 +82,7 @@ classes = (
prop.CostItem,
prop.CostItemQuantity,
prop.CostItemType,
prop.ScheduleColumn,
prop.BIMCostProperties,
ui.BIM_PT_cost_schedules,
ui.BIM_PT_cost_item_quantities,
@@ -19,10 +19,13 @@
import bpy
import ifcopenshell
import ifcopenshell.util.cost
import ifcopenshell.util.date
import ifcopenshell.util.element
import ifcopenshell.util.unit
import blenderbim.tool as tool
import blenderbim.bim.schema
from ifcopenshell.util.doc import get_entity_doc, get_predefined_type_doc
from typing import Any
def refresh():
@@ -34,6 +37,7 @@ def refresh():
class CostSchedulesData:
data = {}
is_loaded = False
_cost_values: dict[int, dict[str, Any]]
@classmethod
def load(cls):
@@ -136,7 +140,7 @@ class CostSchedulesData:
data["UnitBasisUnitSymbol"] = "U"
if cost_value.Category == "*":
is_sum = True
cost_quantity = data["TotalCostQuantity"] or 1
cost_quantity = 1 if data["TotalCostQuantity"] is None else data["TotalCostQuantity"]
if has_unit_basis:
data["TotalCost"] = data["TotalAppliedValue"] * cost_quantity / data["UnitBasisValueComponent"]
else:
@@ -155,6 +159,7 @@ class CostSchedulesData:
data["UnitSymbol"] = "-"
if cost_item.CostQuantities:
quantity = cost_item.CostQuantities[0]
data["QuantityType"] = quantity.is_a()
unit = ifcopenshell.util.unit.get_property_unit(quantity, tool.Ifc.get())
if unit:
data["UnitSymbol"] = ifcopenshell.util.unit.get_unit_symbol(unit)
@@ -73,7 +73,7 @@ class RemoveCostSchedule(bpy.types.Operator, tool.Ifc.Operator):
cost_schedule: bpy.props.IntProperty()
def _execute(self, context):
core.remove_cost_schedule(tool.Ifc, cost_schedule=tool.Ifc.get().by_id(self.cost_schedule))
core.remove_cost_schedule(tool.Ifc, tool.Cost, cost_schedule=tool.Ifc.get().by_id(self.cost_schedule))
class EnableEditingCostSchedule(bpy.types.Operator):
@@ -223,36 +223,40 @@ class EditCostItem(bpy.types.Operator, tool.Ifc.Operator):
class AssignCostItemType(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.assign_cost_item_type"
bl_label = "Assign Cost Item Type Product"
bl_label = "Assign Cost Item To Product Types"
bl_description = "Assign cost item to currently selected or active product types"
bl_options = {"REGISTER", "UNDO"}
cost_item: bpy.props.IntProperty()
prop_name: bpy.props.StringProperty()
def _execute(self, context):
core.assign_cost_item_type(
product_types = core.assign_cost_item_type(
tool.Ifc,
tool.Cost,
tool.Spatial,
cost_item=tool.Ifc.get().by_id(self.cost_item),
prop_name=self.prop_name, # TODO: REVIEW PROP_NAME USABILITY
)
self.report({"INFO"}, f"Cost item was assigned to {len(product_types)} product types.")
class UnassignCostItemType(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.unassign_cost_item_type"
bl_label = "Unassign Cost Item Type"
bl_description = "Unassign cost item from currently selected or active product types"
bl_options = {"REGISTER", "UNDO"}
cost_item: bpy.props.IntProperty()
related_object: bpy.props.IntProperty()
def _execute(self, context):
core.unassign_cost_item_type(
product_types = core.unassign_cost_item_type(
tool.Ifc,
tool.Cost,
tool.Spatial,
cost_item=tool.Ifc.get().by_id(self.cost_item),
product_types=[tool.Ifc.get().by_id(self.related_object)] if self.related_object else [],
product_types=[tool.Ifc.get().by_id(self.related_object)] if self.related_object else None,
)
self.report({"INFO"}, f"Cost item was unassigned from {len(product_types)} product types.")
return {"FINISHED"}
@@ -287,9 +291,11 @@ class UnassignCostItemQuantity(bpy.types.Operator, tool.Ifc.Operator):
tool.Ifc,
tool.Cost,
cost_item=tool.Ifc.get().by_id(self.cost_item),
products=[tool.Ifc.get().by_id(self.related_object)]
if self.related_object
else tool.Spatial.get_selected_products(),
products=(
[tool.Ifc.get().by_id(self.related_object)]
if self.related_object
else tool.Spatial.get_selected_products()
),
)
@@ -509,7 +515,7 @@ class SelectCostScheduleProducts(bpy.types.Operator):
class ImportCostScheduleCsv(bpy.types.Operator, ImportHelper, tool.Ifc.Operator):
bl_idname = "import_cost_schedule_csv.bim"
bl_idname = "bim.import_cost_schedule_csv"
bl_label = "Import Cost Schedule CSV"
bl_options = {"REGISTER", "UNDO"}
filename_ext = ".csv"
@@ -527,6 +533,13 @@ class AddCostColumn(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
name: bpy.props.StringProperty()
@classmethod
def poll(cls, context):
if not context.scene.BIMCostProperties.cost_column:
cls.poll_message_set("Cost column name is empty")
return False
return True
def execute(self, context):
core.add_cost_column(tool.Cost, self.name)
return {"FINISHED"}
@@ -538,7 +551,7 @@ class RemoveCostColumn(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
name: bpy.props.StringProperty()
def _execute(self, context):
def execute(self, context):
core.remove_cost_column(tool.Cost, self.name)
return {"FINISHED"}
@@ -656,13 +669,17 @@ class ExportCostSchedules(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
bl_description = "Export a cost schedule to a CSV, XSLX OR ODS file"
cost_schedule: bpy.props.IntProperty()
format: bpy.props.EnumProperty("Format", items=(("CSV", "CSV", ""), ("XLSX", "XLSX", ""), ("ODS", "ODS", "")))
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
format: bpy.props.EnumProperty(name="Format", items=(("CSV", "CSV", ""), ("XLSX", "XLSX", ""), ("ODS", "ODS", "")))
directory: bpy.props.StringProperty(subtype="FILE_PATH")
filter_folder: bpy.props.BoolProperty(
name="Filter Folders",
default=True,
)
def execute(self, context):
cost_schedule = tool.Ifc.get().by_id(self.cost_schedule) if self.cost_schedule else None
r = core.export_cost_schedules(
tool.Cost, filepath=self.filepath, format=self.format, cost_schedule=cost_schedule
tool.Cost, filepath=self.directory, format=self.format, cost_schedule=cost_schedule
)
if isinstance(r, str):
self.report({"ERROR"}, r)
@@ -676,6 +693,7 @@ class ExportCostSchedules(bpy.types.Operator):
def draw(self, context):
self.layout.label(text="Choose a format")
self.layout.prop(self, "format")
self.layout.label(text="Select a directory.")
class ClearCostItemAssignments(bpy.types.Operator, tool.Ifc.Operator):
@@ -175,6 +175,10 @@ def update_active_cost_item_resources(self, context):
bpy.ops.bim.load_cost_item_resource_quantities()
class ScheduleColumn(PropertyGroup):
schedule_id: IntProperty()
class BIMCostProperties(PropertyGroup):
cost_schedule_predefined_types: EnumProperty(
items=get_schedule_predefined_types, name="Predefined Type", default=None
@@ -210,9 +214,14 @@ class BIMCostProperties(PropertyGroup):
cost_value_attributes: CollectionProperty(name="Cost Value Attributes", type=Attribute)
cost_value_formula: StringProperty(name="Cost Value Formula")
cost_column: StringProperty(name="Cost Column")
should_show_column_ui: BoolProperty(name="Should Show Column UI", default=False)
should_show_column_ui: BoolProperty(
name="Should Show Column UI",
description="Display UI for adding cost schedule columns, column names represent a category for cost item values",
default=False,
)
should_show_currency_ui: BoolProperty(name="Should Show Currency UI", default=False)
columns: CollectionProperty(name="Columns", type=StrProperty)
columns: CollectionProperty(name="Active Schedule Columns", type=StrProperty)
columns_storage: CollectionProperty(name="Columns", type=ScheduleColumn)
active_column_index: IntProperty(name="Active Column Index")
cost_item_products: CollectionProperty(name="Cost Item Products", type=CostItemQuantity)
active_cost_item_product_index: IntProperty(name="Active Cost Item Product Index")
@@ -21,6 +21,7 @@ import blenderbim.bim.module.cost.prop as CostProp
from bpy.types import Panel, UIList
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.module.cost.data import CostSchedulesData
from typing import Any
class BIM_PT_cost_schedules(Panel):
@@ -53,7 +54,7 @@ class BIM_PT_cost_schedules(Panel):
row.label(text="No Cost Schedules found.", icon="TEXT")
row.operator("bim.add_cost_schedule", icon="ADD", text="")
row.operator("import_cost_schedule_csv.bim",icon="IMPORT",text="")
row.operator("bim.import_cost_schedule_csv",icon="IMPORT",text="")
for schedule in CostSchedulesData.data["schedules"]:
self.draw_cost_schedule_ui(schedule)
@@ -178,24 +179,33 @@ class BIM_PT_cost_schedules(Panel):
"active_cost_item_index",
)
if self.props.active_cost_item_id:
cost_item = CostSchedulesData.data["cost_items"][ifc_definition_id]
if self.props.cost_item_editing_type == "ATTRIBUTES":
self.draw_editable_cost_item_attributes_ui()
elif self.props.cost_item_editing_type == "QUANTITIES":
self.draw_editable_cost_item_quantities_ui()
self.draw_editable_cost_item_quantities_ui(cost_item)
elif self.props.cost_item_editing_type == "VALUES":
self.draw_editable_cost_item_values_ui()
def draw_editable_cost_item_attributes_ui(self):
blenderbim.bim.helper.draw_attributes(self.props.cost_item_attributes, self.layout)
def draw_editable_cost_item_quantities_ui(self):
def draw_editable_cost_item_quantities_ui(self, cost_item: dict[str, Any]):
quantities = CostSchedulesData.data["cost_quantities"]
row = self.layout.row(align=True)
row.prop(self.props, "quantity_types", text="")
# In IFC, all quantities of IfcCostTime should have 1 type.
if quantities:
quantity_class = cost_item["QuantityType"]
row.label(text=quantity_class)
else:
row.prop(self.props, "quantity_types", text="")
quantity_class = self.props.quantity_types
op = row.operator("bim.add_cost_item_quantity", text="", icon="ADD")
op.cost_item = self.props.active_cost_item_id
op.ifc_class = self.props.quantity_types
op.ifc_class = quantity_class
for quantity in CostSchedulesData.data["cost_quantities"]:
for quantity in quantities:
row = self.layout.row(align=True)
row.label(text=quantity["name"])
row.label(text=quantity["value"])
@@ -634,7 +644,7 @@ class BIM_UL_cost_items_trait:
layout.label(text=cost_item["UnitBasisUnitSymbol"])
def draw_total_quantity_column(self, layout, cost_item):
if cost_item["TotalCostQuantity"]:
if cost_item["TotalCostQuantity"] is not None:
label = "{0:.2f}".format(cost_item["TotalCostQuantity"]) + f" {cost_item['UnitSymbol'] or '-'}"
layout.label(text=label)
else:
@@ -23,6 +23,7 @@ import ifccsv
import logging
import tempfile
import ifcopenshell
import ifcopenshell.util.selector
import blenderbim.tool as tool
import blenderbim.bim.module.drawing.scheduler as scheduler
from blenderbim.bim.ifc import IfcStore
@@ -60,13 +60,7 @@ class CopyDebugInformation(bpy.types.Operator):
print(text)
print("-" * 80)
if platform.system() == "Windows":
command = "echo | set /p nul=" + text
elif platform.system() == "Darwin": # for MacOS
command = 'printf "' + text.replace("\n", "\\n").replace('"', "") + '" | pbcopy'
else: # Linux
command = 'printf "' + text.replace("\n", "\\n").replace('"', "") + '" | xclip -selection clipboard'
subprocess.run(command, shell=True, check=True)
context.window_manager.clipboard = text
return {"FINISHED"}
@@ -159,9 +159,7 @@ class BaseDecorator:
objecttype = "NOTDEFINED"
def __init__(self):
self.font_id = blf.load(
os.path.join(bpy.context.scene.BIMProperties.data_dir, "fonts", "OpenGost Type B TT.ttf")
)
self.font_id = 0 # 0 is the default font
# POLYLINE_UNIFORM_COLOR is good for smoothed lines since `bgl.enable(GL_LINE_SMOOTH)` is deprecated
self.line_shader = gpu.shader.from_builtin("POLYLINE_UNIFORM_COLOR")
@@ -404,7 +402,6 @@ class BaseDecorator:
line_no += 1 if multiline_to_bottom else -1
return
# 0 is the default font, but we're fancier than that
font_id = self.font_id
color = context.preferences.addons["blenderbim"].preferences.decorations_colour
@@ -2014,6 +2011,13 @@ class DecorationsHandler:
for object_type in ("SLOPE_ANGLE", "SLOPE_FRACTION", "SLOPE_PERCENT"):
self.decorators[object_type] = self.decorators["FALL"]
self.decorators["MULTI_SYMBOL"] = self.decorators["SYMBOL"]
if drawing_font := bpy.context.scene.DocProperties.drawing_font:
drawing_font_path = os.path.join(bpy.context.scene.BIMProperties.data_dir, "fonts", drawing_font)
if os.path.exists(drawing_font_path):
font_id = blf.load(drawing_font_path)
for decorator in self.decorators.values():
decorator.font_id = font_id
def get_objects_and_decorators(self, collection):
# TODO: do it in data instead of the handler for performance?
@@ -315,9 +315,7 @@ class RasterStyleProperty(enum.Enum):
SPACE_SHADING = "space.shading"
RASTER_STYLE_PROPERTIES_EXCLUDE = (
"scene.render.filepath",
)
RASTER_STYLE_PROPERTIES_EXCLUDE = ("scene.render.filepath",)
class DocProperties(PropertyGroup):
@@ -371,6 +369,7 @@ class DocProperties(PropertyGroup):
default=os.path.join("drawings", "assets", "shading_styles.json"), name="Default Shading Styles"
)
shadingstyle_default: StringProperty(default="Blender Default", name="Default Shading Style")
drawing_font: StringProperty(default="OpenGost Type B TT.ttf", name="Drawing Font")
class BIMCameraProperties(PropertyGroup):
@@ -16,22 +16,21 @@
# 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 blenderbim.bim.module.drawing.svgwriter import SvgWriter
import os
import re
import bpy
import string
import svgwrite
import openpyxl
from blenderbim.bim.module.drawing.svgwriter import SvgWriter
from odf.opendocument import load as load_ods
from odf.table import Table, TableRow, TableColumn, TableCell
from odf.text import P
from odf.style import Style
from textwrap import wrap
from pathlib import Path
import string
FONT_SIZE = 4.13
FONT_WIDTH = lambda size: size * 0.45
FONT_SIZE_PT = 12
FONT_FAMILY = "OpenGost Type B TT"
DEBUG = False
@@ -63,11 +62,29 @@ class Scheduler:
)
self.padding = 1
self.margin = 1
self.parse_css(infile)
if infile.endswith("ods"):
self.schedule_ods(infile, outfile)
elif infile.endswith("xlsx"):
self.schedule_xlsx(infile, outfile)
def parse_css(self, infile):
stylesheet_path = os.path.splitext(infile)[0] + ".css"
if not os.path.exists(stylesheet_path):
stylesheet_path = os.path.join(bpy.context.scene.BIMProperties.data_dir, "assets", "schedule.css")
with open(stylesheet_path, "r") as stylesheet:
css = stylesheet.read()
matches = re.search("--font-size-pt:\s*([0-9.]+);", css)
self.font_size_pt = float(matches.groups()[0]) if matches else 12 # Default to 12pt
matches = re.search("--font-size-px:\s*([0-9.]+);", css)
self.font_size_px = float(matches.groups()[0]) if matches else 4.13 # Magic number 4.13px ~= 12pt
matches = re.search("--font-width:\s*([0-9.]+);", css)
self.font_width = float(matches.groups()[0]) if matches else 0.45 # A magic number for OpenGost
self.svg.defs.add(self.svg.style(css))
def schedule_xlsx(self, infile, outfile):
workbook = openpyxl.open(infile, data_only=True)
sheet = workbook.active
@@ -144,8 +161,8 @@ class Scheduler:
x += unmerged_width
continue
font_size = cell.font.size or 11 # 11pt default
font_size = font_size / FONT_SIZE_PT * FONT_SIZE # Magic?
font_size = cell.font.size or self.font_size_pt # 12pt default
font_size = font_size / self.font_size_pt * self.font_size_px # Magic?
text_position = [0.0, 0.0]
if cell.alignment.horizontal == "left":
@@ -424,9 +441,9 @@ class Scheduler:
italic_text = final_cell_style.get("font-style", None) == "italic"
# NOTE: very naive since we're scaling text proportionally
font_size = (
float(final_cell_style.get("font-size", f"{FONT_SIZE_PT}pt")[:-2])
/ FONT_SIZE_PT
* FONT_SIZE
float(final_cell_style.get("font-size", f"{self.font_size_pt}pt")[:-2])
/ self.font_size_pt
* self.font_size_px
)
if p_tags:
@@ -523,10 +540,7 @@ class Scheduler:
"""
text_lines = [str(p) for p in p_tags]
box_alignment_params = SvgWriter.get_box_alignment_parameters(box_alignment)
text_params = {
"font-size": font_size,
"font-family": FONT_FAMILY,
}
text_params = {"font-size": font_size}
if bold:
text_params["font-weight"] = "bold"
if italic:
@@ -546,13 +560,13 @@ class Scheduler:
text_tag = self.svg.text("", **(text_params | {"font-size": "0"} | box_alignment_params))
# TODO: should be done in less naive way
# without using magic number for FONT_WIDTH
# currently it might not work for all fonts and font sizes
# TODO: Should be done without using magic number for self.font_width
if wrap_text:
wrapped_lines = []
for line in text_lines:
wrapped_line = wrap(line, width=int(cell_width // FONT_WIDTH(font_size)), break_long_words=False)
wrapped_line = wrap(
line, width=int(cell_width // (font_size * self.font_width)), break_long_words=False
)
wrapped_lines.extend(wrapped_line)
else:
wrapped_lines = text_lines
@@ -29,6 +29,7 @@ classes = (
operator.GetRepresentationIfcParameters,
operator.DuplicateMoveLinkedAggregate,
operator.DuplicateMoveLinkedAggregateMacro,
operator.DuplicateLinkedAggregateTo3dCursor,
operator.OverrideDelete,
operator.OverrideDuplicateMove,
operator.OverrideDuplicateMoveLinked,
@@ -400,7 +400,11 @@ class UpdateRepresentation(bpy.types.Operator, Operator):
representation_data["profile_set_usage"] = tool.Geometry.get_profile_set_usage(product)
representation_data["text_literal"] = tool.Geometry.get_text_literal(old_representation)
# TODO: replace with core.add_representation?
new_representation = ifcopenshell.api.run("geometry.add_representation", self.file, **representation_data)
if new_representation is None:
self.report({"ERROR"}, "Error creating representation for Blender object.")
return {"CANCELLED"}
if tool.Geometry.is_body_representation(new_representation):
[
@@ -938,7 +942,7 @@ class OverrideDuplicateMove(bpy.types.Operator):
pset = ifcopenshell.util.element.get_pset(new[0], "BBIM_Linked_Aggregate")
if pset:
pset = tool.Ifc.get().by_id(pset["id"])
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), pset=pset)
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), product=new[0],pset=pset)
if new[0].is_a("IfcElementAssembly"):
linked_aggregate_group = [
@@ -973,15 +977,15 @@ class OverrideDuplicateMoveLinked(bpy.types.Operator):
class DuplicateMoveLinkedAggregateMacro(bpy.types.Macro):
bl_description = "Create a new linked aggregate"
bl_description = "Create and move a new linked aggregate"
bl_idname = "bim.object_duplicate_move_linked_aggregate_macro"
bl_label = "IFC Duplicate Linked Aggregate"
bl_label = "IFC Duplicate and Move Linked Aggregate"
bl_options = {"REGISTER", "UNDO"}
class DuplicateMoveLinkedAggregate(bpy.types.Operator):
bl_idname = "bim.object_duplicate_move_linked_aggregate"
bl_label = "IFC Duplicate Linked Aggregate"
bl_label = "IFC Duplicate and Move Linked Aggregate"
bl_options = {"REGISTER", "UNDO"}
is_interactive: bpy.props.BoolProperty(name="Is Interactive", default=True)
@@ -996,7 +1000,7 @@ class DuplicateMoveLinkedAggregate(bpy.types.Operator):
return DuplicateMoveLinkedAggregate.execute_ifc_duplicate_linked_aggregate_operator(self, context)
@staticmethod
def execute_ifc_duplicate_linked_aggregate_operator(self, context):
def execute_ifc_duplicate_linked_aggregate_operator(self, context, location_from_3d_cursor=False):
self.new_active_obj = None
self.group_name = "BBIM_Linked_Aggregate"
self.pset_name = "BBIM_Linked_Aggregate"
@@ -1050,7 +1054,7 @@ class DuplicateMoveLinkedAggregate(bpy.types.Operator):
if self.group_name in product_groups_name:
return
linked_aggregate_group = ifcopenshell.api.run("group.add_group", tool.Ifc.get(), Name=self.group_name)
linked_aggregate_group = ifcopenshell.api.run("group.add_group", tool.Ifc.get(), name=self.group_name)
ifcopenshell.api.run("group.assign_group", tool.Ifc.get(), products=[element], group=linked_aggregate_group)
def custom_incremental_naming_for_element_assembly(old_to_new):
@@ -1109,6 +1113,16 @@ class DuplicateMoveLinkedAggregate(bpy.types.Operator):
]
tool.Ifc.run("group.assign_group", group=linked_aggregate_group[0], products=new)
def get_location_from_3d_cursor(old_to_new, aggregate):
base_obj = tool.Ifc.get_object(aggregate)
base_obj_location = base_obj.location.copy()
for new in old_to_new.values():
new_obj = tool.Ifc.get_object(new[0])
location_diff = new_obj.location - base_obj_location
new_obj.location = context.scene.cursor.location + location_diff
if len(context.selected_objects) != 1:
return {"FINISHED"}
@@ -1135,11 +1149,29 @@ class DuplicateMoveLinkedAggregate(bpy.types.Operator):
custom_incremental_naming_for_element_assembly(old_to_new)
if location_from_3d_cursor:
get_location_from_3d_cursor(old_to_new, selected_element)
blenderbim.bim.handler.refresh_ui_data()
return old_to_new
class DuplicateLinkedAggregateTo3dCursor(bpy.types.Operator):
bl_idname = "bim.duplicate_linked_aggregate_to_3d_cursor"
bl_label = "IFC Duplicate Linked Aggregate to 3d Cursor"
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
return len(context.selected_objects) > 0
def execute(self, context):
return OverrideDuplicateMove.execute_duplicate_operator(self, context, linked=False)
def _execute(self, context):
return DuplicateMoveLinkedAggregate.execute_ifc_duplicate_linked_aggregate_operator(self, context, location_from_3d_cursor=True)
class RefreshLinkedAggregate(bpy.types.Operator):
bl_idname = "bim.refresh_linked_aggregate"
bl_label = "IFC Refresh Linked Aggregate"
@@ -36,31 +36,17 @@ class MaterialsData:
@classmethod
def load(cls):
cls.data = {
"total_materials": cls.total_materials(),
"material_types": cls.material_types(),
"profiles": cls.profiles(),
"styles": cls.styles(),
"contexts": cls.contexts(),
"active_styles": cls.active_styles(),
}
cls.is_loaded = True
cls.data["material_types"] = cls.material_types()
cls.data["total_materials"] = cls.total_materials()
cls.data["profiles"] = cls.profiles()
cls.data["styles"] = cls.styles()
cls.data["contexts"] = cls.contexts()
cls.data["active_styles"] = cls.active_styles()
@classmethod
def total_materials(cls):
if tool.Ifc.get_schema() == "IFC2X3":
return (
len(tool.Ifc.get().by_type("IfcMaterial"))
+ len(tool.Ifc.get().by_type("IfcMaterialLayerSet"))
+ len(tool.Ifc.get().by_type("IfcMaterialList"))
)
return (
len(tool.Ifc.get().by_type("IfcMaterial"))
+ len(tool.Ifc.get().by_type("IfcMaterialConstituentSet"))
+ len(tool.Ifc.get().by_type("IfcMaterialLayerSet"))
+ len(tool.Ifc.get().by_type("IfcMaterialProfileSet"))
+ len(tool.Ifc.get().by_type("IfcMaterialList"))
)
return len(tool.Ifc.get().by_type(bpy.context.scene.BIMMaterialProperties.material_type))
@classmethod
def material_types(cls):
@@ -97,6 +97,10 @@ def get_material_types(self, context):
return MaterialsData.data["material_types"]
def update_material_type(self, context):
MaterialsData.data["total_materials"] = MaterialsData.total_materials()
def get_profiles(self, context):
if not MaterialsData.is_loaded:
MaterialsData.load()
@@ -126,7 +130,7 @@ class Material(PropertyGroup):
class BIMMaterialProperties(PropertyGroup):
is_editing: BoolProperty(name="Is Editing", default=False)
material_type: EnumProperty(items=get_material_types, name="Material Type")
material_type: EnumProperty(items=get_material_types, update=update_material_type, name="Material Type")
materials: CollectionProperty(name="Materials", type=Material)
active_material_index: IntProperty(name="Active Material Index")
profiles: EnumProperty(items=get_profiles, name="Profiles")
@@ -45,7 +45,7 @@ class BIM_PT_materials(Panel):
self.props = context.scene.BIMMaterialProperties
row = self.layout.row(align=True)
row.label(text="{} Materials".format(MaterialsData.data["total_materials"]), icon="NODE_MATERIAL")
row.label(text=f"{MaterialsData.data['total_materials']} Materials", icon="NODE_MATERIAL")
if self.props.is_editing:
row.operator("bim.disable_editing_materials", text="", icon="CANCEL")
else:
@@ -23,6 +23,7 @@ import blenderbim.bim.handler
import blenderbim.tool as tool
import blenderbim.core.misc as core
import blenderbim.core.geometry as core_geometry
import blenderbim.core.root
from blenderbim.bim.ifc import IfcStore
from mathutils import Vector, Matrix, Euler
@@ -134,6 +135,10 @@ class ResizeToStorey(bpy.types.Operator, Operator):
class SplitAlongEdge(bpy.types.Operator, Operator):
bl_idname = "bim.split_along_edge"
bl_label = "Split Along Edge"
bl_description = (
"Active object is considered to be a cutting object."
"Will unassign element from a type if type has a representation."
)
bl_options = {"REGISTER", "UNDO"}
@classmethod
@@ -144,16 +149,25 @@ class SplitAlongEdge(bpy.types.Operator, Operator):
cutter = context.active_object
objs = [o for o in context.selected_objects if o != cutter]
objs_to_cut = []
# Splitting only works on meshes
for obj in objs:
# You cannot split meshes if the representation is mapped.
element = tool.Ifc.get_entity(obj)
if element:
relating_type = tool.Root.get_element_type(element)
if relating_type and tool.Root.does_type_have_representations(relating_type):
bpy.ops.bim.unassign_type(related_object=obj.name)
if not element:
continue
relating_type = tool.Root.get_element_type(element)
if relating_type and tool.Root.does_type_have_representations(relating_type):
bpy.ops.bim.unassign_type(related_object=obj.name)
# refresh representation
representation = tool.Geometry.get_active_representation(obj)
# skip empty objects that might get in the way
if not representation:
continue
core_geometry.switch_representation(
tool.Ifc,
tool.Geometry,
@@ -168,11 +182,13 @@ class SplitAlongEdge(bpy.types.Operator, Operator):
if not tool.Geometry.is_meshlike(representation):
bpy.ops.bim.update_representation(obj=obj.name, ifc_representation_class="IfcTessellatedFaceSet")
new_objs = tool.Misc.split_objects_with_cutter(objs, cutter)
objs_to_cut.append(obj)
new_objs = tool.Misc.split_objects_with_cutter(objs_to_cut, cutter)
for obj in new_objs:
blenderbim.core.root.copy_class(tool.Ifc, tool.Collector, tool.Geometry, tool.Root, obj=obj)
bpy.ops.bim.update_representation(obj=obj.name)
for obj in objs:
for obj in objs_to_cut:
bpy.ops.bim.update_representation(obj=obj.name)
representation = tool.Geometry.get_active_representation(obj)
@@ -187,6 +203,8 @@ class SplitAlongEdge(bpy.types.Operator, Operator):
apply_openings=True,
)
self.report({"INFO"}, f"Splitting finished, {len(new_objs)} new objects created.")
class GetConnectedSystemElements(bpy.types.Operator, Operator):
bl_idname = "bim.get_connected_system_elements"
@@ -83,17 +83,8 @@ class EnableEditingArray(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
obj = context.active_object
element = tool.Ifc.get_entity(obj)
data = json.loads(ifcopenshell.util.element.get_pset(element, "BBIM_Array", "Data"))[self.item]
props = obj.BIMArrayProperties
relating_obj = props.relating_array_object
if relating_obj:
element = tool.Ifc.get_entity(relating_obj)
parent_globalid = ifcopenshell.util.element.get_pset(element, "BBIM_Array", "Parent")
parent_element = tool.Ifc.get().by_guid(parent_globalid)
data = json.loads(ifcopenshell.util.element.get_pset(parent_element, "BBIM_Array", "Data"))[self.item]
else:
data = json.loads(ifcopenshell.util.element.get_pset(element, "BBIM_Array", "Data"))[self.item]
props.count = data["count"]
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
props.x = data["x"] * si_conversion
@@ -102,9 +93,7 @@ class EnableEditingArray(bpy.types.Operator, tool.Ifc.Operator):
props.use_local_space = data.get("use_local_space", False)
props.sync_children = data.get("sync_children", False)
props.method = data.get("method", "OFFSET")
props.is_editing = self.item
return {"FINISHED"}
@@ -148,10 +137,6 @@ class EditArray(bpy.types.Operator, tool.Ifc.Operator):
tool.Blender.Modifier.Array.set_children_lock_state(element, self.item, True)
tool.Blender.Modifier.Array.constrain_children_to_parent(element)
#clears the relating_array_object so it doesn't show again next time
props.relating_array_object = None
return {"FINISHED"}
@@ -208,7 +193,7 @@ class RemoveArray(bpy.types.Operator, tool.Ifc.Operator):
pset = tool.Ifc.get().by_id(pset["id"])
if len(data) == 1:
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), pset=pset)
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), product=element, pset=pset)
else:
del data[self.item]
data = tool.Ifc.get().createIfcText(json.dumps(data))
@@ -53,7 +53,7 @@ class AuthoringData:
cls.data["ifc_element_type"] = cls.ifc_element_type
cls.data["ifc_classes"] = cls.ifc_classes()
cls.data["relating_type_id"] = cls.relating_type_id() # only after .ifc_classes()
cls.data["predefined_type"] = cls.predefined_type()
cls.data["predefined_type"] = cls.predefined_type() # only after .relating_type_id()
cls.data["type_class"] = cls.type_class()
# only after .type_class()
@@ -121,9 +121,10 @@ class AuthoringData:
def type_thumbnail(cls):
if not cls.data["relating_type_id"]:
return 0
if not tool.Blender.enum_property_has_valid_index(cls.props, "relating_type_id", cls.data["relating_type_id"]):
relating_type_id = tool.Blender.get_enum_safe(cls.props, "relating_type_id")
if relating_type_id is None:
return 0
element = tool.Ifc.get().by_id(int(cls.props.relating_type_id))
element = tool.Ifc.get().by_id(int(relating_type_id))
return cls.type_thumbnails.get(element.id(), None) or 0
@classmethod
@@ -251,8 +252,8 @@ class AuthoringData:
@classmethod
def predefined_type(cls):
relating_type_id = cls.props.relating_type_id
if not relating_type_id:
relating_type_id = tool.Blender.get_enum_safe(cls.props, "relating_type_id")
if relating_type_id is None:
return
relating_type = tool.Ifc.get().by_id(int(relating_type_id))
if not hasattr(relating_type, "PredefinedType"):
@@ -22,20 +22,24 @@ import bmesh
from bmesh.types import BMVert, BMFace
import ifcopenshell
import ifcopenshell.api
import ifcopenshell.util.element
import ifcopenshell.util.representation
from ifcopenshell.util.shape_builder import V
import blenderbim
import blenderbim.tool as tool
import blenderbim.core.geometry
import blenderbim.core.geometry as core
from blenderbim.bim.module.model.window import create_bm_window, create_bm_box, update_simple_openings
import blenderbim.core.root
from blenderbim.bim.module.model.window import create_bm_window, create_bm_box
from mathutils import Vector, Matrix
from pprint import pprint
import json
import collections
def update_door_modifier_representation(context, obj):
def update_door_modifier_representation(context: bpy.types.Context, obj: bpy.types.Object) -> None:
props = obj.BIMDoorProperties
element = tool.Ifc.get_entity(obj)
ifc_file = tool.Ifc.get()
@@ -159,7 +163,7 @@ def update_door_modifier_representation(context, obj):
occurrence.OverallWidth = props.overall_width / si_conversion
occurrence.OverallHeight = props.overall_height / si_conversion
update_simple_openings(element, props.overall_width / si_conversion, props.overall_height / si_conversion)
tool.Model.update_simple_openings(element)
# TODO: move it out to tools
@@ -644,6 +648,6 @@ class RemoveDoor(bpy.types.Operator, tool.Ifc.Operator):
obj.BIMDoorProperties.is_editing = False
pset = tool.Pset.get_element_pset(element, "BBIM_Door")
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), pset=pset)
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), product=element, pset=pset)
return {"FINISHED"}
@@ -44,6 +44,7 @@ from bpy.types import SpaceView3D
from bpy.props import FloatProperty
from bpy_extras.object_utils import AddObjectHelper, object_data_add
from gpu_extras.batch import batch_for_shader
from typing import Union, Optional, Any
class AddFilledOpening(bpy.types.Operator, tool.Ifc.Operator):
@@ -59,9 +60,15 @@ class AddFilledOpening(bpy.types.Operator, tool.Ifc.Operator):
class FilledOpeningGenerator:
def generate(self, filling_obj, voided_obj, target=None):
def generate(
self,
filling_obj: Union[bpy.types.Object, None],
voided_obj: Union[bpy.types.Object, None],
target: Optional[Vector] = None,
) -> None:
props = bpy.context.scene.BIMModelProperties
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
opening_thickness_si = 0.0
filling = tool.Ifc.get_entity(filling_obj)
element = tool.Ifc.get_entity(voided_obj)
@@ -92,6 +99,7 @@ class FilledOpeningGenerator:
# In this prototype, we assume openings are only added to axis-based elements
layers = tool.Model.get_material_layer_parameters(element)
if layers["layer_set_direction"] == "AXIS2":
opening_thickness_si = layers["thickness"] * 2
axis = tool.Model.get_wall_axis(voided_obj, layers=layers)["base"]
new_matrix = voided_obj.matrix_world.copy()
point_on_axis = tool.Cad.point_on_edge(target, axis)
@@ -122,44 +130,37 @@ class FilledOpeningGenerator:
existing_opening_occurrence = self.get_existing_opening_occurrence_if_any(filling)
if existing_opening_occurrence:
opening = ifcopenshell.api.run(
"root.create_entity",
tool.Ifc.get(),
ifc_class="IfcOpeningElement",
predefined_type="OPENING",
name="Opening",
)
ifcopenshell.api.run(
"geometry.edit_object_placement", tool.Ifc.get(), product=opening, matrix=filling_obj.matrix_world
)
opening = ifcopenshell.api.run(
"root.create_entity",
tool.Ifc.get(),
ifc_class="IfcOpeningElement",
predefined_type="OPENING",
name="Opening",
)
ifcopenshell.api.run(
"geometry.edit_object_placement",
tool.Ifc.get(),
product=opening,
matrix=np.array(filling_obj.matrix_world),
is_si=True,
)
if existing_opening_occurrence:
representation = ifcopenshell.util.representation.get_representation(
existing_opening_occurrence, "Model", "Body", "MODEL_VIEW"
)
representation = ifcopenshell.util.representation.resolve_representation(representation)
mapped_representation = ifcopenshell.api.run(
"geometry.map_representation", tool.Ifc.get(), representation=representation
)
ifcopenshell.api.run(
"geometry.assign_representation", tool.Ifc.get(), product=opening, representation=mapped_representation
)
else:
representation = self.generate_opening_from_filling(filling, filling_obj)
opening = ifcopenshell.api.run(
"root.create_entity", tool.Ifc.get(), ifc_class="IfcOpeningElement", predefined_type="OPENING"
representation = self.generate_opening_from_filling(
filling, filling_obj, opening_thickness_si=opening_thickness_si
)
matrix = np.array(filling_obj.matrix_world)
ifcopenshell.api.run(
"geometry.edit_object_placement", tool.Ifc.get(), product=opening, matrix=matrix, is_si=True
)
mapped_representation = ifcopenshell.api.run(
"geometry.map_representation", tool.Ifc.get(), representation=representation
)
ifcopenshell.api.run(
"geometry.assign_representation", tool.Ifc.get(), product=opening, representation=mapped_representation
)
mapped_representation = ifcopenshell.api.run(
"geometry.map_representation", tool.Ifc.get(), representation=representation
)
ifcopenshell.api.run(
"geometry.assign_representation", tool.Ifc.get(), product=opening, representation=mapped_representation
)
ifcopenshell.api.run("void.add_opening", tool.Ifc.get(), opening=opening, element=element)
ifcopenshell.api.run("void.add_filling", tool.Ifc.get(), opening=opening, element=filling)
@@ -187,7 +188,7 @@ class FilledOpeningGenerator:
should_sync_changes_first=False,
)
def regenerate_from_type(self, usecase_path, ifc_file, settings):
def regenerate_from_type(self, usecase_path: str, ifc_file: ifcopenshell.file, settings: dict[str, Any]) -> None:
relating_type = settings["relating_type"]
for related_object in settings["related_objects"]:
@@ -252,10 +253,15 @@ class FilledOpeningGenerator:
should_sync_changes_first=False,
)
def generate_opening_from_filling(self, filling, filling_obj):
def generate_opening_from_filling(
self,
filling: ifcopenshell.entity_instance,
filling_obj: bpy.types.Object,
opening_thickness_si: float = 0.0,
) -> ifcopenshell.entity_instance:
# Since openings are reused later, we give a default thickness of 1.2m
# which should cover the majority of curved, or super thick walls.
thickness = 1.2
thickness = max(1.2, opening_thickness_si)
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
shape_builder = ifcopenshell.util.shape_builder.ShapeBuilder(tool.Ifc.get())
@@ -346,7 +352,9 @@ class FilledOpeningGenerator:
return True
return False
def get_existing_opening_occurrence_if_any(self, filling):
def get_existing_opening_occurrence_if_any(
self, filling: ifcopenshell.entity_instance
) -> Union[ifcopenshell.entity_instance, None]:
filling_type = ifcopenshell.util.element.get_type(filling)
if filling_type:
filling_occurrences = ifcopenshell.util.element.get_types(filling_type)
@@ -138,7 +138,10 @@ class AddConstrTypeInstance(bpy.types.Operator):
if not relating_type_id:
return {"FINISHED"}
if self.from_invoke:
# Check relating_type_id enum_items since it's possible
# that we're adding e.g. IfcRoofType being in a Slab Tool
# and roof type id won't be present in the relating_type_id enum.
if self.from_invoke and str(self.relating_type_id) in AuthoringData.data["relating_type_id"]:
props.relating_type_id = str(self.relating_type_id)
relating_type = tool.Ifc.get().by_id(int(relating_type_id))
@@ -94,7 +94,6 @@ class DumbProfileGenerator:
obj=obj,
ifc_class=ifc_class,
should_add_representation=False,
context=self.body_context,
)
ifcopenshell.api.run("type.assign_type", self.file, related_objects=[element], relating_type=self.relating_type)
@@ -931,13 +930,11 @@ class PatchNonParametricMepSegment(bpy.types.Operator, tool.Ifc.Operator):
return context.active_object
def _execute(self, context):
styles = tool.Geometry.get_styles(context.active_object)
blenderbim.core.material.patch_non_parametric_mep_segment(
tool.Ifc, tool.Material, tool.Profile, obj=context.active_object
)
bpy.ops.bim.enable_editing_extrusion_axis()
bpy.ops.bim.edit_extrusion_axis()
styles = tool.Geometry.get_styles(context.active_object)
class EnableEditingExtrusionAxis(bpy.types.Operator, tool.Ifc.Operator):
@@ -60,7 +60,7 @@ def update_ifc_class(self, context):
bpy.ops.bim.load_type_thumbnails(ifc_class=self.ifc_class)
AuthoringData.data["relating_type_id"] = AuthoringData.relating_type_id()
AuthoringData.data["type_thumbnail"] = AuthoringData.type_thumbnail()
if not tool.Blender.enum_property_has_valid_index(self, "relating_type_id", AuthoringData.data["relating_type_id"]):
if tool.Blender.get_enum_safe(self, "relating_type_id") is None:
self["relating_type_id"] = 0
@@ -87,21 +87,6 @@ def update_type_page(self, context):
AuthoringData.data["paginated_relating_types"] = AuthoringData.paginated_relating_types()
def update_relating_array_from_object(self, context):
bpy.ops.bim.enable_editing_array(item=self.is_editing)
return
def is_object_array_applicable(self, obj):
element = tool.Ifc.get_entity(obj)
if not element:
return False
return ifcopenshell.util.element.get_pset(element, "BBIM_Array")
class BIMModelProperties(PropertyGroup):
ifc_class: bpy.props.EnumProperty(items=get_ifc_class, name="Construction Class", update=update_ifc_class)
relating_type_id: bpy.props.EnumProperty(
@@ -218,14 +203,6 @@ class BIMArrayProperties(PropertyGroup):
description="Regenerate all children based on the parent object",
default=False,
)
relating_array_object: bpy.props.PointerProperty(
type=bpy.types.Object,
name="Copy Array Properties",
update=update_relating_array_from_object,
poll=is_object_array_applicable,
)
class BIMStairProperties(PropertyGroup):
@@ -20,8 +20,12 @@
import bpy
import bmesh
import ifcopenshell
import ifcopenshell.api
import ifcopenshell.util.representation
import ifcopenshell.util.unit
from ifcopenshell.util.shape_builder import V
import blenderbim
import blenderbim.core.root
import blenderbim.tool as tool
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.module.model.door import bm_sort_out_geom
@@ -535,5 +539,5 @@ class RemoveRailing(bpy.types.Operator, tool.Ifc.Operator):
obj.BIMRailingProperties.is_editing = False
pset = tool.Pset.get_element_pset(element, "BBIM_Railing")
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), pset=pset)
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), product=element, pset=pset)
return {"FINISHED"}
@@ -20,7 +20,11 @@ import bpy
import bmesh
import ifcopenshell
import ifcopenshell.api
import ifcopenshell.util.representation
import ifcopenshell.util.unit
import blenderbim
import blenderbim.core.root
import blenderbim.tool as tool
from blenderbim.bim.helper import convert_property_group_from_si
from blenderbim.bim.module.model.door import bm_sort_out_geom
@@ -757,7 +761,7 @@ class RemoveRoof(bpy.types.Operator, tool.Ifc.Operator):
obj.BIMRoofProperties.is_editing = False
pset = tool.Pset.get_element_pset(element, "BBIM_Roof")
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), pset=pset)
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), product=element, pset=pset)
return {"FINISHED"}
@@ -176,7 +176,6 @@ class DumbSlabGenerator:
obj=obj,
ifc_class=ifc_class,
should_add_representation=False,
context=self.body_context,
)
ifcopenshell.api.run("type.assign_type", self.file, related_objects=[element], relating_type=self.relating_type)
@@ -20,7 +20,11 @@ import bpy
import json
import bmesh
import ifcopenshell
import ifcopenshell.api
import ifcopenshell.util.representation
import ifcopenshell.util.unit
import blenderbim
import blenderbim.core.root
import blenderbim.tool as tool
from mathutils import Vector
from bmesh.types import BMVert
@@ -217,7 +221,6 @@ class BIM_OT_add_clever_stair(bpy.types.Operator, tool.Ifc.Operator):
collection = context.view_layer.active_layer_collection.collection
collection.objects.link(obj)
body_context = ifcopenshell.util.representation.get_context(ifc_file, "Model", "Body", "MODEL_VIEW")
element = blenderbim.core.root.assign_class(
tool.Ifc,
tool.Collector,
@@ -225,7 +228,6 @@ class BIM_OT_add_clever_stair(bpy.types.Operator, tool.Ifc.Operator):
obj=obj,
ifc_class="IfcStairFlight",
should_add_representation=False,
context=body_context,
)
if tool.Ifc.get_schema() != "IFC2X3":
element.PredefinedType = "STRAIGHT"
@@ -337,6 +339,6 @@ class RemoveStair(bpy.types.Operator, tool.Ifc.Operator):
obj.BIMStairProperties.is_editing = False
pset = tool.Pset.get_element_pset(element, "BBIM_Stair")
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), pset=pset)
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), product=element, pset=pset)
return {"FINISHED"}
@@ -223,8 +223,6 @@ class BIM_PT_array(bpy.types.Panel):
row = col.row(align=True)
row.prop(props, "z")
row.operator("bim.input_cursor_z_array", icon="CURSOR", text="")
row = col.row(align=True)
row.prop(props, "relating_array_object", icon="COPYDOWN")
else:
row = box.row(align=True)
name = f"{array['count']} Items ({array.get('method', 'OFFSET').capitalize()})"
@@ -607,7 +607,6 @@ class DumbWallGenerator:
obj=obj,
ifc_class=ifc_class,
should_add_representation=False,
context=self.body_context,
)
ifcopenshell.api.run("type.assign_type", self.file, related_objects=[element], relating_type=self.relating_type)
if self.axis_context:
@@ -26,66 +26,16 @@ import blenderbim.tool as tool
import blenderbim.core.root
import blenderbim.core.geometry
from ifcopenshell.api.geometry.add_window_representation import DEFAULT_PANEL_SCHEMAS
import ifcopenshell.api
import ifcopenshell.util.element
import ifcopenshell.util.representation
import ifcopenshell.util.shape_builder
import ifcopenshell.util.unit
from ifcopenshell.util.shape_builder import V
from bmesh.types import BMVert
from mathutils import Vector
# TODO: move to some utils helpers/tool module
def update_simple_openings(element, opening_width, opening_height):
ifc_file = tool.Ifc.get()
fillings = tool.Ifc.get_all_element_occurrences(element)
voided_objs = set()
has_replaced_opening_representation = False
for filling in fillings:
if not filling.FillsVoids:
continue
opening = filling.FillsVoids[0].RelatingOpeningElement
voided_obj = tool.Ifc.get_object(opening.VoidsElements[0].RelatingBuildingElement)
voided_objs.add(voided_obj)
# we assume that the same element type (e.g. window)
# will be used only for voiding objects of the same thickness (by y-dimension)
# (e.g. all walls window's attached to will share the same thickness)
# If that's not the case it will some linings will be too thick or too thin
if has_replaced_opening_representation:
continue
old_representation = ifcopenshell.util.representation.get_representation(opening, "Model", "Body", "MODEL_VIEW")
old_representation = tool.Geometry.resolve_mapped_representation(old_representation)
ifcopenshell.api.run(
"geometry.unassign_representation", ifc_file, product=opening, representation=old_representation
)
thickness = voided_obj.dimensions[1] + 0.1 + 0.1
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(ifc_file)
shape_builder = ifcopenshell.util.shape_builder.ShapeBuilder(ifc_file)
context = ifcopenshell.util.representation.get_context(ifc_file, "Model", "Body", "MODEL_VIEW")
extrusion = shape_builder.extrude(
shape_builder.rectangle(size=Vector([opening_width, 0.0, opening_height]).xz),
magnitude=thickness / unit_scale,
position=Vector([0.0, -0.1 / unit_scale, 0.0]),
**shape_builder.extrude_kwargs("Y")
)
new_representation = shape_builder.get_representation(context, extrusion)
for inverse in ifc_file.get_inverse(old_representation):
ifcopenshell.util.element.replace_attribute(inverse, old_representation, new_representation)
ifcopenshell.api.run("geometry.remove_representation", ifc_file, representation=old_representation)
has_replaced_opening_representation = True
tool.Model.reload_body_representation(voided_objs)
if fillings:
with bpy.context.temp_override(selected_objects=[tool.Ifc.get_object(f) for f in fillings]):
bpy.ops.bim.recalculate_fill()
def update_window_modifier_representation(context, obj):
element = tool.Ifc.get_entity(obj)
props = obj.BIMWindowProperties
@@ -175,7 +125,7 @@ def update_window_modifier_representation(context, obj):
occurrence.OverallWidth = props.overall_width / si_conversion
occurrence.OverallHeight = props.overall_height / si_conversion
update_simple_openings(element, props.overall_width / si_conversion, props.overall_height / si_conversion)
tool.Model.update_simple_openings(element)
def create_bm_window_frame(bm, size: Vector, thickness: list, position: Vector = V(0, 0, 0).freeze()):
@@ -590,6 +540,6 @@ class RemoveWindow(bpy.types.Operator, tool.Ifc.Operator):
obj.BIMWindowProperties.is_editing = False
pset = tool.Pset.get_element_pset(element, "BBIM_Window")
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), pset=pset)
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), product=element, pset=pset)
return {"FINISHED"}
@@ -36,8 +36,8 @@ classes = (
operator.EnableCulling,
operator.EnableEditingHeader,
operator.ExportIFC,
operator.ExportIFCDeprecated,
operator.FlipClippingPlane,
operator.ImportIFC,
operator.LinkIfc,
operator.LoadLink,
operator.LoadLinkedProject,
@@ -97,7 +97,7 @@ def register():
addon_keymaps.append((km, kmi))
km = wm.keyconfigs.addon.keymaps.new(name="Window", space_type="EMPTY")
kmi = km.keymap_items.new("export_ifc.bim", "S", "PRESS", ctrl=True)
kmi = km.keymap_items.new("bim.save_project", "S", "PRESS", ctrl=True)
kmi.properties.should_save_as = False
addon_keymaps.append((km, kmi))
@@ -1106,8 +1106,8 @@ class ToggleLinkVisibility(bpy.types.Operator):
]
class ExportIFC(bpy.types.Operator):
bl_idname = "export_ifc.bim"
class ExportIFCBase:
bl_idname = "bim.save_project"
bl_label = "Save IFC"
bl_options = {"REGISTER", "UNDO"}
filename_ext = ".ifc"
@@ -1224,14 +1224,20 @@ class ExportIFC(bpy.types.Operator):
return "Save the IFC file. Will save both .IFC/.BLEND files if synced together"
class ImportIFC(bpy.types.Operator):
bl_idname = "import_ifc.bim"
bl_label = "Import IFC"
bl_options = {"REGISTER", "UNDO"}
class ExportIFC(ExportIFCBase, bpy.types.Operator):
pass
# TODO: remove as deprecated, better wait couple releases since
# this operator is used for saving IFC files in user scripts.
class ExportIFCDeprecated(ExportIFCBase, bpy.types.Operator):
bl_idname = "export_ifc.bim"
def execute(self, context):
bpy.ops.bim.load_project("INVOKE_DEFAULT")
return {"FINISHED"}
msg = f"'{ExportIFCDeprecated.bl_idname}' operator name is deprecated, use '{ExportIFC.bl_idname}'."
self.report({"WARNING"}, msg)
print(msg)
return super().execute(context)
class LoadLinkedProject(bpy.types.Operator):
@@ -68,9 +68,9 @@ def file_menu(self, context):
op = self.layout.operator("bim.load_project", text="Open IFC Project", icon="FILEBROWSER")
op.should_start_fresh_session = True
self.layout.separator()
op = self.layout.operator("export_ifc.bim", icon="FILE_TICK", text="Save IFC Project")
op = self.layout.operator("bim.save_project", icon="FILE_TICK", text="Save IFC Project")
op.should_save_as = False
op = self.layout.operator("export_ifc.bim", text="Save IFC Project As...")
op = self.layout.operator("bim.save_project", text="Save IFC Project As...")
op.should_save_as = True
self.layout.separator()
self.layout.operator("bim.revert_project")
@@ -20,6 +20,7 @@ import bpy
from . import ui, prop, operator
classes = (
operator.AddProposedProp,
operator.AddPset,
operator.AddQto,
operator.CopyPropertyToSelection,
@@ -39,14 +40,6 @@ classes = (
prop.IfcPropertyEnumeratedValue,
prop.IfcProperty,
prop.PsetProperties,
prop.MaterialPsetProperties,
prop.MaterialSetPsetProperties,
prop.MaterialSetItemPsetProperties,
prop.TaskPsetProperties,
prop.ResourcePsetProperties,
prop.GroupPsetProperties,
prop.ProfilePsetProperties,
prop.WorkSchedulePsetProperties,
prop.RenameProperties,
prop.AddEditProperties,
prop.DeletePsets,
@@ -71,14 +64,14 @@ classes = (
def register():
bpy.types.Object.PsetProperties = bpy.props.PointerProperty(type=prop.PsetProperties)
bpy.types.Object.MaterialSetPsetProperties = bpy.props.PointerProperty(type=prop.MaterialSetPsetProperties)
bpy.types.Object.MaterialSetItemPsetProperties = bpy.props.PointerProperty(type=prop.MaterialSetItemPsetProperties)
bpy.types.Material.PsetProperties = bpy.props.PointerProperty(type=prop.MaterialPsetProperties)
bpy.types.Scene.TaskPsetProperties = bpy.props.PointerProperty(type=prop.TaskPsetProperties)
bpy.types.Scene.ResourcePsetProperties = bpy.props.PointerProperty(type=prop.ResourcePsetProperties)
bpy.types.Scene.GroupPsetProperties = bpy.props.PointerProperty(type=prop.GroupPsetProperties)
bpy.types.Scene.ProfilePsetProperties = bpy.props.PointerProperty(type=prop.ProfilePsetProperties)
bpy.types.Scene.WorkSchedulePsetProperties = bpy.props.PointerProperty(type=prop.WorkSchedulePsetProperties)
bpy.types.Object.MaterialSetPsetProperties = bpy.props.PointerProperty(type=prop.PsetProperties)
bpy.types.Object.MaterialSetItemPsetProperties = bpy.props.PointerProperty(type=prop.PsetProperties)
bpy.types.Material.PsetProperties = bpy.props.PointerProperty(type=prop.PsetProperties)
bpy.types.Scene.TaskPsetProperties = bpy.props.PointerProperty(type=prop.PsetProperties)
bpy.types.Scene.ResourcePsetProperties = bpy.props.PointerProperty(type=prop.PsetProperties)
bpy.types.Scene.GroupPsetProperties = bpy.props.PointerProperty(type=prop.PsetProperties)
bpy.types.Scene.ProfilePsetProperties = bpy.props.PointerProperty(type=prop.PsetProperties)
bpy.types.Scene.WorkSchedulePsetProperties = bpy.props.PointerProperty(type=prop.PsetProperties)
bpy.types.Scene.RenameProperties = bpy.props.CollectionProperty(type=prop.RenameProperties)
bpy.types.Scene.AddEditProperties = bpy.props.CollectionProperty(type=prop.AddEditProperties)
bpy.types.Scene.DeletePsets = bpy.props.CollectionProperty(type=prop.DeletePsets)
@@ -31,7 +31,6 @@ import blenderbim.core.qto as QtoCore
import blenderbim.bim.module.pset.data
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.module.pset.qto_calculator import QtoCalculator
from blenderbim.bim.module.pset.calc_quantity_function_mapper import mapper
class Operator:
@@ -41,35 +40,15 @@ class Operator:
return {"FINISHED"}
def get_pset_props(context, obj, obj_type):
if obj_type == "Object":
return bpy.data.objects.get(obj).PsetProperties
elif obj_type == "Material":
return bpy.data.materials.get(obj).PsetProperties
elif obj_type == "MaterialSet":
return bpy.data.objects.get(obj).MaterialSetPsetProperties
elif obj_type == "MaterialSetItem":
return bpy.data.objects.get(obj).MaterialSetItemPsetProperties
elif obj_type == "Task":
return context.scene.TaskPsetProperties
elif obj_type == "Resource":
return context.scene.ResourcePsetProperties
elif obj_type == "Profile":
return context.scene.ProfilePsetProperties
elif obj_type == "WorkSchedule":
return context.scene.WorkSchedulePsetProperties
elif obj_type == "Group":
return context.scene.GroupPsetProperties
class TogglePsetExpansion(bpy.types.Operator, Operator):
bl_idname = "bim.toggle_pset_expansion"
bl_label = "Toggle Pset Expansion"
pset_id: bpy.props.IntProperty()
def _execute(self, context):
blenderbim.bim.module.pset.data.is_expanded[
self.pset_id
] = not blenderbim.bim.module.pset.data.is_expanded.setdefault(self.pset_id, True)
blenderbim.bim.module.pset.data.is_expanded[self.pset_id] = (
not blenderbim.bim.module.pset.data.is_expanded.setdefault(self.pset_id, True)
)
class EnablePsetEditing(bpy.types.Operator):
@@ -83,160 +62,15 @@ class EnablePsetEditing(bpy.types.Operator):
obj_type: bpy.props.StringProperty()
def execute(self, context):
self.props = get_pset_props(context, self.obj, self.obj_type)
self.props.properties.clear()
if self.pset_id:
pset = tool.Ifc.get().by_id(self.pset_id)
self.props.active_pset_name = pset.Name
self.props.active_pset_type = ""
pset_template = blenderbim.bim.schema.ifc.psetqto.get_by_name(pset.Name)
self.pset_name = pset.Name
else:
pset = None
self.props.active_pset_name = self.pset_name
self.props.active_pset_type = self.pset_type
pset_template = blenderbim.bim.schema.ifc.psetqto.get_by_name(self.pset_name)
if pset_template:
self.load_from_pset_template(pset_template, pset)
else:
self.load_from_pset_data(pset)
self.props.active_pset_id = self.pset_id
core.enable_pset_editing(tool.Pset, pset, self.pset_name, self.pset_type, self.obj, self.obj_type)
return {"FINISHED"}
def load_from_pset_template(self, pset_template, pset):
if pset:
data = ifcopenshell.util.element.get_property_definition(pset)
del data["id"]
else:
data = {}
for prop_template in pset_template.HasPropertyTemplates:
if not prop_template.is_a("IfcSimplePropertyTemplate"):
continue # Other types not yet supported
if prop_template.TemplateType == "P_SINGLEVALUE":
self.load_single_value(pset_template, prop_template, data)
elif prop_template.TemplateType.startswith("Q_"):
self.load_single_value(pset_template, prop_template, data)
elif prop_template.TemplateType == "P_ENUMERATEDVALUE":
self.load_enumerated_value(prop_template, data)
else:
# NOTE: currently unsupported types:
# - P_BOUNDEDVALUE
# - P_LISTVALUE
# - P_REFERENCEVALUE
# - P_TABLEVALUE
pass
def load_single_value(self, pset_template, prop_template, data):
prop = self.props.properties.add()
prop.name = prop_template.Name
prop.value_type = "IfcPropertySingleValue"
metadata = prop.metadata
metadata.name = prop_template.Name
metadata.is_null = data.get(prop_template.Name, None) is None
metadata.is_optional = True
metadata.is_uri = prop_template.PrimaryMeasureType == "IfcURIReference"
metadata.has_calculator = bool(mapper.get(pset_template.Name, {}).get(prop_template.Name, None))
metadata.data_type = self.get_data_type(prop_template)
special_type = ""
if prop_template.PrimaryMeasureType in (
"IfcPositiveLengthMeasure",
"IfcLengthMeasure",
) or prop_template.TemplateType == "Q_LENGTH":
special_type = "LENGTH"
elif prop_template.PrimaryMeasureType == "IfcAreaMeasure" or prop_template.TemplateType == "Q_AREA":
special_type = "AREA"
elif prop_template.PrimaryMeasureType == "IfcVolumeMeasure" or prop_template.TemplateType == "Q_VOLUME":
special_type = "VOLUME"
metadata.special_type = special_type
if metadata.data_type == "string":
metadata.string_value = "" if metadata.is_null else str(data[prop_template.Name])
elif metadata.data_type == "integer":
metadata.int_value = 0 if metadata.is_null else int(data[prop_template.Name])
elif metadata.data_type == "float":
metadata.float_value = 0.0 if metadata.is_null else float(data[prop_template.Name])
elif metadata.data_type == "boolean":
metadata.bool_value = False if metadata.is_null else bool(data[prop_template.Name])
metadata.ifc_class = pset_template.Name
blenderbim.bim.helper.add_attribute_description(metadata, prop_template)
def get_data_type(self, prop_template):
if prop_template.TemplateType in ["Q_LENGTH", "Q_AREA", "Q_VOLUME", "Q_WEIGHT", "Q_TIME"]:
return "float"
elif prop_template.TemplateType == "Q_COUNT":
return "integer"
return ifcopenshell.util.attribute.get_primitive_type(
IfcStore.get_schema().declaration_by_name(prop_template.PrimaryMeasureType or "IfcLabel")
)
def load_enumerated_value(self, prop_template, data):
enum_items = [v.wrappedValue for v in prop_template.Enumerators.EnumerationValues]
selected_enum_items = data.get(prop_template.Name, []) or []
prop = self.props.properties.add()
prop.name = prop_template.Name
prop.value_type = "IfcPropertyEnumeratedValue"
metadata = prop.metadata
metadata.name = prop_template.Name
metadata.is_null = data.get(prop_template.Name, None) is None
metadata.is_optional = True
metadata.is_uri = prop_template.PrimaryMeasureType == "IfcURIReference"
# Cute hack to abuse the metadata to find the Blender data_type
metadata.set_value(enum_items[0])
data_type = metadata.get_value_name()
for enum in enum_items:
new = prop.enumerated_value.enumerated_values.add()
setattr(new, data_type, enum)
new.is_selected = enum in selected_enum_items
def load_from_pset_data(self, pset):
props = []
if pset.is_a("IfcElementQuantity"):
props = pset.Quantities
elif pset.is_a("IfcPropertySet"):
props = pset.HasProperties
elif pset.is_a("IfcMaterialProperties") or pset.is_a("IfcProfileProperties"):
props = pset.Properties
for prop in props:
if prop.is_a("IfcPropertyEnumeratedValue"):
simple_prop = self.props.properties.add()
simple_prop.name = prop.Name
simple_prop.value_type = "IfcPropertyEnumeratedValue"
metadata = simple_prop.metadata
metadata.name = prop.Name
metadata.is_null = len(simple_prop.enumerated_value.enumerated_values) == 0
metadata.is_optional = True
metadata.set_value(prop.EnumerationReference.EnumerationValues[0].wrappedValue)
enum_items = [v.wrappedValue for v in prop.EnumerationReference.EnumerationValues]
selected_enum_items = [v.wrappedValue for v in prop.EnumerationValues]
data_type = metadata.get_value_name(display_only=True)
for enum in enum_items:
new = simple_prop.enumerated_value.enumerated_values.add()
setattr(new, data_type, enum)
new.is_selected = enum in selected_enum_items
else:
if prop.is_a("IfcPropertySingleValue"):
value = prop.NominalValue.wrappedValue if prop.NominalValue else None
elif prop.is_a("IfcPhysicalSimpleQuantity"):
value = prop[3]
new_prop = self.props.properties.add()
new_prop.name = prop.Name
metadata = new_prop.metadata
metadata.set_value(value)
metadata.name = prop.Name
metadata.is_null = value is None
metadata.is_optional = True
metadata.set_value(metadata.get_value_default() if metadata.is_null else value)
class DisablePsetEditing(bpy.types.Operator, Operator):
bl_idname = "bim.disable_pset_editing"
@@ -246,7 +80,7 @@ class DisablePsetEditing(bpy.types.Operator, Operator):
obj_type: bpy.props.StringProperty()
def _execute(self, context):
props = get_pset_props(context, self.obj, self.obj_type)
props = tool.Pset.get_pset_props(self.obj, self.obj_type)
if props.active_pset_id:
pset = tool.Ifc.get().by_id(props.active_pset_id)
ifc_definition_id = tool.Blender.get_obj_ifc_definition_id(self.obj, self.obj_type, context)
@@ -270,7 +104,7 @@ class EditPset(bpy.types.Operator, Operator):
def _execute(self, context):
self.file = IfcStore.get_file()
props = get_pset_props(context, self.obj, self.obj_type)
props = tool.Pset.get_pset_props(self.obj, self.obj_type)
ifc_definition_id = tool.Blender.get_obj_ifc_definition_id(self.obj, self.obj_type, context)
element = tool.Ifc.get().by_id(ifc_definition_id)
properties = {}
@@ -310,6 +144,8 @@ class EditPset(bpy.types.Operator, Operator):
for key, value in properties.items():
if isinstance(value, float):
properties[key] = round(value, 4)
elif not isinstance(value, int):
properties[key] = 0
ifcopenshell.api.run(
"pset.edit_qto",
self.file,
@@ -341,7 +177,7 @@ class RemovePset(bpy.types.Operator, Operator):
objects = [self.obj]
pset_name = tool.Ifc.get().by_id(self.pset_id).Name
for obj in objects:
props = get_pset_props(context, obj, self.obj_type)
props = tool.Pset.get_pset_props(obj, self.obj_type)
ifc_definition_id = tool.Blender.get_obj_ifc_definition_id(obj, self.obj_type, context)
element = tool.Ifc.get().by_id(ifc_definition_id)
pset = ifcopenshell.util.element.get_psets(element, should_inherit=False).get(pset_name, None)
@@ -372,11 +208,9 @@ class AddQto(bpy.types.Operator, Operator):
def _execute(self, context):
self.file = IfcStore.get_file()
props = get_pset_props(context, self.obj, self.obj_type)
ifc_definition_id = tool.Blender.get_obj_ifc_definition_id(self.obj, self.obj_type, context)
element = tool.Ifc.get().by_id(ifc_definition_id)
qto_name = tool.Pset.get_pset_name(self.obj, self.obj_type, pset_type="QTO")
bpy.ops.bim.enable_pset_editing(
pset_id=0, pset_name=props.qto_name, pset_type="QTO", obj=self.obj, obj_type=self.obj_type
pset_id=0, pset_name=qto_name, pset_type="QTO", obj=self.obj, obj_type=self.obj_type
)
@@ -634,3 +468,17 @@ class BIM_OT_bulk_remove_psets(bpy.types.Operator):
self.report({"INFO"}, "Finished applying changes")
return {"FINISHED"}
class AddProposedProp(bpy.types.Operator):
bl_idname = "bim.add_proposed_prop"
bl_label = "Add Proposed Prop"
bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
obj_type: bpy.props.StringProperty()
prop_name: bpy.props.StringProperty()
prop_value: bpy.props.StringProperty()
def execute(self, context):
core.add_proposed_prop(tool.Pset, self.obj, self.obj_type, self.prop_name, self.prop_value)
return {"FINISHED"}
@@ -19,6 +19,7 @@
import bpy
import blenderbim.bim.schema
import ifcopenshell
import ifcopenshell.util.attribute
import ifcopenshell.util.element
import blenderbim.tool as tool
from blenderbim.bim.prop import Attribute, StrProperty
@@ -58,6 +59,30 @@ def blender_formatted_enum_from_psets(psets):
def get_pset_name(self, context):
pset_type = repr(self)
prop_type = pset_type.split(".")[-1]
results = []
if "bpy.data.objects" in pset_type:
if prop_type == "PsetProperties":
results = get_object_pset_name(self, context)
elif prop_type == "MaterialSetPsetProperties":
results = get_material_set_pset_names(self, context)
elif prop_type == "MaterialSetItemPsetProperties":
results = get_material_set_item_pset_names(self, context)
elif "bpy.data.materials" in pset_type:
results = get_material_pset_names(self, context)
elif prop_type == "ResourcePsetProperties":
results = get_resource_pset_names(self, context)
elif prop_type == "GroupPsetProperties":
results = get_group_pset_names(self, context)
elif prop_type == "ProfilePsetProperties":
results = get_profile_pset_names(self, context)
elif prop_type == "WorkSchedulePsetProperties":
results = get_work_schedule_pset_names(self, context)
return [("BBIM_CUSTOM", "Custom Pset", "Create a property set without using a template."), None] + results
def get_object_pset_name(self, context):
if not ObjectPsetsData.is_loaded:
ObjectPsetsData.load()
return ObjectPsetsData.data["pset_name"]
@@ -167,6 +192,21 @@ def get_work_schedule_pset_names(self, context):
def get_qto_name(self, context):
pset_type = repr(self)
prop_type = pset_type.split(".")[-1]
if "bpy.data.objects" in pset_type:
if prop_type == "PsetProperties":
results = get_object_qto_name(self, context)
elif prop_type == "TaskPsetProperties":
results = get_task_qto_names(self, context)
elif prop_type == "ResourcePsetProperties":
results = get_resource_qto_names(self, context)
elif prop_type == "GroupPsetProperties":
results = get_group_qto_names(self, context)
return [("BBIM_CUSTOM", "Custom Qto", "Create a quantity set without using a template."), None] + results
def get_object_qto_name(self, context):
if not ObjectPsetsData.is_loaded:
ObjectPsetsData.load()
return ObjectPsetsData.data["qto_name"]
@@ -198,76 +238,14 @@ class IfcProperty(PropertyGroup):
class PsetProperties(PropertyGroup):
active_pset_id: IntProperty(name="Active Pset ID")
active_pset_has_template: BoolProperty(name="Active Pset Has Template")
active_pset_name: StringProperty(name="Pset Name")
active_pset_type: StringProperty(name="Active Pset Type")
properties: CollectionProperty(name="Properties", type=IfcProperty)
pset_name: EnumProperty(items=get_pset_name, name="Pset Name")
qto_name: EnumProperty(items=get_qto_name, name="Qto Name")
class MaterialPsetProperties(PropertyGroup):
active_pset_id: IntProperty(name="Active Pset ID")
active_pset_name: StringProperty(name="Pset Name")
active_pset_type: StringProperty(name="Active Pset Type")
properties: CollectionProperty(name="Properties", type=IfcProperty)
pset_name: EnumProperty(items=get_material_pset_names, name="Pset Name")
class MaterialSetPsetProperties(PropertyGroup):
active_pset_id: IntProperty(name="Active Pset ID")
active_pset_name: StringProperty(name="Pset Name")
active_pset_type: StringProperty(name="Active Pset Type")
properties: CollectionProperty(name="Properties", type=IfcProperty)
pset_name: EnumProperty(items=get_material_set_pset_names, name="Pset Name")
class MaterialSetItemPsetProperties(PropertyGroup):
active_pset_id: IntProperty(name="Active Pset ID")
active_pset_name: StringProperty(name="Pset Name")
active_pset_type: StringProperty(name="Active Pset Type")
properties: CollectionProperty(name="Properties", type=IfcProperty)
pset_name: EnumProperty(items=get_material_set_item_pset_names, name="Pset Name")
class TaskPsetProperties(PropertyGroup):
active_pset_id: IntProperty(name="Active Pset ID")
active_pset_name: StringProperty(name="Pset Name")
active_pset_type: StringProperty(name="Active Pset Type")
properties: CollectionProperty(name="Properties", type=IfcProperty)
qto_name: EnumProperty(items=get_task_qto_names, name="Qto Name")
class ResourcePsetProperties(PropertyGroup):
active_pset_id: IntProperty(name="Active Pset ID")
active_pset_name: StringProperty(name="Pset Name")
active_pset_type: StringProperty(name="Active Pset Type")
properties: CollectionProperty(name="Properties", type=IfcProperty)
pset_name: EnumProperty(items=get_resource_pset_names, name="Pset Name")
qto_name: EnumProperty(items=get_resource_qto_names, name="Qto Name")
class GroupPsetProperties(PropertyGroup):
active_pset_id: IntProperty(name="Active Pset ID")
active_pset_name: StringProperty(name="Pset Name")
active_pset_type: StringProperty(name="Active Pset Type")
properties: CollectionProperty(name="Properties", type=IfcProperty)
pset_name: EnumProperty(items=get_group_pset_names, name="Pset Name")
qto_name: EnumProperty(items=get_group_qto_names, name="Qto Name")
class ProfilePsetProperties(PropertyGroup):
active_pset_id: IntProperty(name="Active Pset ID")
active_pset_name: StringProperty(name="Pset Name")
active_pset_type: StringProperty(name="Active Pset Type")
properties: CollectionProperty(name="Properties", type=IfcProperty)
pset_name: EnumProperty(items=get_profile_pset_names, name="Pset Name")
class WorkSchedulePsetProperties(PropertyGroup):
active_pset_id: IntProperty(name="Active Pset ID")
active_pset_name: StringProperty(name="Pset Name")
active_pset_type: StringProperty(name="Active Pset Type")
properties: CollectionProperty(name="Properties", type=IfcProperty)
pset_name: EnumProperty(items=get_work_schedule_pset_names, name="Pset Name")
prop_name: StringProperty(name="Property Name", default="MyProperty")
prop_value: StringProperty(name="Property Value", default="Some Value")
class RenameProperties(PropertyGroup):
@@ -101,6 +101,7 @@ def draw_psetqto_ui(context, pset_id, pset, props, layout, obj_type, allow_remov
if props.active_pset_id == pset_id:
row.prop(props, "active_pset_name", icon="COPY_ID", text="")
op = row.operator("bim.edit_pset", icon="CHECKMARK", text="")
op.pset_id = pset_id
op.obj = obj_name
op.obj_type = obj_type
op = row.operator("bim.disable_pset_editing", icon="CANCEL", text="")
@@ -132,6 +133,16 @@ def draw_psetqto_ui(context, pset_id, pset, props, layout, obj_type, allow_remov
if props.active_pset_id == pset_id:
for prop in props.properties:
draw_psetqto_editable_ui(box, props, prop)
if not props.active_pset_has_template:
row = box.row(align=True)
row.prop(props, "prop_name", text="")
row.prop(props, "prop_value", text="")
op = row.operator("bim.add_proposed_prop", text="", icon="ADD")
op.obj = obj_name
op.obj_type = obj_type
op.prop_name = props.prop_name
op.prop_value = props.prop_value
else:
has_props_displayed = False
for prop in pset["Properties"]:
@@ -143,7 +154,9 @@ def draw_psetqto_ui(context, pset_id, pset, props, layout, obj_type, allow_remov
row = box.row(align=True)
row.scale_y = 0.8
row.label(text=prop["Name"])
row.label(text=str(prop["NominalValue"]))
op = row.operator("bim.select_similar", text=str(prop["NominalValue"]), icon="NONE", emboss=False)
op.key = pset['Name']
if not has_props_displayed:
row = box.row()
row.scale_y = 0.8
@@ -21,6 +21,7 @@ import bpy
import pathlib
import ifcopenshell
import ifcopenshell.util.attribute
import ifcopenshell.util.doc
import blenderbim.tool as tool
from blenderbim.bim.ifc import IfcStore
@@ -36,10 +37,14 @@ class PsetTemplatesData:
@classmethod
def load(cls):
cls.is_loaded = True
cls.data["pset_template_files"] = cls.pset_template_files()
# after pset_template_files
cls.data["pset_templates"] = cls.pset_templates()
# after pset_template_files because it loads IfcStore.pset_template_file
cls.data["primary_measure_type"] = cls.primary_measure_type()
cls.data["property_template_type"] = cls.property_template_type()
cls.data["pset_template_files"] = cls.pset_template_files()
cls.data["pset_templates"] = cls.pset_templates()
cls.data["pset_template"] = cls.pset_template()
cls.data["prop_templates"] = cls.prop_templates()
@@ -190,4 +190,4 @@ class BIMPsetTemplateProperties(PropertyGroup):
active_prop_template_id: IntProperty(name="Active Prop Template Id")
active_pset_template: PointerProperty(type=PsetTemplate)
active_prop_template: PointerProperty(type=PropTemplate)
new_template_filename: StringProperty("New TemplateFileName")
new_template_filename: StringProperty(name="New TemplateFileName")
@@ -19,6 +19,7 @@
import bpy
import blenderbim.tool as tool
import ifcopenshell
import ifcopenshell.util.resource
def refresh():
@@ -28,7 +29,6 @@ def refresh():
class ResourceData:
data = {}
is_loaded = False
cost_values = {}
@classmethod
def load(cls):
@@ -139,7 +139,7 @@ class ResourceData:
return results
@classmethod
def active_resource_ids(cls):
def active_resource_ids(cls) -> list[int]:
obj = bpy.context.active_object
element = tool.Ifc.get_entity(obj)
if not element:
@@ -128,9 +128,9 @@ class ContractResource(bpy.types.Operator):
class AssignResource(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.assign_resource"
bl_label = "Assign Resource"
bl_description = "Assign resource to the selected objects"
bl_options = {"REGISTER", "UNDO"}
resource: bpy.props.IntProperty()
related_object: bpy.props.StringProperty()
def _execute(self, context):
core.assign_resource(tool.Ifc, tool.Spatial, resource=tool.Ifc.get().by_id(self.resource))
@@ -139,9 +139,9 @@ class AssignResource(bpy.types.Operator, tool.Ifc.Operator):
class UnassignResource(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.unassign_resource"
bl_label = "Unassign Resource"
bl_description = "Unassign resource from the selected objects"
bl_options = {"REGISTER", "UNDO"}
resource: bpy.props.IntProperty()
related_object: bpy.props.StringProperty()
def _execute(self, context):
core.unassign_resource(tool.Ifc, tool.Spatial, resource=tool.Ifc.get().by_id(self.resource))
@@ -328,7 +328,7 @@ class EditResourceQuantity(bpy.types.Operator, tool.Ifc.Operator):
class ImportResources(bpy.types.Operator, tool.Ifc.Operator, ImportHelper):
bl_idname = "import_resources.bim"
bl_idname = "bim.import_resources"
bl_label = "Import Resources"
bl_options = {"REGISTER", "UNDO"}
filename_ext = ".csv"
@@ -51,7 +51,7 @@ class BIM_PT_resources(Panel):
row.operator("bim.disable_resource_editing_ui", text="", icon="CANCEL")
else:
row.operator("bim.load_resources", text="", icon="GREASEPENCIL")
row.operator("import_resources.bim", text="", icon="IMPORT")
row.operator("bim.import_resources", text="", icon="IMPORT")
if not self.props.is_editing:
return
@@ -20,6 +20,7 @@ import re
import bpy
import json
import ifcopenshell
import ifcopenshell.api
import ifcopenshell.guid
import ifcopenshell.util.element
import ifcopenshell.util.selector
@@ -214,7 +215,7 @@ class SaveSearch(Operator, tool.Ifc.Operator):
group = group[0]
group.Description = description
else:
group = ifcopenshell.api.run("group.add_group", tool.Ifc.get(), Name=self.name, Description=description)
group = ifcopenshell.api.run("group.add_group", tool.Ifc.get(), name=self.name, description=description)
if results:
ifcopenshell.api.run("group.assign_group", tool.Ifc.get(), products=list(results), group=group)
@@ -367,7 +368,7 @@ class SaveColourscheme(Operator, tool.Ifc.Operator):
description = json.dumps(
{"type": "BBIM_Search", "colourscheme": colourscheme, "colourscheme_query": query}
)
group = ifcopenshell.api.run("group.add_group", tool.Ifc.get(), Name=self.name, Description=description)
group = ifcopenshell.api.run("group.add_group", tool.Ifc.get(), name=self.name, description=description)
def invoke(self, context, event):
return context.window_manager.invoke_props_dialog(self)
@@ -634,12 +635,14 @@ class SelectSimilar(Operator, tool.Ifc.Operator):
bl_label = "Select Similar"
bl_options = {"REGISTER", "UNDO"}
key: bpy.props.StringProperty()
def _execute(self, context):
props = context.scene.BIMSearchProperties
obj = context.active_object
element = tool.Ifc.get_entity(obj)
key = props.element_key
if props.element_key == "PredefinedType":
key = self.key
if key == "PredefinedType":
key = "predefined_type"
value = ifcopenshell.util.selector.get_element_value(element, key)
for obj in context.visible_objects:
@@ -113,7 +113,8 @@ class BIM_PT_select_similar(Panel):
if SelectSimilarData.data["element_key"]:
row = self.layout.row(align=True)
row.prop(props, "element_key", text="")
row.operator("bim.select_similar", text="", icon="RESTRICT_SELECT_OFF")
op = row.operator("bim.select_similar", text="", icon="RESTRICT_SELECT_OFF")
op.key = props.element_key
else:
row = self.layout.row()
row.label(text=f"No Active Element")
@@ -628,7 +628,7 @@ class DisableEditingWorkCalendar(bpy.types.Operator):
class ImportCSV(bpy.types.Operator, tool.Ifc.Operator, ImportHelper):
bl_idname = "import_csv.bim"
bl_idname = "bim.import_csv"
bl_label = "Import CSV"
bl_options = {"REGISTER", "UNDO"}
filename_ext = ".csv"
@@ -653,7 +653,7 @@ class ImportCSV(bpy.types.Operator, tool.Ifc.Operator, ImportHelper):
class ImportP6(bpy.types.Operator, ImportHelper):
bl_idname = "import_p6.bim"
bl_idname = "bim.import_p6"
bl_label = "Import P6"
bl_options = {"REGISTER", "UNDO"}
filename_ext = ".xml"
@@ -679,7 +679,7 @@ class ImportP6(bpy.types.Operator, ImportHelper):
class ImportP6XER(bpy.types.Operator, ImportHelper):
bl_idname = "import_p6xer.bim"
bl_idname = "bim.import_p6xer"
bl_label = "Import P6 XER"
bl_options = {"REGISTER", "UNDO"}
filename_ext = ".xer"
@@ -705,7 +705,7 @@ class ImportP6XER(bpy.types.Operator, ImportHelper):
class ImportPP(bpy.types.Operator, ImportHelper):
bl_idname = "import_pp.bim"
bl_idname = "bim.import_pp"
bl_label = "Import Powerproject .pp"
bl_options = {"REGISTER", "UNDO"}
filename_ext = ".pp"
@@ -731,7 +731,7 @@ class ImportPP(bpy.types.Operator, ImportHelper):
class ImportMSP(bpy.types.Operator, ImportHelper):
bl_idname = "import_msp.bim"
bl_idname = "bim.import_msp"
bl_label = "Import MSP"
bl_options = {"REGISTER", "UNDO"}
filename_ext = ".xml"
@@ -757,7 +757,7 @@ class ImportMSP(bpy.types.Operator, ImportHelper):
class ExportMSP(bpy.types.Operator, ImportHelper):
bl_idname = "export_msp.bim"
bl_idname = "bim.export_msp"
bl_label = "Export MSP"
bl_options = {"REGISTER", "UNDO"}
filename_ext = ".xml"
@@ -787,7 +787,7 @@ class ExportMSP(bpy.types.Operator, ImportHelper):
class ExportP6(bpy.types.Operator, ImportHelper):
bl_idname = "export_p6.bim"
bl_idname = "bim.export_p6"
bl_label = "Export P6"
bl_options = {"REGISTER", "UNDO"}
filename_ext = ".xml"
@@ -44,6 +44,8 @@ def update_elevation(self, context):
def update_active_container_index(self, context):
if self.active_container_index < 0:
return
self.active_container_id = self.containers[self.active_container_index].ifc_definition_id
self.container_name = self.containers[self.active_container_index].name
self.elevation = self.containers[self.active_container_index].elevation
@@ -116,7 +116,7 @@ class BIM_PT_SpatialManager(Panel):
self.props = context.scene.BIMSpatialManagerProperties
row = self.layout.row()
row.operator("bim.load_container_manager", icon="FILE_REFRESH", text="Load Spatial Structure")
if self.props.active_container_index < len(self.props.containers):
if 0 <= self.props.active_container_index < len(self.props.containers):
ifc_definition_id = self.props.containers[self.props.active_container_index].ifc_definition_id
row = self.layout.row()
row.alignment = "RIGHT"
@@ -134,7 +134,7 @@ class BIM_PT_SpatialManager(Panel):
"active_container_index",
)
row = self.layout.row()
if self.props.active_container_index < len(self.props.containers):
if 0 <= self.props.active_container_index < len(self.props.containers):
row.prop(self.props, "container_name", text="")
row.prop(self.props, "elevation", text="")
op = row.operator("bim.edit_container_attributes", icon="CHECKMARK", text="Apply")
@@ -20,6 +20,7 @@ import bpy
import json
import ifcopenshell
import ifcopenshell.api
import ifcopenshell.util.attribute
import blenderbim.bim.helper
import blenderbim.bim.handler
import blenderbim.core.structural as core
@@ -122,7 +122,7 @@ class ColourRgb(PropertyGroup):
name: StringProperty()
color_value: FloatVectorProperty(size=3, subtype="COLOR", default=(1, 1, 1))
# not exposed in the UI, here just to preserve the data
color_name: StringProperty("Color Name")
color_name: StringProperty(name="Color Name")
# to fit blender.bim.helper.export_attributes
def get_value(self):
@@ -22,6 +22,7 @@ import ifcopenshell.util.element
import ifcopenshell.util.schema
import ifcopenshell.util.representation
import ifcopenshell.util.type
import ifcopenshell.util.unit
import ifcopenshell.api
import blenderbim.tool as tool
import blenderbim.core.geometry
@@ -454,7 +455,6 @@ class AddType(bpy.types.Operator, tool.Ifc.Operator):
predefined_type=predefined_type,
ifc_class="IfcStairFlightType",
should_add_representation=False,
context=body,
)
tool.Blender.select_and_activate_single_object(context, obj)
bpy.ops.bim.add_stair()
@@ -512,10 +512,7 @@ class RemoveType(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
element = tool.Ifc.get().by_id(self.element)
obj = tool.Ifc.get_object(element)
ifcopenshell.api.run("root.remove_product", tool.Ifc.get(), product=element)
if obj:
tool.Ifc.unlink(obj=obj)
bpy.data.objects.remove(obj)
tool.Geometry.delete_ifc_object(obj)
class RenameType(bpy.types.Operator, tool.Ifc.Operator):
@@ -21,6 +21,7 @@ import ifcopenshell.api
import ifcopenshell.util.representation
import blenderbim.tool as tool
import blenderbim.core.geometry
import blenderbim.core.root
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.module.model.opening import FilledOpeningGenerator
@@ -77,6 +78,10 @@ class AddOpening(bpy.types.Operator, tool.Ifc.Operator):
self.report({"INFO"}, "You can't add an opening to another opening.")
continue
if not hasattr(element1, "HasOpenings"):
self.report({"INFO"}, f"An {element1.is_a()} is not allowed to have an opening.")
continue
if tool.Ifc.is_moved(obj1):
blenderbim.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj1)
+19 -14
View File
@@ -19,6 +19,7 @@
import os
import bpy
import addon_utils
import platform
from pathlib import Path
from bpy.types import Panel
from bpy.props import StringProperty, IntProperty, BoolProperty
@@ -290,8 +291,6 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
row = layout.row()
row.prop(self, "spatial_elements_unselectable")
row = layout.row()
row.prop(context.scene.BIMProjectProperties, "should_disable_undo_on_save")
row = layout.row()
@@ -320,28 +319,30 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
row.prop(context.scene.BIMProperties, "data_dir")
row.operator("bim.select_data_dir", icon="FILE_FOLDER", text="")
row = self.layout.row(align=True)
row = self.layout.row()
row.prop(context.scene.BIMProperties, "pset_dir")
row = self.layout.row(align=True)
row = self.layout.row()
row.prop(context.scene.DocProperties, "sheets_dir")
row = self.layout.row(align=True)
row = self.layout.row()
row.prop(context.scene.DocProperties, "layouts_dir")
row = self.layout.row(align=True)
row = self.layout.row()
row.prop(context.scene.DocProperties, "titleblocks_dir")
row = self.layout.row(align=True)
row = self.layout.row()
row.prop(context.scene.DocProperties, "drawings_dir")
row = self.layout.row(align=True)
row = self.layout.row()
row.prop(context.scene.DocProperties, "stylesheet_path")
row = self.layout.row(align=True)
row = self.layout.row()
row.prop(context.scene.DocProperties, "markers_path")
row = self.layout.row(align=True)
row = self.layout.row()
row.prop(context.scene.DocProperties, "symbols_path")
row = self.layout.row(align=True)
row = self.layout.row()
row.prop(context.scene.DocProperties, "patterns_path")
row = self.layout.row(align=True)
row = self.layout.row()
row.prop(context.scene.DocProperties, "shadingstyles_path")
row = self.layout.row(align=True)
row = self.layout.row()
row.prop(context.scene.DocProperties, "shadingstyle_default")
row = self.layout.row()
row.prop(context.scene.DocProperties, "drawing_font")
# Scene panel groups
@@ -406,10 +407,14 @@ class BIM_PT_tabs(Panel):
if blenderbim.last_error:
box = self.layout.box()
box.alert=True
row = box.row(align=True)
row.label(text="BlenderBIM experienced an error :(", icon="ERROR")
row.operator("bim.close_error", text="", icon="CANCEL")
box.label(text="View the console for full logs.", icon="CONSOLE")
if platform.system() == "Windows":
box.operator("wm.console_toggle", text="View the console for full logs.", icon="CONSOLE")
else:
box.label(text="View the console for full logs.", icon="CONSOLE")
box.operator("bim.copy_debug_information", text="Copy Error Message To Clipboard")
op = box.operator("bim.open_uri", text="How Can I Fix This?")
op.uri = "https://docs.blenderbim.org/users/troubleshooting.html"
+112 -61
View File
@@ -1,183 +1,232 @@
def add_cost_schedule(ifc, name, predefined_type):
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 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/>.
from __future__ import annotations
from typing import TYPE_CHECKING, Optional, Union
if TYPE_CHECKING:
import bpy
import ifcopenshell
import blenderbim.tool as tool
def add_cost_schedule(ifc: tool.Ifc, name, predefined_type):
ifc.run("cost.add_cost_schedule", name=name, predefined_type=predefined_type)
def edit_cost_schedule(ifc, cost, cost_schedule):
def edit_cost_schedule(ifc: tool.Ifc, cost: tool.Cost, cost_schedule: ifcopenshell.entity_instance):
attributes = cost.get_cost_schedule_attributes()
ifc.run("cost.edit_cost_schedule", cost_schedule=cost_schedule, attributes=attributes)
cost.disable_editing_cost_schedule()
def disable_editing_cost_schedule(cost):
def disable_editing_cost_schedule(cost: tool.Cost):
cost.disable_editing_cost_schedule()
def remove_cost_schedule(ifc, cost_schedule):
def remove_cost_schedule(ifc: tool.Ifc, cost: tool.Cost, cost_schedule: ifcopenshell.entity_instance) -> None:
ifc.run("cost.remove_cost_schedule", cost_schedule=cost_schedule)
cost.remove_stored_schedule_columns(cost_schedule)
def enable_editing_cost_schedule_attributes(cost, cost_schedule):
def enable_editing_cost_schedule_attributes(cost: tool.Cost, cost_schedule: ifcopenshell.entity_instance):
cost.load_cost_schedule_attributes(cost_schedule)
cost.enable_editing_cost_schedule_attributes(cost_schedule)
def enable_editing_cost_items(cost, cost_schedule):
def enable_editing_cost_items(cost: tool.Cost, cost_schedule: ifcopenshell.entity_instance):
cost.enable_editing_cost_items(cost_schedule)
cost.load_active_schedule_columns()
cost.load_cost_schedule_tree()
cost.play_sound()
def add_summary_cost_item(ifc, cost, cost_schedule):
def add_summary_cost_item(ifc: tool.Ifc, cost: tool.Cost, cost_schedule: ifcopenshell.entity_instance):
ifc.run("cost.add_cost_item", cost_schedule=cost_schedule)
cost.load_cost_schedule_tree()
# cost.play_sound()
def add_cost_item(ifc, cost, cost_item):
def add_cost_item(ifc: tool.Ifc, cost: tool.Cost, cost_item: ifcopenshell.entity_instance):
ifc.run("cost.add_cost_item", cost_item=cost_item)
cost.load_cost_schedule_tree()
# cost.enable_editing_cost_schedule_attributes(cost_schedule)
def expand_cost_item(cost, cost_item):
def expand_cost_item(cost: tool.Cost, cost_item: ifcopenshell.entity_instance):
cost.expand_cost_item(cost_item)
cost.load_cost_schedule_tree()
def expand_cost_items(cost):
def expand_cost_items(cost: tool.Cost):
cost.expand_cost_items()
cost.load_cost_schedule_tree()
def contract_cost_item(cost, cost_item):
def contract_cost_item(cost: tool.Cost, cost_item: ifcopenshell.entity_instance):
cost.contract_cost_item(cost_item)
cost.load_cost_schedule_tree()
def contract_cost_items(cost):
def contract_cost_items(cost: tool.Cost):
cost.contract_cost_items()
cost.load_cost_schedule_tree()
def remove_cost_item(ifc, cost, cost_item_id):
def remove_cost_item(ifc: tool.Ifc, cost: tool.Cost, cost_item_id: int):
cost_item = ifc.get().by_id(cost_item_id)
ifc.run("cost.remove_cost_item", cost_item=cost_item)
cost.clean_up_cost_item_tree(cost_item_id)
cost.load_cost_schedule_tree()
def enable_editing_cost_item_attributes(cost, cost_item):
def enable_editing_cost_item_attributes(cost: tool.Cost, cost_item: ifcopenshell.entity_instance):
cost.enable_editing_cost_item_attributes(cost_item)
cost.load_cost_item_attributes(cost_item)
def disable_editing_cost_item(cost):
def disable_editing_cost_item(cost: tool.Cost):
cost.disable_editing_cost_item()
def edit_cost_item(ifc, cost):
def edit_cost_item(ifc: tool.Ifc, cost: tool.Cost):
attributes = cost.get_cost_item_attributes()
ifc.run("cost.edit_cost_item", cost_item=cost.get_active_cost_item(), attributes=attributes)
cost.disable_editing_cost_item()
cost.load_cost_schedule_tree()
def assign_cost_item_type(ifc, cost, spatial, cost_item, prop_name):
product_types = spatial.get_selected_product_types()
[
def assign_cost_item_type(
ifc: tool.Ifc, cost: tool.Cost, spatial: tool.Spatial, cost_item: ifcopenshell.entity_instance, prop_name
) -> list[ifcopenshell.entity_instance]:
"""
Returns:
List of found product types.
"""
product_types = list(spatial.get_selected_product_types())
rels = [
ifc.run("control.assign_control", relating_control=cost_item, related_object=product_type)
for product_type in product_types
]
cost.load_cost_item_types(cost_item)
return product_types
def unassign_cost_item_type(ifc, cost, spatial, cost_item, product_types):
def unassign_cost_item_type(
ifc: tool.Ifc,
cost: tool.Cost,
spatial: tool.Spatial,
cost_item: ifcopenshell.entity_instance,
product_types: Optional[list[ifcopenshell.entity_instance]] = None,
) -> list[ifcopenshell.entity_instance]:
"""
Returns:
List of found product types.
"""
if not product_types:
product_types = spatial.get_selected_product_types()
product_types = list(spatial.get_selected_product_types())
[
ifc.run("control.unassign_control", relating_control=cost_item, related_object=product_type)
for product_type in product_types
]
cost.load_cost_item_types(cost_item)
return product_types
def load_cost_item_types(cost):
def load_cost_item_types(cost: tool.Cost):
cost_item = cost.get_active_cost_item()
cost.load_cost_item_types(cost_item)
def assign_cost_item_quantity(ifc, cost, cost_item, related_object_type, prop_name):
def assign_cost_item_quantity(
ifc: tool.Ifc, cost: tool.Cost, cost_item: ifcopenshell.entity_instance, related_object_type, prop_name
):
products = cost.get_products(related_object_type)
if products:
ifc.run("cost.assign_cost_item_quantity", cost_item=cost_item, products=products, prop_name=prop_name)
cost.load_cost_item_quantity_assignments(cost_item, related_object_type=related_object_type)
def load_cost_item_quantities(cost):
def load_cost_item_quantities(cost: tool.Cost):
cost.load_cost_item_quantities()
def load_cost_item_element_quantities(cost):
def load_cost_item_element_quantities(cost: tool.Cost):
cost_item = cost.get_highlighted_cost_item()
cost.load_cost_item_quantity_assignments(cost_item, related_object_type="PRODUCT")
def load_cost_item_task_quantities(cost):
def load_cost_item_task_quantities(cost: tool.Cost):
cost_item = cost.get_highlighted_cost_item()
cost.load_cost_item_quantity_assignments(cost_item, related_object_type="PROCESS")
def load_cost_item_resource_quantities(cost):
def load_cost_item_resource_quantities(cost: tool.Cost):
cost_item = cost.get_highlighted_cost_item()
cost.load_cost_item_quantity_assignments(cost_item, related_object_type="RESOURCE")
def assign_cost_value(ifc, cost_item, cost_rate):
def assign_cost_value(ifc: tool.Ifc, cost_item: ifcopenshell.entity_instance, cost_rate):
ifc.run("cost.assign_cost_value", cost_item=cost_item, cost_rate=cost_rate)
def load_schedule_of_rates(cost, schedule_of_rates):
def load_schedule_of_rates(cost: tool.Cost, schedule_of_rates):
cost.load_schedule_of_rates_tree(schedule_of_rates)
def unassign_cost_item_quantity(ifc, cost, cost_item, products):
def unassign_cost_item_quantity(ifc: tool.Ifc, cost: tool.Cost, cost_item: ifcopenshell.entity_instance, products):
ifc.run("cost.unassign_cost_item_quantity", cost_item=cost_item, products=products)
cost.load_cost_item_quantities()
def enable_editing_cost_item_quantities(cost, cost_item):
def enable_editing_cost_item_quantities(cost: tool.Cost, cost_item: ifcopenshell.entity_instance):
cost.enable_editing_cost_item_quantities(cost_item)
def enable_editing_cost_item_values(cost, cost_item):
def enable_editing_cost_item_values(cost: tool.Cost, cost_item: ifcopenshell.entity_instance):
cost.enable_editing_cost_item_values(cost_item)
def add_cost_item_quantity(ifc, cost_item, ifc_class):
def add_cost_item_quantity(ifc: tool.Ifc, cost_item: ifcopenshell.entity_instance, ifc_class):
ifc.run("cost.add_cost_item_quantity", cost_item=cost_item, ifc_class=ifc_class)
def remove_cost_item_quantity(ifc, cost_item, physical_quantity):
def remove_cost_item_quantity(ifc: tool.Ifc, cost_item: ifcopenshell.entity_instance, physical_quantity):
ifc.run("cost.remove_cost_item_quantity", cost_item=cost_item, physical_quantity=physical_quantity)
def enable_editing_cost_item_quantity(cost, physical_quantity):
def enable_editing_cost_item_quantity(cost: tool.Cost, physical_quantity):
cost.load_cost_item_quantity_attributes(physical_quantity)
cost.enable_editing_cost_item_quantity(physical_quantity)
def disable_editing_cost_item_quantity(cost):
def disable_editing_cost_item_quantity(cost: tool.Cost):
cost.disable_editing_cost_item_quantity()
def edit_cost_item_quantity(ifc, cost, physical_quantity):
def edit_cost_item_quantity(ifc: tool.Ifc, cost: tool.Cost, physical_quantity):
attributes = cost.get_cost_item_quantity_attributes()
ifc.run("cost.edit_cost_item_quantity", physical_quantity=physical_quantity, attributes=attributes)
cost.disable_editing_cost_item_quantity()
cost.load_cost_item_quantities()
def add_cost_value(ifc, cost, parent, cost_type, cost_category):
def add_cost_value(ifc: tool.Ifc, cost: tool.Cost, parent, cost_type, cost_category):
value = ifc.run("cost.add_cost_value", parent=parent)
ifc.run(
"cost.edit_cost_value",
@@ -186,82 +235,84 @@ def add_cost_value(ifc, cost, parent, cost_type, cost_category):
)
def remove_cost_value(ifc, parent, cost_value):
def remove_cost_value(ifc: tool.Ifc, parent, cost_value):
ifc.run("cost.remove_cost_value", parent=parent, cost_value=cost_value)
def enable_editing_cost_item_value(cost, cost_value):
def enable_editing_cost_item_value(cost: tool.Cost, cost_value):
cost.load_cost_item_value_attributes(cost_value)
cost.enable_editing_cost_item_value(cost_value)
def disable_editing_cost_item_value(cost):
def disable_editing_cost_item_value(cost: tool.Cost):
cost.disable_editing_cost_item_value()
def enable_editing_cost_item_value_formula(cost, cost_value):
def enable_editing_cost_item_value_formula(cost: tool.Cost, cost_value):
cost.load_cost_item_value_formula_attributes(cost_value)
cost.enable_editing_cost_item_value_formula(cost_value)
def edit_cost_item_value_formula(ifc, cost, cost_value):
def edit_cost_item_value_formula(ifc: tool.Ifc, cost: tool.Cost, cost_value):
formula = cost.get_cost_item_value_formula()
ifc.run("cost.edit_cost_value_formula", cost_value=cost_value, formula=formula)
cost.disable_editing_cost_item_value()
def edit_cost_value(ifc, cost, cost_value):
def edit_cost_value(ifc: tool.Ifc, cost: tool.Cost, cost_value):
attributes = cost.get_cost_value_attributes()
ifc.run("cost.edit_cost_value", cost_value=cost_value, attributes=attributes)
cost.disable_editing_cost_item_value()
# cost.load_cost_item_values(cost.get_highlighted_cost_item())
def copy_cost_item_values(ifc, cost, source, destination):
def copy_cost_item_values(ifc: tool.Ifc, cost: tool.Cost, source, destination):
ifc.run("cost.copy_cost_item_values", source=source, destination=destination)
def select_cost_item_products(cost, spatial, cost_item):
def select_cost_item_products(cost: tool.Cost, spatial: tool.Spatial, cost_item: ifcopenshell.entity_instance):
is_deep = cost.show_nested_cost_item_elements()
products = cost.get_cost_item_products(cost_item, is_deep)
spatial.select_products(products)
def select_cost_schedule_products(cost, spatial, cost_schedule):
def select_cost_schedule_products(cost: tool.Cost, spatial: tool.Spatial, cost_schedule: ifcopenshell.entity_instance):
products = cost.get_cost_schedule_products(cost_schedule)
spatial.select_products(products)
def import_cost_schedule_csv(cost, file_path, is_schedule_of_rates):
def import_cost_schedule_csv(cost: tool.Cost, file_path, is_schedule_of_rates):
cost.import_cost_schedule_csv(file_path, is_schedule_of_rates)
def add_cost_column(cost, name):
def add_cost_column(cost: tool.Cost, name):
cost.add_cost_column(name)
def remove_cost_column(cost, name):
def remove_cost_column(cost: tool.Cost, name):
cost.remove_cost_column(name)
def expand_cost_item_rate(cost, cost_item):
def expand_cost_item_rate(cost: tool.Cost, cost_item: ifcopenshell.entity_instance):
cost.expand_cost_item_rate(cost_item)
def contract_cost_item_rate(cost, cost_item):
def contract_cost_item_rate(cost: tool.Cost, cost_item: ifcopenshell.entity_instance):
cost.contract_cost_item_rate(cost_item)
def calculate_cost_item_resource_value(ifc, cost_item):
def calculate_cost_item_resource_value(ifc: tool.Ifc, cost_item: ifcopenshell.entity_instance):
ifc.run("cost.calculate_cost_item_resource_value", cost_item=cost_item)
def export_cost_schedules(cost, filepath, format, cost_schedule=None):
def export_cost_schedules(cost: tool.Cost, filepath: str, format: str, cost_schedule=None):
cost.play_sound()
return cost.export_cost_schedules(filepath, format, cost_schedule)
def clear_cost_item_assignments(ifc, cost, cost_item, related_object_type):
def clear_cost_item_assignments(
ifc: tool.Ifc, cost: tool.Cost, cost_item: ifcopenshell.entity_instance, related_object_type
):
products = cost.get_cost_item_assignments(cost_item, filter_by_type=related_object_type)
if products:
ifc.run("cost.unassign_cost_item_quantity", cost_item=cost_item, products=products)
@@ -269,7 +320,7 @@ def clear_cost_item_assignments(ifc, cost, cost_item, related_object_type):
cost.load_cost_schedule_tree()
def select_unassigned_products(ifc, cost, spatial):
def select_unassigned_products(ifc: tool.Ifc, cost: tool.Cost, spatial: tool.Spatial):
spatial.deselect_objects()
products = ifc.get().by_type("IfcElement")
cost_schedule = cost.get_active_cost_schedule()
@@ -277,11 +328,11 @@ def select_unassigned_products(ifc, cost, spatial):
spatial.select_products(selection)
def load_product_cost_items(cost, product):
def load_product_cost_items(cost: tool.Cost, product):
cost.load_product_cost_items(product)
def highlight_product_cost_item(spatial, cost, cost_item):
def highlight_product_cost_item(spatial: tool.Spatial, cost: tool.Cost, cost_item: ifcopenshell.entity_instance):
cost_schedule = cost.get_cost_schedule(cost_item)
is_cost_schedule_active = cost.is_cost_schedule_active(cost_schedule)
if is_cost_schedule_active:
@@ -290,7 +341,7 @@ def highlight_product_cost_item(spatial, cost, cost_item):
return "Cost schedule is not active"
def change_parent_cost_item(ifc, cost, new_parent):
def change_parent_cost_item(ifc: tool.Ifc, cost: tool.Cost, new_parent):
cost_item = cost.get_active_cost_item()
if cost_item and cost.is_root_cost_item(cost_item):
return "Cannot change root cost item"
@@ -300,7 +351,7 @@ def change_parent_cost_item(ifc, cost, new_parent):
cost.load_cost_schedule_tree()
def copy_cost_item(ifc, cost):
def copy_cost_item(ifc: tool.Ifc, cost: tool.Cost):
cost_item = cost.get_highlighted_cost_item()
if cost_item:
cost_item = ifc.run("cost.copy_cost_item", cost_item=cost_item)
@@ -308,7 +359,7 @@ def copy_cost_item(ifc, cost):
cost.load_cost_schedule_tree()
def add_currency(ifc, cost):
def add_currency(ifc: tool.Ifc, cost: tool.Cost):
unit = ifc.run("unit.add_monetary_unit")
attributes = cost.get_currency_attributes()
ifc.run("unit.edit_monetary_unit", unit=unit, attributes=attributes)
+71 -35
View File
@@ -16,36 +16,44 @@
# 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 __future__ import annotations
from pathlib import Path
import ifcopenshell
from typing import TYPE_CHECKING, Optional
if TYPE_CHECKING:
import bpy
import ifcopenshell
import blenderbim.tool as tool
def enable_editing_text(drawing, obj=None):
def enable_editing_text(drawing: tool.Drawing, obj: bpy.types.Object) -> None:
drawing.enable_editing_text(obj)
drawing.import_text_attributes(obj)
def disable_editing_text(drawing, obj=None):
def disable_editing_text(drawing: tool.Drawing, obj: bpy.types.Object) -> None:
drawing.disable_editing_text(obj)
def edit_text(drawing, obj=None):
def edit_text(drawing: tool.Drawing, obj: bpy.types.Object) -> None:
drawing.synchronise_ifc_and_text_attributes(obj)
drawing.update_text_size_pset(obj)
drawing.update_text_value(obj)
drawing.disable_editing_text(obj)
def enable_editing_assigned_product(drawing, obj=None):
def enable_editing_assigned_product(drawing: tool.Drawing, obj: bpy.types.Object) -> None:
drawing.enable_editing_assigned_product(obj)
drawing.import_assigned_product(obj)
def disable_editing_assigned_product(drawing, obj=None):
def disable_editing_assigned_product(drawing: tool.Drawing, obj: bpy.types.Object) -> None:
drawing.disable_editing_assigned_product(obj)
def edit_assigned_product(ifc, drawing, obj=None, product=None):
def edit_assigned_product(
ifc: tool.Ifc, drawing: tool.Drawing, obj: bpy.types.Object, product: Optional[ifcopenshell.entity_instance] = None
) -> None:
element = ifc.get_entity(obj)
existing_product = drawing.get_assigned_product(element)
if existing_product != product:
@@ -58,16 +66,16 @@ def edit_assigned_product(ifc, drawing, obj=None, product=None):
drawing.disable_editing_assigned_product(obj)
def load_sheets(drawing):
def load_sheets(drawing: tool.Drawing) -> None:
drawing.import_sheets()
drawing.enable_editing_sheets()
def disable_editing_sheets(drawing):
def disable_editing_sheets(drawing: tool.Drawing) -> None:
drawing.disable_editing_sheets()
def add_sheet(ifc, drawing, titleblock: ifcopenshell.entity_instance):
def add_sheet(ifc: tool.Ifc, drawing, titleblock: ifcopenshell.entity_instance) -> None:
sheet = ifc.run("document.add_information")
layout = ifc.run("document.add_reference", information=sheet)
titleblock_reference = ifc.run("document.add_reference", information=sheet)
@@ -93,7 +101,7 @@ def add_sheet(ifc, drawing, titleblock: ifcopenshell.entity_instance):
drawing.import_sheets()
def regenerate_sheet(drawing, sheet=None):
def regenerate_sheet(drawing: tool.Drawing, sheet: ifcopenshell.entity_instance) -> None:
titleblock_uri = drawing.get_document_uri(sheet, "TITLEBLOCK")
drawing.create_svg_sheet(sheet, drawing.sanitise_filename(Path(titleblock_uri).stem))
try:
@@ -104,11 +112,11 @@ def regenerate_sheet(drawing, sheet=None):
drawing.delete_file(path_layout)
def open_sheet(drawing, sheet=None):
def open_sheet(drawing: tool.Drawing, sheet: ifcopenshell.entity_instance) -> None:
drawing.open_layout_svg(drawing.get_document_uri(sheet, "LAYOUT"))
def remove_sheet(ifc, drawing, sheet=None):
def remove_sheet(ifc: tool.Ifc, drawing: tool.Drawing, sheet: ifcopenshell.entity_instance) -> None:
for reference in drawing.get_document_references(sheet):
if drawing.get_reference_description(reference) in ("LAYOUT", "SHEET", "REVISION", "RASTER"):
uri = ifc.resolve_uri(drawing.get_document_uri(reference))
@@ -118,7 +126,7 @@ def remove_sheet(ifc, drawing, sheet=None):
drawing.import_sheets()
def rename_sheet(ifc, drawing, sheet: ifcopenshell.entity_instance, identification: str, name: str) -> None:
def rename_sheet(ifc: tool.Ifc, drawing, sheet: ifcopenshell.entity_instance, identification: str, name: str) -> None:
if ifc.get_schema() == "IFC2X3":
attributes = {"DocumentId": identification, "Name": name}
else:
@@ -144,30 +152,32 @@ def rename_sheet(ifc, drawing, sheet: ifcopenshell.entity_instance, identificati
drawing.move_file(old_location, ifc.resolve_uri(new_location))
def rename_reference(ifc, drawing, reference=None, identification=None):
def rename_reference(
ifc: tool.Ifc, drawing: tool.Drawing, reference: ifcopenshell.entity_instance, identification: str
) -> None:
attributes = drawing.generate_reference_attributes(reference, Identification=identification)
ifc.run("document.edit_reference", reference=reference, attributes=attributes)
def load_schedules(drawing):
def load_schedules(drawing: tool.Drawing) -> None:
drawing.import_documents("SCHEDULE")
drawing.enable_editing_schedules()
def load_references(drawing):
def load_references(drawing: tool.Drawing) -> None:
drawing.import_documents("REFERENCE")
drawing.enable_editing_references()
def disable_editing_schedules(drawing):
def disable_editing_schedules(drawing: tool.Drawing) -> None:
drawing.disable_editing_schedules()
def disable_editing_references(drawing):
def disable_editing_references(drawing: tool.Drawing) -> None:
drawing.disable_editing_references()
def add_document(ifc, drawing, document_type, uri=None):
def add_document(ifc: tool.Ifc, drawing: tool.Drawing, document_type: tool.Drawing.DOCUMENT_TYPE, uri: str) -> None:
document = ifc.run("document.add_information")
reference = ifc.run("document.add_reference", information=document)
name = drawing.get_path_filename(uri)
@@ -180,34 +190,43 @@ def add_document(ifc, drawing, document_type, uri=None):
drawing.import_documents(document_type)
def remove_document(ifc, drawing, document_type, document=None):
def remove_document(
ifc: tool.Ifc,
drawing: tool.Drawing,
document_type: tool.Drawing.DOCUMENT_TYPE,
document: ifcopenshell.entity_instance,
) -> None:
ifc.run("document.remove_information", information=document)
drawing.import_documents(document_type)
def open_schedule(drawing, schedule=None):
def open_schedule(drawing: tool.Drawing, schedule: ifcopenshell.entity_instance) -> None:
drawing.open_spreadsheet(drawing.get_document_uri(schedule))
def open_reference(drawing, reference=None):
def open_reference(drawing: tool.Drawing, reference: ifcopenshell.entity_instance) -> None:
drawing.open_svg(drawing.get_document_uri(reference))
def update_document_name(ifc, drawing, document=None, name=None):
def update_document_name(
ifc: tool.Ifc, drawing: tool.Drawing, document: ifcopenshell.entity_instance, name=None
) -> None:
if drawing.get_name(document) != name:
ifc.run("document.edit_information", information=document, attributes={"Name": name})
def load_drawings(drawing):
def load_drawings(drawing: tool.Drawing) -> None:
drawing.import_drawings()
drawing.enable_editing_drawings()
def disable_editing_drawings(drawing):
def disable_editing_drawings(drawing: tool.Drawing) -> None:
drawing.disable_editing_drawings()
def add_drawing(ifc, collector, drawing, target_view=None, location_hint=None):
def add_drawing(
ifc: tool.Ifc, collector: tool.Collector, drawing: tool.Drawing, target_view=None, location_hint=None
) -> None:
drawing_name = drawing.ensure_unique_drawing_name(drawing.generate_drawing_name(target_view, location_hint))
drawing_matrix = drawing.generate_drawing_matrix(target_view, location_hint)
camera = drawing.create_camera(drawing_name, drawing_matrix, location_hint)
@@ -265,7 +284,12 @@ def add_drawing(ifc, collector, drawing, target_view=None, location_hint=None):
drawing.import_drawings()
def duplicate_drawing(ifc, drawing_tool, drawing=None, should_duplicate_annotations=False):
def duplicate_drawing(
ifc: tool.Ifc,
drawing_tool: tool.Drawing,
drawing: ifcopenshell.entity_instance,
should_duplicate_annotations: bool = False,
) -> ifcopenshell.entity_instance:
drawing_name = drawing_tool.ensure_unique_drawing_name(drawing_tool.get_name(drawing))
new_drawing = ifc.run("root.copy_class", product=drawing)
drawing_tool.copy_representation(drawing, new_drawing)
@@ -302,7 +326,7 @@ def duplicate_drawing(ifc, drawing_tool, drawing=None, should_duplicate_annotati
return new_drawing
def remove_drawing(ifc, drawing_tool, drawing=None):
def remove_drawing(ifc: tool.Ifc, drawing_tool: tool.Drawing, drawing: ifcopenshell.entity_instance) -> None:
if drawing_tool.is_active_drawing(drawing):
drawing_tool.run_drawing_activate_model()
@@ -330,7 +354,9 @@ def remove_drawing(ifc, drawing_tool, drawing=None):
drawing_tool.import_drawings()
def update_drawing_name(ifc, drawing_tool, drawing=None, name=None):
def update_drawing_name(
ifc: tool.Ifc, drawing_tool: tool.Drawing, drawing: ifcopenshell.entity_instance, name=None
) -> None:
if drawing_tool.get_name(drawing) != name:
ifc.run("attribute.edit_attributes", product=drawing, attributes={"Name": name})
group = drawing_tool.get_drawing_group(drawing)
@@ -364,7 +390,13 @@ def update_drawing_name(ifc, drawing_tool, drawing=None, name=None):
drawing_tool.import_sheets()
def add_annotation(ifc, collector, drawing_tool, drawing=None, object_type=None):
def add_annotation(
ifc: tool.Ifc,
collector: tool.Collector,
drawing_tool: tool.Drawing,
drawing: ifcopenshell.entity_instance,
object_type: str,
) -> None:
target_view = drawing_tool.get_drawing_target_view(drawing)
context = drawing_tool.get_annotation_context(target_view, object_type)
if not context:
@@ -387,12 +419,14 @@ def add_annotation(ifc, collector, drawing_tool, drawing=None, object_type=None)
drawing_tool.enable_editing(obj)
def build_schedule(drawing, schedule=None):
def build_schedule(drawing: tool.Drawing, schedule: ifcopenshell.entity_instance) -> None:
drawing.create_svg_schedule(schedule)
drawing.open_svg(drawing.get_path_with_ext(drawing.get_document_uri(schedule), "svg"))
def sync_references(ifc, collector, drawing_tool, drawing=None):
def sync_references(
ifc: tool.Ifc, collector: tool.Collector, drawing_tool: tool.Drawing, drawing: ifcopenshell.entity_instance
) -> None:
if not drawing_tool.has_annotation(drawing):
return
@@ -437,11 +471,13 @@ def sync_references(ifc, collector, drawing_tool, drawing=None):
drawing_tool.sync_object_representation(reference_obj)
def select_assigned_product(drawing, context):
def select_assigned_product(drawing: tool.Drawing, context: bpy.types.Context) -> None:
drawing.select_assigned_product(context)
def activate_drawing_view(ifc, blender, drawing_tool, drawing):
def activate_drawing_view(
ifc: tool.Ifc, blender: tool.Blender, drawing_tool: tool.Drawing, drawing: ifcopenshell.entity_instance
) -> None:
camera = ifc.get_object(drawing)
if not camera:
camera = drawing_tool.import_drawing(drawing)
+21 -13
View File
@@ -18,10 +18,11 @@
from __future__ import annotations
from typing import TYPE_CHECKING, Optional
import blenderbim.core.tool as tool
if TYPE_CHECKING:
import bpy
import ifcopenshell
import blenderbim.tool as tool
def edit_object_placement(
@@ -37,8 +38,15 @@ def edit_object_placement(
def add_representation(
ifc, geometry, style, surveyor, obj=None, context=None, ifc_representation_class=None, profile_set_usage=None
):
ifc: tool.Ifc,
geometry: tool.Geometry,
style: tool.Style,
surveyor: tool.Surveyor,
obj: bpy.types.Object,
context: ifcopenshell.entity_instance,
ifc_representation_class: Optional[str] = None,
profile_set_usage: Optional[ifcopenshell.entity_instance] = None,
) -> ifcopenshell.entity_instance:
element = ifc.get_entity(obj)
if not element:
return
@@ -47,7 +55,7 @@ def add_representation(
data = geometry.get_object_data(obj)
if not data and ifc_representation_class != "IfcTextLiteral":
raise IncompatibleRepresentationError()
return
representation = ifc.run(
"geometry.add_representation",
@@ -89,15 +97,15 @@ def add_representation(
def switch_representation(
ifc,
geometry,
obj=None,
representation=None,
should_reload=True,
is_global=True,
should_sync_changes_first=False,
apply_openings=True,
):
ifc: tool.Ifc,
geometry: tool.Geometry,
obj: bpy.types.Object,
representation: ifcopenshell.entity_instance,
should_reload: bool = True,
is_global: bool = True,
should_sync_changes_first: bool = False,
apply_openings: bool = True,
) -> None:
"""Function can switch to representation that wasn't yet assigned to that object. See #2766.
`should_sync_changes_first` - sync ifc representation with current state of `obj.data`;
@@ -85,6 +85,7 @@ def enable_editing_material(material_tool, material):
def edit_material(ifc, material_tool, material):
attributes = material_tool.get_material_attributes()
ifc.run("material.edit_material", material=material, attributes=attributes)
material_tool.sync_blender_material_name(material)
material_tool.disable_editing_material()
material_type = material_tool.get_active_material_type()
material_tool.import_material_definitions(material_type)
+10 -2
View File
@@ -16,8 +16,16 @@
# 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 __future__ import annotations
from typing import TYPE_CHECKING, Optional
def create_project(ifc, project, schema=None, template=None):
if TYPE_CHECKING:
import bpy
import ifcopenshell
import blenderbim.tool as tool
def create_project(ifc: tool.Ifc, project: tool.Project, schema: str, template: Optional[str] = None) -> None:
if ifc.get():
return
@@ -34,7 +42,7 @@ def create_project(ifc, project, schema=None, template=None):
building = project.create_empty("My Building")
storey = project.create_empty("My Storey")
project.run_root_assign_class(obj=project_obj, ifc_class="IfcProject")
project.run_root_assign_class(obj=project_obj, ifc_class="IfcProject", should_add_representation=False)
project.run_unit_assign_scene_units()
model = project.run_context_add_context(context_type="Model", context_identifier="", target_view="", parent=0)
+26 -2
View File
@@ -31,7 +31,7 @@ def copy_property_to_selection(ifc, pset, is_pset=True, obj=None, pset_name=None
def add_pset(ifc, pset, blender, obj_name, obj_type):
pset_name = pset.get_pset_name(obj_name, obj_type)
pset_name = pset.get_pset_name(obj_name, obj_type, pset_type="PSET")
if obj_type == "Object":
elements = [ifc.get_entity(obj) for obj in blender.get_selected_objects()]
else:
@@ -39,6 +39,30 @@ def add_pset(ifc, pset, blender, obj_name, obj_type):
for element in elements:
if not element:
continue
if not pset.is_pset_applicable(element, pset_name):
if pset_name and not pset.is_pset_applicable(element, pset_name):
continue
pset.enable_pset_editing(pset_id=0, pset_name=pset_name, pset_type="PSET", obj=obj_name, obj_type=obj_type)
def enable_pset_editing(pset_tool, pset, pset_name, pset_type, obj_name, obj_type):
props = pset_tool.get_pset_props(obj_name, obj_type)
pset_tool.clear_blender_pset_properties(props)
pset_template = pset_tool.get_pset_template(pset_name)
if pset_template:
pset_tool.import_pset_from_template(pset_template, pset, props)
has_template = True
else:
has_template = False
if pset:
pset_tool.import_pset_from_existing(pset, props)
pset_tool.set_active_pset(props, pset, has_template)
else:
pset_tool.enable_proposed_pset(props, pset_name, pset_type, has_template)
def add_proposed_prop(pset, obj_name, obj_type, name, value):
props = pset.get_pset_props(obj_name, obj_type)
pset.add_proposed_property(name, pset.cast_string_to_primitive(value), props)
+78 -33
View File
@@ -18,42 +18,57 @@
# ############################################################################ #
from __future__ import annotations
from typing import TYPE_CHECKING, Optional, Union, Iterable
def load_resources(resource):
if TYPE_CHECKING:
import bpy
import ifcopenshell
import blenderbim.tool as tool
def load_resources(resource: tool.Resource) -> None:
resource.load_resources()
def add_resource(tool_ifc, resource_tool, ifc_class, parent_resource=None):
def add_resource(
tool_ifc: tool.Ifc,
resource_tool: tool.Resource,
ifc_class,
parent_resource: Optional[ifcopenshell.entity_instance] = None,
) -> None:
tool_ifc.run("resource.add_resource", ifc_class=ifc_class, parent_resource=parent_resource)
resource_tool.load_resources()
def disable_editing_resource(resource_tool):
def disable_editing_resource(resource_tool: tool.Resource) -> None:
resource_tool.disable_editing_resource()
def disable_resource_editing_ui(resource_tool):
def disable_resource_editing_ui(resource_tool: tool.Resource) -> None:
resource_tool.disable_resource_editing_ui()
def enable_editing_resource(resource_tool, resource):
def enable_editing_resource(resource_tool: tool.Resource, resource) -> None:
resource_tool.enable_editing_resource(resource)
resource_tool.load_resource_attributes(resource)
def edit_resource(ifc, resource_tool, resource):
def edit_resource(ifc: tool.Ifc, resource_tool: tool.Resource, resource: ifcopenshell.entity_instance) -> None:
attributes = resource_tool.get_resource_attributes()
ifc.run("resource.edit_resource", resource=resource, attributes=attributes)
resource_tool.load_resource_properties()
resource_tool.disable_editing_resource()
def remove_resource(ifc, resource_tool, resource=None):
def remove_resource(ifc: tool.Ifc, resource_tool: tool.Resource, resource: ifcopenshell.entity_instance) -> None:
ifc.run("resource.remove_resource", resource=resource)
resource_tool.load_resources()
def enable_editing_resource_time(ifc_tool, resource_tool, resource):
def enable_editing_resource_time(
ifc_tool, resource_tool: tool.Resource, resource: ifcopenshell.entity_instance
) -> None:
resource_time = resource_tool.get_resource_time(resource)
if resource_time is None:
resource_time = ifc_tool.run("resource.add_resource_time", resource=resource)
@@ -61,17 +76,21 @@ def enable_editing_resource_time(ifc_tool, resource_tool, resource):
resource_tool.load_resource_time_attributes(resource_time)
def edit_resource_time(ifc, resource_tool, resource_time):
def edit_resource_time(
ifc: tool.Ifc, resource_tool: tool.Resource, resource_time: ifcopenshell.entity_instance
) -> None:
attributes = resource_tool.get_resource_time_attributes()
ifc.run("resource.edit_resource_time", resource_time=resource_time, attributes=attributes)
resource_tool.disable_editing_resource()
def disable_editing_resource_time(resource_tool):
def disable_editing_resource_time(resource_tool: tool.Resource) -> None:
resource_tool.disable_editing_resource()
def calculate_resource_work(ifc, resource_tool, resource):
def calculate_resource_work(
ifc: tool.Ifc, resource_tool: tool.Resource, resource: ifcopenshell.entity_instance
) -> None:
if resource_tool.get_task_assignments(resource):
ifc.run("resource.calculate_resource_work", resource=resource)
else:
@@ -81,92 +100,112 @@ def calculate_resource_work(ifc, resource_tool, resource):
resource_tool.load_resources()
def enable_editing_resource_costs(resource_tool, resource):
def enable_editing_resource_costs(resource_tool: tool.Resource, resource: ifcopenshell.entity_instance) -> None:
resource_tool.enable_editing_resource_costs(resource)
resource_tool.disable_editing_resource_cost_value()
def disable_editing_resource_cost_value(resource_tool):
def disable_editing_resource_cost_value(resource_tool: tool.Resource) -> None:
resource_tool.disable_editing_resource_cost_value()
def enable_editing_resource_cost_value(resource_tool, cost_value):
def enable_editing_resource_cost_value(resource_tool: tool.Resource, cost_value: ifcopenshell.entity_instance) -> None:
resource_tool.enable_editing_cost_value_attributes(cost_value)
resource_tool.load_cost_value_attributes(cost_value)
def enable_editing_resource_cost_value_formula(resource_tool, cost_value):
def enable_editing_resource_cost_value_formula(
resource_tool: tool.Resource, cost_value: ifcopenshell.entity_instance
) -> None:
resource_tool.enable_editing_resource_cost_value_formula(cost_value)
def edit_resource_cost_value_formula(ifc, resource_tool, cost_value):
def edit_resource_cost_value_formula(
ifc: tool.Ifc, resource_tool: tool.Resource, cost_value: ifcopenshell.entity_instance
) -> None:
formula = resource_tool.get_resource_cost_value_formula()
ifc.run("cost.edit_cost_value_formula", cost_value=cost_value, formula=formula)
resource_tool.disable_editing_resource_cost_value()
def edit_resource_cost_value(ifc, resource_tool, cost_value):
def edit_resource_cost_value(
ifc: tool.Ifc, resource_tool: tool.Resource, cost_value: ifcopenshell.entity_instance
) -> None:
attributes = resource_tool.get_resource_cost_value_attributes()
ifc.run("cost.edit_cost_value", cost_value=cost_value, attributes=attributes)
resource_tool.disable_editing_resource_cost_value()
def enable_editing_resource_base_quantity(resource_tool, resource):
def enable_editing_resource_base_quantity(resource_tool: tool.Resource, resource: ifcopenshell.entity_instance) -> None:
resource_tool.enable_editing_resource_base_quantity(resource)
def add_resource_quantity(ifc, ifc_class, resource):
def add_resource_quantity(ifc: tool.Ifc, ifc_class: str, resource: ifcopenshell.entity_instance) -> None:
ifc.run("resource.add_resource_quantity", resource=resource, ifc_class=ifc_class)
def remove_resource_quantity(ifc, resource):
def remove_resource_quantity(ifc: tool.Ifc, resource: ifcopenshell.entity_instance) -> None:
ifc.run("resource.remove_resource_quantity", resource=resource)
def enable_editing_resource_quantity(resource_tool, resource_quantity=None):
def enable_editing_resource_quantity(
resource_tool: tool.Resource, resource_quantity: ifcopenshell.entity_instance
) -> None:
resource_tool.enable_editing_resource_quantity(resource_quantity)
def disable_editing_resource_quantity(resource_tool):
def disable_editing_resource_quantity(resource_tool: tool.Resource) -> None:
resource_tool.disable_editing_resource_quantity()
def edit_resource_quantity(resource_tool, ifc, physical_quantity=None):
def edit_resource_quantity(
resource_tool: tool.Resource, ifc: tool.Ifc, physical_quantity: ifcopenshell.entity_instance
) -> None:
attributes = resource_tool.get_resource_quantity_attributes()
ifc.run("resource.edit_resource_quantity", physical_quantity=physical_quantity, attributes=attributes)
resource_tool.disable_editing_resource_quantity()
def import_resources(resource_tool, file_path):
def import_resources(resource_tool: tool.Resource, file_path: str) -> None:
resource_tool.import_resources(file_path)
resource_tool.load_resources()
def expand_resource(resource_tool, resource):
def expand_resource(resource_tool: tool.Resource, resource: ifcopenshell.entity_instance) -> None:
resource_tool.expand_resource(resource)
resource_tool.load_resources()
def contract_resource(resource_tool, resource):
def contract_resource(resource_tool: tool.Resource, resource: ifcopenshell.entity_instance) -> None:
resource_tool.contract_resource(resource)
resource_tool.load_resources()
def assign_resource(ifc, spatial, resource=None, products=None):
def assign_resource(
ifc: tool.Ifc,
spatial: tool.Spatial,
resource: ifcopenshell.entity_instance,
products: Optional[Iterable[ifcopenshell.entity_instance]] = None,
) -> None:
if not products:
products = spatial.get_selected_products()
for product in products:
rel = ifc.run("resource.assign_resource", relating_resource=resource, related_object=product)
def unassign_resource(ifc, spatial, resource=None, products=None):
def unassign_resource(
ifc: tool.Ifc,
spatial: tool.Spatial,
resource: ifcopenshell.entity_instance,
products: Optional[Iterable[ifcopenshell.entity_instance]] = None,
) -> None:
if not products:
products = spatial.get_selected_products()
for product in products:
ifc.run("resource.unassign_resource", relating_resource=resource, related_object=product)
def edit_productivity_pset(ifc, resource_tool):
def edit_productivity_pset(ifc: tool.Ifc, resource_tool: tool.Resource) -> None:
resource = resource_tool.get_highlighted_resource()
if resource is None:
return
@@ -178,7 +217,9 @@ def edit_productivity_pset(ifc, resource_tool):
ifc.run("pset.edit_pset", pset=pset, properties=resource_tool.get_productivity_attributes())
def add_usage_constraint(ifc, resource_tool, resource=None, reference_path=None):
def add_usage_constraint(
ifc: tool.Ifc, resource_tool: tool.Resource, resource: ifcopenshell.entity_instance, reference_path: str
) -> None:
metric = resource_tool.has_metric_constraint(resource, "Usage")
if metric:
return print("Must remove existing metric first")
@@ -198,7 +239,9 @@ def add_usage_constraint(ifc, resource_tool, resource=None, reference_path=None)
ifc.run("constraint.assign_constraint", products=[resource], constraint=objective)
def remove_usage_constraint(ifc, resource_tool, resource, reference_path):
def remove_usage_constraint(
ifc: tool.Ifc, resource_tool: tool.Resource, resource: ifcopenshell.entity_instance, reference_path: str
) -> None:
constraints = resource_tool.get_constraints(resource)
for constraint in constraints:
metrics = resource_tool.get_metrics(constraint)
@@ -210,10 +253,12 @@ def remove_usage_constraint(ifc, resource_tool, resource, reference_path):
ifc.run("constraint.remove_constraint", constraint=constraint)
def go_to_resource(resource_tool, resource):
def go_to_resource(resource_tool: tool.Resource, resource: ifcopenshell.entity_instance) -> None:
resource_tool.go_to_resource(resource)
def calculate_resource_usage(ifc, resource_tool, resource):
def calculate_resource_usage(
ifc: tool.Resource, resource_tool: tool.Resource, resource: ifcopenshell.entity_instance
) -> None:
ifc.run("resource.calculate_resource_usage", resource=resource)
resource_tool.load_resources()
+26 -11
View File
@@ -16,8 +16,18 @@
# 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 __future__ import annotations
from typing import TYPE_CHECKING, Optional
def copy_class(ifc, collector, geometry, root, obj=None):
if TYPE_CHECKING:
import bpy
import ifcopenshell
import blenderbim.tool as tool
def copy_class(
ifc: tool.Ifc, collector: tool.Collector, geometry: tool.Geometry, root: tool.Root, obj: bpy.types.Object
) -> ifcopenshell.entity_instance:
element = ifc.get_entity(obj)
if not element:
return
@@ -48,16 +58,20 @@ def copy_class(ifc, collector, geometry, root, obj=None):
def assign_class(
ifc,
collector,
root,
obj=None,
ifc_class=None,
predefined_type=None,
should_add_representation=True,
context=None,
ifc_representation_class=None,
):
ifc: tool.Ifc,
collector: tool.Collector,
root: tool.Root,
obj: bpy.types.Object,
ifc_class: str,
context: Optional[ifcopenshell.entity_instance] = None,
predefined_type: Optional[str] = None,
should_add_representation: bool = True,
ifc_representation_class: Optional[str] = None,
) -> ifcopenshell.entity_instance:
"""
Args:
context: is not optional if `should_add_representation` is True
"""
if ifc.get_entity(obj):
return
@@ -67,6 +81,7 @@ def assign_class(
ifc.link(element, obj)
if should_add_representation:
assert context, "Context is required for adding a representation"
root.run_geometry_add_representation(
obj=obj, context=context, ifc_representation_class=ifc_representation_class, profile_set_usage=None
)
+160 -96
View File
@@ -16,103 +16,115 @@
# 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 __future__ import annotations
from typing import TYPE_CHECKING, Optional, Union
def add_work_plan(ifc):
if TYPE_CHECKING:
import bpy
import ifcopenshell
import blenderbim.tool as tool
def add_work_plan(ifc: tool.Ifc) -> ifcopenshell.entity_instance:
return ifc.run("sequence.add_work_plan")
def remove_work_plan(ifc, work_plan=None):
def remove_work_plan(ifc: tool.Ifc, work_plan: ifcopenshell.entity_instance) -> None:
ifc.run("sequence.remove_work_plan", work_plan=work_plan)
def enable_editing_work_plan(sequence, work_plan=None):
def enable_editing_work_plan(sequence: tool.Sequence, work_plan: ifcopenshell.entity_instance) -> None:
sequence.load_work_plan_attributes(work_plan)
sequence.enable_editing_work_plan(work_plan)
def disable_editing_work_plan(sequence):
def disable_editing_work_plan(sequence: tool.Sequence) -> None:
sequence.disable_editing_work_plan()
def edit_work_plan(ifc, sequence, work_plan=None):
def edit_work_plan(ifc: tool.Ifc, sequence: tool.Sequence, work_plan: ifcopenshell.entity_instance) -> None:
attributes = sequence.get_work_plan_attributes()
ifc.run("sequence.edit_work_plan", work_plan=work_plan, attributes=attributes)
sequence.disable_editing_work_plan()
def edit_work_schedule(ifc, sequence, work_schedule=None):
def edit_work_schedule(ifc: tool.Ifc, sequence: tool.Sequence, work_schedule: ifcopenshell.entity_instance) -> None:
attributes = sequence.get_work_schedule_attributes()
ifc.run("sequence.edit_work_schedule", work_schedule=work_schedule, attributes=attributes)
sequence.disable_editing_work_schedule()
def enable_editing_work_plan_schedules(sequence, work_plan=None):
def enable_editing_work_plan_schedules(
sequence: tool.Sequence, work_plan: Optional[ifcopenshell.entity_instance] = None
) -> None:
sequence.enable_editing_work_plan_schedules(work_plan)
def add_work_schedule(ifc, sequence, name=None):
def add_work_schedule(ifc: tool.Ifc, sequence: tool.Sequence, name: str) -> ifcopenshell.entity_instance:
predefined_type, object_type = sequence.get_user_predefined_type()
return ifc.run("sequence.add_work_schedule", name=name, predefined_type=predefined_type, object_type=object_type)
def remove_work_schedule(ifc, work_schedule=None):
def remove_work_schedule(ifc: tool.Ifc, work_schedule: ifcopenshell.entity_instance) -> None:
ifc.run("sequence.remove_work_schedule", work_schedule=work_schedule)
def assign_work_schedule(ifc, work_plan=None, work_schedule=None):
def assign_work_schedule(
ifc: tool.Ifc, work_plan: ifcopenshell.entity_instance, work_schedule: ifcopenshell.entity_instance
) -> Union[ifcopenshell.entity_instance, None]:
if work_schedule:
return ifc.run("aggregate.assign_object", relating_object=work_plan, products=[work_schedule])
def unassign_work_schedule(ifc, work_schedule=None):
def unassign_work_schedule(ifc: tool.Ifc, work_schedule: ifcopenshell.entity_instance) -> None:
ifc.run("aggregate.unassign_object", products=[work_schedule])
def enable_editing_work_schedule(sequence, work_schedule=None):
def enable_editing_work_schedule(sequence: tool.Sequence, work_schedule: ifcopenshell.entity_instance) -> None:
sequence.load_work_schedule_attributes(work_schedule)
sequence.enable_editing_work_schedule(work_schedule)
def enable_editing_work_schedule_tasks(sequence, work_schedule=None):
def enable_editing_work_schedule_tasks(sequence: tool.Sequence, work_schedule: ifcopenshell.entity_instance) -> None:
sequence.enable_editing_work_schedule_tasks(work_schedule)
sequence.load_task_tree(work_schedule)
sequence.load_task_properties()
def load_task_tree(sequence, work_schedule):
def load_task_tree(sequence: tool.Sequence, work_schedule) -> None:
sequence.load_task_tree(work_schedule)
sequence.load_task_properties()
def expand_task(sequence, task=None):
def expand_task(sequence: tool.Sequence, task: ifcopenshell.entity_instance) -> None:
sequence.expand_task(task)
work_schedule = sequence.get_active_work_schedule()
sequence.load_task_tree(work_schedule)
sequence.load_task_properties()
def expand_all_tasks(sequence):
def expand_all_tasks(sequence: tool.Sequence) -> None:
sequence.expand_all_tasks()
work_schedule = sequence.get_active_work_schedule()
sequence.load_task_tree(work_schedule)
sequence.load_task_properties()
def contract_task(sequence, task=None):
def contract_task(sequence: tool.Sequence, task: ifcopenshell.entity_instance) -> None:
sequence.contract_task(task)
work_schedule = sequence.get_active_work_schedule()
sequence.load_task_tree(work_schedule)
sequence.load_task_properties()
def contract_all_tasks(sequence):
def contract_all_tasks(sequence: tool.Sequence) -> None:
sequence.contract_all_tasks()
work_schedule = sequence.get_active_work_schedule()
sequence.load_task_tree(work_schedule)
sequence.load_task_properties()
def remove_task(ifc, sequence, task=None):
def remove_task(ifc: tool.Ifc, sequence: tool.Sequence, task: ifcopenshell.entity_instance) -> None:
ifc.run("sequence.remove_task", task=task)
work_schedule = sequence.get_active_work_schedule()
sequence.load_task_tree(work_schedule)
@@ -120,44 +132,42 @@ def remove_task(ifc, sequence, task=None):
sequence.disable_selecting_deleted_task()
def load_task_properties(sequence):
def load_task_properties(sequence: tool.Sequence) -> None:
sequence.load_task_properties()
def disable_editing_work_schedule(sequence):
def disable_editing_work_schedule(sequence: tool.Sequence) -> None:
sequence.disable_editing_work_schedule()
def add_summary_task(ifc, sequence, work_schedule=None):
def add_summary_task(ifc: tool.Ifc, sequence: tool.Sequence, work_schedule: ifcopenshell.entity_instance) -> None:
ifc.run("sequence.add_task", work_schedule=work_schedule)
sequence.load_task_tree(work_schedule)
sequence.load_task_properties()
def add_task(ifc, sequence, parent_task=None):
def add_task(
ifc: tool.Ifc, sequence: tool.Sequence, parent_task: Optional[ifcopenshell.entity_instance] = None
) -> None:
ifc.run("sequence.add_task", parent_task=parent_task)
work_schedule = sequence.get_active_work_schedule()
sequence.load_task_tree(work_schedule)
sequence.load_task_properties()
def enable_editing_task(sequence, task=None):
sequence.enable_editing_task(task)
def enable_editing_task_attributes(sequence, task=None):
def enable_editing_task_attributes(sequence: tool.Sequence, task: ifcopenshell.entity_instance) -> None:
sequence.load_task_attributes(task)
sequence.enable_editing_task_attributes(task)
def edit_task(ifc, sequence, task=None):
def edit_task(ifc: tool.Ifc, sequence: tool.Sequence, task: ifcopenshell.entity_instance) -> None:
attributes = sequence.get_task_attributes()
ifc.run("sequence.edit_task", task=task, attributes=attributes)
sequence.load_task_properties(task=task)
sequence.disable_editing_task()
def copy_task_attribute(ifc, sequence, attribute_name=None):
def copy_task_attribute(ifc: tool.Ifc, sequence: tool.Sequence, attribute_name: str) -> None:
for task in sequence.get_checked_tasks():
ifc.run(
"sequence.edit_task",
@@ -167,18 +177,18 @@ def copy_task_attribute(ifc, sequence, attribute_name=None):
sequence.load_task_properties(task)
def duplicate_task(ifc, sequence, task=None):
def duplicate_task(ifc: tool.Ifc, sequence: tool.Sequence, task: ifcopenshell.entity_instance) -> None:
ifc.run("sequence.duplicate_task", task=task)
work_schedule = sequence.get_active_work_schedule()
sequence.load_task_tree(work_schedule)
sequence.load_task_properties()
def disable_editing_task(sequence):
def disable_editing_task(sequence: tool.Sequence) -> None:
sequence.disable_editing_task()
def enable_editing_task_time(ifc, sequence, task=None):
def enable_editing_task_time(ifc: tool.Ifc, sequence: tool.Sequence, task: ifcopenshell.entity_instance) -> None:
task_time = sequence.get_task_time(task)
if task_time is None:
task_time = ifc.run("sequence.add_task_time", task=task)
@@ -186,7 +196,7 @@ def enable_editing_task_time(ifc, sequence, task=None):
sequence.enable_editing_task_time(task)
def edit_task_time(ifc, sequence, resource, task_time=None):
def edit_task_time(ifc: tool.Ifc, sequence: tool.Sequence, resource, task_time: ifcopenshell.entity_instance) -> None:
attributes = sequence.get_task_time_attributes()
# TODO: nasty loop goes on when calendar props are messed up
ifc.run("sequence.edit_task_time", task_time=task_time, attributes=attributes)
@@ -196,59 +206,83 @@ def edit_task_time(ifc, sequence, resource, task_time=None):
resource.load_resource_properties()
def assign_predecessor(ifc, sequence, task=None):
def assign_predecessor(ifc: tool.Ifc, sequence: tool.Sequence, task: ifcopenshell.entity_instance) -> None:
predecessor_task = sequence.get_highlighted_task()
ifc.run("sequence.assign_sequence", relating_process=task, related_process=predecessor_task)
sequence.load_task_properties()
def unassign_predecessor(ifc, sequence, task=None):
def unassign_predecessor(ifc: tool.Ifc, sequence: tool.Sequence, task: ifcopenshell.entity_instance) -> None:
predecessor_task = sequence.get_highlighted_task()
ifc.run("sequence.unassign_sequence", relating_process=task, related_process=predecessor_task)
sequence.load_task_properties()
def assign_successor(ifc, sequence, task=None):
def assign_successor(ifc: tool.Ifc, sequence: tool.Sequence, task: ifcopenshell.entity_instance) -> None:
successor_task = sequence.get_highlighted_task()
ifc.run("sequence.assign_sequence", relating_process=successor_task, related_process=task)
sequence.load_task_properties()
def unassign_successor(ifc, sequence, task=None):
def unassign_successor(ifc: tool.Ifc, sequence: tool.Sequence, task: ifcopenshell.entity_instance) -> None:
successor_task = sequence.get_highlighted_task()
ifc.run("sequence.unassign_sequence", relating_process=successor_task, related_process=task)
sequence.load_task_properties()
def assign_products(ifc, sequence, spatial, task=None, products=None):
def assign_products(
ifc: tool.Ifc,
sequence: tool.Sequence,
spatial: tool.Spatial,
task: ifcopenshell.entity_instance,
products: Optional[list[ifcopenshell.entity_instance]] = None,
) -> None:
for product in products or spatial.get_selected_products() or []:
ifc.run("sequence.assign_product", relating_product=product, related_object=task)
outputs = sequence.get_task_outputs(task)
sequence.load_task_outputs(outputs)
def unassign_products(ifc, sequence, spatial, task=None, products=None):
def unassign_products(
ifc: tool.Ifc,
sequence: tool.Sequence,
spatial: tool.Spatial,
task: ifcopenshell.entity_instance,
products: Optional[list[ifcopenshell.entity_instance]] = None,
) -> None:
for product in products or spatial.get_selected_products() or []:
ifc.run("sequence.unassign_product", relating_product=product, related_object=task)
outputs = sequence.get_task_outputs(task)
sequence.load_task_outputs(outputs)
def assign_input_products(ifc, sequence, spatial, task=None, products=None):
def assign_input_products(
ifc: tool.Ifc,
sequence: tool.Sequence,
spatial: tool.Spatial,
task: ifcopenshell.entity_instance,
products: Optional[list[ifcopenshell.entity_instance]] = None,
) -> None:
for product in products or spatial.get_selected_products() or []:
ifc.run("sequence.assign_process", relating_process=task, related_object=product)
inputs = sequence.get_task_inputs(task)
sequence.load_task_inputs(inputs)
def unassign_input_products(ifc, sequence, spatial, task=None, products=None):
def unassign_input_products(
ifc: tool.Ifc,
sequence: tool.Sequence,
spatial: tool.Spatial,
task: ifcopenshell.entity_instance,
products: Optional[list[ifcopenshell.entity_instance]] = None,
) -> None:
for product in products or spatial.get_selected_products() or []:
ifc.run("sequence.unassign_process", relating_process=task, related_object=product)
inputs = sequence.get_task_inputs(task)
sequence.load_task_inputs(inputs)
def assign_resource(ifc, sequence, resource_tool, task=None):
def assign_resource(ifc: tool.Ifc, sequence: tool.Sequence, resource_tool, task: ifcopenshell.entity_instance) -> None:
resource = resource_tool.get_highlighted_resource()
sub_resource = ifc.run(
"resource.add_resource",
@@ -261,59 +295,67 @@ def assign_resource(ifc, sequence, resource_tool, task=None):
resource_tool.load_resources()
def unassign_resource(ifc, sequence, resource_tool, task=None, resource=None):
def unassign_resource(
ifc: tool.Ifc,
sequence: tool.Sequence,
resource_tool,
task: ifcopenshell.entity_instance,
resource: ifcopenshell.entity_instance,
) -> None:
ifc.run("sequence.unassign_process", relating_process=task, related_object=resource)
ifc.run("resource.remove_resource", resource=resource)
sequence.load_task_resources(task)
resource_tool.load_resources()
def remove_work_calendar(ifc, work_calendar=None):
def remove_work_calendar(ifc: tool.Ifc, work_calendar: ifcopenshell.entity_instance) -> None:
ifc.run("sequence.remove_work_calendar", work_calendar=work_calendar)
def add_work_calendar(ifc):
def add_work_calendar(ifc: tool.Ifc) -> ifcopenshell.entity_instance:
return ifc.run("sequence.add_work_calendar")
def edit_work_calendar(ifc, sequence, work_calendar=None):
def edit_work_calendar(ifc: tool.Ifc, sequence: tool.Sequence, work_calendar: ifcopenshell.entity_instance) -> None:
attributes = sequence.get_work_calendar_attributes()
ifc.run("sequence.edit_work_calendar", work_calendar=work_calendar, attributes=attributes)
sequence.disable_editing_work_calendar()
sequence.load_task_properties()
def enable_editing_work_calendar(sequence, work_calendar=None):
def enable_editing_work_calendar(sequence: tool.Sequence, work_calendar: ifcopenshell.entity_instance) -> None:
sequence.load_work_calendar_attributes(work_calendar)
sequence.enable_editing_work_calendar(work_calendar)
def disable_editing_work_calendar(sequence):
def disable_editing_work_calendar(sequence: tool.Sequence) -> None:
sequence.disable_editing_work_calendar()
def enable_editing_work_calendar_times(sequence, work_calendar=None):
def enable_editing_work_calendar_times(sequence: tool.Sequence, work_calendar: ifcopenshell.entity_instance) -> None:
sequence.enable_editing_work_calendar_times(work_calendar)
def add_work_time(ifc, work_calendar=None, time_type=None):
def add_work_time(
ifc: tool.Ifc, work_calendar: ifcopenshell.entity_instance, time_type: ifcopenshell.entity_instance
) -> ifcopenshell.entity_instance:
return ifc.run("sequence.add_work_time", work_calendar=work_calendar, time_type=time_type)
def enable_editing_work_time(sequence, work_time=None):
def enable_editing_work_time(sequence: tool.Sequence, work_time: ifcopenshell.entity_instance) -> None:
sequence.load_work_time_attributes(work_time)
sequence.enable_editing_work_time(work_time)
def disable_editing_work_time(sequence):
def disable_editing_work_time(sequence: tool.Sequence) -> None:
sequence.disable_editing_work_time()
def remove_work_time(ifc, work_time=None):
def remove_work_time(ifc: tool.Ifc, work_time=None) -> None:
ifc.run("sequence.remove_work_time", work_time=work_time)
def edit_work_time(ifc, sequence):
def edit_work_time(ifc: tool.Ifc, sequence: tool.Sequence) -> None:
work_time = sequence.get_active_work_time()
ifc.run("sequence.edit_work_time", work_time=work_time, attributes=sequence.get_work_time_attributes())
recurrence_pattern = work_time.RecurrencePattern
@@ -326,100 +368,118 @@ def edit_work_time(ifc, sequence):
sequence.disable_editing_work_time()
def assign_recurrence_pattern(ifc, work_time=None, recurrence_type=None):
def assign_recurrence_pattern(
ifc: tool.Ifc, work_time: ifcopenshell.entity_instance, recurrence_type: ifcopenshell.entity_instance
) -> ifcopenshell.entity_instance:
return ifc.run("sequence.assign_recurrence_pattern", parent=work_time, recurrence_type=recurrence_type)
def unassign_recurrence_pattern(ifc, recurrence_pattern=None):
def unassign_recurrence_pattern(ifc: tool.Ifc, recurrence_pattern: ifcopenshell.entity_instance) -> None:
ifc.run("sequence.unassign_recurrence_pattern", recurrence_pattern=recurrence_pattern)
def add_time_period(ifc, sequence, recurrence_pattern=None):
def add_time_period(ifc: tool.Ifc, sequence: tool.Sequence, recurrence_pattern: ifcopenshell.entity_instance) -> None:
start_time, end_time = sequence.get_recurrence_pattern_times()
ifc.run("sequence.add_time_period", recurrence_pattern=recurrence_pattern, start_time=start_time, end_time=end_time)
sequence.reset_time_period()
def remove_time_period(ifc, time_period=None):
def remove_time_period(ifc: tool.Ifc, time_period: ifcopenshell.entity_instance) -> None:
ifc.run("sequence.remove_time_period", time_period=time_period)
def enable_editing_task_calendar(sequence, task=None):
def enable_editing_task_calendar(sequence: tool.Sequence, task: ifcopenshell.entity_instance) -> None:
sequence.enable_editing_task_calendar(task)
def edit_task_calendar(ifc, sequence, task=None, work_calendar=None):
def edit_task_calendar(
ifc: tool.Ifc,
sequence: tool.Sequence,
task: ifcopenshell.entity_instance,
work_calendar: ifcopenshell.entity_instance,
) -> None:
ifc.run("control.assign_control", relating_control=work_calendar, related_object=task)
ifc.run("sequence.cascade_schedule", task=task)
sequence.load_task_properties()
def remove_task_calendar(ifc, sequence, task=None, work_calendar=None):
def remove_task_calendar(
ifc: tool.Ifc,
sequence: tool.Sequence,
task: ifcopenshell.entity_instance,
work_calendar: ifcopenshell.entity_instance,
) -> None:
ifc.run("control.unassign_control", relating_control=work_calendar, related_object=task)
ifc.run("sequence.cascade_schedule", task=task)
sequence.load_task_properties()
def enable_editing_task_sequence(sequence):
def enable_editing_task_sequence(sequence: tool.Sequence) -> None:
sequence.enable_editing_task_sequence()
sequence.load_task_properties()
def disable_editing_task_time(sequence):
def disable_editing_task_time(sequence: tool.Sequence) -> None:
sequence.disable_editing_task_time()
def enable_editing_sequence_attributes(sequence, rel_sequence=None):
def enable_editing_sequence_attributes(sequence: tool.Sequence, rel_sequence: ifcopenshell.entity_instance) -> None:
sequence.enable_editing_rel_sequence_attributes(rel_sequence)
sequence.load_rel_sequence_attributes(rel_sequence)
def enable_editing_sequence_lag_time(sequence, rel_sequence=None, lag_time=None):
def enable_editing_sequence_lag_time(
sequence: tool.Sequence, rel_sequence: ifcopenshell.entity_instance, lag_time: ifcopenshell.entity_instance
) -> None:
sequence.load_lag_time_attributes(lag_time)
sequence.enable_editing_sequence_lag_time(rel_sequence)
def unassign_lag_time(ifc, sequence, rel_sequence=None):
def unassign_lag_time(ifc: tool.Ifc, sequence: tool.Sequence, rel_sequence: ifcopenshell.entity_instance) -> None:
ifc.run("sequence.unassign_lag_time", rel_sequence=rel_sequence)
sequence.load_task_properties()
def assign_lag_time(ifc, rel_sequence=None):
def assign_lag_time(ifc: tool.Ifc, rel_sequence: ifcopenshell.entity_instance) -> None:
ifc.run("sequence.assign_lag_time", rel_sequence=rel_sequence, lag_value="P1D")
def edit_sequence_attributes(ifc, sequence, rel_sequence=None):
def edit_sequence_attributes(
ifc: tool.Ifc, sequence: tool.Sequence, rel_sequence: ifcopenshell.entity_instance
) -> None:
attributes = sequence.get_rel_sequence_attributes()
ifc.run("sequence.edit_sequence", rel_sequence=rel_sequence, attributes=attributes)
sequence.disable_editing_rel_sequence()
sequence.load_task_properties()
def edit_sequence_lag_time(ifc, sequence, lag_time=None):
def edit_sequence_lag_time(ifc: tool.Ifc, sequence: tool.Sequence, lag_time: ifcopenshell.entity_instance) -> None:
attributes = sequence.get_lag_time_attributes()
ifc.run("sequence.edit_lag_time", lag_time=lag_time, attributes=attributes)
sequence.disable_editing_rel_sequence()
sequence.load_task_properties()
def disable_editing_rel_sequence(sequence):
def disable_editing_rel_sequence(sequence: tool.Sequence) -> None:
sequence.disable_editing_rel_sequence()
def select_task_outputs(sequence, spatial, task=None):
def select_task_outputs(sequence: tool.Sequence, spatial: tool.Spatial, task: ifcopenshell.entity_instance) -> None:
spatial.select_products(products=sequence.get_task_outputs(task))
def select_task_inputs(sequence, spatial, task=None):
def select_task_inputs(sequence: tool.Sequence, spatial: tool.Spatial, task: ifcopenshell.entity_instance) -> None:
spatial.select_products(products=sequence.get_task_inputs(task))
def select_work_schedule_products(sequence, spatial, work_schedule=None):
def select_work_schedule_products(
sequence: tool.Sequence, spatial: tool.Spatial, work_schedule: ifcopenshell.entity_instance
) -> None:
products = sequence.get_work_schedule_products(work_schedule)
spatial.select_products(products)
def select_unassigned_work_schedule_products(ifc, sequence, spatial):
def select_unassigned_work_schedule_products(ifc: tool.Ifc, sequence: tool.Sequence, spatial: tool.Spatial) -> None:
spatial.deselect_objects()
products = ifc.get().by_type("IfcElement")
work_schedule = sequence.get_active_work_schedule()
@@ -428,11 +488,11 @@ def select_unassigned_work_schedule_products(ifc, sequence, spatial):
spatial.select_products(selection)
def recalculate_schedule(ifc, work_schedule=None):
def recalculate_schedule(ifc: tool.Ifc, work_schedule: ifcopenshell.entity_instance) -> None:
ifc.run("sequence.recalculate_schedule", work_schedule=work_schedule)
def add_task_column(sequence, column_type=None, name=None, data_type=None):
def add_task_column(sequence: tool.Sequence, column_type: str, name: str, data_type: str) -> None:
sequence.add_task_column(column_type, name, data_type)
work_schedule = sequence.get_active_work_schedule()
if work_schedule:
@@ -440,11 +500,11 @@ def add_task_column(sequence, column_type=None, name=None, data_type=None):
sequence.load_task_properties()
def remove_task_column(sequence, name=None):
def remove_task_column(sequence: tool.Sequence, name: str) -> None:
sequence.remove_task_column(name)
def set_task_sort_column(sequence, column=None):
def set_task_sort_column(sequence: tool.Sequence, column: str) -> None:
sequence.set_task_sort_column(column)
work_schedule = sequence.get_active_work_schedule()
if work_schedule:
@@ -452,7 +512,7 @@ def set_task_sort_column(sequence, column=None):
sequence.load_task_properties()
def calculate_task_duration(ifc, sequence, task=None):
def calculate_task_duration(ifc: tool.Ifc, sequence: tool.Sequence, task: ifcopenshell.entity_instance) -> None:
ifc.run("sequence.calculate_task_duration", task=task)
work_schedule = sequence.get_active_work_schedule()
if work_schedule:
@@ -460,11 +520,11 @@ def calculate_task_duration(ifc, sequence, task=None):
sequence.load_task_properties()
def load_animation_color_scheme(sequence, scheme):
def load_animation_color_scheme(sequence: tool.Sequence, scheme: Union[ifcopenshell.entity_instance, None]) -> None:
sequence.load_animation_color_scheme(scheme)
def go_to_task(sequence, task=None):
def go_to_task(sequence: tool.Sequence, task: ifcopenshell.entity_instance) -> Union[None, str]:
work_schedule = sequence.get_work_schedule(task)
is_work_schedule_active = sequence.is_work_schedule_active(work_schedule)
if is_work_schedule_active:
@@ -473,7 +533,7 @@ def go_to_task(sequence, task=None):
return "Work schedule is not active"
def highlight_product_related_task(sequence, spatial, product_type=None):
def highlight_product_related_task(sequence: tool.Sequence, spatial: tool.Spatial, product_type=None) -> None:
products = spatial.get_selected_products()
if products:
if product_type == "Output":
@@ -487,26 +547,26 @@ def highlight_product_related_task(sequence, spatial, product_type=None):
sequence.go_to_task(task)
def guess_date_range(sequence, work_schedule=None):
def guess_date_range(sequence: tool.Sequence, work_schedule: ifcopenshell.entity_instance) -> None:
start, finish = sequence.guess_date_range(work_schedule)
sequence.update_visualisation_date(start, finish)
def setup_default_task_columns(sequence):
def setup_default_task_columns(sequence: tool.Sequence) -> None:
sequence.setup_default_task_columns()
def add_task_bars(sequence):
def add_task_bars(sequence: tool.Sequence) -> None:
tasks = sequence.get_animation_bar_tasks()
if tasks:
sequence.create_bars(tasks)
def load_default_animation_color_scheme(sequence):
def load_default_animation_color_scheme(sequence: tool.Sequence) -> None:
sequence.load_default_animation_color_scheme()
def visualise_work_schedule_date_range(sequence, work_schedule=None):
def visualise_work_schedule_date_range(sequence: tool.Sequence, work_schedule: ifcopenshell.entity_instance) -> None:
sequence.clear_objects_animation(include_blender_objects=False)
settings = sequence.get_animation_settings()
if settings:
@@ -520,7 +580,7 @@ def visualise_work_schedule_date_range(sequence, work_schedule=None):
sequence.set_object_shading()
def visualise_work_schedule_date(sequence, work_schedule=None):
def visualise_work_schedule_date(sequence: tool.Sequence, work_schedule: ifcopenshell.entity_instance) -> None:
sequence.clear_objects_animation(include_blender_objects=False)
start_date = sequence.get_start_date()
product_states = sequence.process_construction_state(work_schedule, start_date)
@@ -528,12 +588,12 @@ def visualise_work_schedule_date(sequence, work_schedule=None):
sequence.set_object_shading()
def generate_gantt_chart(sequence, work_schedule):
def generate_gantt_chart(sequence: tool.Sequence, work_schedule: ifcopenshell.entity_instance) -> None:
json = sequence.create_tasks_json(work_schedule)
sequence.generate_gantt_browser_chart(json, work_schedule)
def load_product_related_tasks(sequence, product=None):
def load_product_related_tasks(sequence: tool.Sequence, product: ifcopenshell.entity_instance) -> Union[None, str]:
filter_by_schedule = sequence.is_filter_by_active_schedule()
if filter_by_schedule:
work_schedule = sequence.get_active_work_schedule()
@@ -546,7 +606,9 @@ def load_product_related_tasks(sequence, product=None):
sequence.load_product_related_tasks(task_inputs, task_ouputs)
def reorder_task_nesting(ifc, sequence, task, new_index):
def reorder_task_nesting(
ifc: tool.Ifc, sequence: tool.Sequence, task: ifcopenshell.entity_instance, new_index: int
) -> Union[None, str]:
is_sorting_enabled = sequence.is_sorting_enabled()
is_sort_reversed = sequence.is_sort_reversed()
if is_sorting_enabled or is_sort_reversed:
@@ -558,17 +620,19 @@ def reorder_task_nesting(ifc, sequence, task, new_index):
sequence.load_task_properties()
def create_baseline(ifc, sequence, work_schedule, name):
def create_baseline(
ifc: tool.Ifc, sequence: tool.Sequence, work_schedule: ifcopenshell.entity_instance, name: Optional[str] = None
) -> None:
ifc.run("sequence.create_baseline", work_schedule=work_schedule, name=name)
def clear_previous_animation(sequence):
def clear_previous_animation(sequence: tool.Sequence) -> None:
sequence.clear_objects_animation(include_blender_objects=False)
def add_animation_camera(sequence):
def add_animation_camera(sequence: tool.Sequence) -> None:
sequence.add_animation_camera()
def save_animation_color_scheme(sequence, name):
def save_animation_color_scheme(sequence: tool.Sequence, name: str) -> None:
sequence.save_animation_color_scheme(name)
+1 -1
View File
@@ -18,11 +18,11 @@
from __future__ import annotations
from typing import TYPE_CHECKING, Optional, Union
import blenderbim.core.tool as tool
if TYPE_CHECKING:
import bpy
import ifcopenshell
import blenderbim.tool as tool
def reference_structure(
+1 -1
View File
@@ -94,7 +94,7 @@ def hide_ports(ifc, system, element=None):
def add_port(ifc, system, element=None):
system.load_ports(element, system.get_ports(element))
obj = system.create_empty_at_cursor_with_element_orientation(element)
port = system.run_root_assign_class(obj=obj, ifc_class="IfcDistributionPort")
port = system.run_root_assign_class(obj=obj, ifc_class="IfcDistributionPort", should_add_representation=False)
ifc.run("system.assign_port", element=element, port=port)
+15 -3
View File
@@ -487,12 +487,12 @@ class Material:
def disable_editing_materials(cls): pass
def enable_editing_material(cls, material): pass
def enable_editing_materials(cls): pass
def get_active_material_type(cls): pass
def get_active_material(cls): pass
def get_active_material_type(cls): pass
def get_active_object_material(cls, obj): pass
def get_elements_by_material(cls, material): pass
def get_material_attributes(cls): pass
def get_material(cls, element, should_inherit): pass
def get_material_attributes(cls): pass
def get_name(cls, obj): pass
def get_type(cls, element): pass
def has_material_profile(cls, element): pass
@@ -503,6 +503,7 @@ class Material:
def is_material_used_in_sets(cls, material): pass
def load_material_attributes(cls, material): pass
def replace_material_with_material_profile(cls, element): pass
def sync_blender_material_name(cls, material): pass
@interface
@@ -617,9 +618,20 @@ class Profile:
@interface
class Pset:
def add_proposed_property(cls, name, value, props): pass
def cast_string_to_primitive(cls, value: str): pass
def clear_blender_pset_properties(cls, props): pass
def enable_proposed_pset(cls, props, pset_name, pset_type, has_template): pass
def get_element_pset(cls, element, pset_name): pass
def get_pset_name(cls, obj, obj_type): pass
def get_prop_template_primitive_type(cls, prop_template): pass
def get_pset_name(cls, obj, obj_type, pset_type): pass
def get_pset_template(cls, name): pass
def import_enumerated_value_from_template(cls, prop_template, data, props): pass
def import_pset_from_existing(cls, pset, props): pass
def import_pset_from_template(cls, pset_template, pset, props): pass
def import_single_value_from_template(cls, pset_template, prop_template, data, props): pass
def is_pset_applicable(cls,element, pset_name): pass
def set_active_pset(cls, props, pset, has_template): pass
@interface
+32 -6
View File
@@ -468,15 +468,41 @@ class Blender(blenderbim.core.tool.Blender):
active_object.select_set(True)
@classmethod
def enum_property_has_valid_index(cls, props, prop_name: str, enum_items: tuple) -> bool:
def get_enum_safe(cls, props: bpy.types.PropertyGroup, prop_name: str) -> Union[str, None]:
"""method created for readibility and to avoid console warnings like
`pyrna_enum_to_py: current value '17' matches no enum in 'BIMModelProperties', '', 'relating_type_id'`
"""
current_value_index = props.get(prop_name, None)
# assuming the default value is fine
if current_value_index is None:
return True
return current_value_index < len(enum_items)
# Yes, accessing items through annotations is a bit hacky
# but it's the only way to get the dynamic enum items
# besides providing them to get_enum_safe explicitly.
prop_keywords = props.__annotations__[prop_name].keywords
items = prop_keywords.get("items")
if items is None:
return None
if not isinstance(items, (list, tuple)):
# items are retrieved through a callback, not a static list / tuple :
items = items(props, bpy.context)
items_amount = len(items)
# If enum has no items it seems to always produce a warning.
# E.g. if you try to get it's value directly: `BIMModelProperties.relating_type_id`.
if items_amount == 0:
return None
index = props.get(prop_name)
# If value was never changed (still default), we can just retrieve it from the enum.
if index is None:
default_value = prop_keywords.get("default", 0)
if isinstance(default_value, int):
index = default_value
else:
# If default value is a string then it's a static enum
# and we can just return it.
return default_value
# Ensure index is valid.
if items_amount > index >= 0:
return items[index][0]
return None
@classmethod
def append_data_block(cls, filepath: str, data_block_type: str, name: str, link=False, relative=False) -> dict:
+104 -28
View File
@@ -1,11 +1,14 @@
import os
import bpy
import blenderbim.core.tool
import blenderbim.tool as tool
import ifcopenshell.util.element
import ifcopenshell.util.date
import ifcopenshell.util.cost
import ifcopenshell.util.unit
import blenderbim.bim.helper
import json
from typing import Optional, Any, Generator, Union
class Cost(blenderbim.core.tool.Cost):
@@ -16,9 +19,59 @@ class Cost(blenderbim.core.tool.Cost):
@classmethod
def disable_editing_cost_schedule(cls):
cls.store_active_schedule_columns()
bpy.context.scene.BIMCostProperties.active_cost_schedule_id = 0
cls.disable_editing_cost_item()
@classmethod
def load_active_schedule_columns(cls) -> None:
props = bpy.context.scene.BIMCostProperties
active_columns = props.columns
storage = props.columns_storage
active_cost_schedule_id = cls.get_active_cost_schedule().id()
# store column names to keep the original order
cols_to_add = []
# collection property only support removal by index
for storage_col_i, storage_col in reversed(list(enumerate(storage[:]))):
if storage_col.schedule_id != active_cost_schedule_id:
continue
cols_to_add.insert(0, storage_col.name)
# We don't store active schedule columns in storage
# so it will be easy to edit them.
storage.remove(storage_col_i)
for col_name in cols_to_add:
col = active_columns.add()
col.name = col_name
@classmethod
def store_active_schedule_columns(cls) -> None:
props = bpy.context.scene.BIMCostProperties
active_columns = props.columns
storage = props.columns_storage
active_cost_schedule_id = cls.get_active_cost_schedule().id()
for col in active_columns:
storage_col = storage.add()
storage_col.name = col.name
storage_col.schedule_id = active_cost_schedule_id
props.columns.clear()
@classmethod
def remove_stored_schedule_columns(cls, cost_schedule: ifcopenshell.entity_instance) -> None:
props = bpy.context.scene.BIMCostProperties
storage = props.columns_storage
active_cost_schedule_id = cost_schedule.id()
# collection property only support removal by index
for storage_col_i, storage_col in reversed(list(enumerate(storage[:]))):
if storage_col.schedule_id != active_cost_schedule_id:
continue
storage.remove(storage_col_i)
@classmethod
def enable_editing_cost_schedule_attributes(cls, cost_schedule):
bpy.context.scene.BIMCostProperties.active_cost_schedule_id = cost_schedule.id()
@@ -36,7 +89,7 @@ class Cost(blenderbim.core.tool.Cost):
blenderbim.bim.helper.import_attributes2(cost_schedule, props.cost_schedule_attributes, callback=special_import)
@classmethod
def enable_editing_cost_items(cls, cost_schedule):
def enable_editing_cost_items(cls, cost_schedule: ifcopenshell.entity_instance) -> None:
props = bpy.context.scene.BIMCostProperties
props.active_cost_schedule_id = cost_schedule.id()
props.is_editing = "COST_ITEMS"
@@ -147,7 +200,7 @@ class Cost(blenderbim.core.tool.Cost):
return blenderbim.bim.helper.export_attributes(props.cost_item_attributes)
@classmethod
def get_active_cost_item(cls):
def get_active_cost_item(cls) -> Union[ifcopenshell.entity_instance, None]:
props = bpy.context.scene.BIMCostProperties
if not props.active_cost_item_id:
return None
@@ -163,7 +216,7 @@ class Cost(blenderbim.core.tool.Cost):
return
@classmethod
def load_cost_item_types(cls, cost_item=None):
def load_cost_item_types(cls, cost_item: Optional[ifcopenshell.entity_instance] = None) -> None:
if not cost_item:
cost_item = cls.get_highlighted_cost_item()
if not cost_item:
@@ -186,7 +239,7 @@ class Cost(blenderbim.core.tool.Cost):
new.name = related_object.Name or "Unnamed"
@classmethod
def load_cost_item_quantity_assignments(cls, cost_item, related_object_type):
def load_cost_item_quantity_assignments(cls, cost_item: ifcopenshell.entity_instance, related_object_type):
def create_list_items(collection, cost_item, is_deep):
products = cls.get_cost_item_assignments(cost_item, filter_by_type=related_object_type, is_deep=False)
for product in products:
@@ -215,12 +268,16 @@ class Cost(blenderbim.core.tool.Cost):
create_list_items(props.cost_item_resources, cost_item, is_deep)
@classmethod
def calculate_parametric_quantity(cls, cost_item=None, product=None):
def calculate_parametric_quantity(
cls, cost_item: ifcopenshell.entity_instance, product: ifcopenshell.entity_instance
) -> tuple[float, Union[str, None]]:
quantities, unit = cls.get_assigned_quantities(cost_item, product)
return sum(quantity[3] for quantity in quantities), unit
@classmethod
def get_assigned_quantities(cls, cost_item, product):
def get_assigned_quantities(
cls, cost_item: ifcopenshell.entity_instance, product: ifcopenshell.entity_instance
) -> tuple[list[ifcopenshell.entity_instance], Union[str, None]]:
selected_quantitites = []
unit = ""
for quantities in ifcopenshell.util.element.get_psets(product, qtos_only=True).values():
@@ -259,7 +316,7 @@ class Cost(blenderbim.core.tool.Cost):
blenderbim.bim.helper.import_attributes2(physical_quantity, props.quantity_attributes)
@classmethod
def enable_editing_cost_item_values(cls, cost_item=None):
def enable_editing_cost_item_values(cls, cost_item: ifcopenshell.entity_instance) -> None:
props = bpy.context.scene.BIMCostProperties
props.active_cost_item_id = cost_item.id()
props.cost_item_editing_type = "VALUES"
@@ -287,7 +344,7 @@ class Cost(blenderbim.core.tool.Cost):
return attributes
@classmethod
def load_cost_item_value_attributes(cls, cost_value=None):
def load_cost_item_value_attributes(cls, cost_value: ifcopenshell.entity_instance) -> None:
def import_attributes(name, prop, data, cost_value, is_rates, props_collection):
if name == "AppliedValue":
# TODO: for now, only support simple IfcValues (which are effectively IfcMonetaryMeasure)
@@ -334,36 +391,38 @@ class Cost(blenderbim.core.tool.Cost):
blenderbim.bim.helper.import_attributes2(cost_value, props.cost_value_attributes, callback=callback)
@classmethod
def calculate_applied_value(cls, cost_item, cost_value):
def calculate_applied_value(
cls, cost_item: ifcopenshell.entity_instance, cost_value: ifcopenshell.entity_instance
) -> float:
return ifcopenshell.util.cost.calculate_applied_value(cost_item, cost_value)
@classmethod
def is_active_schedule_of_rates(cls):
def is_active_schedule_of_rates(cls) -> bool:
return (
tool.Ifc.get().by_id(bpy.context.scene.BIMCostProperties.active_cost_schedule_id).PredefinedType
== "SCHEDULEOFRATES"
)
@classmethod
def enable_editing_cost_item_value(cls, cost_value=None):
def enable_editing_cost_item_value(cls, cost_value: ifcopenshell.entity_instance) -> None:
props = bpy.context.scene.BIMCostProperties
props.active_cost_value_id = cost_value.id()
props.cost_value_editing_type = "ATTRIBUTES"
@classmethod
def disable_editing_cost_item_value(cls):
def disable_editing_cost_item_value(cls) -> None:
props = bpy.context.scene.BIMCostProperties
props.active_cost_value_id = 0
props.cost_value_editing_type = ""
@classmethod
def load_cost_item_value_formula_attributes(cls, cost_value=None):
def load_cost_item_value_formula_attributes(cls, cost_value: ifcopenshell.entity_instance) -> None:
props = bpy.context.scene.BIMCostProperties
props.cost_value_attributes.clear()
bpy.context.scene.BIMCostProperties.cost_value_formula = ifcopenshell.util.cost.serialise_cost_value(cost_value)
@classmethod
def enable_editing_cost_item_value_formula(cls, cost_value=None):
def enable_editing_cost_item_value_formula(cls, cost_value: ifcopenshell.entity_instance) -> None:
props = bpy.context.scene.BIMCostProperties
props.active_cost_value_id = cost_value.id()
props.cost_value_editing_type = "FORMULA"
@@ -373,7 +432,7 @@ class Cost(blenderbim.core.tool.Cost):
return bpy.context.scene.BIMCostProperties.cost_value_formula
@classmethod
def get_cost_value_attributes(cls):
def get_cost_value_attributes(cls) -> dict[str, Any]:
def export_attributes(attributes, prop):
if prop.name == "UnitBasisValue":
if prop.is_null:
@@ -392,13 +451,18 @@ class Cost(blenderbim.core.tool.Cost):
return blenderbim.bim.helper.export_attributes(props.cost_value_attributes, callback)
@classmethod
def get_cost_value_unit_component(cls):
def get_cost_value_unit_component(cls) -> ifcopenshell.entity_instance:
return tool.Ifc.get().by_id(
int(bpy.context.scene.BIMCostProperties.cost_value_attributes.get("UnitBasisUnit").enum_value)
)
@classmethod
def get_cost_item_assignments(cls, cost_item, filter_by_type=None, is_deep=False):
def get_cost_item_assignments(
cls,
cost_item: ifcopenshell.entity_instance,
filter_by_type: Optional[ifcopenshell.util.cost.FILTER_BY_TYPE] = None,
is_deep: bool = False,
) -> list[ifcopenshell.entity_instance]:
return ifcopenshell.util.cost.get_cost_item_assignments(
cost_item, filter_by_type=filter_by_type, is_deep=is_deep
)
@@ -408,30 +472,40 @@ class Cost(blenderbim.core.tool.Cost):
return bpy.context.scene.BIMCostProperties.show_nested_elements
@classmethod
def get_cost_item_products(cls, cost_item, is_deep=False):
def get_cost_item_products(
cls, cost_item: ifcopenshell.entity_instance, is_deep: bool = False
) -> list[ifcopenshell.entity_instance]:
return cls.get_cost_item_assignments(cost_item, filter_by_type="PRODUCT", is_deep=is_deep)
@classmethod
def get_cost_item_resources(cls, cost_item, is_deep=False):
def get_cost_item_resources(
cls, cost_item: ifcopenshell.entity_instance, is_deep: bool = False
) -> list[ifcopenshell.entity_instance]:
return cls.get_cost_item_assignments(cost_item, filter_by_type="RESOURCE", is_deep=is_deep)
@classmethod
def get_cost_item_processes(cls, cost_item, is_deep=False):
def get_cost_item_processes(
cls, cost_item: ifcopenshell.entity_instance, is_deep: bool = False
) -> list[ifcopenshell.entity_instance]:
return cls.get_cost_item_assignments(cost_item, filter_by_type="PROCESS", is_deep=is_deep)
@classmethod
def get_schedule_cost_items(cls, cost_schedule):
def get_schedule_cost_items(
cls, cost_schedule: ifcopenshell.entity_instance
) -> Generator[ifcopenshell.entity_instance, None, None]:
return ifcopenshell.util.cost.get_schedule_cost_items(cost_schedule)
@classmethod
def get_cost_schedule_products(cls, cost_schedule):
def get_cost_schedule_products(
cls, cost_schedule: ifcopenshell.entity_instance
) -> list[ifcopenshell.entity_instance]:
products = []
for cost_item in ifcopenshell.util.cost.get_schedule_cost_items(cost_schedule):
products.extend(cls.get_cost_item_products(cost_item))
return products
@classmethod
def import_cost_schedule_csv(cls, file_path=None, is_schedule_of_rates=False):
def import_cost_schedule_csv(cls, file_path: Optional[str] = None, is_schedule_of_rates: bool = False) -> None:
if not file_path:
return
from ifc5d.csv2ifc import Csv2Ifc
@@ -446,13 +520,13 @@ class Cost(blenderbim.core.tool.Cost):
print("Import finished in {:.2f} seconds".format(time.time() - start))
@classmethod
def add_cost_column(cls, name):
def add_cost_column(cls, name: str) -> None:
props = bpy.context.scene.BIMCostProperties
new = props.columns.add()
new.name = name
@classmethod
def remove_cost_column(cls, name):
def remove_cost_column(cls, name: str) -> None:
props = bpy.context.scene.BIMCostProperties
props.columns.remove(props.columns.find(name))
@@ -473,7 +547,9 @@ class Cost(blenderbim.core.tool.Cost):
cls.load_schedule_of_rates_tree(schedule_of_rates=tool.Ifc.get().by_id(int(props.schedule_of_rates)))
@classmethod
def create_new_cost_item_li(cls, props_collection, cost_item, level_index, type="cost_rate"):
def create_new_cost_item_li(
cls, props_collection, cost_item: ifcopenshell.entity_instance, level_index: int, type: str = "cost_rate"
) -> None:
new = props_collection.add()
new.ifc_definition_id = cost_item.id()
new.name = cost_item.Name or "Unnamed"
@@ -508,7 +584,7 @@ class Cost(blenderbim.core.tool.Cost):
props.is_cost_update_enabled = True
@classmethod
def export_cost_schedules(cls, filepath, format=None, cost_schedule=None):
def export_cost_schedules(cls, filepath: str, format: str, cost_schedule=None):
import subprocess
import os
import sys
@@ -621,7 +697,7 @@ class Cost(blenderbim.core.tool.Cost):
return bool(cost_items)
@classmethod
def load_product_cost_items(cls, product):
def load_product_cost_items(cls, product: ifcopenshell.entity_instance) -> None:
props = bpy.context.scene.BIMCostProperties
props.is_cost_update_enabled = False
props.product_cost_items.clear()
+37 -55
View File
@@ -51,11 +51,14 @@ from blenderbim.bim.module.drawing.prop import get_diagram_scales, BOX_ALIGNMENT
from lxml import etree
from mathutils import Vector, Matrix
from fractions import Fraction
from typing import Optional, Union, Iterable, Any
from typing import Optional, Union, Iterable, Any, Literal
from pathlib import Path
class Drawing(blenderbim.core.tool.Drawing):
ANNOTATION_DATA_TYPE = Literal["empty", "curve", "mesh"]
DOCUMENT_TYPE = Literal["SCHEDULE", "REFERENCE"]
@classmethod
def canonicalise_class_name(cls, name):
return re.sub("[^0-9a-zA-Z]+", "", name)
@@ -68,11 +71,11 @@ class Drawing(blenderbim.core.tool.Drawing):
)
@classmethod
def get_annotation_data_type(cls, object_type):
def get_annotation_data_type(cls, object_type: str) -> ANNOTATION_DATA_TYPE:
return ANNOTATION_TYPES_DATA[object_type][3]
@classmethod
def create_annotation_object(cls, drawing, object_type):
def create_annotation_object(cls, drawing: ifcopenshell.entity_instance, object_type: str) -> bpy.types.Object:
data_type = cls.get_annotation_data_type(object_type)
obj = annotation.Annotator.get_annotation_obj(drawing, object_type, data_type)
if object_type == "FILL_AREA":
@@ -298,15 +301,15 @@ class Drawing(blenderbim.core.tool.Drawing):
bpy.context.scene.DocProperties.is_editing_sheets = False
@classmethod
def disable_editing_text(cls, obj):
def disable_editing_text(cls, obj: bpy.types.Object) -> None:
obj.BIMTextProperties.is_editing = False
@classmethod
def disable_editing_assigned_product(cls, obj):
def disable_editing_assigned_product(cls, obj: bpy.types.Object) -> None:
obj.BIMAssignedProductProperties.is_editing_product = False
@classmethod
def enable_editing(cls, obj):
def enable_editing(cls, obj: bpy.types.Object) -> None:
bpy.ops.object.select_all(action="DESELECT")
bpy.context.view_layer.objects.active = obj
obj.select_set(True)
@@ -330,11 +333,11 @@ class Drawing(blenderbim.core.tool.Drawing):
bpy.context.scene.DocProperties.is_editing_sheets = True
@classmethod
def enable_editing_text(cls, obj):
def enable_editing_text(cls, obj: bpy.types.Object) -> None:
obj.BIMTextProperties.is_editing = True
@classmethod
def enable_editing_assigned_product(cls, obj):
def enable_editing_assigned_product(cls, obj: bpy.types.Object) -> None:
obj.BIMAssignedProductProperties.is_editing_product = True
@classmethod
@@ -428,7 +431,7 @@ class Drawing(blenderbim.core.tool.Drawing):
return location
@classmethod
def get_path_filename(cls, path):
def get_path_filename(cls, path: str) -> str:
return os.path.splitext(os.path.basename(path))[0]
@classmethod
@@ -483,7 +486,7 @@ class Drawing(blenderbim.core.tool.Drawing):
return ""
@classmethod
def get_name(cls, element):
def get_name(cls, element: ifcopenshell.entity_instance) -> Union[str, None]:
return element.Name
@classmethod
@@ -563,7 +566,7 @@ class Drawing(blenderbim.core.tool.Drawing):
ifcopenshell.util.element.remove_deep2(ifc_file, literal)
@classmethod
def synchronise_ifc_and_text_attributes(cls, obj):
def synchronise_ifc_and_text_attributes(cls, obj: bpy.types.Object) -> None:
literals = cls.get_text_literal(obj, return_list=True)
literals_attributes = cls.export_text_literal_attributes(obj)
defined_ifc_ids = [l.ifc_definition_id for l in obj.BIMTextProperties.literals]
@@ -784,7 +787,7 @@ class Drawing(blenderbim.core.tool.Drawing):
new.ifc_definition_id = drawing.id() # Last, to prevent unnecessary prop callbacks
@classmethod
def import_documents(cls, document_type):
def import_documents(cls, document_type: DOCUMENT_TYPE) -> None:
dprops = bpy.context.scene.DocProperties
if document_type == "SCHEDULE":
documents_collection = dprops.schedules
@@ -845,7 +848,7 @@ class Drawing(blenderbim.core.tool.Drawing):
return next(s for s in props.sheets[: props.active_sheet_index + 1][::-1] if s.is_sheet)
@classmethod
def import_text_attributes(cls, obj):
def import_text_attributes(cls, obj: bpy.types.Object) -> None:
props = obj.BIMTextProperties
props.literals.clear()
@@ -863,7 +866,7 @@ class Drawing(blenderbim.core.tool.Drawing):
props.font_size = str(text_data["FontSize"])
@classmethod
def import_assigned_product(cls, obj):
def import_assigned_product(cls, obj: bpy.types.Object) -> None:
element = tool.Ifc.get_entity(obj)
product = cls.get_assigned_product(element)
if product:
@@ -934,38 +937,16 @@ class Drawing(blenderbim.core.tool.Drawing):
bpy.context.scene.DocProperties.should_draw_decorations = True
@classmethod
def update_text_value(cls, obj):
element = tool.Ifc.get_entity(obj)
if element.is_a("IfcTypeProduct"):
objs = [obj]
for occurrence in ifcopenshell.util.element.get_types(element):
obj = tool.Ifc.get_object(occurrence)
if obj:
objs.append(obj)
else:
objs = []
element_type = ifcopenshell.util.element.get_type(element)
if element_type and element_type.RepresentationMaps:
obj = tool.Ifc.get_object(element_type)
if obj:
objs.append(obj)
for occurrence in ifcopenshell.util.element.get_types(element_type):
obj = tool.Ifc.get_object(occurrence)
if obj:
objs.append(obj)
else:
objs = [obj]
for obj in objs:
props = obj.BIMTextProperties
literals = cls.get_text_literal(obj, return_list=True)
cls.import_text_attributes(obj)
for i, literal in enumerate(literals):
product = cls.get_assigned_product(tool.Ifc.get_entity(obj)) or tool.Ifc.get_entity(obj)
props.literals[i].value = cls.replace_text_literal_variables(literal.Literal, product)
def update_text_value(cls, obj: bpy.types.Object) -> None:
props = obj.BIMTextProperties
literals = cls.get_text_literal(obj, return_list=True)
cls.import_text_attributes(obj)
for i, literal in enumerate(literals):
product = cls.get_assigned_product(tool.Ifc.get_entity(obj)) or tool.Ifc.get_entity(obj)
props.literals[i].value = cls.replace_text_literal_variables(literal.Literal, product)
@classmethod
def update_text_size_pset(cls, obj):
def update_text_size_pset(cls, obj: bpy.types.Object) -> None:
"""updates pset `EPset_Annotation.Classes` value
based on current font size from `obj.BIMTextProperties.font_size`
"""
@@ -1544,7 +1525,7 @@ class Drawing(blenderbim.core.tool.Drawing):
tool.Geometry.record_object_position(obj)
@classmethod
def get_document_references(cls, document):
def get_document_references(cls, document: ifcopenshell.entity_instance) -> list[ifcopenshell.entity_instance]:
if tool.Ifc.get_schema() == "IFC2X3":
return document.DocumentReferences or []
return document.HasDocumentReferences or []
@@ -1575,7 +1556,9 @@ class Drawing(blenderbim.core.tool.Drawing):
return reference.Description
@classmethod
def generate_reference_attributes(cls, reference: ifcopenshell.entity_instance, **attributes: Any) -> dict[str, Any]:
def generate_reference_attributes(
cls, reference: ifcopenshell.entity_instance, **attributes: Any
) -> dict[str, Any]:
"""will automatically convert attributes below for IFC2X3 compatibility:
- Identification -> ItemReference
@@ -1657,23 +1640,22 @@ class Drawing(blenderbim.core.tool.Drawing):
base_elements = set(ifc_file.by_type("IfcElement") + ifc_file.by_type("IfcSpatialElement"))
elements = {e for e in (elements & base_elements) if e.is_a() != "IfcSpace"}
updated_set = set()
for i in elements:
# exclude annotations to avoid including annotations from other drawings
if not i.is_a("IfcAnnotation"):
if not i.is_a("IfcAnnotation"):
updated_set.add(i)
#add aggregate too, if element is host by one
# add aggregate too, if element is host by one
if i.Decomposes:
aggregate = i.Decomposes[0].RelatingObject
#remove IfcProject for class iterator. See https://github.com/IfcOpenShell/IfcOpenShell/issues/4361#issuecomment-2081223615
if not aggregate.is_a("IfcProject"):
# remove IfcProject for class iterator. See https://github.com/IfcOpenShell/IfcOpenShell/issues/4361#issuecomment-2081223615
if not aggregate.is_a("IfcProject"):
updated_set.add(aggregate)
# After the iteration is complete, update elements with updated set
# After the iteration is complete, update elements with updated set
elements.update(updated_set)
# add annotations from the current drawing
annotations = tool.Drawing.get_group_elements(tool.Drawing.get_drawing_group(drawing))
elements.update(annotations)
@@ -1716,7 +1698,7 @@ class Drawing(blenderbim.core.tool.Drawing):
return reference.ReferencedDocument
@classmethod
def select_assigned_product(cls, context):
def select_assigned_product(cls, context: bpy.types.Context) -> None:
obj = context.active_object
element = tool.Ifc.get_entity(obj)
product = cls.get_assigned_product(element)
@@ -1735,7 +1717,7 @@ class Drawing(blenderbim.core.tool.Drawing):
return True if (camera and camera.data.type == "ORTHO") else False
@classmethod
def is_active_drawing(cls, drawing):
def is_active_drawing(cls, drawing: ifcopenshell.entity_instance) -> bool:
return drawing.id() == bpy.context.scene.DocProperties.active_drawing_id
@classmethod
+21 -8
View File
@@ -24,8 +24,10 @@ import logging
import numpy as np
import ifcopenshell
import ifcopenshell.api
import ifcopenshell.geom
import ifcopenshell.guid
import ifcopenshell.util.element
import ifcopenshell.util.representation
import ifcopenshell.util.system
import blenderbim.core.tool
import blenderbim.core.drawing
@@ -88,7 +90,7 @@ class Geometry(blenderbim.core.tool.Geometry):
bpy.data.meshes.remove(data)
@classmethod
def delete_ifc_object(cls, obj):
def delete_ifc_object(cls, obj: bpy.types.Object) -> None:
element = tool.Ifc.get_entity(obj)
if not element:
return
@@ -319,7 +321,7 @@ class Geometry(blenderbim.core.tool.Geometry):
return new_mesh
@classmethod
def get_active_representation(cls, obj):
def get_active_representation(cls, obj: bpy.types.Object) -> Union[ifcopenshell.entity_instance, None]:
"""< IfcShapeRepresentation or None"""
if obj.data and hasattr(obj.data, "BIMMeshProperties") and obj.data.BIMMeshProperties.ifc_definition_id:
return tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id)
@@ -459,7 +461,9 @@ class Geometry(blenderbim.core.tool.Geometry):
return f"{representation.ContextOfItems.id()}/{representation.id()}"
@classmethod
def get_styles(cls, obj, only_assigned_to_faces=False):
def get_styles(
cls, obj: bpy.types.Object, only_assigned_to_faces: bool = False
) -> list[Union[ifcopenshell.entity_instance, None]]:
styles = [tool.Style.get_style(s.material) for s in obj.material_slots if s.material]
if not only_assigned_to_faces:
return styles
@@ -467,8 +471,15 @@ class Geometry(blenderbim.core.tool.Geometry):
usage_count = [0] * len(obj.material_slots)
if not usage_count: # if there are no materials, polygons will still use index 0
return []
for poly in obj.data.polygons:
usage_count[poly.material_index] += 1
# remove usages for empty material slots
for i, slot in reversed(list(enumerate(obj.material_slots))):
if not slot.material:
del usage_count[i]
styles = [style for style, usage in zip(styles, usage_count, strict=True) if usage > 0]
return styles
@@ -560,11 +571,11 @@ class Geometry(blenderbim.core.tool.Geometry):
new.value = element[i]
@classmethod
def is_body_representation(cls, representation):
def is_body_representation(cls, representation: ifcopenshell.entity_instance) -> bool:
return representation.ContextOfItems.ContextIdentifier == "Body"
@classmethod
def is_box_representation(cls, representation):
def is_box_representation(cls, representation: ifcopenshell.entity_instance) -> bool:
return representation.ContextOfItems.ContextIdentifier == "Box"
@classmethod
@@ -572,11 +583,11 @@ class Geometry(blenderbim.core.tool.Geometry):
return not all([tool.Cad.is_x(o, 1.0) for o in obj.scale]) or obj in IfcStore.edited_objs
@classmethod
def is_mapped_representation(cls, representation):
def is_mapped_representation(cls, representation: ifcopenshell.entity_instance) -> bool:
return representation.RepresentationType == "MappedRepresentation"
@classmethod
def is_meshlike(cls, representation):
def is_meshlike(cls, representation: ifcopenshell.entity_instance) -> bool:
if ifcopenshell.util.representation.resolve_representation(representation).RepresentationType in (
"AdvancedBrep",
"Annotation2D",
@@ -656,7 +667,9 @@ class Geometry(blenderbim.core.tool.Geometry):
bpy.data.objects.remove(obj)
@classmethod
def resolve_mapped_representation(cls, representation):
def resolve_mapped_representation(
cls, representation: ifcopenshell.entity_instance
) -> ifcopenshell.entity_instance:
if representation.RepresentationType == "MappedRepresentation":
return cls.resolve_mapped_representation(representation.Items[0].MappingSource.MappedRepresentation)
return representation
@@ -242,3 +242,16 @@ class Material(blenderbim.core.tool.Material):
meshes_to_objects[mesh] = obj
for obj in meshes_to_objects.values():
tool.Geometry.reload_representation(obj)
@classmethod
def sync_blender_material_name(cls, material):
name = material.Name or "Unnamed"
obj = tool.Ifc.get_object(material)
if obj:
obj.name = name
style = tool.Style.get_style(obj)
if style:
style.Name = name
obj = tool.Ifc.get_object(style)
if obj:
obj.name = name
+3 -1
View File
@@ -97,7 +97,9 @@ class Misc(blenderbim.core.tool.Misc):
IfcStore.edited_objs.add(obj)
@classmethod
def split_objects_with_cutter(cls, objs, cutter):
def split_objects_with_cutter(
cls, objs: list[bpy.types.Object], cutter: bpy.types.Object
) -> list[bpy.types.Object]:
cutter_mesh = cutter.data
bm = bmesh.new()
+53 -4
View File
@@ -23,9 +23,11 @@ import collections
import collections.abc
import numpy as np
import ifcopenshell
import ifcopenshell.util.element
import ifcopenshell.util.unit
import ifcopenshell.util.placement
import ifcopenshell.util.representation
import blenderbim.core.geometry
import blenderbim.core.tool
import blenderbim.tool as tool
import blenderbim.core.geometry as geometry
@@ -36,8 +38,9 @@ from functools import partial
from blenderbim.bim import import_ifc
from blenderbim.bim.module.geometry.helper import Helper
from blenderbim.bim.module.model.data import AuthoringData, RailingData, RoofData, WindowData, DoorData
from blenderbim.bim.module.model.opening import FilledOpeningGenerator
from ifcopenshell.util.shape_builder import V, ShapeBuilder
from typing import Optional, Union, TypeVar, Any
from typing import Optional, Union, TypeVar, Any, Iterable
T = TypeVar("T")
@@ -468,7 +471,7 @@ class Model(blenderbim.core.tool.Model):
has_deleted_opening = True
@classmethod
def get_material_layer_parameters(cls, element):
def get_material_layer_parameters(cls, element: ifcopenshell.entity_instance) -> dict[str, Any]:
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
layer_set_direction = "AXIS2"
offset = 0.0
@@ -551,7 +554,7 @@ class Model(blenderbim.core.tool.Model):
data = tool.Ifc.get().createIfcText(json.dumps(data))
ifcopenshell.api.run("pset.edit_pset", tool.Ifc.get(), pset=pset, properties={"Data": data})
else:
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), pset=pset)
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), product=element, pset=pset)
@classmethod
def get_flow_segment_axis(cls, obj):
@@ -902,7 +905,7 @@ class Model(blenderbim.core.tool.Model):
return Matrix(placement)
@classmethod
def reload_body_representation(cls, obj_or_objects):
def reload_body_representation(cls, obj_or_objects: Union[bpy.types.Object, Iterable[bpy.types.Object]]) -> None:
"""Update body representation including all decomposed objects"""
if isinstance(obj_or_objects, collections.abc.Iterable):
objects = set(obj_or_objects)
@@ -1203,3 +1206,49 @@ class Model(blenderbim.core.tool.Model):
vertices = (v.to_3d().xzy for v in vertices)
return (vertices, edges, faces)
@classmethod
def update_simple_openings(cls, element: ifcopenshell.entity_instance) -> None:
ifc_file = tool.Ifc.get()
fillings = {e: tool.Ifc.get_object(e) for e in tool.Ifc.get_all_element_occurrences(element)}
voided_objs = set()
has_replaced_opening_representation = False
for filling in fillings:
if not filling.FillsVoids:
continue
opening = filling.FillsVoids[0].RelatingOpeningElement
voided_obj = tool.Ifc.get_object(opening.VoidsElements[0].RelatingBuildingElement)
voided_objs.add(voided_obj)
# We assume all occurrences of the same element type (e.g. a window)
# will use openings of the same thickness.
# Generator we use by default will create a really thick opening representation
# to make sure it will fit for walls with different thickness.
if has_replaced_opening_representation:
continue
old_representation = ifcopenshell.util.representation.get_representation(
opening, "Model", "Body", "MODEL_VIEW"
)
old_representation = tool.Geometry.resolve_mapped_representation(old_representation)
ifcopenshell.api.run(
"geometry.unassign_representation", ifc_file, product=opening, representation=old_representation
)
new_representation = FilledOpeningGenerator().generate_opening_from_filling(
filling, fillings[filling], voided_obj.dimensions[1]
)
for inverse in ifc_file.get_inverse(old_representation):
ifcopenshell.util.element.replace_attribute(inverse, old_representation, new_representation)
ifcopenshell.api.run("geometry.remove_representation", ifc_file, representation=old_representation)
has_replaced_opening_representation = True
tool.Model.reload_body_representation(voided_objs)
if fillings:
with bpy.context.temp_override(selected_objects=list(fillings.values())):
bpy.ops.bim.recalculate_fill()
+15 -8
View File
@@ -19,17 +19,24 @@
import os
import bpy
import ifcopenshell
import ifcopenshell.util.representation
import ifcopenshell.util.unit
import blenderbim.core.aggregate
import blenderbim.core.context
import blenderbim.core.tool
import blenderbim.core.root
import blenderbim.core.unit
import blenderbim.core.owner
import blenderbim.bim.schema
import blenderbim.tool as tool
from blenderbim.bim.ifc import IfcStore
from pathlib import Path
from typing import Optional
class Project(blenderbim.core.tool.Project):
@classmethod
def append_all_types_from_template(cls, template):
def append_all_types_from_template(cls, template: str) -> None:
# TODO refactor
filepath = os.path.join(bpy.context.scene.BIMProperties.data_dir, "templates", "projects", template)
bpy.ops.bim.select_library_file(filepath=filepath)
@@ -95,13 +102,13 @@ class Project(blenderbim.core.tool.Project):
@classmethod
def run_root_assign_class(
cls,
obj=None,
ifc_class=None,
predefined_type=None,
should_add_representation=True,
context=None,
ifc_representation_class=None,
):
obj: bpy.types.Object,
ifc_class: str,
predefined_type: Optional[str] = None,
should_add_representation: bool = True,
context: Optional[ifcopenshell.entity_instance] = None,
ifc_representation_class: Optional[str] = None,
) -> ifcopenshell.entity_instance:
return blenderbim.core.root.assign_class(
tool.Ifc,
tool.Collector,
+205 -4
View File
@@ -23,6 +23,7 @@ import blenderbim.core.tool
import blenderbim.tool as tool
import blenderbim.bim.schema
from typing import Union
from blenderbim.bim.module.pset.calc_quantity_function_mapper import mapper
class Pset(blenderbim.core.tool.Pset):
@@ -56,9 +57,12 @@ class Pset(blenderbim.core.tool.Pset):
return bpy.context.scene.GroupPsetProperties
@classmethod
def get_pset_name(cls, obj, obj_type):
pset = cls.get_pset_props(obj, obj_type)
return pset.pset_name
def get_pset_name(cls, obj, obj_type, pset_type="PSET"):
props = cls.get_pset_props(obj, obj_type)
name = props.pset_name if pset_type == "PSET" else props.qto_name
if name == "BBIM_CUSTOM":
return ""
return name
@classmethod
def is_pset_applicable(cls, element: ifcopenshell.entity_instance, pset_name: str) -> bool:
@@ -82,5 +86,202 @@ class Pset(blenderbim.core.tool.Pset):
def enable_pset_editing(cls, pset_id=None, pset_name=None, pset_type=None, obj=None, obj_type=None):
# TODO REFACTOR ONCE toll/CORE functions are available
bpy.ops.bim.enable_pset_editing(
pset_id=0, pset_name=tool.Pset.get_pset_name(obj, obj_type), pset_type="PSET", obj=obj, obj_type=obj_type
pset_id=0, pset_name=cls.get_pset_name(obj, obj_type), pset_type="PSET", obj=obj, obj_type=obj_type
)
@classmethod
def import_pset_from_existing(cls, pset, props):
pset_props = []
if pset.is_a("IfcElementQuantity"):
pset_props = pset.Quantities
elif pset.is_a("IfcPropertySet"):
pset_props = pset.HasProperties
elif pset.is_a("IfcMaterialProperties") or pset.is_a("IfcProfileProperties"):
pset_props = pset.Properties
for prop in pset_props:
if props.properties.get(prop.Name):
continue # This property has already been added from a template
if prop.is_a("IfcPropertyEnumeratedValue"):
simple_prop = props.properties.add()
simple_prop.name = prop.Name
simple_prop.value_type = "IfcPropertyEnumeratedValue"
metadata = simple_prop.metadata
metadata.name = prop.Name
metadata.is_null = len(simple_prop.enumerated_value.enumerated_values) == 0
metadata.is_optional = True
metadata.set_value(prop.EnumerationReference.EnumerationValues[0].wrappedValue)
enum_items = [v.wrappedValue for v in prop.EnumerationReference.EnumerationValues]
selected_enum_items = [v.wrappedValue for v in prop.EnumerationValues]
data_type = metadata.get_value_name(display_only=True)
for enum in enum_items:
new = simple_prop.enumerated_value.enumerated_values.add()
setattr(new, data_type, enum)
new.is_selected = enum in selected_enum_items
else:
if prop.is_a("IfcPropertySingleValue"):
value = prop.NominalValue.wrappedValue if prop.NominalValue else None
elif prop.is_a("IfcPhysicalSimpleQuantity"):
value = prop[3]
new_prop = props.properties.add()
new_prop.name = prop.Name
metadata = new_prop.metadata
metadata.set_value(value)
metadata.name = prop.Name
metadata.is_null = value is None
metadata.is_optional = True
metadata.set_value(metadata.get_value_default() if metadata.is_null else value)
@classmethod
def get_prop_template_primitive_type(cls, prop_template):
if prop_template.TemplateType in ["Q_LENGTH", "Q_AREA", "Q_VOLUME", "Q_WEIGHT", "Q_TIME"]:
return "float"
elif prop_template.TemplateType == "Q_COUNT":
return "integer"
return ifcopenshell.util.attribute.get_primitive_type(
tool.Ifc.schema().declaration_by_name(prop_template.PrimaryMeasureType or "IfcLabel")
)
@classmethod
def import_enumerated_value_from_template(cls, prop_template, data, props):
enum_items = [v.wrappedValue for v in prop_template.Enumerators.EnumerationValues]
selected_enum_items = data.get(prop_template.Name, []) or []
prop = props.properties.add()
prop.name = prop_template.Name
prop.value_type = "IfcPropertyEnumeratedValue"
metadata = prop.metadata
metadata.name = prop_template.Name
metadata.is_null = data.get(prop_template.Name, None) is None
metadata.is_optional = True
metadata.is_uri = prop_template.PrimaryMeasureType == "IfcURIReference"
# Cute hack to abuse the metadata to find the Blender data_type
metadata.set_value(enum_items[0])
data_type = metadata.get_value_name()
for enum in enum_items:
new = prop.enumerated_value.enumerated_values.add()
setattr(new, data_type, enum)
new.is_selected = enum in selected_enum_items
@classmethod
def import_single_value_from_template(cls, pset_template, prop_template, data, props):
prop = props.properties.add()
prop.name = prop_template.Name
prop.value_type = "IfcPropertySingleValue"
metadata = prop.metadata
metadata.name = prop_template.Name
metadata.is_null = data.get(prop_template.Name, None) is None
metadata.is_optional = True
metadata.is_uri = prop_template.PrimaryMeasureType == "IfcURIReference"
metadata.has_calculator = bool(mapper.get(pset_template.Name, {}).get(prop_template.Name, None))
metadata.data_type = cls.get_prop_template_primitive_type(prop_template)
special_type = ""
if (
prop_template.PrimaryMeasureType
in (
"IfcPositiveLengthMeasure",
"IfcLengthMeasure",
)
or prop_template.TemplateType == "Q_LENGTH"
):
special_type = "LENGTH"
elif prop_template.PrimaryMeasureType == "IfcAreaMeasure" or prop_template.TemplateType == "Q_AREA":
special_type = "AREA"
elif prop_template.PrimaryMeasureType == "IfcVolumeMeasure" or prop_template.TemplateType == "Q_VOLUME":
special_type = "VOLUME"
metadata.special_type = special_type
if metadata.data_type == "string":
metadata.string_value = "" if metadata.is_null else str(data[prop_template.Name])
elif metadata.data_type == "integer":
metadata.int_value = 0 if metadata.is_null else int(data[prop_template.Name])
elif metadata.data_type == "float":
metadata.float_value = 0.0 if metadata.is_null else float(data[prop_template.Name])
elif metadata.data_type == "boolean":
metadata.bool_value = False if metadata.is_null else bool(data[prop_template.Name])
metadata.ifc_class = pset_template.Name
blenderbim.bim.helper.add_attribute_description(metadata, prop_template)
@classmethod
def import_pset_from_template(cls, pset_template, pset, props):
if pset:
data = ifcopenshell.util.element.get_property_definition(pset)
del data["id"]
else:
data = {}
for prop_template in pset_template.HasPropertyTemplates:
if not prop_template.is_a("IfcSimplePropertyTemplate"):
continue # Other types not yet supported
if prop_template.TemplateType == "P_SINGLEVALUE":
cls.import_single_value_from_template(pset_template, prop_template, data, props)
elif prop_template.TemplateType.startswith("Q_"):
cls.import_single_value_from_template(pset_template, prop_template, data, props)
elif prop_template.TemplateType == "P_ENUMERATEDVALUE":
cls.import_enumerated_value_from_template(prop_template, data, props)
else:
# NOTE: currently unsupported types:
# - P_BOUNDEDVALUE
# - P_LISTVALUE
# - P_REFERENCEVALUE
# - P_TABLEVALUE
pass
@classmethod
def clear_blender_pset_properties(cls, props):
props.properties.clear()
@classmethod
def set_active_pset(cls, props, pset, has_template):
props.active_pset_id = pset.id()
props.active_pset_name = pset.Name
props.active_pset_has_template = has_template
@classmethod
def enable_proposed_pset(cls, props, pset_name, pset_type, has_template):
props.active_pset_id = 0
props.active_pset_name = pset_name or "My_Data"
props.active_pset_type = pset_type
props.active_pset_has_template = has_template
@classmethod
def get_pset_template(cls, name):
return blenderbim.bim.schema.ifc.psetqto.get_by_name(name)
@classmethod
def add_proposed_property(cls, name, value, props):
if props.properties.get(name):
return
prop = props.properties.add()
prop.name = name
metadata = prop.metadata
metadata.set_value(value)
metadata.name = name
metadata.is_null = value is None
metadata.is_optional = True
metadata.set_value(metadata.get_value_default() if metadata.is_null else value)
@classmethod
def cast_string_to_primitive(cls, value: str):
value = value.strip()
if value.lower() == "true":
return True
elif value.lower() == "false":
return False
elif value.lower() == "null" or value == "":
return None
try:
value = int(value)
return value
except:
try:
value = float(value)
return value
except:
return value
return value
+49 -46
View File
@@ -33,10 +33,12 @@ import ifcopenshell.util.cost
import ifcopenshell.util.resource
import blenderbim.bim.schema
import ifcopenshell.util.constraint
from typing import Any, Union
class Resource(blenderbim.core.tool.Resource):
@classmethod
def load_resources(cls):
def load_resources(cls) -> None:
def create_new_resource_li(resource, level_index):
new = bpy.context.scene.BIMResourceTreeProperties.resources.add()
new.ifc_definition_id = resource.id()
@@ -66,42 +68,44 @@ class Resource(blenderbim.core.tool.Resource):
props.is_editing = True
@classmethod
def load_resource_properties(cls):
def load_resource_properties(cls) -> None:
props = bpy.context.scene.BIMResourceProperties
tprops = bpy.context.scene.BIMResourceTreeProperties
props.is_resource_update_enabled = False
for item in tprops.resources:
resource = tool.Ifc.get().by_id(item.ifc_definition_id)
item.name = resource.Name if resource.Name else "Unnamed"
item.schedule_usage = resource.Usage.ScheduleUsage if (resource.Usage and resource.Usage.ScheduleUsage) else 0
item.schedule_usage = (
resource.Usage.ScheduleUsage if (resource.Usage and resource.Usage.ScheduleUsage) else 0
)
props.is_resource_update_enabled = True
@classmethod
def disable_editing_resource(cls):
def disable_editing_resource(cls) -> None:
bpy.context.scene.BIMResourceProperties.active_resource_id = 0
bpy.context.scene.BIMResourceProperties.active_resource_time_id = 0
@classmethod
def disable_resource_editing_ui(cls):
def disable_resource_editing_ui(cls) -> None:
bpy.context.scene.BIMResourceProperties.is_editing = False
@classmethod
def load_resource_attributes(cls, resource):
def load_resource_attributes(cls, resource: ifcopenshell.entity_instance) -> None:
blenderbim.bim.helper.import_attributes2(resource, bpy.context.scene.BIMResourceProperties.resource_attributes)
@classmethod
def enable_editing_resource(cls, resource):
def enable_editing_resource(cls, resource: ifcopenshell.entity_instance) -> None:
props = bpy.context.scene.BIMResourceProperties
props.active_resource_id = resource.id()
props.resource_attributes.clear()
props.editing_resource_type = "ATTRIBUTES"
@classmethod
def get_resource_attributes(cls):
def get_resource_attributes(cls) -> dict[str, Any]:
return blenderbim.bim.helper.export_attributes(bpy.context.scene.BIMResourceProperties.resource_attributes)
@classmethod
def enable_editing_resource_time(cls, resource):
def enable_editing_resource_time(cls, resource: ifcopenshell.entity_instance) -> None:
props = bpy.context.scene.BIMResourceProperties
props.resource_time_attributes.clear()
props.active_resource_time_id = resource.Usage.id()
@@ -109,11 +113,11 @@ class Resource(blenderbim.core.tool.Resource):
props.editing_resource_type = "USAGE"
@classmethod
def get_resource_time(cls, resource):
return resource.Usage if resource.Usage else None
def get_resource_time(cls, resource: ifcopenshell.entity_instance) -> Union[ifcopenshell.entity_instance, None]:
return resource.Usage or None
@classmethod
def load_resource_time_attributes(cls, resource_time):
def load_resource_time_attributes(cls, resource_time: ifcopenshell.entity_instance) -> None:
def callback(name, prop, data):
if prop.data_type == "string":
if isinstance(data[name], datetime):
@@ -128,7 +132,7 @@ class Resource(blenderbim.core.tool.Resource):
)
@classmethod
def get_resource_time_attributes(cls):
def get_resource_time_attributes(cls) -> dict[str, Any]:
def callback(attributes, prop):
if "Start" in prop.name or "Finish" in prop.name or prop.name == "StatusTime":
if prop.is_null:
@@ -147,20 +151,19 @@ class Resource(blenderbim.core.tool.Resource):
return blenderbim.bim.helper.export_attributes(props.resource_time_attributes, callback)
@classmethod
def enable_editing_resource_costs(cls, resource):
def enable_editing_resource_costs(cls, resource: ifcopenshell.entity_instance) -> None:
props = bpy.context.scene.BIMResourceProperties
props.active_resource_id = resource.id()
props.editing_resource_type = "COSTS"
resource
@classmethod
def disable_editing_resource_cost_value(cls):
def disable_editing_resource_cost_value(cls) -> None:
props = bpy.context.scene.BIMResourceProperties
props.active_cost_value_id = 0
props.cost_value_editing_type = ""
@classmethod
def enable_editing_resource_cost_value_formula(cls, cost_value):
def enable_editing_resource_cost_value_formula(cls, cost_value: ifcopenshell.entity_instance) -> None:
props = bpy.context.scene.BIMResourceProperties
props.cost_value_attributes.clear()
props.active_cost_value_id = cost_value.id()
@@ -168,7 +171,7 @@ class Resource(blenderbim.core.tool.Resource):
props.cost_value_formula = ifcopenshell.util.cost.serialise_cost_value(cost_value) if cost_value else ""
@classmethod
def load_cost_value_attributes(cls, cost_value):
def load_cost_value_attributes(cls, cost_value: ifcopenshell.entity_instance):
def callback(name, prop, data):
if name == "AppliedValue":
# TODO: for now, only support simple IfcValues (which are effectively IfcMonetaryMeasure)
@@ -221,7 +224,7 @@ class Resource(blenderbim.core.tool.Resource):
blenderbim.bim.helper.import_attributes2(cost_value, props.cost_value_attributes, callback)
@classmethod
def enable_editing_cost_value_attributes(cls, cost_value):
def enable_editing_cost_value_attributes(cls, cost_value: ifcopenshell.entity_instance) -> None:
props = bpy.context.scene.BIMResourceProperties
props.cost_value_attributes.clear()
props.active_cost_value_id = cost_value.id()
@@ -232,7 +235,7 @@ class Resource(blenderbim.core.tool.Resource):
return bpy.context.scene.BIMResourceProperties.cost_value_formula
@classmethod
def get_resource_cost_value_attributes(cls):
def get_resource_cost_value_attributes(cls) -> dict[str, Any]:
def callback(attributes, prop):
if prop.name == "UnitBasisValue":
if prop.is_null:
@@ -257,28 +260,28 @@ class Resource(blenderbim.core.tool.Resource):
)
@classmethod
def enable_editing_resource_base_quantity(cls, resource):
def enable_editing_resource_base_quantity(cls, resource: ifcopenshell.entity_instance) -> None:
props = bpy.context.scene.BIMResourceProperties
props.active_resource_id = resource.id()
props.editing_resource_type = "QUANTITY"
@classmethod
def enable_editing_resource_quantity(cls, resource_quantity):
def enable_editing_resource_quantity(cls, resource_quantity: ifcopenshell.entity_instance) -> None:
props = bpy.context.scene.BIMResourceProperties
props.quantity_attributes.clear()
props.is_editing_quantity = True
blenderbim.bim.helper.import_attributes2(resource_quantity, props.quantity_attributes)
@classmethod
def disable_editing_resource_quantity(cls):
def disable_editing_resource_quantity(cls) -> None:
bpy.context.scene.BIMResourceProperties.is_editing_quantity = False
@classmethod
def get_resource_quantity_attributes(cls):
def get_resource_quantity_attributes(cls) -> dict[str, Any]:
return blenderbim.bim.helper.export_attributes(bpy.context.scene.BIMResourceProperties.quantity_attributes)
@classmethod
def expand_resource(cls, resource):
def expand_resource(cls, resource: ifcopenshell.entity_instance) -> None:
props = bpy.context.scene.BIMResourceProperties
contracted_resources = json.loads(props.contracted_resources)
if not resource.id() in contracted_resources:
@@ -287,14 +290,14 @@ class Resource(blenderbim.core.tool.Resource):
props.contracted_resources = json.dumps(contracted_resources)
@classmethod
def contract_resource(cls, resource):
def contract_resource(cls, resource: ifcopenshell.entity_instance) -> None:
props = bpy.context.scene.BIMResourceProperties
contracted_resources = json.loads(props.contracted_resources)
contracted_resources.append(resource.id())
props.contracted_resources = json.dumps(contracted_resources)
@classmethod
def import_resources(cls, file_path):
def import_resources(cls, file_path: str) -> None:
from ifc4d.csv2ifc import Csv2Ifc
start = time.time()
@@ -305,7 +308,7 @@ class Resource(blenderbim.core.tool.Resource):
print("Importing Resources CSV finished in {:.2f} seconds".format(time.time() - start))
@classmethod
def get_highlighted_resource(cls):
def get_highlighted_resource(cls) -> Union[ifcopenshell.entity_instance, None]:
resources = len(bpy.context.scene.BIMResourceTreeProperties.resources)
if resources and resources > bpy.context.scene.BIMResourceProperties.active_resource_index:
return tool.Ifc.get().by_id(
@@ -315,7 +318,7 @@ class Resource(blenderbim.core.tool.Resource):
)
@classmethod
def clear_productivity_data(cls, props):
def clear_productivity_data(cls, props: bpy.types.PropertyGroup) -> None:
for duration_prop in props.quantity_consumed or []:
if duration_prop.name == "BaseQuantityConsumed":
duration_prop.years = 0
@@ -328,7 +331,7 @@ class Resource(blenderbim.core.tool.Resource):
props.quantity_produced_name = ""
@classmethod
def load_productivity_data(cls):
def load_productivity_data(cls) -> None:
duration_props = None
for collection_prop in bpy.context.scene.BIMResourceProductivity.quantity_consumed:
duration_props = collection_prop if collection_prop.name == "BaseQuantityConsumed" else None
@@ -358,7 +361,7 @@ class Resource(blenderbim.core.tool.Resource):
duration_props.seconds = durations_attributes["seconds"]
@classmethod
def get_productivity_attributes(cls):
def get_productivity_attributes(cls) -> dict[str, Any]:
props = bpy.context.scene.BIMResourceProductivity
productivity = {}
if props.quantity_consumed:
@@ -370,7 +373,9 @@ class Resource(blenderbim.core.tool.Resource):
return productivity
@classmethod
def get_productivity(cls, resource, should_inherit=False):
def get_productivity(
cls, resource: ifcopenshell.entity_instance, should_inherit: bool = False
) -> ifcopenshell.util.resource.PRODUCTIVITY_PSET_DATA:
return ifcopenshell.util.resource.get_productivity(resource, should_inherit=should_inherit)
@classmethod
@@ -380,29 +385,29 @@ class Resource(blenderbim.core.tool.Resource):
return
return tool.Ifc.run(
"pset.edit_pset",
pset= tool.Ifc.get().by_id(productivity["id"]),
pset=tool.Ifc.get().by_id(productivity["id"]),
properties=attributes,
)
@classmethod
def get_constraints(cls, resource):
def get_constraints(cls, resource: ifcopenshell.entity_instance) -> list[ifcopenshell.entity_instance]:
return ifcopenshell.util.constraint.get_constraints(product=resource)
@classmethod
def get_metrics(cls, constraint):
def get_metrics(cls, constraint: ifcopenshell.entity_instance) -> list[ifcopenshell.entity_instance]:
return ifcopenshell.util.constraint.get_metrics(constraint)
@classmethod
def get_metric_reference(cls, metric, is_deep=True):
def get_metric_reference(cls, metric: ifcopenshell.entity_instance, is_deep: bool = True):
return ifcopenshell.util.constraint.get_metric_reference(metric, is_deep=is_deep)
@classmethod
def has_metric_constraint(cls, resource, attribute):
def has_metric_constraint(cls, resource: ifcopenshell.entity_instance, attribute):
metrics = ifcopenshell.util.constraint.get_metric_constraints(resource, attribute)
return True if metrics else False
@classmethod
def run_edit_resource_time(cls, resource, attributes):
def run_edit_resource_time(cls, resource: ifcopenshell.entity_instance, attributes: dict[str, Any]) -> None:
if not resource.Usage:
tool.Ifc.run(
"resource.add_resource_time",
@@ -411,7 +416,7 @@ class Resource(blenderbim.core.tool.Resource):
tool.Ifc.run("resource.edit_resource_time", resource_time=resource.Usage, attributes=attributes)
@classmethod
def go_to_resource(cls, resource):
def go_to_resource(cls, resource: ifcopenshell.entity_instance) -> None:
def get_ancestors_ids(resource):
ids = []
for rel in resource.Nests or []:
@@ -427,24 +432,22 @@ class Resource(blenderbim.core.tool.Resource):
bpy.context.scene.BIMResourceProperties.contracted_resources = json.dumps(contracted_resources)
cls.load_resources()
resource_props = bpy.context.scene.BIMResourceTreeProperties
expanded_resources = [item.ifc_definition_id for item in resource_props.resources]
bpy.context.scene.BIMResourceProperties.active_resource_index = expanded_resources.index(resource.id())
@classmethod
def run_calculate_resource_usage(cls, resource):
def run_calculate_resource_usage(cls, resource: ifcopenshell.entity_instance) -> None:
tool.Ifc.run("resource.calculate_resource_usage", resource=resource)
@classmethod
def get_task_assignments(cls, resource):
def get_task_assignments(cls, resource: ifcopenshell.entity_instance) -> Union[ifcopenshell.entity_instance, None]:
return ifcopenshell.util.resource.get_task_assignments(resource)
@classmethod
def get_nested_resources(cls, resource):
def get_nested_resources(cls, resource: ifcopenshell.entity_instance) -> list[ifcopenshell.entity_instance]:
return ifcopenshell.util.resource.get_nested_resources(resource)
@classmethod
def is_attribute_locked(cls, resource, attribute):
return ifcopenshell.util.constraint.is_attribute_locked(resource, attribute)
def is_attribute_locked(cls, resource: ifcopenshell.entity_instance, attribute) -> bool:
return ifcopenshell.util.constraint.is_attribute_locked(resource, attribute)
+8 -3
View File
@@ -29,6 +29,7 @@ import blenderbim.core.style
import blenderbim.tool as tool
from mathutils import Vector
from blenderbim.bim.module.model.opening import FilledOpeningGenerator
from typing import Union, Optional
class Root(blenderbim.core.tool.Root):
@@ -129,7 +130,7 @@ class Root(blenderbim.core.tool.Root):
return ifcopenshell.util.representation.get_representation(element, context=context.ContextType)
@classmethod
def get_element_type(cls, element):
def get_element_type(cls, element: ifcopenshell.entity_instance) -> Union[ifcopenshell.entity_instance, None]:
return ifcopenshell.util.element.get_type(element)
@classmethod
@@ -282,8 +283,12 @@ class Root(blenderbim.core.tool.Root):
@classmethod
def run_geometry_add_representation(
cls, obj=None, context=None, ifc_representation_class=None, profile_set_usage=None
):
cls,
obj: bpy.types.Object,
context: ifcopenshell.entity_instance,
ifc_representation_class: Optional[str] = None,
profile_set_usage: Optional[ifcopenshell.entity_instance] = None,
) -> ifcopenshell.entity_instance:
return blenderbim.core.geometry.add_representation(
tool.Ifc,
tool.Geometry,
+118 -104
View File
@@ -36,11 +36,12 @@ import blenderbim.bim.helper
import blenderbim.bim.module.sequence.helper as helper
from dateutil import parser
from datetime import datetime
from typing import Optional, Any, Union
class Sequence(blenderbim.core.tool.Sequence):
@classmethod
def get_work_plan_attributes(cls):
def get_work_plan_attributes(cls) -> dict[str, Any]:
def callback(attributes, prop):
if "Date" in prop.name or "Time" in prop.name:
if prop.is_null:
@@ -59,7 +60,7 @@ class Sequence(blenderbim.core.tool.Sequence):
return blenderbim.bim.helper.export_attributes(props.work_plan_attributes, callback)
@classmethod
def load_work_plan_attributes(cls, work_plan):
def load_work_plan_attributes(cls, work_plan: ifcopenshell.entity_instance) -> None:
def callback(name, prop, data):
if name in ["CreationDate", "StartTime", "FinishTime"]:
prop.string_value = "" if prop.is_null else data[name]
@@ -70,23 +71,23 @@ class Sequence(blenderbim.core.tool.Sequence):
blenderbim.bim.helper.import_attributes2(work_plan, props.work_plan_attributes, callback)
@classmethod
def enable_editing_work_plan(cls, work_plan):
def enable_editing_work_plan(cls, work_plan: Union[ifcopenshell.entity_instance, None]) -> None:
if work_plan:
bpy.context.scene.BIMWorkPlanProperties.active_work_plan_id = work_plan.id()
bpy.context.scene.BIMWorkPlanProperties.editing_type = "ATTRIBUTES"
@classmethod
def disable_editing_work_plan(cls):
def disable_editing_work_plan(cls) -> None:
bpy.context.scene.BIMWorkPlanProperties.active_work_plan_id = 0
@classmethod
def enable_editing_work_plan_schedules(cls, work_plan):
def enable_editing_work_plan_schedules(cls, work_plan: Union[ifcopenshell.entity_instance, None]) -> None:
if work_plan:
bpy.context.scene.BIMWorkPlanProperties.active_work_plan_id = work_plan.id()
bpy.context.scene.BIMWorkPlanProperties.editing_type = "SCHEDULES"
@classmethod
def get_work_schedule_attributes(cls):
def get_work_schedule_attributes(cls) -> dict[str, Any]:
def callback(attributes, prop):
if "Date" in prop.name or "Time" in prop.name:
if prop.is_null:
@@ -105,7 +106,7 @@ class Sequence(blenderbim.core.tool.Sequence):
return blenderbim.bim.helper.export_attributes(props.work_schedule_attributes, callback)
@classmethod
def load_work_schedule_attributes(cls, work_schedule):
def load_work_schedule_attributes(cls, work_schedule: ifcopenshell.entity_instance) -> None:
def callback(name, prop, data):
if name in ["CreationDate", "StartTime", "FinishTime"]:
prop.string_value = "" if prop.is_null else data[name]
@@ -116,23 +117,23 @@ class Sequence(blenderbim.core.tool.Sequence):
blenderbim.bim.helper.import_attributes2(work_schedule, props.work_schedule_attributes, callback)
@classmethod
def enable_editing_work_schedule(cls, work_schedule):
def enable_editing_work_schedule(cls, work_schedule: ifcopenshell.entity_instance) -> None:
bpy.context.scene.BIMWorkScheduleProperties.active_work_schedule_id = work_schedule.id()
bpy.context.scene.BIMWorkScheduleProperties.editing_type = "WORK_SCHEDULE"
@classmethod
def disable_editing_work_schedule(cls):
def disable_editing_work_schedule(cls) -> None:
bpy.context.scene.BIMWorkScheduleProperties.active_work_schedule_id = 0
@classmethod
def enable_editing_work_schedule_tasks(cls, work_schedule):
def enable_editing_work_schedule_tasks(cls, work_schedule: Union[ifcopenshell.entity_instance, None]) -> None:
if work_schedule:
props = bpy.context.scene.BIMWorkScheduleProperties
props.active_work_schedule_id = work_schedule.id()
props.editing_type = "TASKS"
@classmethod
def load_task_tree(cls, work_schedule):
def load_task_tree(cls, work_schedule: ifcopenshell.entity_instance) -> None:
bpy.context.scene.BIMTaskTreeProperties.tasks.clear()
props = bpy.context.scene.BIMWorkScheduleProperties
cls.contracted_tasks = json.loads(props.contracted_tasks)
@@ -142,7 +143,7 @@ class Sequence(blenderbim.core.tool.Sequence):
cls.create_new_task_li(related_object_id, 0)
@classmethod
def get_sorted_tasks_ids(cls, tasks):
def get_sorted_tasks_ids(cls, tasks: list[ifcopenshell.entity_instance]) -> list[int]:
def get_sort_key(task):
# Sorting only applies to actual tasks, not the WBS
# for rel in task.IsNestedBy:
@@ -170,7 +171,7 @@ class Sequence(blenderbim.core.tool.Sequence):
return related_object_ids
@classmethod
def create_new_task_li(cls, related_object_id, level_index):
def create_new_task_li(cls, related_object_id: int, level_index: int) -> None:
task = tool.Ifc.get().by_id(related_object_id)
new = bpy.context.scene.BIMTaskTreeProperties.tasks.add()
new.ifc_definition_id = related_object_id
@@ -182,8 +183,9 @@ class Sequence(blenderbim.core.tool.Sequence):
for related_object_id in cls.get_sorted_tasks_ids(ifcopenshell.util.sequence.get_nested_tasks(task)):
cls.create_new_task_li(related_object_id, level_index + 1)
# TODO: task argument is never used?
@classmethod
def load_task_properties(cls, task=None):
def load_task_properties(cls, task: Optional[ifcopenshell.entity_instance] = None) -> None:
props = bpy.context.scene.BIMWorkScheduleProperties
task_props = bpy.context.scene.BIMTaskTreeProperties
tasks_with_visual_bar = cls.get_task_bar_list()
@@ -248,24 +250,24 @@ class Sequence(blenderbim.core.tool.Sequence):
props.is_task_update_enabled = True
@classmethod
def get_active_work_schedule(cls):
def get_active_work_schedule(cls) -> Union[ifcopenshell.entity_instance, None]:
if not bpy.context.scene.BIMWorkScheduleProperties.active_work_schedule_id:
return None
return tool.Ifc.get().by_id(bpy.context.scene.BIMWorkScheduleProperties.active_work_schedule_id)
@classmethod
def expand_task(cls, task):
def expand_task(cls, task: ifcopenshell.entity_instance) -> None:
props = bpy.context.scene.BIMWorkScheduleProperties
contracted_tasks = json.loads(props.contracted_tasks)
contracted_tasks.remove(task.id())
props.contracted_tasks = json.dumps(contracted_tasks)
@classmethod
def expand_all_tasks(cls):
def expand_all_tasks(cls) -> None:
bpy.context.scene.BIMWorkScheduleProperties.contracted_tasks = json.dumps([])
@classmethod
def contract_all_tasks(cls):
def contract_all_tasks(cls) -> None:
props = bpy.context.scene.BIMWorkScheduleProperties
contracted_tasks = json.loads(props.contracted_tasks)
for task_item in bpy.context.scene.BIMTaskTreeProperties.tasks:
@@ -274,18 +276,18 @@ class Sequence(blenderbim.core.tool.Sequence):
props.contracted_tasks = json.dumps(contracted_tasks)
@classmethod
def contract_task(cls, task):
def contract_task(cls, task: ifcopenshell.entity_instance) -> None:
props = bpy.context.scene.BIMWorkScheduleProperties
contracted_tasks = json.loads(props.contracted_tasks)
contracted_tasks.append(task.id())
props.contracted_tasks = json.dumps(contracted_tasks)
@classmethod
def disable_work_schedule(cls):
def disable_work_schedule(cls) -> None:
bpy.context.scene.BIMWorkScheduleProperties.active_work_schedule_id = 0
@classmethod
def disable_selecting_deleted_task(cls):
def disable_selecting_deleted_task(cls) -> None:
props = bpy.context.scene.BIMWorkScheduleProperties
if props.active_task_id not in [
task.ifc_definition_id for task in bpy.context.scene.BIMTaskTreeProperties.tasks
@@ -294,7 +296,7 @@ class Sequence(blenderbim.core.tool.Sequence):
bpy.context.scene.BIMWorkScheduleProperties.active_task_time_id = 0
@classmethod
def get_checked_tasks(cls):
def get_checked_tasks(cls) -> list[ifcopenshell.entity_instance]:
return [
tool.Ifc.get().by_id(task.ifc_definition_id)
for task in bpy.context.scene.BIMTaskTreeProperties.tasks
@@ -302,39 +304,39 @@ class Sequence(blenderbim.core.tool.Sequence):
] or []
@classmethod
def get_task_attribute_value(cls, attribute_name):
def get_task_attribute_value(cls, attribute_name: str) -> Any:
return bpy.context.scene.BIMWorkScheduleProperties.task_attributes.get(attribute_name).get_value()
@classmethod
def get_active_task(cls):
def get_active_task(cls) -> ifcopenshell.entity_instance:
return tool.Ifc.get().by_id(bpy.context.scene.BIMWorkScheduleProperties.active_task_id)
@classmethod
def get_active_work_time(cls):
def get_active_work_time(cls) -> ifcopenshell.entity_instance:
return tool.Ifc.get().by_id(bpy.context.scene.BIMWorkCalendarProperties.active_work_time_id)
@classmethod
def get_task_time(cls, task):
return task.TaskTime if task.TaskTime else None
def get_task_time(cls, task: ifcopenshell.entity_instance) -> Union[ifcopenshell.entity_instance, None]:
return task.TaskTime or None
@classmethod
def load_task_attributes(cls, task):
def load_task_attributes(cls, task: ifcopenshell.entity_instance) -> None:
props = bpy.context.scene.BIMWorkScheduleProperties
props.task_attributes.clear()
blenderbim.bim.helper.import_attributes2(task, props.task_attributes)
@classmethod
def enable_editing_task_attributes(cls, task):
def enable_editing_task_attributes(cls, task: ifcopenshell.entity_instance) -> None:
props = bpy.context.scene.BIMWorkScheduleProperties
props.active_task_id = task.id()
props.editing_task_type = "ATTRIBUTES"
@classmethod
def get_task_attributes(cls):
def get_task_attributes(cls) -> dict[str, Any]:
return blenderbim.bim.helper.export_attributes(bpy.context.scene.BIMWorkScheduleProperties.task_attributes)
@classmethod
def load_task_time_attributes(cls, task_time):
def load_task_time_attributes(cls, task_time: ifcopenshell.entity_instance) -> None:
def callback(name, prop, data):
if prop and prop.data_type == "string":
duration_props = bpy.context.scene.BIMWorkScheduleProperties.durations_attributes.add()
@@ -358,20 +360,20 @@ class Sequence(blenderbim.core.tool.Sequence):
blenderbim.bim.helper.import_attributes2(task_time, props.task_time_attributes, callback)
@classmethod
def enable_editing_task_time(cls, task):
def enable_editing_task_time(cls, task: ifcopenshell.entity_instance) -> None:
props = bpy.context.scene.BIMWorkScheduleProperties
props.active_task_id = task.id()
props.active_task_time_id = task.TaskTime.id()
props.editing_task_type = "TASKTIME"
@classmethod
def disable_editing_task(cls):
def disable_editing_task(cls) -> None:
bpy.context.scene.BIMWorkScheduleProperties.active_task_id = 0
bpy.context.scene.BIMWorkScheduleProperties.active_task_time_id = 0
bpy.context.scene.BIMWorkScheduleProperties.editing_task_type = ""
@classmethod
def get_task_time_attributes(cls):
def get_task_time_attributes(cls) -> dict[str, Any]:
def callback(attributes, prop):
if "Start" in prop.name or "Finish" in prop.name or prop.name == "StatusTime":
if prop.is_null:
@@ -400,7 +402,7 @@ class Sequence(blenderbim.core.tool.Sequence):
return blenderbim.bim.helper.export_attributes(props.task_time_attributes, callback)
@classmethod
def load_task_resources(cls, task):
def load_task_resources(cls, task: ifcopenshell.entity_instance) -> None:
props = bpy.context.scene.BIMWorkScheduleProperties
rprops = bpy.context.scene.BIMResourceProperties
props.task_resources.clear()
@@ -413,36 +415,38 @@ class Sequence(blenderbim.core.tool.Sequence):
rprops.is_resource_update_enabled = True
@classmethod
def get_task_inputs(cls, task):
def get_task_inputs(cls, task: ifcopenshell.entity_instance) -> list[ifcopenshell.entity_instance]:
is_deep = bpy.context.scene.BIMWorkScheduleProperties.show_nested_inputs
return ifcopenshell.util.sequence.get_task_inputs(task, is_deep)
@classmethod
def get_task_outputs(cls, task):
def get_task_outputs(cls, task: ifcopenshell.entity_instance) -> list[ifcopenshell.entity_instance]:
is_deep = bpy.context.scene.BIMWorkScheduleProperties.show_nested_outputs
return ifcopenshell.util.sequence.get_task_outputs(task, is_deep)
@classmethod
def are_entities_same_class(cls, entities):
def are_entities_same_class(cls, entities: list[ifcopenshell.entity_instance]) -> bool:
if not entities:
return False
if len(entities) == 1:
return True
first = entities[0]
first_class = entities[0].is_a()
for entity in entities:
if entity.is_a() != first.is_a():
if entity.is_a() != first_class:
return False
return True
@classmethod
def get_task_resources(cls, task):
def get_task_resources(
cls, task: Union[ifcopenshell.entity_instance, None]
) -> Union[list[ifcopenshell.entity_instance], None]:
if not task:
return
is_deep = bpy.context.scene.BIMWorkScheduleProperties.show_nested_resources
return ifcopenshell.util.sequence.get_task_resources(task, is_deep)
@classmethod
def load_task_inputs(cls, inputs):
def load_task_inputs(cls, inputs: list[ifcopenshell.entity_instance]) -> None:
props = bpy.context.scene.BIMWorkScheduleProperties
props.task_inputs.clear()
for input in inputs:
@@ -451,7 +455,7 @@ class Sequence(blenderbim.core.tool.Sequence):
new.name = input.Name or "Unnamed"
@classmethod
def load_task_outputs(cls, outputs):
def load_task_outputs(cls, outputs: list[ifcopenshell.entity_instance]) -> None:
props = bpy.context.scene.BIMWorkScheduleProperties
props.task_outputs.clear()
if outputs:
@@ -461,7 +465,7 @@ class Sequence(blenderbim.core.tool.Sequence):
new.name = output.Name or "Unnamed"
@classmethod
def get_highlighted_task(cls):
def get_highlighted_task(cls) -> Union[ifcopenshell.entity_instance, None]:
tasks = bpy.context.scene.BIMTaskTreeProperties.tasks
if len(tasks) and len(tasks) > bpy.context.scene.BIMWorkScheduleProperties.active_task_index:
return tool.Ifc.get().by_id(
@@ -469,47 +473,42 @@ class Sequence(blenderbim.core.tool.Sequence):
)
@classmethod
def get_direct_nested_tasks(cls, task):
def get_direct_nested_tasks(cls, task: ifcopenshell.entity_instance) -> list[ifcopenshell.entity_instance]:
return ifcopenshell.util.sequence.get_nested_tasks(task)
@classmethod
def get_direct_task_outputs(cls, task):
def get_direct_task_outputs(cls, task: ifcopenshell.entity_instance) -> list[ifcopenshell.entity_instance]:
return ifcopenshell.util.sequence.get_direct_task_outputs(task)
@classmethod
def get_task_outputs(cls, task):
is_deep = bpy.context.scene.BIMWorkScheduleProperties.show_nested_outputs
return ifcopenshell.util.sequence.get_task_outputs(task, is_deep)
@classmethod
def enable_editing_work_calendar_times(cls, work_calendar):
def enable_editing_work_calendar_times(cls, work_calendar: ifcopenshell.entity_instance) -> None:
props = bpy.context.scene.BIMWorkCalendarProperties
props.active_work_calendar_id = work_calendar.id()
props.editing_type = "WORKTIMES"
@classmethod
def load_work_calendar_attributes(cls, work_calendar):
def load_work_calendar_attributes(cls, work_calendar: ifcopenshell.entity_instance) -> dict[str, Any]:
props = bpy.context.scene.BIMWorkCalendarProperties
props.work_calendar_attributes.clear()
return blenderbim.bim.helper.import_attributes2(work_calendar, props.work_calendar_attributes)
@classmethod
def enable_editing_work_calendar(cls, work_calendar):
def enable_editing_work_calendar(cls, work_calendar: ifcopenshell.entity_instance) -> None:
bpy.context.scene.BIMWorkCalendarProperties.active_work_calendar_id = work_calendar.id()
bpy.context.scene.BIMWorkCalendarProperties.editing_type = "ATTRIBUTES"
@classmethod
def disable_editing_work_calendar(cls):
def disable_editing_work_calendar(cls) -> None:
bpy.context.scene.BIMWorkCalendarProperties.active_work_calendar_id = 0
@classmethod
def get_work_calendar_attributes(cls):
def get_work_calendar_attributes(cls) -> dict[str, Any]:
return blenderbim.bim.helper.export_attributes(
bpy.context.scene.BIMWorkCalendarProperties.work_calendar_attributes
)
@classmethod
def load_work_time_attributes(cls, work_time):
def load_work_time_attributes(cls, work_time: ifcopenshell.entity_instance) -> None:
def callback(name, prop, data):
if name in ["Start", "Finish"]:
prop.string_value = "" if prop.is_null else data[name]
@@ -521,7 +520,7 @@ class Sequence(blenderbim.core.tool.Sequence):
blenderbim.bim.helper.import_attributes2(work_time, props.work_time_attributes, callback)
@classmethod
def enable_editing_work_time(cls, work_time):
def enable_editing_work_time(cls, work_time: ifcopenshell.entity_instance) -> None:
def initialise_recurrence_components(props):
if len(props.day_components) == 0:
for i in range(0, 31):
@@ -568,7 +567,7 @@ class Sequence(blenderbim.core.tool.Sequence):
props.editing_type = "WORKTIMES"
@classmethod
def get_work_time_attributes(cls):
def get_work_time_attributes(cls) -> dict[str, Any]:
def callback(attributes, prop):
if "Start" in prop.name or "Finish" in prop.name:
if prop.is_null:
@@ -608,11 +607,11 @@ class Sequence(blenderbim.core.tool.Sequence):
return attributes
@classmethod
def disable_editing_work_time(cls):
def disable_editing_work_time(cls) -> None:
bpy.context.scene.BIMWorkCalendarProperties.active_work_time_id = 0
@classmethod
def get_recurrence_pattern_times(cls):
def get_recurrence_pattern_times(cls) -> Union[tuple[datetime, datetime], None]:
props = bpy.context.scene.BIMWorkCalendarProperties
try:
start_time = parser.parse(props.start_time)
@@ -622,40 +621,40 @@ class Sequence(blenderbim.core.tool.Sequence):
return # improve UI / refactor to add user hints
@classmethod
def reset_time_period(cls):
def reset_time_period(cls) -> None:
bpy.context.scene.BIMWorkCalendarProperties.start_time = ""
bpy.context.scene.BIMWorkCalendarProperties.end_time = ""
@classmethod
def enable_editing_task_calendar(cls, task):
def enable_editing_task_calendar(cls, task: ifcopenshell.entity_instance) -> None:
props = bpy.context.scene.BIMWorkScheduleProperties
props.active_task_id = task.id()
props.editing_task_type = "CALENDAR"
@classmethod
def enable_editing_task_sequence(cls):
def enable_editing_task_sequence(cls) -> None:
props = bpy.context.scene.BIMWorkScheduleProperties
props.editing_task_type = "SEQUENCE"
@classmethod
def disable_editing_task_time(cls):
def disable_editing_task_time(cls) -> None:
bpy.context.scene.BIMWorkScheduleProperties.active_task_id = 0
bpy.context.scene.BIMWorkScheduleProperties.active_task_time_id = 0
@classmethod
def load_rel_sequence_attributes(cls, rel_sequence):
def load_rel_sequence_attributes(cls, rel_sequence: ifcopenshell.entity_instance) -> None:
props = bpy.context.scene.BIMWorkScheduleProperties
props.sequence_attributes.clear()
blenderbim.bim.helper.import_attributes2(rel_sequence, props.sequence_attributes)
@classmethod
def enable_editing_rel_sequence_attributes(cls, rel_sequence):
def enable_editing_rel_sequence_attributes(cls, rel_sequence: ifcopenshell.entity_instance) -> None:
props = bpy.context.scene.BIMWorkScheduleProperties
props.active_sequence_id = rel_sequence.id()
props.editing_sequence_type = "ATTRIBUTES"
@classmethod
def load_lag_time_attributes(cls, lag_time):
def load_lag_time_attributes(cls, lag_time: ifcopenshell.entity_instance) -> None:
def callback(name, prop, data):
if name == "LagValue":
prop = bpy.context.scene.BIMWorkScheduleProperties.lag_time_attributes.add()
@@ -673,21 +672,21 @@ class Sequence(blenderbim.core.tool.Sequence):
blenderbim.bim.helper.import_attributes2(lag_time, props.lag_time_attributes, callback)
@classmethod
def enable_editing_sequence_lag_time(cls, rel_sequence):
def enable_editing_sequence_lag_time(cls, rel_sequence: ifcopenshell.entity_instance) -> None:
props = bpy.context.scene.BIMWorkScheduleProperties
props.active_sequence_id = rel_sequence.id()
props.editing_sequence_type = "LAG_TIME"
@classmethod
def get_rel_sequence_attributes(cls):
def get_rel_sequence_attributes(cls) -> dict[str, Any]:
return blenderbim.bim.helper.export_attributes(bpy.context.scene.BIMWorkScheduleProperties.sequence_attributes)
@classmethod
def disable_editing_rel_sequence(cls):
def disable_editing_rel_sequence(cls) -> None:
bpy.context.scene.BIMWorkScheduleProperties.active_sequence_id = 0
@classmethod
def get_lag_time_attributes(cls):
def get_lag_time_attributes(cls) -> dict[str, Any]:
return blenderbim.bim.helper.export_attributes(bpy.context.scene.BIMWorkScheduleProperties.lag_time_attributes)
@classmethod
@@ -698,14 +697,14 @@ class Sequence(blenderbim.core.tool.Sequence):
obj.select_set(True) if obj else None
@classmethod
def add_task_column(cls, column_type, name, data_type):
def add_task_column(cls, column_type: str, name: str, data_type: str):
props = bpy.context.scene.BIMWorkScheduleProperties
new = props.columns.add()
new.name = f"{column_type}.{name}"
new.data_type = data_type
@classmethod
def setup_default_task_columns(cls):
def setup_default_task_columns(cls) -> None:
props = bpy.context.scene.BIMWorkScheduleProperties
props.columns.clear()
default_columns = ["ScheduleStart", "ScheduleFinish", "ScheduleDuration"]
@@ -715,14 +714,14 @@ class Sequence(blenderbim.core.tool.Sequence):
new.data_type = "string"
@classmethod
def remove_task_column(cls, name):
def remove_task_column(cls, name: str) -> None:
props = bpy.context.scene.BIMWorkScheduleProperties
props.columns.remove(props.columns.find(name))
if props.sort_column == name:
props.sort_column = ""
@classmethod
def set_task_sort_column(cls, column):
def set_task_sort_column(cls, column: str) -> None:
props = bpy.context.scene.BIMWorkScheduleProperties
props.sort_column = column
@@ -743,7 +742,7 @@ class Sequence(blenderbim.core.tool.Sequence):
return related_tasks
@classmethod
def get_work_schedule(cls, task):
def get_work_schedule(cls, task: ifcopenshell.entity_instance) -> Union[ifcopenshell.entity_instance, None]:
for rel in task.HasAssignments or []:
if rel.is_a("IfcRelAssignsToControl") and rel.RelatingControl.is_a("IfcWorkSchedule"):
return rel.RelatingControl
@@ -779,8 +778,9 @@ class Sequence(blenderbim.core.tool.Sequence):
expanded_tasks = [item.ifc_definition_id for item in task_props.tasks]
bpy.context.scene.BIMWorkScheduleProperties.active_task_index = expanded_tasks.index(task.id()) or 0
# TODO: proper typing
@classmethod
def guess_date_range(cls, work_schedule):
def guess_date_range(cls, work_schedule: ifcopenshell.entity_instance) -> tuple[Any, Any]:
return ifcopenshell.util.sequence.guess_date_range(work_schedule)
@classmethod
@@ -792,7 +792,7 @@ class Sequence(blenderbim.core.tool.Sequence):
props.visualisation_finish = ifcopenshell.util.date.canonicalise_time(finish_date)
@classmethod
def get_animation_bar_tasks(cls):
def get_animation_bar_tasks(cls) -> list[ifcopenshell.entity_instance]:
return [tool.Ifc.get().by_id(task_id) for task_id in cls.get_task_bar_list()]
@classmethod
@@ -830,12 +830,16 @@ class Sequence(blenderbim.core.tool.Sequence):
def create_task_bar_data(tasks, vertical_increment, collection):
props = bpy.context.scene.BIMWorkScheduleProperties
settings = {
"viz_start": parser.parse(props.visualisation_start, dayfirst=True, fuzzy=True)
if props.visualisation_start
else None,
"viz_finish": parser.parse(props.visualisation_finish, dayfirst=True, fuzzy=True)
if props.visualisation_finish
else None,
"viz_start": (
parser.parse(props.visualisation_start, dayfirst=True, fuzzy=True)
if props.visualisation_start
else None
),
"viz_finish": (
parser.parse(props.visualisation_finish, dayfirst=True, fuzzy=True)
if props.visualisation_finish
else None
),
"start_frame": bpy.context.scene.frame_start,
"end_frame": bpy.context.scene.frame_end,
}
@@ -1076,19 +1080,19 @@ class Sequence(blenderbim.core.tool.Sequence):
predefined_type_item.color = data["Color"]
@classmethod
def get_start_date(cls):
def get_start_date(cls) -> Union[datetime, None]:
start = parser.parse(bpy.context.scene.BIMWorkScheduleProperties.visualisation_start, dayfirst=True, fuzzy=True)
return start if start else None
return start or None
@classmethod
def get_finish_date(cls):
def get_finish_date(cls) -> Union[datetime, None]:
finish = parser.parse(
bpy.context.scene.BIMWorkScheduleProperties.visualisation_finish, dayfirst=True, fuzzy=True
)
return finish if finish else None
return finish or None
@classmethod
def process_construction_state(cls, work_schedule, date):
def process_construction_state(cls, work_schedule: ifcopenshell.entity_instance, date: datetime) -> dict[str, Any]:
cls.to_build = set()
cls.in_construction = set()
cls.completed = set()
@@ -1109,7 +1113,7 @@ class Sequence(blenderbim.core.tool.Sequence):
}
@classmethod
def process_task_status(cls, task, date):
def process_task_status(cls, task: ifcopenshell.entity_instance, date: datetime) -> None:
for rel in task.IsNestedBy or []:
[cls.process_task_status(related_object, date) for related_object in rel.RelatedObjects]
start = ifcopenshell.util.sequence.derive_date(task, "ScheduleStart", is_earliest=True)
@@ -1234,7 +1238,7 @@ class Sequence(blenderbim.core.tool.Sequence):
}
@classmethod
def get_animation_product_frames(cls, work_schedule, settings):
def get_animation_product_frames(cls, work_schedule: ifcopenshell.entity_instance, settings: dict[str, Any]):
def preprocess_task(task):
for subtask in ifcopenshell.util.sequence.get_nested_tasks(task):
preprocess_task(subtask)
@@ -1445,7 +1449,7 @@ class Sequence(blenderbim.core.tool.Sequence):
append_handler(animate_text_handler)
@classmethod
def create_tasks_json(cls, work_schedule=None):
def create_tasks_json(cls, work_schedule: ifcopenshell.entity_instance) -> list[dict[str, Any]]:
sequence_type_map = {
None: "FS",
"START_START": "SS",
@@ -1513,9 +1517,11 @@ class Sequence(blenderbim.core.tool.Sequence):
"pParent": task.Nests[0].RelatingObject.id() if task.Nests else 0,
"pOpen": 1,
"pCost": 1,
"ifcduration": str(ifcopenshell.util.date.ifc2datetime(task_time.ScheduleDuration))
if (task_time and task_time.ScheduleDuration)
else "",
"ifcduration": (
str(ifcopenshell.util.date.ifc2datetime(task_time.ScheduleDuration))
if (task_time and task_time.ScheduleDuration)
else ""
),
"resourceUsage": resources_usage,
}
if task_time and task_time.IsCritical:
@@ -1535,7 +1541,9 @@ class Sequence(blenderbim.core.tool.Sequence):
cls.create_new_task_json(nested_task, json, type_map, baseline_schedule)
@classmethod
def generate_gantt_browser_chart(cls, task_json, work_schedule):
def generate_gantt_browser_chart(
cls, task_json: list[dict[str, Any]], work_schedule: ifcopenshell.entity_instance
) -> None:
with open(os.path.join(bpy.context.scene.BIMProperties.data_dir, "gantt", "index.html"), "w") as f:
with open(os.path.join(bpy.context.scene.BIMProperties.data_dir, "gantt", "index.mustache"), "r") as t:
task_b64 = base64.b64encode(bytes(json.dumps(task_json), "utf-8")).decode("utf-8")
@@ -1545,15 +1553,19 @@ class Sequence(blenderbim.core.tool.Sequence):
webbrowser.open("file://" + os.path.join(bpy.context.scene.BIMProperties.data_dir, "gantt", "index.html"))
@classmethod
def is_filter_by_active_schedule(cls):
def is_filter_by_active_schedule(cls) -> bool:
return bpy.context.scene.BIMWorkScheduleProperties.filter_by_active_schedule
@classmethod
def get_tasks_for_product(cls, product, work_schedule=None):
def get_tasks_for_product(
cls, product: ifcopenshell.entity_instance, work_schedule: Optional[ifcopenshell.entity_instance] = None
) -> tuple[list[ifcopenshell.entity_instance], list[ifcopenshell.entity_instance]]:
return ifcopenshell.util.sequence.get_tasks_for_product(product, work_schedule)
@classmethod
def load_product_related_tasks(cls, task_inputs, task_ouputs):
def load_product_related_tasks(
cls, task_inputs: list[ifcopenshell.entity_instance], task_ouputs: list[ifcopenshell.entity_instance]
) -> None:
props = bpy.context.scene.BIMWorkScheduleProperties
props.product_input_tasks.clear()
props.product_output_tasks.clear()
@@ -1567,7 +1579,9 @@ class Sequence(blenderbim.core.tool.Sequence):
new.ifc_definition_id = task.id()
@classmethod
def get_work_schedule_products(cls, work_schedule):
def get_work_schedule_products(
cls, work_schedule: ifcopenshell.entity_instance
) -> list[ifcopenshell.entity_instance]:
products = []
for task in ifcopenshell.util.sequence.get_root_tasks(work_schedule):
products.extend(ifcopenshell.util.sequence.get_task_inputs(task, is_deep=True))
@@ -1615,7 +1629,7 @@ class Sequence(blenderbim.core.tool.Sequence):
bpy.ops.view3d.camera_to_view_selected()
@classmethod
def save_animation_color_scheme(cls, name):
def save_animation_color_scheme(cls, name: str) -> ifcopenshell.entity_instance:
props = bpy.context.scene.BIMAnimationProperties
colour_scheme = {
"Inputs": {cs.name: cs.color[0:3] for cs in props.task_input_colors},
@@ -1630,11 +1644,11 @@ class Sequence(blenderbim.core.tool.Sequence):
group.Description = json.dumps(description)
else:
description = json.dumps({"type": "BBIM_AnimationColorScheme", "colourscheme": colour_scheme})
group = tool.Ifc.run("group.add_group", Name=name, Description=description)
group = tool.Ifc.run("group.add_group", name=name, description=description)
return group[0]
@classmethod
def load_animation_color_scheme(cls, scheme):
def load_animation_color_scheme(cls, scheme: Optional[ifcopenshell.entity_instance]) -> None:
if not scheme:
return
data = json.loads(scheme.Description)
+5 -4
View File
@@ -31,6 +31,7 @@ import json
from math import pi
from mathutils import Vector, Matrix
from shapely import Polygon, MultiPolygon
from typing import Generator
class Spatial(blenderbim.core.tool.Spatial):
@@ -147,7 +148,7 @@ class Spatial(blenderbim.core.tool.Spatial):
target_obj.matrix_world = relative_to_obj.matrix_world @ matrix
@classmethod
def select_products(cls, products, unhide=False):
def select_products(cls, products: list[ifcopenshell.entity_instance], unhide: bool = False) -> None:
bpy.ops.object.select_all(action="DESELECT")
for product in products:
obj = tool.Ifc.get_object(product)
@@ -187,15 +188,15 @@ class Spatial(blenderbim.core.tool.Spatial):
]
@classmethod
def get_selected_products(cls):
def get_selected_products(cls) -> Generator[ifcopenshell.entity_instance, None, None]:
for obj in bpy.context.selected_objects:
entity = tool.Ifc.get_entity(obj)
if entity and entity.is_a("IfcProduct"):
yield entity
@classmethod
def get_selected_product_types(cls):
for obj in bpy.context.selected_objects:
def get_selected_product_types(cls) -> Generator[ifcopenshell.entity_instance, None, None]:
for obj in tool.Blender.get_selected_objects():
entity = tool.Ifc.get_entity(obj)
if entity and entity.is_a("IfcTypeProduct"):
yield entity
+3
View File
@@ -19,6 +19,9 @@
import bpy
import ifcopenshell.util.element
import ifcopenshell.util.system
import blenderbim.bim.helper
import blenderbim.core.geometry
import blenderbim.core.root
import blenderbim.core.tool
import blenderbim.tool as tool
from blenderbim.bim import import_ifc
@@ -105,6 +105,7 @@ For Linux or Mac:
$ rm -r $BLENDER_ADDON_PATH/libs/site/packages/ifccsv.py
$ rm -r $BLENDER_ADDON_PATH/libs/site/packages/ifcdiff.py
$ rm -r $BLENDER_ADDON_PATH/libs/site/packages/bsdd.py
$ rm -r $BLENDER_ADDON_PATH/libs/site/packages/bcf
$ rm -r $BLENDER_ADDON_PATH/libs/site/packages/ifc4d
$ rm -r $BLENDER_ADDON_PATH/libs/site/packages/ifc5d
$ rm -r $BLENDER_ADDON_PATH/libs/site/packages/ifccityjson
@@ -117,6 +118,7 @@ For Linux or Mac:
$ ln -s $PWD/src/ifccsv/ifccsv.py $BLENDER_ADDON_PATH/libs/site/packages/ifccsv.py
$ ln -s $PWD/src/ifcdiff/ifcdiff.py $BLENDER_ADDON_PATH/libs/site/packages/ifcdiff.py
$ ln -s $PWD/src/bsdd/bsdd.py $BLENDER_ADDON_PATH/libs/site/packages/bsdd.py
$ ln -s $PWD/src/bcf/src/bcf $BLENDER_ADDON_PATH/libs/site/packages/bcf
$ ln -s $PWD/src/ifc4d/ifc4d $BLENDER_ADDON_PATH/libs/site/packages/ifc4d
$ ln -s $PWD/src/ifc5d/ifc5d $BLENDER_ADDON_PATH/libs/site/packages/ifc5d
$ ln -s $PWD/src/ifccityjson/ifccityjson $BLENDER_ADDON_PATH/libs/site/packages/ifccityjson
@@ -172,6 +174,7 @@ Before running it follow the instructions descibed after `rem` tags.
del "%blenderbim%\libs\site\packages\ifccsv.py"
del "%blenderbim%\libs\site\packages\ifcdiff.py"
del "%blenderbim%\libs\site\packages\bsdd.py"
rd /S /Q "%blenderbim%\libs\site\packages\bcf"
rd /S /Q "%blenderbim%\libs\site\packages\ifc4d"
rd /S /Q "%blenderbim%\libs\site\packages\ifc5d"
rd /S /Q "%blenderbim%\libs\site\packages\ifccityjson"
@@ -184,6 +187,7 @@ Before running it follow the instructions descibed after `rem` tags.
mklink "%blenderbim%\libs\site\packages\ifccsv.py" "%cd%\src\ifccsv\ifccsv.py"
mklink "%blenderbim%\libs\site\packages\ifcdiff.py" "%cd%\src\ifcdiff\ifcdiff.py"
mklink "%blenderbim%\libs\site\packages\bsdd.py" "%cd%\src\bsdd\bsdd.py"
mklink /D "%blenderbim%\libs\site\packages\bcf" "%cd%\src\bcf\src\bcf"
mklink /D "%blenderbim%\libs\site\packages\ifc4d" "%cd%\src\ifc4d\ifc4d"
mklink /D "%blenderbim%\libs\site\packages\ifc5d" "%cd%\src\ifc5d\ifc5d"
mklink /D "%blenderbim%\libs\site\packages\ifccityjson" "%cd%\src\ifccityjson\ifccityjson"
+1 -1
View File
@@ -42,7 +42,7 @@ Installation
You do not need to unzip the add-on file. You should install it as a zipped file.
You should now see **Import-Export: BlenderBIM** available in your add-ons list. Enable the add-on by pressing the checkbox.
You should now see **System: BlenderBIM** available in your add-ons list. Enable the add-on by pressing the checkbox.
.. image:: images/install-blenderbim-3.png
+1 -1
View File
@@ -960,7 +960,7 @@ def run_test_code():
def saving_sample_test_files(and_open_in_blender=None):
filepath = f"{variables['cwd']}/test/files/temp/sample_test_file"
blend_filepath = f"{filepath}.blend"
bpy.ops.export_ifc.bim(filepath=f"{filepath}.ifc", should_save_as=True)
bpy.ops.bim.save_project(filepath=f"{filepath}.ifc", should_save_as=True)
bpy.ops.wm.save_as_mainfile(filepath=f"{filepath}.blend")
+9 -3
View File
@@ -76,7 +76,9 @@ class TestCreateProject:
project.create_empty("My Site").should_be_called().will_return("site")
project.create_empty("My Building").should_be_called().will_return("building")
project.create_empty("My Storey").should_be_called().will_return("storey")
project.run_root_assign_class(obj="project", ifc_class="IfcProject").should_be_called()
project.run_root_assign_class(
obj="project", ifc_class="IfcProject", should_add_representation=False
).should_be_called()
project.run_unit_assign_scene_units().should_be_called()
self.check_contexts(project)
@@ -108,7 +110,9 @@ class TestCreateProject:
project.create_empty("My Site").should_be_called().will_return("site")
project.create_empty("My Building").should_be_called().will_return("building")
project.create_empty("My Storey").should_be_called().will_return("storey")
project.run_root_assign_class(obj="project", ifc_class="IfcProject").should_be_called()
project.run_root_assign_class(
obj="project", ifc_class="IfcProject", should_add_representation=False
).should_be_called()
project.run_unit_assign_scene_units().should_be_called()
self.check_contexts(project)
@@ -149,7 +153,9 @@ class TestCreateProject:
project.create_empty("My Site").should_be_called().will_return("site")
project.create_empty("My Building").should_be_called().will_return("building")
project.create_empty("My Storey").should_be_called().will_return("storey")
project.run_root_assign_class(obj="project", ifc_class="IfcProject").should_be_called()
project.run_root_assign_class(
obj="project", ifc_class="IfcProject", should_add_representation=False
).should_be_called()
project.run_unit_assign_scene_units().should_be_called()
self.check_contexts(project)
+3 -1
View File
@@ -146,7 +146,9 @@ class TestAddPort:
system.get_ports("element").should_be_called().will_return(["port"])
system.load_ports("element", ["port"]).should_be_called()
system.create_empty_at_cursor_with_element_orientation("element").should_be_called().will_return("obj")
system.run_root_assign_class(obj="obj", ifc_class="IfcDistributionPort").should_be_called().will_return("port")
system.run_root_assign_class(
obj="obj", ifc_class="IfcDistributionPort", should_add_representation=False
).should_be_called().will_return("port")
ifc.run("system.assign_port", element="element", port="port").should_be_called()
subject.add_port(ifc, system, element="element")
@@ -19,6 +19,7 @@
import bpy
import math
import ifcopenshell
import ifcopenshell.api
import blenderbim.core.tool
import blenderbim.tool as tool
from mathutils import Vector

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