Compare commits

...

608 Commits

Author SHA1 Message Date
Ryan Schultz 0f835ca739 removed unnecessary code 2024-05-28 18:43:13 -05:00
Ryan Schultz 87442f549f Update src/blenderbim/blenderbim/bim/module/model/prop.py
Co-authored-by: Bruno Perdigão <57102715+brunoperdigao@users.noreply.github.com>
2024-05-28 11:02:23 -05:00
Ryan Schultz bf27e41275 Update src/blenderbim/blenderbim/bim/module/model/array.py
Co-authored-by: Bruno Perdigão <57102715+brunoperdigao@users.noreply.github.com>
2024-05-28 11:02:09 -05:00
Ryan Schultz 66cf06c990 Update src/blenderbim/blenderbim/bim/module/model/array.py
Co-authored-by: Bruno Perdigão <57102715+brunoperdigao@users.noreply.github.com>
2024-05-27 16:10:20 -05:00
Ryan Schultz 5757a62b6f Update src/blenderbim/blenderbim/bim/module/model/prop.py
Co-authored-by: Bruno Perdigão <57102715+brunoperdigao@users.noreply.github.com>
2024-05-27 16:09:28 -05:00
Ryan Schultz e2552e3d46 Update src/blenderbim/blenderbim/bim/module/model/prop.py
Co-authored-by: Bruno Perdigão <57102715+brunoperdigao@users.noreply.github.com>
2024-05-27 16:09:22 -05:00
Ryan Schultz 1b91540c96 Update src/blenderbim/blenderbim/bim/module/model/prop.py
Co-authored-by: Bruno Perdigão <57102715+brunoperdigao@users.noreply.github.com>
2024-05-27 16:09:15 -05:00
Ryan Schultz 274b12e5a0 Update src/blenderbim/blenderbim/bim/module/model/prop.py
Co-authored-by: Bruno Perdigão <57102715+brunoperdigao@users.noreply.github.com>
2024-05-27 16:09:01 -05:00
Ryan Schultz 496ba6f22d fix #3802 - Copy array attributes from another array 2024-05-26 13:56:52 -05:00
Dion Moult b7737cb6d3 Fix #4655. profiles is now available as a keyword in the selector syntax. 2024-05-11 22:57:16 +10:00
Dion Moult 55026ca996 Fix #4616. You can now create a new representation based off any other object. This makes it easy to custom model whatever you want as a starting point. 2024-05-11 22:29:39 +10:00
Dion Moult 3fa573e3c1 Fix #4615. Report to user if they are attempting to create an incompatible context-representation combination. 2024-05-11 20:48:38 +10:00
Dion Moult 00ae6809a9 See #4657. Fallback to copying cache if moving cache fails due to permission error. 2024-05-11 19:59:39 +10:00
Dion Moult c3fbb11093 See #4657. Restrict annotation psets to their particular predefined type. 2024-05-11 19:38:22 +10:00
Kurt Battisti a0d27c3f1c Make Whole status UI message clearer (#4658)
Replace UI text "No Aggregate" with "No Whole relation defined" to avoid misinterpretation when instance is a whole with parts.
2024-05-11 19:04:35 +10:00
Dion Moult 811a908cc3 More segfault fixes in test suite (maybe order matters only on Linux?) 2024-05-11 19:01:57 +10:00
Dion Moult 94e5baf48a Fix #4647. Better error reporting if the user tries to activate a drawing that isn't available. 2024-05-11 19:01:21 +10:00
Dion Moult cd2570cdbb Fix #4649. Bug where editing property set template enumerations didn't clean up properly. 2024-05-11 15:18:18 +10:00
Dion Moult 6df6553935 Notify user if duration parsing not available in util.date 2024-05-11 15:14:39 +10:00
Dion Moult 8e6c372a4f Fix ability to run tests and segfaulting test 2024-05-11 15:00:30 +10:00
Andrej730 371e11de9b root.remove_product to consider IfcRelConnectsPortToElement 2024-05-10 19:18:09 +05:00
Andrej730 d2a708fcc4 fix bug in ifc2x3 tests bootstrap
it was creating new applications and users every time `ifcopenshell.api.owner.settings.get_user` or `ifcopenshell.api.owner.settings.get_application` was called
2024-05-10 19:18:09 +05:00
Andrej730 845d772a08 move sequence.get_related_products to util module 2024-05-10 19:18:09 +05:00
Andrej730 1e630ede08 ifc2x3 tests 2024-05-10 19:18:09 +05:00
Andrej730 c50d1ea2fe typing 2024-05-10 19:18:08 +05:00
Dion Moult e7eb00eaa3 See #4652. Fix bug where auto-detection of a false origin could be incorrect
We were incorrectly multiplying a shape matrix in SI units with a vertex in project units. We also didn't do a final check whether or not that final resultant coordinate was far away or not (for example, origin and vertex can cancel each other out)
2024-05-10 23:28:37 +10:00
Dion Moult cd6eac3c8f See #4652. Fix bug where using create_mesh won't work if you pass it a triangulation. 2024-05-10 22:39:56 +10:00
Dion Moult 69e21da60c Minor naming changes in IfcFM to be more true to IFC jargon 2024-05-10 22:13:39 +10:00
Dion Moult 57b905862a Fix #4650. When the bbim build hotfixes the bot build, it should replace, not copy over. 2024-05-10 18:20:41 +10:00
Dion Moult 51d3a2b77b Whoops 2024-05-10 16:16:43 +10:00
Andrej730 b2920996be remove deprecated api method type.get_related_objects 2024-05-09 18:15:49 +05:00
Andrej730 bdc5a7a01b more descriptive errors creating entities - setting incorrect attributes
example:

```
ifc_file = ifcopenshell.file(schema="IFC4")

entity = ifc_file.create_entity("IfcRoot", "xxx", None, None, None, None)
entity = ifc_file.create_entity("IfcRoot", Location=None)
```
2024-05-09 18:15:49 +05:00
Andrej730 1c6a9e2c49 more descriptive errors settings incorrect data type to attributes
Example:
```
ifc_file = ifcopenshell.file(schema="IFC2X3")
entity = ifc_file.create_entity("IfcCostSchedule")

entity.UpdateDate = ifcopenshell.util.date.datetime2ifc(datetime.now(), "IfcDateTime")
```
2024-05-09 18:15:49 +05:00
Andrej730 8ffb77551f more tests for ifc2x3 2024-05-09 18:15:49 +05:00
Andrej730 2d35869c41 typing 2024-05-09 18:00:17 +05:00
Andrej730 359dab573b document.remove_information to support ifc2x3 #4636 2024-05-09 14:02:10 +05:00
Andrej730 6e2edbf101 explicitly specify available imported symbols
Fix LSP warnings like ""entity_instance" is not exported from module "ifcopenshell"PylancereportPrivateImportUsage" for `ifcopenshell.file`, `ifcopenshell.entity_instance` etc.

After adding py.typed in 722201a1a it's now a typed library and all imported symbols in modules are considered private by default and should be added to `__all__` if they are supposed to be generally available from imported module.

See https://github.com/microsoft/pyright/blob/main/docs/typed-libraries.md#library-interface
2024-05-09 13:35:29 +05:00
Dion Moult 4c8f93d737 Add error catching to model loading and let users hide error dialog 2024-05-09 16:33:05 +10:00
Dion Moult 2789bc565a Write troubleshooting documentation 2024-05-09 16:21:24 +10:00
Dion Moult a1e0b8858e Track last actions and friendlier error reporting when something breaks 2024-05-09 13:37:57 +10:00
Dion Moult f075a6178b Experimental code for serialising API settings 2024-05-09 13:29:26 +10:00
Dion Moult 5fa385e1e0 Allow for having wildcard listeners in the api 2024-05-09 13:27:08 +10:00
Dion Moult b57141a80c New type of IfcTester report which doesn't require audit to be run to see a spreadsheet view of requirements 2024-05-09 10:46:57 +10:00
Dion Moult d453adb7cf Forgot to commit IfcTester fix for missing failed entities 2024-05-09 10:36:39 +10:00
Andrej730 c5a7a8680e fix removing references in ifc2x3 #4636 2024-05-08 18:18:03 +05:00
Andrej730 d4ccadb832 fix circular import after 10d30b0de 2024-05-08 18:18:03 +05:00
Andrej730 cbf04546b8 fix many implicit ifcopenshell.guid imports
a bit related to 10d30b0de
2024-05-08 18:18:02 +05:00
Dion Moult 37c0084874 Friendlier error reporting if bbim fails to install 2024-05-08 22:26:54 +10:00
Andrej730 875afd69d5 skip objects without placements importing containers
It seems that IFC doesn't specify that IfcRelAggregates.RelatedObjects should necessary have a placement. E.g. FreeCAD is using IfcGroups in RelatedObjects to store hierarchy in IFC.

Example - https://community.osarch.org/discussion/2144/object-information-spatial-container
2024-05-08 13:47:11 +05:00
Andrej730 67f4ed00d0 fix couple silent bugs importing containers
1) we were setting OwnerHistory to the "elevation" attribute of SpatialElement (in BIMSpatialProperties.containers)
2) we were setting "elevation" attribute at all when SpatialElement doesn't have "elevation" attribute

Though those bugs were silent since PropertyGroup doesn't alarm when we add a completely new attribute to it.
2024-05-08 13:47:11 +05:00
Dion Moult 10d30b0de7 Document api modules and require explicit imports of API submodules 2024-05-08 17:00:35 +10:00
Dion Moult 984b1212a6 Whoops 2024-05-08 09:45:12 +10:00
Andrej730 bc72c927c1 replace deprecated api call
noticed fixing #4632
2024-05-07 21:41:01 +05:00
Andrej730 b8275f2802 fix errors using deprecated api after ab696b9 #4632 2024-05-07 21:39:09 +05:00
Andrej730 267527f3fc fix issue after removing ifcopenshell.main in d76462ca4 2024-05-07 21:26:48 +05:00
Ryan Schultz c5b4513f65 fix #4622 - can now reassign IfcWindowStyle and IfcDoorStyle 2024-05-07 09:46:55 -05:00
Dion Moult 5da4fbb39c Fix #4631. Fix packaging problem on PyPI for IfcPatch. 2024-05-07 23:36:56 +10:00
Dion Moult 93639e9e50 Even more cleaning of documentation references 2024-05-07 19:08:13 +10:00
Dion Moult d76462ca42 Write more documentation
Sphinx autoapi also now only shows subpackages 1 level deep. This prevents us having a huge long list. Also don't show private or special members. Also show imported members so ifcopenshell.file and ifcopenshell.entity_instance works in docs too.
2024-05-07 17:32:47 +10:00
Dion Moult 722201a1af Add py.typed for static analysis with mypy 2024-05-07 17:15:58 +10:00
Dion Moult 0a3dddef2f More Python 2 to Python 3 upgrades 2024-05-07 16:03:07 +10:00
Dion Moult 26434f0331 Fix #4589. Symlink entire ifcopenshell dir for dev setups. 2024-05-07 15:51:48 +10:00
Dion Moult f6c2e2c20d Fix #4530. Fix various styling issues on docs. 2024-05-07 15:44:06 +10:00
Dion Moult 424a06f6c8 More cleaning up of forward type hints to fix import errors 2024-05-07 14:58:12 +10:00
Dion Moult 89c4cbeb05 Drop support for Python 2. 2024-05-07 12:17:46 +10:00
Dion Moult 7e13ed746e Don't rely on util for basic ifcopenshell module capabilities. Keep util as an optional module for users to load. 2024-05-07 10:32:02 +10:00
Andrej730 28d205b4a3 show info message on saving/loading csv from bbim 2024-05-06 18:56:20 +05:00
Andrej730 dcc52fc4e4 pset.edit_pset not to fail silently on invalid enum values #4608 2024-05-06 18:22:52 +05:00
Andrej730 a9d785c28f typing 2024-05-06 18:22:52 +05:00
Bruno Postle 54a3730c20 fix d11ec67 mathutils dependency regression
mathutils is a blender module, wrap imports in try/except
2024-05-06 13:55:20 +01:00
Andrej730 31d322a28c descriptions for operators removing classifications/contexts #4614 2024-05-06 17:29:30 +05:00
Andrej730 3b90086109 Fix loading search queries with exclusion #4609
It was failing to load queries like "!IfcWall" or "!2Mg7PHubX0xxOfF0DdA9Wd"
2024-05-06 17:06:10 +05:00
Gorgious56 30b870b98d Fix #4617 : Switching representation in edit mode is no longer possible 2024-05-06 13:20:33 +02:00
Thomas Krijnen b3e7975b83 Consider material profile set in single material determination 2024-05-06 13:18:05 +02:00
Andrej730 8f7f6223de unlink pasted blender objects if there is no active ifc file #4619 2024-05-06 15:03:27 +05:00
Dion Moult 6c313dd25c Fix for static analysis of ifcopenshell.api submodules. See #2693. 2024-05-06 18:39:13 +10:00
Bruno Postle b718bd19bd fix TypeError
File "/usr/lib64/python3.12/site-packages/ifcopenshell/api/project/assign_declaration.py", line 126, in execute
    related_definitions = set(has_context.RelatedDefinitions) - objects_with_contexts
                          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~
TypeError: unsupported operand type(s) for -: 'set' and 'list'
2024-05-06 09:05:56 +01:00
Dion Moult ab5ea4c853 Implement listener wrapper for new API functions. See #2693. 2024-05-06 14:43:43 +10:00
Dion Moult d11ec67129 Generate functions for all API usecases for better static code features. See #2693. 2024-05-06 14:43:43 +10:00
Ryan Schultz 10f894e2ea Find the "Types" Collection regardless of the IfcProject.Name 2024-05-05 20:46:07 -05:00
Ryan Schultz 34bbc8f1ea for 'Add Fitting' changed the hot key from Shift+F to Shift+Y. per https://community.osarch.org/discussion/2124/ui-discussion-around-add-fitting-and-add-bend
Also added Shift+F (bim.flip_object) to all profile-based objects
2024-05-05 19:20:13 -05:00
Dion Moult a213ab6760 Fix docstrings to reference datatype as it would be used for users 2024-05-06 08:39:48 +10:00
Ryan Schultz d694d8fcc2 fix #4610: bim.select_type() from type launcher throws error 2024-05-05 13:16:30 -05:00
ppaawweeuu 5f2d451c5e Update selector_syntax.rst (#4618)
info about 'Must contain..' and 'Must not contain..' comparisons added
2024-05-05 23:40:32 +10:00
Dion Moult ab696b980a See #2693. Expose static functions of IfcOpenShell API. 2024-05-05 16:26:41 +10:00
Andrej730 9f2df7fb9d preview external shading style as material is selected from other .blend file
previously it required to save external style attributes to preview the changes, now it's couple clicks saved if you want to preview different blender materials for your styles
2024-05-03 17:55:30 +05:00
Andrej730 9e5c3ff675 fix bug appending blender material when style.Location isn't .blend
it wasn't considering that .Location could be either None or not a .blend file and was failing in those cases
2024-05-03 17:38:04 +05:00
Andrej730 a67108e6c1 Reload current shading style after editing it #4567
Examples:
1) active shading type = EXTERNAL, now after changing it's SHADING attributes and accepting the changes, it will reload EXTERNAL shading back since it's the one that's active.
2) active shading type = SHADING, after adding EXTERNAL shader will reload SHADING style to the material.
2024-05-03 17:36:14 +05:00
Andrej730 b1706f2eac fix error launching type manager in empty project 2024-05-03 16:28:10 +05:00
Andrej730 ac29e152bb project.unassign_declaration - support batching #4474 2024-05-03 16:28:10 +05:00
Andrej730 de690a385c project.assign_declaration - support batching #4474 2024-05-03 16:28:10 +05:00
Andrej730 a973b62918 small comment fix 2024-05-03 16:28:10 +05:00
Andrej730 92426a640a bim.active_model not to unhide all types and drawings elements 2024-05-03 16:23:55 +05:00
Andrej730 93f8638a94 Use active camera drawing id for bim.create_drawing #4581
There was inconsistency between .poll and .execute on what use as active drawing.
2024-05-03 16:23:55 +05:00
Andrej730 16b0b1c3a5 Fix UI error #4575 2024-05-03 15:43:42 +05:00
Dion Moult 25fff3fdd0 Consolidate duplicate operators "Convert to Blender file" and "Purge IFC Links" 2024-05-03 17:02:05 +10:00
dependabot[bot] 0384de46a4 Bump pydantic from 1.10.7 to 1.10.13 in /src/opencdeserver/api/app (#4585)
Bumps [pydantic](https://github.com/pydantic/pydantic) from 1.10.7 to 1.10.13.
- [Release notes](https://github.com/pydantic/pydantic/releases)
- [Changelog](https://github.com/pydantic/pydantic/blob/main/HISTORY.md)
- [Commits](https://github.com/pydantic/pydantic/compare/v1.10.7...v1.10.13)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-03 16:21:08 +10:00
Ryan Schultz 080f325557 extends bim.select_type by selecting multiple types from a selection of objects.
Also when the type is selected and turned on, all other types are automatically turned off.
https://imgur.com/a/DQUF0ai
2024-05-03 00:59:33 -05:00
Andrej730 86cc2bf39a fix bug using "trace outlines" for reprsentation in ifc2x3
Mentioned in #4593

The error message was (it was trying to access curves from the original mesh instead of dummy curve object):
```
Error: Python: Traceback (most recent call last):
  File "\blenderbim\bim\module\geometry\operator.py", line 46, in execute
    IfcStore.execute_ifc_operator(self, context)
  File "\blenderbim\bim\ifc.py", line 349, in execute_ifc_operator
    result = getattr(operator, "_execute")(context)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "\blenderbim\bim\module\geometry\operator.py", line 169, in _execute
    core.add_representation(
  File "\blenderbim\core\geometry.py", line 52, in add_representation
    representation = ifc.run(
                     ^^^^^^^^
  File "\blenderbim\tool\ifc.py", line 34, in run
    return ifcopenshell.api.run(command, IfcStore.get_file(), **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "\blenderbim\libs\site\packages\ifcopenshell\api\__init__.py", line 172, in run
    result = usecase_class(ifc_file, **settings).execute()
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "\blenderbim\libs\site\packages\ifcopenshell\api\geometry\add_representation.py", line 73, in execute
    return self.create_plan_representation()
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "\blenderbim\libs\site\packages\ifcopenshell\api\geometry\add_representation.py", line 170, in create_plan_representation
    return self.create_annotation2d_representation()
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "\blenderbim\libs\site\packages\ifcopenshell\api\geometry\add_representation.py", line 833, in create_annotation2d_representation
    items = [self.file.createIfcGeometricCurveSet(self.create_curves(is_2d=True))]
                                                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "\blenderbim\libs\site\packages\ifcopenshell\api\geometry\add_representation.py", line 492, in create_curves
    curves = self.create_curves_from_curve_ifc2x3(is_2d=is_2d, curve_object_data=dummy.data)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "\blenderbim\libs\site\packages\ifcopenshell\api\geometry\add_representation.py", line 575, in create_curves_from_curve_ifc2x3
    for spline in self.settings["geometry"].splines:
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'Mesh' object has no attribute 'splines'
```
2024-05-02 16:48:52 +05:00
Andrej730 c7effa3f25 More descriptive errors linking ifc files 2024-05-02 12:13:44 +05:00
Andrej730 be262eaef2 fix errors linking ifc2x3 projects
it was failing with something very verbose:

```
  File "\blenderbim\bim\module\project\operator.py", line 1239, in execute
    db = ifcpatch.execute(
         ^^^^^^^^^^^^^^^^^
  File "\blenderbim\libs\site\packages\ifcpatch\__init__.py", line 80, in execute
    patcher.patch()
  File "\blenderbim\libs\site\packages\ifcpatch\recipes\ExtractPropertiesToSQLite.py", line 137, in patch
    properties.append([i, "IFC Material", f"Layer {idx + 1} Name", item.Name])
                                                                   ^^^^^^^^^
  File "\blenderbim\libs\site\packages\ifcopenshell\entity_instance.py", line 197, in __getattr__
    raise AttributeError(
AttributeError: entity instance of type 'IFC2X3.IfcMaterialLayer' has no attribute 'Name'
Error: Python: Traceback (most recent call last):
  File "\blenderbim\bim\module\project\operator.py", line 1239, in execute
    db = ifcpatch.execute(
         ^^^^^^^^^^^^^^^^^
  File "\blenderbim\libs\site\packages\ifcpatch\__init__.py", line 80, in execute
    patcher.patch()
  File "\blenderbim\libs\site\packages\ifcpatch\recipes\ExtractPropertiesToSQLite.py", line 137, in patch
    properties.append([i, "IFC Material", f"Layer {idx + 1} Name", item.Name])
                                                                   ^^^^^^^^^
  File "\blenderbim\libs\site\packages\ifcopenshell\entity_instance.py", line 197, in __getattr__
    raise AttributeError(
AttributeError: entity inTraceback (most recent call last):
  File "\Temp\tmpdggt3syt.py", line 9, in <module>
    run()
  File "\Temp\tmpdggt3syt.py", line 5, in run
    bpy.ops.bim.load_linked_project(filepath="/Dormitory-ARC.ifc", false_origin="0,0,0")
  File "\Blender\4.1\scripts\modules\bpy\ops.py", line 109, in __call__
    ret = _op_call(self.idname_py(), kw)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
RuntimeError: Error: Python: Traceback (most recent call last):
  File "\blenderbim\bim\module\project\operator.py", line 1239, in execute
    db = ifcpatch.execute(
         ^^^^^^^^^^^^^^^^^
  File "\blenderbim\libs\site\packages\ifcpatch\__init__.py", line 80, in execute
    patcher.patch()
  File "\blenderbim\libs\site\packages\ifcpatch\recipes\ExtractPropertiesToSQLite.py", line 137, in patch
    properties.append([i, "IFC Material", f"Layer {idx + 1} Name", item.Name])
                                                                   ^^^^^^^^^
  File "\blenderbim\libs\site\packages\ifcopenshell\entity_instance.py", line 197, in __getattr__
    raise AttributeError(
AttributeError: entity instance of type 'IFC2X3.IfcMaterialLayer' has no attribute 'Name'
Location: \Blender\4.1\scripts\modules\bpy\ops.py:109

... truncatedUnregistered Snippets Library
BMAX Connector - UnRegistred!
An error occurred while processing your IFC.
Traceback (most recent call last):
  File "\blenderbim\bim\module\project\operator.py", line 949, in execute
    self.link_ifc()
  File "\blenderbim\bim\module\project\operator.py", line 991, in link_ifc
    self.link_blend(blend_filepath)
  File "\blenderbim\bim\module\project\operator.py", line 953, in link_blend
    with bpy.data.libraries.load(filepath, link=True) as (data_from, data_to):
OSError: load: \Dormitory-ARC.ifc.cache.blend failed to open blend file
Error: Python: Traceback (most recent call last):
  File "\blenderbim\bim\module\project\operator.py", line 949, in execute
    self.link_ifc()
  File "\blenderbim\bim\module\project\operator.py", line 991, in link_ifc
    self.link_blend(blend_filepath)
  File "\blenderbim\bim\module\project\operator.py", line 953, in link_blend
    with bpy.data.libraries.load(filepath, link=True) as (data_from, data_to):
OSError: load: \Dormitory-ARC.ifc.cache.blend failed to open blend file
Location: \Blender\4.1\scripts\modules\bpy\ops.py:109
Traceback (most recent call last):
  File "\blenderbim\bim\module\project\operator.py", line 870, in execute
    bpy.ops.bim.load_link(filepath=filepath, false_origin=self.false_origin)
  File "\Blender\4.1\scripts\modules\bpy\ops.py", line 109, in __call__
    ret = _op_call(self.idname_py(), kw)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
RuntimeError: Error: Python: Traceback (most recent call last):
  File "\blenderbim\bim\module\project\operator.py", line 949, in execute
    self.link_ifc()
  File "\blenderbim\bim\module\project\operator.py", line 991, in link_ifc
    self.link_blend(blend_filepath)
  File "\blenderbim\bim\module\project\operator.py", line 953, in link_blend
    with bpy.data.libraries.load(filepath, link=True) as (data_from, data_to):
OSError: load: \Dormitory-ARC.ifc.cache.blend failed to open blend file
Location: \Blender\4.1\scripts\modules\bpy\ops.py:109
```
2024-05-01 18:26:58 +05:00
Andrej730 523e51c775 use json serialization for #4599
changed my mind about 5410f14aa, probably better to use `json.dumps` to keep it consistent with how we serialize other attributes
2024-05-01 18:26:58 +05:00
Andrej730 5410f14aa1 ifc2sql - stringify psets list values #4599 2024-05-01 18:03:25 +05:00
Andrej730 45b2411c2b Fix #4598 after 229c7285c 2024-05-01 17:43:31 +05:00
Andrej730 9f12091c7f fix bug assigning materials
it was clearing all other blender materials - e.g. blender materials associated with representation items styles. Also had the same issue with affecting extra elements as with unassigning materials
2024-05-01 16:42:20 +05:00
Andrej730 bef5d3cca5 blenderbim - fix bug with unassigning material
When you would unassign material (e.g. implicitly by removing the object), it might have also removed materials slots from other occurrences of the same type.

Which may had some unexpected sideffects later on - as unassigned styles saving IFC project (BBIM unassigned styles based on a assumption that it was the user decision to remove the materials and related styles).
2024-05-01 16:42:20 +05:00
Andrej730 0f548eb93e Indicate in UI if type material was overridden by occurrence material
Example - https://i.imgur.com/T1hidJg.png

+small optimization in material_name
2024-05-01 16:42:20 +05:00
Andrej730 374348bb81 IfcImporter small optimization 2024-05-01 16:42:20 +05:00
Andrej730 b42f6b1827 ifc delete to show time it took if it was more than 10 secs 2024-05-01 16:42:20 +05:00
Andrej730 addcf531f0 owner.update_owner_history - optimization
as it may be used very often
2024-05-01 16:42:20 +05:00
Andrej730 0ef683013a RegenerateGlobalIds - more informative fixing duplicates 2024-05-01 16:42:20 +05:00
Andrej730 137a306269 typing 2024-05-01 16:42:20 +05:00
Andrej730 fc0ffd1c02 fix a typo #4596 😬 2024-05-01 15:40:45 +05:00
Bruno Perdigão 97e0c43702 Merge pull request #4440 from brunoperdigao/fix-normal-duplication-of-linked-aggregate
Fix normal duplication of linked aggregate
2024-04-30 17:01:44 -03:00
Andrej730 8dde98f543 fix #4592 (after 0da3270)
Before IFC4 Shape Aspects counldn't be part of IfcRepresentationMap
2024-04-29 15:22:26 +05:00
Ryan Schultz 9962a0c905 small fix to previous commit. Solves https://github.com/IfcOpenShell/IfcOpenShell/commit/4b56b341243d6b0eed82e8533848b124e82e1344#commitcomment-141442796 2024-04-28 18:57:42 -05:00
Ryan Schultz 4b56b34124 Tweaked UI for Linked Aggregates https://imgur.com/a/rExISKO 2024-04-28 10:34:52 -05:00
Thomas Krijnen f857c513c5 submodule 2024-04-28 10:17:57 +02:00
Kaare Hansen 41df6410bd Comment std::cout causing console spam 2024-04-28 10:11:32 +02:00
Ryan Schultz dcbd6a925d small fix for #4361. remove IfcProject for class iterator. See https://github.com/IfcOpenShell/IfcOpenShell/issues/4361#issuecomment-2081223615 2024-04-27 18:13:38 -05:00
Ryan Schultz 3848a21136 fix #4361: Have the aggregate empty turn on too, if at least one object in the aggregate, is turned on as well 2024-04-27 11:31:39 -05:00
Andrej730 8af37cb4ec Fix #4586 occurred after e14d96e6e 2024-04-27 12:03:56 +05:00
Ryan Schultz 44c28e9a4c fix #4327: reorder format as well 2024-04-26 20:48:59 -05:00
Ryan Schultz 677324927c fix #4431: prompt for name, when creating aggregate 2024-04-26 20:09:20 -05:00
Massimo Fabbro d212344c54 Covering tool more user friendly UI ... 2024-04-26 17:19:28 +02:00
Massimo Fabbro 74da327803 Fix create space from cursor bug where spaces were created always in zero elevation 2024-04-26 17:19:28 +02:00
Massimo Fabbro f021041b31 Remove the (maybe) unnecessary poll functions for spatial tool and cleaner error messages 2024-04-26 17:19:28 +02:00
Massimo Fabbro c05d2df116 now spaces created with spatial tool have relative zero elevation origin point 2024-04-26 17:19:28 +02:00
Andrej730 2b926f7906 more descriptive error setting non-optional attributes with None
Example:

```
import ifcopenshell
import ifcopenshell.api.owner.settings

ifc_file = ifcopenshell.file(schema="IFC2X3")
ifc_file.createIfcOwnerHistory(OwningUser=None)

# Before:
# Traceback (most recent call last):
#   File "\test.py", line 16, in <module>
#     ifcopenshell.api.run("owner.create_owner_history", ifc_file)
#   File "\ifcopenshell\api\__init__.py", line 172, in run
#     result = usecase_class(ifc_file, **settings).execute()
#              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
#   File "\ifcopenshell\api\owner\create_owner_history.py", line 111, in execute
#     return self.file.create_entity(
#            ^^^^^^^^^^^^^^^^^^^^^^^^
#   File "\ifcopenshell\file.py", line 350, in create_entity
#     e[idx] = arg
#     ~^^^^^
#   File "\ifcopenshell\entity_instance.py", line 305, in __setitem__
#     self.wrapped_data.setArgumentAsNull(idx)
#   File "\ifcopenshell\ifcopenshell_wrapper.py", line 5285, in setArgumentAsNull
#     return _ifcopenshell_wrapper.entity_instance_setArgumentAsNull(self, i)
#            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
# RuntimeError: Attribute not set

# After:
# Traceback (most recent call last):
#   File "\test.py", line 16, in <module>
#     ifcopenshell.api.run("owner.create_owner_history", ifc_file)
#   File "\ifcopenshell\api\__init__.py", line 172, in run
#     result = usecase_class(ifc_file, **settings).execute()
#              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
#   File "\ifcopenshell\api\owner\create_owner_history.py", line 111, in execute
#     return self.file.create_entity(
#            ^^^^^^^^^^^^^^^^^^^^^^^^
#   File "\ifcopenshell\file.py", line 350, in create_entity
#     e[idx] = arg
#     ~^^^^^
#   File "\ifcopenshell\entity_instance.py", line 316, in __setitem__
#     raise ValueError(
# ValueError: attribute 'OwningUser' is not optional for entity instance of type 'IFC2X3.IfcOwnerHistory'
```
2024-04-26 18:23:19 +05:00
Andrej730 3ac69043d6 bbim to provide default user and organisation in ifc2x3 #4574
previously every operation that would try to create owner history would fail with the error below if there wasn't IfcPersonAndOrganisation in the project.
2024-04-26 18:23:19 +05:00
Andrej730 f726708815 edit_object_placement - small optimizations 2024-04-26 18:23:19 +05:00
Andrej730 21e153087a edit_object_placement - adherence support
Update edit_object_placement.py
2024-04-26 18:23:18 +05:00
Andrej730 31d85b715a typing 2024-04-26 18:23:18 +05:00
Andrej730 344cc1fb7d file.by_type docs note 2024-04-26 11:49:44 +05:00
Andrej730 1cea3d21e8 BBIM support importing IfcRelAdheresToElement #4565 2024-04-26 11:44:32 +05:00
Andrej730 640320f70d shape_builder - create non-optional position for profiles in ifc2x3 2024-04-25 18:05:47 +05:00
Andrej730 29352092aa fix issue creating 3d location for 2d placement in ifc2x3 2024-04-25 18:05:47 +05:00
Andrej730 5b877b549a fix numpy typing #4579
On older numpy versions, np.ndarray was less forgiving and wasn't allowing passing 1 argument instead of required 2.

And turned out numpy doesn't yet have typing for shapes (https://github.com/numpy/numpy/issues/16544), so all matrices and other shapes specified as `npt.NDArray[np.float64]`.

Fixed type discrepancies for `get_edges` and `get_faces` and also had to fix `import_ifc` as Blender apparently has problems with storing np.int32 in custom attributes (https://projects.blender.org/blender/blender/issues/121072), tested that Blender is okay with np.int32 in other cases we had (addressing BMesh.verts[i] where `i` is np.int32).
2024-04-25 18:05:47 +05:00
Andrej730 721466c429 typing 2024-04-25 18:05:47 +05:00
Andrej730 cce2fee1fa More descriptive error setting non-present attributes
Example:

```
import ifcopenshell

ifc_file = ifcopenshell.file(schema="IFC2X3")
# ifc_file.begin_transaction()

wall = ifc_file.createIfcWall()
wall.Identification = "25"

# Before:
# Traceback (most recent call last):
#   File "test.py", line 4, in <module>
#     wall.Identification = "25"
#     ^^^^^^^^^^^^^^^^^^^
#   File "\ifcopenshell\entity_instance.py", line 279, in __setattr__
#     self[index] = value
#     ~~~~^^^^^^^
#   File "\ifcopenshell\entity_instance.py", line 293, in __setitem__
#     method = self.method_list[idx]
#              ~~~~~~~~~~~~~~~~^^^^^
# IndexError: list index out of range

# or this (if file had a transaction going)
#   File "\ifcopenshell\entity_instance.py", line 279, in __setattr__
#     self[index] = value
#     ~~~~^^^^^^^
#   File "\ifcopenshell\entity_instance.py", line 288, in __setitem__
#     self.wrapped_data.file.transaction.store_edit(self, idx, value)
#   File "\ifcopenshell\file.py", line 101, in store_edit
#     "old": self.serialise_value(element, element[index]),
#                                          ~~~~~~~^^^^^^^
#   File "\ifcopenshell\entity_instance.py", line 283, in __getitem__
#     raise IndexError("Attribute index {} out of range for instance of type {}".format(key, self.is_a()))
# IndexError: Attribute index 4294967295 out of range for instance of type IfcWall

# After:
# Traceback (most recent call last):
#   File "test.py", line 4, in <module>
#     wall.Identification = "25"
#     ^^^^^^^^^^^^^^^^^^^
#   File "ifcopenshell\entity_instance.py", line 283, in __setattr__
#     raise AttributeError(
# AttributeError: entity instance of type 'IFC2X3.IfcWall' has no attribute 'Identification'

```
2024-04-25 18:05:47 +05:00
Andrej730 087d55f02a fix editing sheets in ifc2x3 #4576 2024-04-25 18:05:47 +05:00
Andrej730 f1037de14d small fix for 60a70f523 2024-04-25 18:05:46 +05:00
Ryan Schultz 4711672170 small tweak to 3ff31b577c and cd013079f4 - duplicate material and duplicate style have '_copy' suffix. 2024-04-24 21:48:26 -05:00
Bruno Postle c0b4e099af fix sheet regeneration missing drawing Identifier
Was passing LAYOUT document reference to sheeter instead of
DRAWING document reference
2024-04-24 23:05:44 +01:00
Andrej730 f41fbf24c3 small fix for e14d96e6e 2024-04-24 18:19:20 +05:00
Andrej730 e14d96e6ee Use .Name instead of .Description if IFC2X3 for sheets #4576
As Description was added only in IFC4
2024-04-24 18:17:37 +05:00
Andrej730 716a33f347 convert_file_length_units - fix issue converting to imperial units
ifcpatch ConvertLengthUnit - change used units from plural to singular names, to make it consistent across the api

more test coverage - test converting to more units and back from them
2024-04-24 15:15:30 +05:00
Andrej730 c5f084a4af ConvertLengthUnit test 2024-04-24 14:12:13 +05:00
Andrej730 606a0b46f2 MergeProject - support merging projects with different units 2024-04-24 14:12:13 +05:00
Andrej730 1c729a74d1 util.unit to support converting aggregate of aggregates 2024-04-24 14:12:13 +05:00
Andrej730 db31574103 typing 2024-04-24 14:12:13 +05:00
stefkeB d03b47e1e6 Fix HDF5 conditional inclusion in GeomTree
IfcGeomTree uses HDF5, but this is an optional module, so we have to wrap it with #ifdef WITH_HDF5
2024-04-23 18:46:10 +02:00
Ryan Schultz ae62ac2777 small tweak to b8a9674483 - duplicate profile has '_copy' suffix. 2024-04-23 07:42:02 -05:00
Andrej730 998f9c6a1e make styles ui more similar to profiles and materials ui
before - https://i.imgur.com/bTbyt2t.png
after - https://i.imgur.com/U53h8Yh.png
2024-04-23 16:39:49 +05:00
Andrej730 3ff31b577c bim.duplicate_style 2024-04-23 16:39:49 +05:00
Andrej730 cd013079f4 bim.duplicate_material 2024-04-23 16:39:49 +05:00
Andrej730 b8a9674483 bim.duplicate_profile
simple operator for profile duplication
example - https://imgur.com/a/dsP78iV
2024-04-23 16:39:49 +05:00
Andrej730 8beb1bef98 preserve selected profile index removing profiles 2024-04-23 16:39:38 +05:00
Andrej730 377676f881 dev environment - add libs/desktop and bsdd 2024-04-23 15:23:16 +05:00
Andrej730 4efe284338 use IFC4X3_ADD2 for ifcopenshell.schema_by_name
as it is the main IFC4X3 schema
2024-04-22 17:30:25 +05:00
Andrej730 34a4e3f37a util.doc - use release schema version for IFC4X3
As it will be more like to be available in the ifcopenshell package. Related to #4565
2024-04-22 17:30:25 +05:00
Andrej730 889c9a9e9f typing 2024-04-22 17:30:25 +05:00
Andrej730 9e79499532 small optimization 2024-04-22 17:30:24 +05:00
Kristof Semjen 589b98053e Fixes #4261
SWIG_Python_str_AsChar and SWIG_Python_str_DelForPy3 are no longer available in swig 4.2 (see : https://github.com/swig/swig/commit/f89dd59d4b82ece899087682fdb86e94d2611513 ),
this commit fixes the build for swig versions > 4.2.
2024-04-21 16:22:56 +02:00
Bruno Postle fc5bb230a9 Abandon SVG layout generation on missing drawings
Drawings need to be generated before layout can be generated, so
remove a partial failed layout caused by missing drawings.
2024-04-21 11:12:24 +01:00
Bruno Postle 3ef823db23 Regenerate missing sheets #4462
BlenderBIM will regenerate missing SVG drawings from the IFC on demand,
now it attempts to regenerate missing SVG sheets too. ie. you can open a
bare IFC file with configured drawings and sheets and continue working.

Only drawings are recognised, spreadsheet and other SVG refs are not
regenerated, all drawings are placed at the 30,30 pixel location.
2024-04-20 11:56:22 +01:00
Andrej730 60a70f5236 support IfcAxis1Placement and warn on IfcAxis2PlacementLinear #4565 2024-04-19 17:11:56 +05:00
Andrej730 757a010172 typing 2024-04-19 16:18:57 +05:00
Andrej730 447625bffd fix failing test in util.selector.format with imperial_length
the error is below, not sure how I haven't noticed it before 🤔

assert subject.format('imperial_length(1, 1)') == "1'"

>               raise VisitError(tree.data, tree, e)
E               lark.exceptions.VisitError: Error trying to process rule "imperial_length":
E
E               cannot access local variable 'output_unit' where it is not associated with a value
2024-04-19 16:18:46 +05:00
Andrej730 25edaa9b89 update broken url for ladybug tools
temporarily use fork repo
2024-04-19 16:07:29 +05:00
Andrej730 9da4215bac ifcopenshell.util.selector.filter_elements - inherited predefined types
mentioned by @theoryshaw on osarch - https://community.osarch.org/discussion/comment/20126/#Comment_20126
2024-04-19 16:05:33 +05:00
Ryan Schultz 6ddf91fb52 bim.select_aggregate(select_parts=True) - selects all the host aggreg… (#4552)
- bim.select_aggregate(select_parts=True) - selects all the host aggregates and all the parts and subaggregates within these host aggregates
- bim.select_linked_aggregates(select_parts=False) - selects all the host aggregates that are linked
- bim.select_linked_aggregates(select_parts=True) - selects all the host aggregates that are linked and all the parts and subaggregates within these host aggregates
2024-04-18 21:08:46 -05:00
Bruno Perdigão 01361cc8f2 Merge branch 'v0.7.0' into fix-normal-duplication-of-linked-aggregate 2024-04-18 21:00:39 -03:00
Dion Moult 28eb2cd628 Fix dateutil dependency 2024-04-19 08:51:55 +10:00
Dion Moult fa4239159d Fix incorrect dateutil dependency 2024-04-18 23:39:25 +10:00
Ryan Schultz ea6f0b054a fixes: #4563 2024-04-17 21:42:51 -05:00
Andrej730 3b637a0234 ifcsverchok - autogenerate node categories from index 2024-04-17 15:24:47 +05:00
Andrej730 45dacc3612 migrate docs urls 2024-04-17 15:18:45 +05:00
Andrej730 f93d79dce0 entity_instance.is_a overload typing 2024-04-17 15:18:45 +05:00
Gorgious56 6f8dcc637f #4532 : You can now store clipping planes to a json file and restore them
The file is stored along the ifc file
2024-04-17 11:52:00 +02:00
Andrej730 a748dda378 mention IfcSverchok in docs 2024-04-17 12:33:03 +05:00
Andrej730 b158a83644 update ifcsverchok builds 2024-04-17 12:32:42 +05:00
Andrej730 31cd20c61b more user-friendly errors enabling ifcsverchok 2024-04-17 12:10:18 +05:00
Andrej 0a542e6957 ci-ifcsverchok-build.yml 2024-04-17 11:38:18 +05:00
Andrej730 89d29cf133 ifctester.reporter.Ods - option for safe excel export #4427
The issue was related to the sheet names - Excel prohibits some symbols in the names and it doesn't allow sheet name to be longer than 31 symbols. If sheet names doesn't match this criteria then Excel says file appears to be corrupted - when file open data itself is not really corrupted besides the spreadsheet names that failed the criteria.
2024-04-16 16:40:05 +05:00
Andrej730 6ee7502920 ifctester.reporter.Txt small fix 2024-04-16 16:39:55 +05:00
Andrej730 708ee7350b ifctester - fix bug adding second .ods extension 2024-04-16 16:39:55 +05:00
Andrej730 836293504c ifctester typing 2024-04-16 16:39:55 +05:00
Andrej730 b458aea6b9 ifctester docs for command line use 2024-04-16 15:17:49 +05:00
Andrej730 e1e169ece8 fix bug in d6e69bd6e 2024-04-16 15:17:49 +05:00
Andrej730 aeea9dfb5e fix test after ac64d0c40 2024-04-16 15:17:49 +05:00
Andrej730 9a9be821d1 constraint.unassign_constraint - support batching #4474 2024-04-16 15:17:49 +05:00
Andrej730 c808382154 constraint.assign_constraint - support batching #4474 2024-04-16 15:17:49 +05:00
Andrej730 81608b0cee ifcopenshell.util.get_constrained_elements 2024-04-16 15:17:49 +05:00
Andrej730 02ae838c45 util.constraint - typing and docs 2024-04-16 15:17:49 +05:00
Andrej730 f1f8b3da6b spatial.dereference_structure - support batching #4474 2024-04-16 15:17:48 +05:00
Andrej730 8ac0d22a95 spatial.reference_structure - support batching #4474 2024-04-16 15:17:48 +05:00
Andrej730 3b6f79cc75 black format 2024-04-16 12:18:05 +05:00
Dion Moult 1e41561fe8 Fix #4345. Removing grid axes are now handled specially. 2024-04-16 16:43:02 +10:00
Dion Moult 23abf674f3 Minor fix 2024-04-16 14:54:36 +10:00
Dion Moult ac64d0c400 Fix #3581. Invalid new document references. 2024-04-16 08:06:10 +10:00
Dion Moult 0983f9e66d Fix bug where saved drawing filters didn't load in the UI. 2024-04-15 23:33:10 +10:00
Dion Moult 5725ed867f Don't unnecessarily update NTS or scale pset data 2024-04-15 23:33:10 +10:00
Dion Moult 4174a78bb5 #4553 2024-04-15 23:33:10 +10:00
Andrej730 96b0a241d9 add tests for get_referenced_structures 2024-04-15 17:24:31 +05:00
Andrej730 ff76850a54 ifcopenshell.util.element.get_structure_referenced_elements 2024-04-15 17:24:31 +05:00
Andrej730 9954e073fa constraint.assign_constraint to handle owner history 2024-04-15 17:24:31 +05:00
Andrej730 5da68721d6 document.unassign_document - support batching #4474 2024-04-15 17:24:31 +05:00
Andrej730 097f44284f document.assign_document - support batching #4474 2024-04-15 17:24:31 +05:00
Andrej730 1f605484d9 library.unassign_reference - support batching #4474 2024-04-15 17:24:31 +05:00
Andrej730 81dcad6a14 library.assign_reference - support batching #4474 2024-04-15 17:24:31 +05:00
Andrej730 03af1013a0 add tests for util.element.get_referenced_elements 2024-04-15 17:23:25 +05:00
Andrej730 144831f9ee classification.remove_reference - support batching #4474 2024-04-15 17:23:24 +05:00
Andrej730 d6e69bd6e9 classification.add_reference - support batching #4474 2024-04-15 17:23:14 +05:00
Andrej730 7d6fd03eb4 rename tests for consistency with api 2024-04-15 17:21:10 +05:00
Andrej730 75c854da01 ifcopenshell.util.get_referenced_elements 2024-04-15 17:21:10 +05:00
Andrej730 01ddf8e769 util.classification small optimization 2024-04-15 14:47:32 +05:00
Andrej730 0a9c9f2a28 typing 2024-04-15 14:47:32 +05:00
Andrej730 919dcbe2d9 black format 2024-04-15 14:47:32 +05:00
Dion Moult 5e237a1670 Fix #4553. 2024-04-15 17:22:12 +10:00
Dion Moult acf6628f23 Fix #4547. Handle both null inherited predefined types and disable editing predefined types on occurrences which inherit from a type. 2024-04-15 11:53:37 +10:00
Dion Moult 8a62558575 Fix #4484. Clarify inheritance rules of predefined types. 2024-04-15 09:27:58 +10:00
Dion Moult 0e0861c84b Further clarification of type vs occurrence psets/qtos.
Previously even if you had a type selected it would still say occurrence.
2024-04-15 09:27:00 +10:00
Dion Moult 5a8209e2ad Fix #4549. Drawing decorators are now refreshed like the rest of the UI after operations. 2024-04-15 00:01:42 +10:00
Dion Moult 50b41f5af6 Fix #4486. Sync camera properties from IFC on both drawing load as well as drawing activation.
Also there are now defaults in case pset values don't exist.
2024-04-14 15:18:23 +10:00
Dion Moult fc6678f5ce Fix #3402. 2024-04-14 14:41:37 +10:00
Dion Moult ee1183481f Fix #4525. Fix #4546. Specify dependencies for ifcopenshell and ifcpatch.
No longer optional for a better first experience. They aren't huge deps.
2024-04-14 08:52:19 +10:00
Dion Moult 6ddf7dae01 See #4546. Fix docs link to filtering elements query syntax. 2024-04-14 08:34:49 +10:00
Dion Moult 8232c9fa24 See #4541. Clarify how to use include args for IfcConvert 2024-04-14 08:33:23 +10:00
Dion Moult 07b6c95f54 Fix #4550. Update doc urls. 2024-04-14 08:11:57 +10:00
Andrej730 ca2f683af3 Update BBIM version in docs 2024-04-13 10:59:56 +05:00
Andrej730 eb46de9658 material.unassign_material - support batching #4474 2024-04-12 17:41:37 +05:00
Andrej730 59cc9c0a16 material.assign_material - support batching #4474 2024-04-12 17:41:37 +05:00
Andrej730 32d2f1d629 material.assign_material - update owner history 2024-04-12 17:41:37 +05:00
Andrej730 38a73d6510 ifcpatch - add ifc2x3 tests and test for MergeDuplicateTypes 2024-04-12 17:41:36 +05:00
Andrej730 59b496d4b5 typing 2024-04-12 17:41:36 +05:00
Dion Moult e56aa45a82 Fix #4526. Bug when exporting IfcTester results to BCF due to new "failures" attribute. 2024-04-12 10:09:52 +10:00
Dion Moult a3c99b8362 See #4541. Add documentation examples on how to use include filters in IfcConvert. 2024-04-12 10:09:52 +10:00
Andrej730 e66ac3d7db system.unassign_system - support batching #4474 2024-04-11 17:50:31 +05:00
Andrej730 6b48e8b4b9 system.assign_system - support batching #4474 2024-04-11 17:50:31 +05:00
Andrej730 3d243b5eee system.add_system - fix failing default agrument in ifc2x3 2024-04-11 17:50:31 +05:00
Andrej730 fa0dbac914 new util method ifcopenshell.util.element.get_groups 2024-04-11 17:50:30 +05:00
Andrej730 49e7abb4dc type.unassign_type - support batching #4474 2024-04-11 17:09:41 +05:00
Andrej730 5098550399 assign_type not to remap if it's type was assigned previously 2024-04-11 17:09:41 +05:00
Andrej730 e12c05eea0 typing 2024-04-11 17:09:41 +05:00
Andrej730 39433fb5a5 remove unused lines from tests 2024-04-11 17:09:41 +05:00
Andrej730 1ea80016cd fix bug generating profiles on assign_type after df4943205 2024-04-11 17:09:41 +05:00
Vukas Pajic 58b39d3926 fix: address test fails 2024-04-11 13:03:13 +02:00
Vukas Pajic 946c868388 fix: cascade_schedule correctly handles timelag with IfcRatioMeasure 2024-04-11 11:35:34 +02:00
Andrej730 df4943205a type.assign_type - support batching #4474 2024-04-10 18:58:44 +05:00
Andrej730 ba4eb2a891 fix bug in nest.assign_object 2024-04-10 18:58:44 +05:00
Andrej730 90bb2f0e16 unify tests for ifc4 and ifc2x3 2024-04-10 18:45:37 +05:00
Andrej730 c2840d92c2 fix test files names 2024-04-10 18:45:37 +05:00
Andrej730 81ff9c168e nest.assign_object to support ifc2x3 2024-04-10 18:45:37 +05:00
Andrej730 239ab5b587 get_pset() fix bug getting inherited psets with psets_only/qtos_only = True 2024-04-10 18:45:37 +05:00
Andrej730 37a33d62d5 further ifcopenshell.util.element optimization
and readibility for props addressed by the index
2024-04-10 18:45:37 +05:00
Andrej730 13c6fdd3c6 maintain verbosity getting complex property subproperties 2024-04-10 18:45:36 +05:00
Andrej730 e69597f72b typing 2024-04-10 18:45:36 +05:00
Andrej730 9c633c8b6c black format 2024-04-10 18:17:19 +05:00
Alper Kantarci 4d906578c7 Use "gltf" instead of "gtlf" (#4538)
- Fix "gtlf" typo
2024-04-10 21:02:56 +10:00
Dion Moult de744dddda Fix #4290. You can now format custom thousands separators and decimal separators in IfcCSV. 2024-04-10 17:32:39 +10:00
Dion Moult 0a04b88a52 Document how MacOS users using Blender from Steam should still use Mac Intel 2024-04-10 14:30:38 +10:00
Dion Moult bee1e21f0e Fix #4490. Fix fundamental bug where get_pset would return none for inherited props with a boolean False value. 2024-04-10 12:57:52 +10:00
Dion Moult f5c5fba151 Fix issue where the underlay cache option was not yet implemented and ignored. 2024-04-10 11:09:58 +10:00
Dion Moult 4f9badc063 Fix issue where reloading the drawing list would cause drawing names to be synced
It would run useless sync / API calls to edit psets on potentially hundreds of drawings.
2024-04-10 11:07:17 +10:00
Dion Moult b61d870922 See #4481. Defer setting render visibility to drawing generation to speed up switching between drawings.
Doing obj.hide_render = True/False in a loop is slow and there is no bulk set command.
2024-04-10 10:37:33 +10:00
Andrej730 153389fe4a ifcopenshell.util.element optimization
Tried to reduce amount of IFC attributes retrieved by reusing already retrieved values.

E.g. previously get_nest() was retrieving same attribute 3 times on each call, now it does it only once and it's 2 times faster (2.4 ms -> 1.2 ms).
2024-04-09 18:36:55 +05:00
Andrej730 965a45d65b ifcopenshell.util.element.get_nest test 2024-04-09 18:33:26 +05:00
Andrej730 06b84cfa24 nest.unassign_object - support batching #4474 2024-04-09 18:33:25 +05:00
Andrej730 97db95085a nest.assign_object - support batching #4474 2024-04-09 18:33:25 +05:00
Andrej730 d1bcec7a6a typing 2024-04-09 18:33:25 +05:00
Andrej730 7b46c6a443 black format 2024-04-09 17:05:36 +05:00
Andrej730 6db0c14fa8 ifcopenshell.api.run optimization
cache usecase classes instead of importing their modules and getting them each time, makes empty api call twice as fast
2024-04-09 16:18:11 +05:00
Andrej730 d03f9237e2 deprecate spatial.remove_container in favor of spatial.assign_container
added temporary support for old version with warnings:

WARNING. `spatial.remove_container` api method is deprecated and should be replaced with spatial.unassign_container
WARNING. `product` argument is deprecated for API method "spatial.remove_container" and should be replaced with `products`.
2024-04-09 16:09:04 +05:00
Dion Moult 24716a0089 See #4481. Faster implementation of object hiding and toggline viewport visibility. 2024-04-09 18:39:41 +10:00
Andrej730 437d19e1f5 normalize line endings in CCI(EN).ifc (was commited as CRLF, not LF) 2024-04-09 12:43:50 +05:00
Dion Moult bd5fd3539a Fix #4497. Drawing text annotations are now based on actual line breaks. 2024-04-09 15:56:16 +10:00
Dion Moult bcc67ecb31 See #4497. Show invisible newlines and allow newline editing in attributes. 2024-04-09 15:25:10 +10:00
Dion Moult c50810a24a Fix #4533. Add convenience link to launch type manager when no types exist. 2024-04-09 14:10:01 +10:00
Benoit DAMAY 7f9ce37a98 Inline help feature (#4409)
* Help operator in grouping and filtering Panel

Opens up a webbrowser on click

* Help button on the right of filtering panel header

* replacing os.rename() by shutil.move in ifcpatch

* removing debug print from previous commit

* Removing extra operator
Using bim.open_uri to open help page

* Update docs url

* Clean up whitespace

---------

Co-authored-by: Dion Moult <dion@thinkmoult.com>
2024-04-09 11:50:08 +10:00
ppaawweeuu ac23d08717 Update CCI(EN).ifc
added last two rows with ending markers
2024-04-09 11:45:34 +10:00
ppaawweeuu a8211ed592 Add CCI Classification schema
I'd like to add CCI classification to BBIM. It is very similar to CSS, developed by CCI-C and licensed under CC BY 4.0
More details here: https://cci-collaboration.org/the-standard/

This is my first commit, I used CCS(EN).ifc file as reference. Any feedback welcomed.
2024-04-09 11:45:34 +10:00
Gorgious e38f3beb91 New UI List in Sandbox panel to access, flip, remove, add, edit clipping planes
and some tweaks :
- Disable possibility to edit clipping plane quad geometry
- Add select and delete operators
- Remove polls from CreateClippingPlane that prevented from using it in the Properties editor. The clipping plane is added near the origin when using the Add button in the panel
2024-04-08 21:31:23 +02:00
Andrej730 f14bf8557a aggregate.unassign_object - support batching #4474 2024-04-08 17:49:48 +05:00
Andrej730 cd11d2de27 aggregate.assign_object - support batching #4474 2024-04-08 17:49:48 +05:00
Andrej730 d764c0af23 temporary support for deprecated api arguments #4531 2024-04-08 17:49:48 +05:00
Andrej730 d24e5df7c1 typing 2024-04-08 17:27:41 +05:00
Andrej730 de5075aafe black format 2024-04-08 17:27:36 +05:00
Andrej730 9474d17867 fix test after 7dd0d7601 2024-04-08 16:28:05 +05:00
Andrej730 abe6dbdfc9 layer.unassign_layer - support batching #4474 2024-04-08 16:28:05 +05:00
Andrej730 7ecec973dd layer.assign_layer - support batching #4474 2024-04-08 14:51:07 +05:00
Andrej730 f34aef1ebf validate - add examples 2024-04-08 13:56:52 +05:00
Andrej730 3253bdf7aa fix possible infinite recursion in bim.debug_active_drawing #4500
also fixed the issue with mixed up results at the end of the operator execution
2024-04-08 12:19:57 +05:00
Dion Moult 8e2f9d2bb5 Fix #4509. Don't show scary error message if you haven't specified a filter query. 2024-04-08 17:19:17 +10:00
Dion Moult 7dd0d7601a Fix #3759. IFC4X3 files should use ADD2 identifier. 2024-04-08 12:34:59 +10:00
Dion Moult 5bd53b973f Fix #4303. Declare dependencies for ifctester. 2024-04-08 11:57:21 +10:00
Dion Moult 911cfe010e Publish IfcClash to PyPI 2024-04-08 11:19:13 +10:00
Dion Moult 35dd03664a Publish IfcCSV to PyPI 2024-04-08 10:55:59 +10:00
Dion Moult c89b84ff57 Whoops again 2024-04-08 10:31:37 +10:00
Dion Moult cda13af7a3 See #4529. Publish IfcPatch to PyPI. 2024-04-08 10:24:04 +10:00
Dion Moult 6e84eb0acc Add bSDD makefile 2024-04-08 10:09:58 +10:00
Dion Moult b9ede5c739 Whoops 2024-04-08 10:08:01 +10:00
Dion Moult 44b980c1e0 Publish bSDD to PyPI 2024-04-08 10:06:28 +10:00
Dion Moult 08f4dea47f Materials with no style colours now default to Blender's default colour when linking models 2024-04-08 08:33:18 +10:00
Dion Moult 8f2a7be903 Fix #4521. 2024-04-08 08:32:28 +10:00
Dion Moult 63b541917e Minor fix in doc utility for entities which have no predefined types 2024-04-06 23:48:14 +11:00
Dion Moult c8706bddc9 Fix bug where drawing psets were needlessly updated and random objects would get drawing psets added on them
The prop update callbacks on has_underlay, has_linework, and has_annotation would edit the active object, which is not necessarily the camera. Yikes! Also, this means that when activating the drawing, it would sync references and every time it synced a reference it would attempt to sync these properties too which was slow and wasteful.
2024-04-06 23:37:42 +11:00
Dion Moult e969d407d3 Fix #4524. Update README.md 2024-04-06 22:21:16 +11:00
Dion Moult 066b198ac9 Fix #4492. Refix bug where there was an extra equal sign when loading saved queries. 2024-04-06 21:42:49 +11:00
sebphi 5d6119e542 Fix import of ifcopenshell.util.unit in Python <3.10 #4520 2024-04-05 17:46:55 +02:00
Andrej730 845e229a9c group.unassign_group - support batching #4474 2024-04-05 17:31:13 +05:00
Andrej730 cefa088b59 spatial.unassign_container - support batching #4474 2024-04-05 17:31:13 +05:00
Andrej730 109787d869 spatial.assign_container - support batching #4474 2024-04-05 17:28:30 +05:00
Andrej730 78abca16e6 black format 2024-04-05 16:47:14 +05:00
Andrej730 02aaf1b409 typing 2024-04-05 16:47:13 +05:00
Andrej730 02e102f2d2 Collector.sync skip_unlinking option #4500
To speed up the drawing process in large projects and reduce time on syncing stage I've found that if object is already assigned to it's parent collection (both as part of blender collection and in IFC) we can skip assigning stage significantly reducing time on syncing in general (from 20-25 secs -> 9 secs).

The only thing we'll be missing in that case that object won't be unlinked from other unrelated blender collections which shouldn't break anything in the case of drawing generation.
2024-04-05 16:30:44 +05:00
Andrej730 ec0dcb7f8c documentation and small refactor 2024-04-05 16:30:43 +05:00
Andrej730 ba63ed81a1 small optimizations
1) stop on first parent collection
2) ensure object's own collection's parent has BIMCollectionProperties.obj during the loop
3) collector._get_collections - returns project's own collection for consistency
4) couple tweaks skipping unnecessary iterations in loops
2024-04-05 15:55:01 +05:00
Dion Moult fb6660361d See #4518. Fix pypi python version requirements. 2024-04-05 21:13:24 +11:00
Dion Moult ecae9943e2 Add missing build dep 2024-04-05 21:12:27 +11:00
Thomas Krijnen 104e896531 #4278 Update cd.yml bump ubuntu version 2024-04-05 09:27:33 +02:00
Dion Moult 6555e56647 Fix evil hacks in ifcopenshell pip dist packaging and support py312 on PyPI 2024-04-05 16:54:29 +11:00
Dion Moult f9a590a22c Ship py39 to py312 for PyPI 2024-04-05 16:49:14 +11:00
Dion Moult ad4a1576d3 Allow manual workflow dispatch of ci-ifcopenshell-pypi.yml 2024-04-05 16:44:53 +11:00
Dion Moult c35c3f06b5 Fix #4504. Extract CAD layers when linking in models. 2024-04-05 15:03:47 +11:00
Dion Moult fada808a2f See #4504. Extract material data for linked models. 2024-04-05 14:53:11 +11:00
Dion Moult e221e6fa6a See #4508. Update example code to serialise to obj. 2024-04-05 08:25:59 +11:00
jaymeandretta 725dac04d0 Updates links to BlenderBIM and IfcOpenShell documentation in README 2024-04-05 08:12:43 +11:00
Andrej730 5c365990b9 bim.debug_active_drawing debug operator #4500
Operator can help narrow down failing elements when drawing is failing with "RuntimeError: An unknown error occurred".

it's located in debug section - https://i.imgur.com/b03jmkZ.png

The output is noisy but the results are highlighted:
https://i.imgur.com/0ZA0u60.png
https://i.imgur.com/oXzqIXU.png
https://i.imgur.com/Y0nMRMz.png
2024-04-04 16:42:00 +05:00
Andrej730 797e2e41a5 bim.create_drawing to sync only once if printing multiple drawings
Turned out syncing process takes too much time on large projects, so we can speed up the printing process by syncing just once. Also exposed option not to sync to operator's properties to allow debugging failing elements.

A bit related to #4500
2024-04-04 16:41:59 +05:00
Andrej730 57fe40839b black format, cleanup, notes
bim.generate_global_id - forgot to remove it previously, this operator isn't intended for debug section
2024-04-04 16:21:10 +05:00
Andrej730 b4c5ad1e35 typing 2024-04-04 16:07:31 +05:00
Dion Moult 635ddce93d Autodetect CSG representation types in shape builder 2024-04-04 18:28:26 +11:00
Dion Moult 24e807cac7 Detect binary data types in util.attribute 2024-04-04 18:28:26 +11:00
Massimo Fabbro 33ceba38a8 Qto_Calculator more consistent calculation about the PredefinedType 2024-04-03 22:26:50 +02:00
Massimo Fabbro 90ed1f9b28 improve the flooring height object quantity calculation 2024-04-03 22:26:50 +02:00
Massimo Fabbro e431cb205c improve the ceiling height object quantity calculation 2024-04-03 22:26:50 +02:00
Massimo Fabbro 09bb45f5bc fix add ceiling from walls elevation bug 2024-04-03 22:15:11 +02:00
Andrej730 7087452cde log external styles names when they fail to load 2024-04-03 17:37:38 +05:00
Andrej730 af4b58444d typing 2024-04-03 17:37:38 +05:00
Andrej730 711423cae5 use default shading_styles.json in case if project's file is missing
to make it consistent with how we use in similar cases default styles, symbols, etc
2024-04-03 17:37:38 +05:00
Andrej730 eb8b6062b4 move override panel methods to tool.Blender
to keep them close, so it will be easier to keep them in sync
2024-04-03 17:37:38 +05:00
Andrej730 5ac96cb5b9 change .poll for overridden panels instead of creating new classes
Needed to avoid errors disabling addons that have overridden panels
2024-04-03 17:37:38 +05:00
Andrej730 c462fc9337 move scene panels from different addons to "Blender Properties"
now we also automatically get list of default blender scene panels instead of hardcoding them
2024-04-03 17:37:38 +05:00
Andrej730 95f7a2bcde override blender ui panels when should_setup_workspace = False
Previously with should_setup_workspace = False it wouldn't override Blender UI scene panels and they would get mixed up with BBIM panels. After fix Blender panels are always moved to "Blender Properties" tab

before - https://i.imgur.com/bPvcAEe.png
now - https://i.imgur.com/lRUox29.png
2024-04-03 17:37:38 +05:00
Dion Moult 04cf9c870b Fix bug when linking in models with no chunks 2024-04-03 21:24:03 +11:00
Andrej730 bf9bddc867 ifcopenshell.validate to check unique guids (IfcRoot.UR1) 2024-04-02 17:17:23 +05:00
Andrej730 20a2fda066 typing 2024-04-02 15:45:42 +05:00
Andrej730 f03afb20f3 validate.py - black format 2024-04-02 15:45:42 +05:00
Gorgious56 e0df22ffec The button to remove an inherited pset is no longer enabled on instances
Previously it would fail silently if clicked
2024-04-02 09:52:43 +02:00
Andrej730 96302a5c07 Return of the missing button for regenerating global id
Was about to implement to find that we already have "bim.generate_global_id" and it just was lost at some point. Returned it to the attributes UI. It also allows Updating guids for all selected elements on Shift+Click.

This also can be useful to solve MergeProject and other guids conflicts, see example issue in https://community.osarch.org/discussion/2062/bbim-mergeproject-patch-not-separating-elements-in-different-buildings

Example - https://imgur.com/a/mvRtyER
2024-04-02 12:44:06 +05:00
Andrej730 38afce1628 RegenerateGlobalIds - tests 2024-04-02 12:32:36 +05:00
Andrej730 cd5afacc97 Fix test after cf9a94a79 2024-04-01 17:22:49 +05:00
Andrej730 c2623e5dce prevent reloading reference annotations on loading .ifc #4478
1) if reference annotation was changed, it would reload discarding the changes
2) even if reference annotation wasn't changed it would still reload them producing noise in .ifc changes
2024-04-01 17:06:20 +05:00
Andrej730 42ecd7e127 typing 2024-04-01 17:04:00 +05:00
Andrej730 c80a7cfcc9 fix typo 2024-04-01 16:00:18 +05:00
Andrej730 6d8f1a3980 experimental svg to dxf conversion for drawings
Example - https://imgur.com/a/AkvLlbZ
2024-04-01 15:50:43 +05:00
Andrej730 5c4c61bb15 fix issue with prohibited facets in 0065c5f, add a test 2024-04-01 13:49:42 +05:00
Andrej730 d8af8b58bb ifctester.Specification.set_usage convenience method
sets minOccurs and maxOccurs according to the agreement mentioned in the latest ids.xsd
https://github.com/buildingSMART/IDS/blob/6d71cdf3547a0383c6cfbbead81a7cef7521ac3a/Development/ids.xsd#L67-L72
2024-04-01 12:26:07 +05:00
Andrej730 3c0405249b typing 2024-04-01 12:26:07 +05:00
Dion Moult 7f57649d10 Whoops. 2024-04-01 17:49:44 +11:00
Dion Moult fbde23870e Fix #4321. The PurgeData recipe now also works on IFC2X3. 2024-04-01 17:00:47 +11:00
Dion Moult 7b968030ca Fix #4060. The colour by property panel now shows the number of results in the legend. 2024-04-01 15:45:34 +11:00
Dion Moult b5d672ba42 Fix #4069. IfcClash Blender interface now uses the new filter UI. 2024-04-01 15:28:34 +11:00
Dion Moult efa8da83ba Bump IOS. 2024-04-01 10:19:40 +11:00
Dion Moult 64cf7237db IfcDiff now uses the new selector syntax with the new UI 2024-04-01 10:16:30 +11:00
Dion Moult cf9a94a799 Fix #4306. Remove superseded selector UI. 2024-04-01 09:44:50 +11:00
Ryan Schultz 46c1a8c8ff added search for styles - in BIM_PT_representation_items 2024-03-31 11:57:55 -05:00
Thomas Krijnen ac7f2aeb62 #4450 todo note 2024-03-31 15:16:30 +02:00
Dion Moult 9378a763f3 Fix #4328. Fix incorrect documentation about how to use the imperial_length formatting function. 2024-03-31 22:32:07 +11:00
Dion Moult 0bc83fedae Use indices instead of WorkTimeDatesInterface
Checking start/end dates are done very, very often in date calculations (often looping through every day in a date range). So I'd prefer more opaque code but less overhead.
2024-03-31 22:11:17 +11:00
Thomas Krijnen f7c03db752 #4450 don't fail on empty bbox in prefiltering 2024-03-31 11:25:56 +02:00
Dion Moult b6b8c21741 See #4484. Editing predefined type attributes now enforces valid combinations of userdefined and object/element type 2024-03-31 15:50:31 +11:00
Dion Moult c8af51cfd2 Fix #4323. Bug where running a filter could fail if you tried to compare values which had incompatible types. 2024-03-30 23:07:06 +11:00
Dion Moult 77de3820e7 More minor doc changes and polish 2024-03-30 23:06:07 +11:00
Dion Moult adc5bea4e9 Fix #4265. Bug where when loading a saved filter it showed an unnecessary = comparison (which is by default). 2024-03-30 23:05:37 +11:00
Dion Moult 1eddcb9ee0 Minor doc fixes 2024-03-30 17:37:50 +11:00
Dion Moult eae542f90f Fix #4461. Write documentation on how to build and edit documentation. 2024-03-30 17:37:14 +11:00
Dion Moult fab0d05279 Fix #4483. 2024-03-30 17:14:13 +11:00
Dion Moult d2e495a69f Fix #4318. Theme docs to match website branding and add edit button, footer links, and icons. 2024-03-30 16:47:56 +11:00
Dion Moult 02058efa4d Fix #4286. IfcCSV summaries are now formatted the same as the rows. 2024-03-30 15:40:02 +11:00
Dion Moult ed79cbb1cc Fix bug where loading drawings didn't correctly link shared meshes (e.g. from a type of an annotation) 2024-03-30 15:05:49 +11:00
Dion Moult 0065c5fba8 Fix incorrect handling of prohibited specifications 2024-03-30 00:13:02 +11:00
Andrej730 1ab24e8362 import referenced drawings activating the current drawing #4478 2024-03-29 15:13:40 +05:00
Andrej730 232b4ec34d typing 2024-03-29 15:11:47 +05:00
Thomas Krijnen 75e6147dbc #4474 Commented out code perhaps for future use 2024-03-29 10:46:25 +01:00
Dion Moult 8d7b293381 Fix #4305. Bug where importing spreadsheets into IFC didn't intelligently cast attribute data types 2024-03-29 20:37:34 +11:00
Andrej730 19c8afb652 Fix #4477 2024-03-29 11:51:14 +05:00
Dion Moult 84dcee6b36 Fix #4368. Update to latest Brickschema alpha to cut dep size 2024-03-29 15:48:52 +11:00
Dion Moult dadcf03f55 See #4473. Cull entourage library.
We haven't confirmed the legality of the other assets.
2024-03-29 15:33:47 +11:00
Dion Moult 413886ac6b Fix #4411. Fallback to absolute paths if relative paths fail. 2024-03-29 15:29:19 +11:00
Dion Moult 377f68c020 Run black on IfcTester 2024-03-29 15:28:33 +11:00
Dion Moult b718a37db2 Fix #4427. Failures are now stored in single dictionary.
There was a missing clear() call to failed_reasons which could lead to incorrect reporting. It also makes no sense to store them separately.
2024-03-29 15:06:59 +11:00
Dion Moult e21043b038 Fix #4465. Update IfcTester to IDS 0.9.7 2024-03-28 22:47:48 +11:00
Andrej730 395598d8e3 Fix invalid profiles after subdividing edges near arcs #4471 2024-03-28 14:02:03 +05:00
Andrej730 813518bcb5 use blender units subtype for cad props 2024-03-28 14:02:03 +05:00
Andrej730 4ce7a898c9 typing, format 2024-03-28 14:02:02 +05:00
Thomas Krijnen 78f235ea7c #4273 ignore degenerate triangles in mesher output 2024-03-28 09:06:41 +01:00
Vukas Pajic bda2f50f9d sync with latest 2024-03-28 17:15:58 +11:00
Vukas Pajic 5e5ba5b3d4 Additional metadata as property set #4425
https://github.com/IfcOpenShell/IfcOpenShell/issues/4425
2024-03-28 17:15:58 +11:00
Dion Moult 22b0e9dcc5 Minor doc fixes to refer to Mac intel and silicon 2024-03-28 17:13:50 +11:00
Dion Moult 68420d43ea Clash results now are sorted and show text overlays of clash type and distance 2024-03-28 17:11:46 +11:00
Dion Moult 924e337c90 Don't consider borderline points to be inside a shape for more robust protrusion results 2024-03-28 16:57:54 +11:00
Dion Moult 04599a10d2 Change precision check of "is point on line" to be 0.01mm
Precision::Confusion() is 1e-7 which is too small apparently when doing this math. There are plenty of results which are things like 5e-7.
2024-03-28 16:57:54 +11:00
Dion Moult 84c931bb5f Fix bug where hidden elements weren't selected sometimes 2024-03-28 16:52:30 +11:00
Dion Moult 572aedb3eb Fix #4269. Include dateutil for ifcopenshell on pypi 2024-03-28 10:48:19 +11:00
Dion Moult 1db86c24e3 Bump IfcOpenShell and remove py37 py38 from installation docs 2024-03-28 10:47:44 +11:00
Andrej730 74adde3030 ExtractElemenets - add some tests 2024-03-27 17:52:18 +05:00
Andrej730 f68e8f0e00 ExtractElements - fix bug where IfcElementAssembly elements wasn't added to the spatial tree
Example issue - https://community.osarch.org/discussion/2054/bbim-extractelements-patch-and-missing-elements-in-ifcelementassembly
2024-03-27 16:59:10 +05:00
Andrej730 98386984f3 Fix tests involving changes in Blender 4.1 (will fail on Blender <4.1)
Basically in Blender 4.1 they've changed a way renaming IDs works if name already existed before and those tests will stop working on Blender <4.1, see https://projects.blender.org/blender/blender/issues/119955
2024-03-27 15:23:46 +05:00
Andrej730 7b35ed97ca tests - reduce precision to our usual Cad tool precision to fix some tests 2024-03-27 15:07:49 +05:00
Andrej730 b3302ed679 Fix test in 1c1035e
tag @brunoperdigao for information
2024-03-27 15:07:49 +05:00
Andrej730 e12f060af8 black format 2024-03-27 15:07:49 +05:00
Dion Moult 6d4f47a2ca New entourage library including people and cars 2024-03-27 20:23:44 +11:00
Thomas Krijnen e49ab7e0cc #4070 don't increment aggregate length on invalid members 2024-03-27 09:36:05 +01:00
Dion Moult ccfe7ab80a Optimise property / quantity checks for IDS and other purposes 2024-03-27 16:09:13 +11:00
Dion Moult 7b95358975 Forgot to commit clash tool 2024-03-27 16:06:47 +11:00
Dion Moult bcc50aaadc Switch over clash to latest add_element signature 2024-03-27 16:06:15 +11:00
Dion Moult 30f63b7d67 Minor note about which OCCT version is currently supported 2024-03-27 15:59:25 +11:00
Dion Moult ead13e23bc Write docs about new geometry tree clash functions 2024-03-27 15:59:25 +11:00
Thomas Krijnen 24257d2a85 checkout v0.7.0 -- src/ifcgeom_schema_agnostic/IfcGeomRepresentation.cpp 2024-03-26 15:13:36 +01:00
Dion Moult 5c4dbb924d Reinclude missing max_protrusions_ 2024-03-26 15:13:36 +01:00
Thomas Krijnen 88e1b3d2f9 Fix is_manifold(), sorry :( 2024-03-26 15:13:36 +01:00
Thomas Krijnen f67b80c6a6 Integrate @Moult's add_element(TriangulationElement*) 2024-03-26 15:13:36 +01:00
Dion Moult c7fd42f2cf Clash detection features 2024-03-26 15:13:36 +01:00
Thomas Krijnen bed03bf83c #4307 2024-03-26 12:27:05 +01:00
Andrej730 1e49400e93 fix styles tests 2024-03-26 16:12:13 +05:00
Andrej730 608c0dc05d Fix tests, clean up file creating empty project
To fix tests changed bim.unlink_object not to unlink original blender materials as they might be used elsewhere.
2024-03-26 15:59:23 +05:00
Andrej730 921dbe9dae restore accidentally unregistered operator in 9be7d95 (caused issue in tests) 2024-03-26 15:54:46 +05:00
Andrej730 a7e0837f96 small optimization
not to calculate each quantity twice
2024-03-26 15:54:46 +05:00
Andrej730 071c80f845 fix tests 2024-03-26 15:54:46 +05:00
Andrej730 06d823b9d8 typing 2024-03-26 15:54:45 +05:00
Andrej730 b15e33e5e9 fix test after 7634e71 2024-03-26 14:32:16 +05:00
Thomas Krijnen 55885f7a24 #4329 Don't accept non-finite floats 2024-03-26 10:23:14 +01:00
Ian e5fd6d9152 Update build-deps.cmd 2024-03-26 19:32:56 +11:00
Gorgious56 4dda7e84a0 You can now select only the aggregate or the aggregate and all its part using the interface 2024-03-26 08:33:18 +01:00
Dion Moult bb6be4d52d Blender IfcClash interface now supports new IfcClash modes
The dumb clash manager has been merged in with the main clash sets window. Loading and saving clashes now also bring in clash results, so the dumb clash manager's load/save is being deprecated. New decorator highlights clash result visually.
2024-03-26 15:24:47 +11:00
Dion Moult 01d487f585 Upgrade IfcClash to use new clash capabilities (currently unmerged from feature-clash2) 2024-03-26 15:22:56 +11:00
Andrej730 67a17ddd1b Fix hiding annotation objects without repr on drawing activation 2024-03-25 14:44:51 +05:00
Andrej730 f916edce1e typing 2024-03-25 14:43:53 +05:00
Andrej730 b0d810199b fix duplicated function names in test_feature 2024-03-25 14:18:35 +05:00
Andrej730 515a72cec9 fix linked ifc selectability toggle 2024-03-25 14:01:08 +05:00
Andrej730 7464f7519f fix load/unload linked ifc tests 2024-03-25 14:01:08 +05:00
Andrej730 f0dde4f825 fix linked ifc visibility toggle tests 2024-03-25 14:01:08 +05:00
Andrej730 54fefb3684 fix linked ifc wireframe mode toggle 2024-03-25 14:01:08 +05:00
tim 628fcb2999 Remove object text from subheadings (#4445)
https://github.com/IfcOpenShell/IfcOpenShell/issues/4312
2024-03-25 13:37:11 +05:00
Thomas Krijnen 22f830b053 #4130 setOnlyValid() option that skips and logs invalid topology 2024-03-24 16:45:31 +01:00
Bruno Postle 579c72fc69 Drawings panel: fix select_all_drawings operator
Previously it always deselected all drawings, now works as advertised
2024-03-24 10:31:47 +00:00
Andrej730 642a0f7084 black format 2024-03-22 17:46:21 +05:00
Andrej730 7cd5aab1c5 Fix ifc linking tests 2024-03-22 17:44:38 +05:00
Andrej730 4b15535c9c Fix issue removing material set
1) core.remove_material_set were removing material set and calling material_tool.get_active_material_type()
2) material_tool.get_active_material_type() called MaterialsData which failed with the traceback below since props.materias[current_index] had ifc id that were just removed

Traceback (most recent call last):
  File "\prop.py", line 96, in get_material_types
    MaterialsData.load()
  File "\data.py", line 43, in load
    "active_styles": cls.active_styles(),
                     ^^^^^^^^^^^^^^^^^^^
  File "\data.py", line 119, in active_styles
    material = tool.Ifc.get().by_id(material)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File \blenderbim\libs\site\packages\ifcopenshell\file.py", line 411, in by_id
    return self[id]
           ~~~~^^^^
  File \blenderbim\libs\site\packages\ifcopenshell\file.py", line 396, in __getitem__
    return entity_instance(self.wrapped_data.by_id(key), self)
                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File \blenderbim\libs\site\packages\ifcopenshell\ifcopenshell_wrapper.py", line 4619, in by_id
    return _ifcopenshell_wrapper.file_by_id(self, id)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
RuntimeError: Instance # 66 not found
File "\prop.py", line 94, in get_material_types
WARN (bpy.rna): C:\Users\blender\git\blender-v410\blender.git\source\blender\python\intern\bpy_rna.cc:1366 pyrna_enum_to_py: current value '2' matches no enum in 'BIMMaterialProperties', '', 'material_type'
2024-03-22 16:54:42 +05:00
Andrej730 b978da24d9 fix ui error getting active styles for non IfcMaterial materials
Error was:

```
Traceback (most recent call last):
  File "\blenderbim\bim\module\material\ui.py", line 43, in draw
    MaterialsData.load()
  File "\blenderbim\bim\module\material\data.py", line 43, in load
    "active_styles": cls.active_styles(),
                     ^^^^^^^^^^^^^^^^^^^
  File "\blenderbim\bim\module\material\data.py", line 117, in active_styles
    return results

  File "\blenderbim\libs\site\packages\ifcopenshell\entity_instance.py", line 194, in __getattr__
    raise AttributeError(
AttributeError: entity instance of type 'IFC4.IfcMaterialLayerSet' has no attribute 'HasRepresentation'
```
2024-03-22 16:54:42 +05:00
Andrej730 7132c4e9bf typing and small refactor 2024-03-22 16:54:42 +05:00
Andrej730 5aa6123806 Blender 4.1 - fix errors due to renamed type manager icon 2024-03-22 15:58:45 +05:00
FabioPiccinini e48299cb3f Update sample.css (#4459)
Changed unescaped ; with , because it may cause problems if the style is included in svg
2024-03-22 11:51:57 +01:00
FabioPiccinini c65088a7a7 Unescaped ; was causing problems in Inkscape (#4456)
* Update default.css

An unescaped ; was causing problems when opening svgs that included this style with Inkscape

* Update default.css
2024-03-22 10:06:57 +01:00
Thomas Krijnen 4f53d8c5f1 #4452 release gil in ifcopenshell.open() and read() 2024-03-21 14:11:34 +01:00
Andrej730 424b2589d9 add macos py3.11-3.12 to docs 2024-03-21 16:59:53 +05:00
Andrej730 05f6ed1084 bump ifc version #4174 2024-03-21 15:10:57 +05:00
Andrej730 9ec80cee0d TesselateElements - avoid processing queries that affect no elements
Previously it was failing with

 File "\ifcopenshell\geom\main.py", line 119, in __init__
    self.this = initializer(
  File "\ifcopenshell\ifcopenshell_wrapper.py", line 2467, in construct_iterator_with_include_exclude
    return _ifcopenshell_wrapper.construct_iterator_with_include_exclude(settings, file, elems, include, num_threads)
TypeError: Attribute of type AGGREGATE OF STRING needs a python sequence of strs
2024-03-21 15:06:32 +05:00
Andrej730 a5daf4177a add_mesh_representation - option to force faceted breps 2024-03-21 15:06:32 +05:00
Andrej730 1b1eccb0c1 typing 2024-03-21 15:06:32 +05:00
Andrej730 ece9e5c834 grammar 😬 2024-03-21 11:57:42 +05:00
Andrej730 6005ff641b Clarifying info message saving guids to the text inside .blend #4446
Example - https://i.imgur.com/YtuQ9YA.png
2024-03-21 11:55:28 +05:00
Andrej730 fa603244b9 black format 2024-03-21 11:55:20 +05:00
Andrej730 6d6010be3b typing 2024-03-21 11:32:53 +05:00
Andrej730 3691052f06 bim.select_similar_type optimization
1) avoid selecting elements of the same type multiple times
2) iterate over occurrences of the type rather than visible objects, as it's usually smaller amount of objects
2024-03-21 11:23:45 +05:00
Thomas Krijnen e38eafd034 #4447 build-all.py 2024-03-20 14:58:06 +01:00
Andrej730 976eeaef20 make spatial elements unselectable from viewport by default
It seems most of the time they only get in the way trying to select some objects at scene origin.

fyi @theoryshaw
2024-03-20 17:30:58 +05:00
Andrej730 09da42d649 typing 2024-03-20 17:30:58 +05:00
Andrej730 97b23a9ac6 unload decorations on unlinking ifc file 2024-03-20 17:30:58 +05:00
Andrej730 5e8a57f089 fix issue with removing collections and scenes unlinking .ifc
it couldn't find linked collections and scenes since it was comparing `filepath` from `props.links` (.ifc file) and collection.library.filepath (which is .blend file)
2024-03-20 17:30:58 +05:00
Andrej730 7f34d18bbf hide "no object selected" label when there are no linked ifc files 2024-03-20 17:30:58 +05:00
Andrej730 97cc19ed43 Remove appending occurrences from library in favor of fcaac6b 2024-03-20 17:30:57 +05:00
aothms d49faf7412 #4174 debug osx install of py311 and 312 2024-03-20 06:22:33 -04:00
Andrej730 fcaac6bd98 Append IfcElements from linked IFC files
Example - https://imgur.com/a/phPcKSo

Appending ifc elements from library files will be deprecated as it's much more limited than appending it from linked files (in library you can only see object's name).
2024-03-19 17:45:48 +05:00
Andrej730 c35cdcfcad util.selector.set_element_value to work w/o preassigned placement #4439 2024-03-19 17:45:21 +05:00
Andrej730 205da63d8e util.selector.set_element_value ignore values within tolerance #4439 2024-03-19 17:45:21 +05:00
Andrej730 3f529ae21b collector.sync note 2024-03-19 17:45:21 +05:00
Andrej730 89f7105e9c typing 2024-03-19 17:45:20 +05:00
Andrej730 e5dd06c461 Fix for loading IFC files with non-IfcElement based IfcRelNests
Example is below, it was using IfcAlignment to aggregate IfcAnnotations and IfcReferents.

I've added support loading collections for IfcPositioningElements besides IfcGrid (grids already handled separately).

https://community.osarch.org/discussion/2034/ifc-not-visible-after-loaded-in-blender-bim
2024-03-19 15:25:38 +05:00
Andrej730 ec7af4e719 typing 2024-03-19 15:24:14 +05:00
Bruno Perdigão e6bef16c45 small deletions 2024-03-18 23:39:06 -03:00
Andrej730 a755ec99f9 small fix 2024-03-18 17:08:39 +05:00
Andrej730 812389bce2 ifccsv - support for assigning x/y/z coordinates from csv 2024-03-18 17:06:00 +05:00
Andrej730 b67b5e8d9f typing 2024-03-18 17:05:55 +05:00
M!l!nd e2a06e10b6 fixed qsets to display separately (#4423)
* fixed qsets to display separately

Signed-off-by: milinddethe15 <milinddethe15@gmail.com>

* updated data.py and run black formattor

---------

Signed-off-by: milinddethe15 <milinddethe15@gmail.com>
2024-03-18 15:14:15 +05:00
Ryan Schultz 7fa26b2d60 small fix to cdd332d1ee, for clarity 2024-03-17 14:37:38 -05:00
Ryan Schultz cdd332d1ee fixes #4434 2024-03-17 14:20:27 -05:00
Bruno Perdigão 4829c4c02b fix: when subaggregate that is a linked aggregate is unassinged from the main aggregate, it remains a linked aggregate. 2024-03-16 15:27:04 -03:00
Bruno Perdigão 3bf4a8ef52 small refactor 2024-03-16 12:44:26 -03:00
Bruno Perdigão c95a47ca97 fix #4430. Shift+D whem selecting all the objects of a linked aggregate creates a normal copy that is not linked 2024-03-15 21:34:27 -03:00
Thomas Krijnen 22f21a3f0b Wait for threads in iterator destructor 2024-03-15 13:57:44 +01:00
Andrej730 ee27bc3b74 fix error loading representation without styles
If current representation didn't had any styles, only a material and it was failing during `assign_material_slots_to_faces` since it was looking for blender material only in self.styles and not in self.materials.

Note: `assign_material_slots_to_faces` is not skipped since `parse_representations` returns `True` because other representation has a style and it's added to the materials.
2024-03-15 15:55:23 +05:00
Andrej730 8307bf5877 typing 2024-03-15 15:50:38 +05:00
Andrej730 09e473a0dc python code style docs 2024-03-15 12:02:43 +05:00
Benoit DAMAY abeefeeee7 removing debug print from previous commit 2024-03-15 14:05:09 +11:00
Benoit DAMAY d949180461 replacing os.rename() by shutil.move in ifcpatch 2024-03-15 14:05:09 +11:00
Jordan McManus 708bd3207e changed type comparison to GlobalId 2024-03-15 14:04:53 +11:00
Jordan McManus 68d9d04e5b changed comparison for type relationships 2024-03-15 14:04:53 +11:00
jgunstone 7c7bd3dc53 add get_properties test validating that offset and limit are correctly applied 2024-03-15 14:03:58 +11:00
jgunstone 24d2b59a2e add limit and offset to client functions 2024-03-15 14:03:58 +11:00
Andrej730 ac2684af8d fix bug after 8291a0ca6 2024-03-14 20:21:52 +05:00
Andrej730 8291a0ca6a viewport decorations for IfcAnnotation/MULTI_SYMBOL 2024-03-14 16:50:17 +05:00
Andrej730 b72b23741e common method for getting annotation symbol 2024-03-14 16:46:22 +05:00
Andrej730 858692f3f5 Indicate rotated/scaled symbols in viewport 2024-03-14 16:46:22 +05:00
Andrej730 c74bfa24d2 Support for rotated and scaled symbols with IfcAnnotation/SYMBOL
See: https://community.osarch.org/discussion/2027/bbim-rotation-of-ifcsymbol
2024-03-14 15:33:19 +05:00
Andrej730 66c29f0da1 black format 2024-03-14 15:07:11 +05:00
Andrej730 f20fd3357f typing 2024-03-14 14:50:34 +05:00
Andrej730 e4eb76d4e0 indicate IfcText symbol rotation in viewport 2024-03-13 17:21:10 +05:00
Andrej730 22c95828ce BBIM IfcTester - option to generate ODS report
https://i.imgur.com/0JyXVK6.png
2024-03-13 16:16:50 +05:00
Andrej730 e87ca960e8 Fix #4421
1) Excluded annotations from other drawings that user might accidentally include in IfcAnnotation
2) Excluded from EPset_Drawing.Include to avoid getting there other element types besides spaces
2024-03-13 15:32:42 +05:00
Andrej730 6881e7d220 utils.filter_elements docs and tests 2024-03-13 15:32:42 +05:00
Andrej730 28b54c869a typing 2024-03-13 15:32:42 +05:00
Andrej730 9596117953 small fixes 2024-03-13 15:32:42 +05:00
Sigma Dimensions (Yass) 8dbeb928e3 fix construction schedule import without IfcProject #4412 2024-03-13 03:15:27 +00:00
Andrej730 4fdb78aebc spatial.workspace - add hotkeys using add_layout_hotkey 2024-03-12 15:04:49 +05:00
Andrej730 01724913e1 bim.decorate_boundaries to decorate all boundaries in the scene
It's added as alt+v hotkey in Spatial Tool
Now it's possible to combine it with bim.load_project_space_boundaries to show decorations for all the boundaries or to keep the boundaries after Alt+B and just disable the decorations.

example - https://imgur.com/a/ecbPz13
2024-03-12 15:04:49 +05:00
Andrej730 44dd07547e Fix issue with drawing same boundaries multiple times
If you'd hit alt+b in space tool multiple times it would redraw them again and again instead of switching between hiding and showing.
2024-03-12 12:12:12 +05:00
Andrej730 cc157239a2 model.space.py - small fixes 2024-03-12 12:10:56 +05:00
Andrej730 0576914e07 small fixes after d209cc9ff, added a test 2024-03-11 15:15:17 +05:00
Andrej730 a064173ffe BBIM - option to append occurrences from the other ifc files
Example - https://imgur.com/a/yASjbs8
2024-03-11 15:15:17 +05:00
Andrej730 8e502c9bb7 util.unit.py - black format 2024-03-11 15:15:17 +05:00
Andrej730 4d23706145 more typing 2024-03-11 15:15:17 +05:00
Geert Hesselink 6e8de292c6 Redundant closing symbol ' (#4413)
* scale placeangleunit

* add radian si_conversions

* return conversion value of planeangleunit

* Update unit.py

* redundant closing

* redundant closing
2024-03-11 11:12:23 +01:00
Geert Hesselink d209cc9ff4 Scale placeangelunit unit type (#4406)
* scale placeangleunit

* add radian si_conversions

* return conversion value of planeangleunit

* Update unit.py
2024-03-11 08:54:20 +01:00
Andrej730 ddfaf3308b fix error reloading representation for objects without representation 2024-03-11 11:39:18 +05:00
Andrej730 301ddab1ee commented out apil.run settings serialization as it's not used currently 2024-03-11 11:39:18 +05:00
Andrej730 1160d20d33 small fixes 2024-03-11 11:39:18 +05:00
Ryan Schultz 28099ddc8f Add parameter for angle tolerance, and change 'deg' to '°' 2024-03-09 09:53:21 +11:00
Andrej730 c1bca22638 add test for util.unit.calculate_unit_scale 2024-03-08 16:40:43 +05:00
Andrej730 3d5d868812 fix error in typing 2024-03-08 16:40:30 +05:00
fescale b015647543 Replace the specific "libpython3.8" debian package dependency for the generic "libpython3-dev". 2024-03-07 09:59:58 +01:00
Ryan Schultz c54f2131af added python version for Blender 4.1 2024-03-07 09:34:13 +11:00
Andrej730 2a832202fa Fix bug joining multiple extruded profiles in non-meters projects
Demo if someone missed that feature - https://imgur.com/a/JywJHyc
2024-03-06 17:30:55 +05:00
Andrej730 a3d970e014 shape_builder.create_axis2_placement_3d utility method and docs 2024-03-06 17:30:55 +05:00
Andrej730 24912bb827 Fix bug resetting rotations after editing profile axis
Before - https://imgur.com/a/czjAoSb
After - https://imgur.com/a/ajiNcdK
2024-03-06 16:22:15 +05:00
Adrian Insaurralde Avalos 1cd0bc9e7d occt 7.8 compatibility 2024-03-06 09:01:01 +01:00
Dion Moult 8880ab0fe9 Aggressively remove pandas test files 2024-03-06 17:11:33 +11:00
Andrej730 1c052d1417 fix demo library references after 7a7c89e 2024-03-06 10:53:19 +05:00
Dion Moult 20cb6ab007 See #4368. Don't ship with mostly unused and bloaty boto3 dep. Augin users can install boto3 with new debug tool. 2024-03-06 16:46:26 +11:00
Dion Moult e854447714 Little debug tool for people to easily install pip packages graphically
BBIM is being used in companies as ways to automate various BIM processes, and it ends up being such that Blender Python becomes their dev environment.
2024-03-06 16:44:32 +11:00
Dion Moult 7f394a5807 Purge hppfcl in preparation for new clash features 2024-03-05 23:16:47 +11:00
Andrej730 c80b437682 load clipping planes decorator reopening .blend file 2024-03-05 15:01:23 +05:00
Andrej730 b793ab63cc fix poll errors for clipping plane gizmo #4392 2024-03-05 14:37:30 +05:00
Andrej730 8c28f52e6a fix duplicated ifcstyleditems using append_asset
since we don't use `file.add` to add inverse items and we use `file.create_entity` it was duplicating new elements, added optional parameter that makes it possible to track already added relationships between `append_asset`s.
2024-03-05 14:18:18 +05:00
Benoit DAMAY 9d71b7483f Merge pull request #4397 from bdamay/v0.7.0
Fix proposal for # 3515 
OK I've deleted the unnecessary commented lines and left the option that feed only the description tooltip. 

BTW, I have 2 questions i would like to ask you
1. about comment, i noticed there are not many comments in the code. I understand it is deliberate choice of design, is it ? 
I usually comment a lot inside  the code sometimes simply to order my thoughts and remember what the code is intending to do when i come to it later on. 
I understand it is better not to leave unneccessary  lines like here, but in general, do you prefer me to avoid comments when writing in blenderbim ?    

2. As i'm in a discovering phase, I would like to feed some other bl_description operators to provide some additionnal inline information instead (undocumented operator) , is it something you planned or can i go for it. I could simply add the bl_description in each one i find while reading code and testing app.    

Thank you for your kind help. and G'day !
2024-03-05 09:14:06 +01:00
Benoit DAMAY e6e71adba7 Removing unnecessary comment lines 2024-03-05 08:53:56 +01:00
Benoit DAMAY 7ab8a5921b Merge branch 'v0.7.0' of https://github.com/bdamay/IfcOpenShell into v0.7.0 2024-03-04 17:11:54 +01:00
Benoit DAMAY d68d4d54a9 Replaced panel row with operator descriptor 2024-03-04 17:10:19 +01:00
Andrej730 bce0f0d8d4 fix typing loading issues 2024-03-04 18:14:50 +05:00
Andrej730 50648fd350 append_asset - keep preexisting contexts valid #4280
issue occurred with ifcpatch ExtractElements - it was adding project from other ifc file (with the contexts) and then adding some elements from it using `append_asset`. During `append_asset` it was considering existing context with the duplicated one and was partially purging it.
2024-03-04 18:04:24 +05:00
Andrej730 b7e26bacea typing 2024-03-04 18:02:49 +05:00
Andrej730 a2fdd93e6b Optimise patch - lazy import toposort
to fix empty Optimise patch description in BlenderBIM
2024-03-04 18:02:49 +05:00
Dion Moult aef00a4dfc See #4389. Accommodate null pset values when serializing to SVG. 2024-03-04 17:53:53 +11:00
Dion Moult efae8ebdcd Add tree species to tree library 2024-03-04 17:51:48 +11:00
Dion Moult 61403fcf42 See #4389. Accommodate null metadata. 2024-03-04 17:51:07 +11:00
Benoit DAMAY badee8fdc5 Fix proposition to 3515 - extra empty lines removed from previous commit 2024-03-03 19:29:02 +01:00
Benoit DAMAY 36e779e32f Fix proposition to 3515 2024-03-03 19:23:51 +01:00
M!l!nd c50ac3db4f Update Makefile 2024-03-03 23:10:27 +11:00
Dion Moult b17c261344 Minor fix to shape aspects because not everything has a mesh (e.g. text annotations) 2024-03-03 23:09:40 +11:00
Dion Moult 956aae37a8 Fix #4377. Add support for adding filled elements in AXIS3 layered elements. 2024-03-03 23:09:17 +11:00
Dion Moult 48d6cc8a28 Fix #4383. Demo library should use Model/Annotation/MODEL_VIEW for text annotations so that they can be used anywhere, not just on plans. 2024-03-03 16:12:18 +11:00
Dion Moult cf9d3026f4 Fix #4384. Accommodate null values when getting data from pset. 2024-03-03 15:46:55 +11:00
Dion Moult e2f0b1ed5c Fix #4336. Close #4337. Remove rounding for more accurate fills. 2024-03-03 15:36:19 +11:00
Dion Moult b833cffbd3 Minor fixes to IfcCSV documentation 2024-03-03 15:25:15 +11:00
Dion Moult 7a7c89e475 Separate project templates and libraries and provide dropdown to built-in libraries to aid discoverability. 2024-03-03 15:24:46 +11:00
Dion Moult 01b0164e0d Fix #4385. Probably a mistake in the bool argument for removing datablocks. 2024-03-03 14:25:55 +11:00
Dion Moult 13f26d9b70 Tweak generated tree parameters and add manual trees to landscape library thanks to SavyGust27 2024-03-02 22:59:12 +11:00
jgunstone 8a88b0b3a1 🔧update pip install command assuming that this branch will be merged back 2024-03-02 10:56:20 +11:00
jgunstone a15e293db5 update license and version 2024-03-02 10:56:20 +11:00
jgunstone 9509f07da8 📝add docs about so to install and develop this package standalone 2024-03-02 10:56:20 +11:00
jgunstone 37b15de3e4 add simple tests to ensure the package is working as expected 2024-03-02 10:56:20 +11:00
jgunstone 66b4509b06 🐛 add limit and offset kwargs to get_classes. TODO- I think this needs doing throughout... 2024-03-02 10:56:20 +11:00
jgunstone 41dcb83bdb add version 2024-03-02 10:56:20 +11:00
jgunstone 0bda41710c 🔧 add pyproject.toml and __init__.py 2024-03-02 10:56:20 +11:00
Dion Moult cbd85fafda Fix #889. Write docs for how to do translations. 2024-03-01 23:16:05 +11:00
Andrej730 dc7ebab0d1 Clipping planes with cappings
Added new experimental section that allows you to cut objects based on current clipping planes in the scene. The difference from other clipping plane methods - it actually (temporarily) edits meshes geometry and adds cappings at the cut. So, the clipped meshes can be seen both on render and cutted parts won't get in the way as they do with temporary section cutaways (shader based method)

Example - https://imgur.com/a/SOqovOM
2024-03-01 16:22:58 +05:00
Andrej730 0c2a3ff400 remove_data_block - option to skip some checks to speed it up 2024-03-01 16:22:58 +05:00
Dion Moult 6af6fda799 See #4380. Remove support for EOL Python 3.6 2024-03-01 21:04:09 +11:00
Dion Moult 2c9e443425 Fix #4300. You can now select spatial elements by their location too. 2024-03-01 20:02:15 +11:00
Dion Moult 2bede1cdf0 Fix #4340. Indicate user needs to setup IFC2X3 user/app when using API directly. 2024-03-01 19:46:50 +11:00
Dion Moult 4f3397a04e Fix #4333. Ensure order of info is correct for valid IDSes. 2024-03-01 16:42:38 +11:00
Dion Moult 9b8adad8c8 See #4380. Standardise filepaths when linking in IFCs. 2024-03-01 16:32:41 +11:00
Dion Moult c48bccf727 You can now specify a false origin when linking in a model 2024-03-01 15:02:01 +11:00
Dion Moult 5d41336342 Add Python3.12 to installation docs 2024-03-01 12:39:39 +11:00
Dion Moult b8628cc9df See #4380. Bump IOS. 2024-03-01 12:23:42 +11:00
Dion Moult 519cb86163 New cable carrier and cable tool grouped into MEP tool submenu 2024-03-01 12:12:35 +11:00
Dion Moult 0008d8bd2a Add icon and convenience functions for walk and set orbit center in explore tool 2024-03-01 12:12:35 +11:00
Andrej730 bc7946091a bump svgfill 2024-02-29 16:22:08 +05:00
ChristophMellueh ac2db7b4c9 add missing Props request 2024-02-29 21:23:23 +11:00
Dion Moult 0b57d5fc5f You can now flip clipping planes 2024-02-29 20:39:29 +11:00
Dion Moult 82af25d09a New clipping planes feature. 2024-02-29 16:49:23 +11:00
Dion Moult 4a82525bdb You can now query data about a linked object from a SQLite cache. 2024-02-29 16:48:46 +11:00
Dion Moult 002112dd50 Fix #4279. You can now link in IFCs directly with efficient chunking instead of having to first create blend files 2024-02-29 16:42:54 +11:00
Dion Moult 844a9842cf See #4279. Viewport object culling feature to increase FPS when navigating large models. 2024-02-29 16:40:48 +11:00
Dion Moult 3a098e55c4 New IfcPatch recipe to extract flattened properties and relationships to SQLite for simple queries 2024-02-29 16:23:21 +11:00
Dion Moult e186b81f0c Fix #4335. Fix compilation error. 2024-02-29 16:22:39 +11:00
patrick.joerg 527d16c5f0 Small doc change to direct readers to advanced mode 2024-02-28 23:54:05 +11:00
Andrej730 8c1b33e1d5 Fix issues with accessing invalid entities in bim.reassign_class #4371
bim.reassign_class wasn't considering that root.reassign_class will also reassign class for occurrences of the same type and was reaccessing invalid elements
2024-02-28 16:54:11 +05:00
Andrej730 e46f18f686 type.py - some typing 2024-02-28 16:54:11 +05:00
Andrej730 931fd9cc9f reassign_class - cover with tests 2024-02-28 16:54:11 +05:00
Andrej730 aebc073416 fix pass-valid-selected-enumeration.ifc after 5270045
.ifc was failing during test_validate.py
1) Changed schema to rc2 as rc3-4 doesn't have IfcBridgePart
2) PredefinedType and UsageType were mixed up
3) With just `.DECK.` it was failing with error below
```
With attribute:
    <attribute PredefinedType: <select IfcFacilityPartTypeSelect: (<enumeration IfcBridgePartTypeEnum: (ABUTMENT, DECK, DECK_SEGMENT, FOUNDATION, NOTDEFINED, PIER, PIER_SEGMENT, PYLON, SUBSTRUCTURE, SUPERSTRUCTURE, SURFACESTRUCTURE, USERDEFINED)> | <enumeration IfcFacilityPartCommonTypeEnum: (ABOVEGROUND, BELOWGROUND, JUNCTION, LEVELCROSSING, NOTDEFINED, SEGMENT, SUBSTRUCTURE, SUPERSTRUCTURE, TERMINAL, USERDEFINED)> | <enumeration IfcMarinePartTypeEnum: (ABOVEWATERLINE, ANCHORAGE, APPROACHCHANNEL, BELOWWATERLINE, BERTHINGSTRUCTURE, CHAMBER, CILL_LEVEL, COPELEVEL, CORE, CREST, GATEHEAD, GUDINGSTRUCTURE, HIGHWATERLINE, LANDFIELD, LEEWARDSIDE, LOWWATERLINE, MANUFACTURING, NAVIGATIONALAREA, NOTDEFINED, PROTECTION, SHIPTRANSFER, STORAGE, USERDEFINED, VEHICLESERVICING, WATERFIELD, WEATHERSIDE)> | <enumeration IfcRailwayPartTypeEnum: (DILATATIONSUPERSTRUCTURE, LINESIDESTRUCTURE, LINESIDESTRUCTUREPART, NOTDEFINED, PLAINTRACKSUPESTRUCTURE, SUPERSTRUCTURE, TRACKSTRUCTURE, TRACKSTRUCTUREPART, TURNOUTSUPERSTRUCTURE, USERDEFINED)> | <enumeration IfcRoadPartTypeEnum: (BICYCLECROSSING, BUS_STOP, CARRIAGEWAY, CENTRALISLAND, CENTRALRESERVE, HARDSHOULDER, INTERSECTION, LAYBY, NOTDEFINED, PARKINGBAY, PASSINGBAY, PEDESTRIAN_CROSSING, RAILWAYCROSSING, REFUGEISLAND, ROADSEGMENT, ROADSIDE, ROADSIDEPART, ROADWAYPLATEAU, ROUNDABOUT, SHOULDER, SIDEWALK, SOFTSHOULDER, TOLLPLAZA, TRAFFICISLAND, TRAFFICLANE, USERDEFINED)>)>>
Value:
    DECK
Not valid
```
2024-02-28 16:54:11 +05:00
Andrej730 56e00d059b fix error removing pset from material or profile 2024-02-28 16:54:11 +05:00
Andrej730 8a6b4aa5fb fix tests after a971860 2024-02-28 16:54:11 +05:00
Andrej730 46bee8aed9 fix errors in tests
they were failing because import_reference_attributes were failing to get docs for unset schema version since ifc file wasn't attached to Blender
2024-02-28 16:54:11 +05:00
Thomas Krijnen eaa2aa05a4 Add buffer (bytestring) accessors to material colors - plus small fixes 2024-02-28 11:12:17 +01:00
Thomas Krijnen abfa6edc5c Add buffer (bytestring) accessors to geometry data 2024-02-27 16:18:40 +01:00
858 changed files with 45362 additions and 20799 deletions
+2 -2
View File
@@ -16,7 +16,7 @@ jobs:
- run: echo ok go
build:
runs-on: ubuntu-20.04
runs-on: ubuntu-latest
needs: activate
steps:
- uses: actions/checkout@v2
@@ -80,7 +80,7 @@ jobs:
path: build/assets/Ifc*
deliver:
runs-on: ubuntu-20.04
runs-on: ubuntu-latest
needs: build
name: Docker Build, Tag, Push
+48
View File
@@ -0,0 +1,48 @@
name: ci-bsdd-pypi
on:
schedule:
# ┌───────────── minute (0 - 59)
# │ ┌───────────── hour (0 - 23)
# │ │ ┌───────────── day of the month (1 - 31)
# │ │ │ ┌───────────── month (1 - 12 or JAN-DEC)
# │ │ │ │ ┌───────────── day of the week (0 - 6 or SUN-SAT)
# * * * * *
- cron: "0 0 18 * *"
workflow_dispatch:
env:
major: 0
minor: 0
name: ifcopenshell
jobs:
activate:
runs-on: ubuntu-latest
if: |
github.repository == 'IfcOpenShell/IfcOpenShell'
steps:
- name: Set env
run: echo ok go
build:
needs: activate
runs-on: ubuntu-latest
strategy:
fail-fast: false
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2 # https://github.com/actions/setup-python
with:
python-version: '3.11' # Version range or exact version of a Python version to use, using SemVer's version range syntax
- name: Compile
run: |
pip install build
cd src/bsdd &&
make dist
- name: Publish a Python distribution to PyPI
uses: ortega2247/pypi-upload-action@master
with:
user: __token__
password: ${{ secrets.PYPI_API_TOKEN }}
packages_dir: src/bsdd/dist
+48
View File
@@ -0,0 +1,48 @@
name: ci-ifcclash-pypi
on:
schedule:
# ┌───────────── minute (0 - 59)
# │ ┌───────────── hour (0 - 23)
# │ │ ┌───────────── day of the month (1 - 31)
# │ │ │ ┌───────────── month (1 - 12 or JAN-DEC)
# │ │ │ │ ┌───────────── day of the week (0 - 6 or SUN-SAT)
# * * * * *
- cron: "0 0 18 * *"
workflow_dispatch:
env:
major: 0
minor: 0
name: ifcopenshell
jobs:
activate:
runs-on: ubuntu-latest
if: |
github.repository == 'IfcOpenShell/IfcOpenShell'
steps:
- name: Set env
run: echo ok go
build:
needs: activate
runs-on: ubuntu-latest
strategy:
fail-fast: false
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2 # https://github.com/actions/setup-python
with:
python-version: '3.11' # Version range or exact version of a Python version to use, using SemVer's version range syntax
- name: Compile
run: |
pip install build
cd src/ifcclash &&
make dist
- name: Publish a Python distribution to PyPI
uses: ortega2247/pypi-upload-action@master
with:
user: __token__
password: ${{ secrets.PYPI_API_TOKEN }}
packages_dir: src/ifcclash/dist
+48
View File
@@ -0,0 +1,48 @@
name: ci-ifccsv-pypi
on:
schedule:
# ┌───────────── minute (0 - 59)
# │ ┌───────────── hour (0 - 23)
# │ │ ┌───────────── day of the month (1 - 31)
# │ │ │ ┌───────────── month (1 - 12 or JAN-DEC)
# │ │ │ │ ┌───────────── day of the week (0 - 6 or SUN-SAT)
# * * * * *
- cron: "0 0 18 * *"
workflow_dispatch:
env:
major: 0
minor: 0
name: ifcopenshell
jobs:
activate:
runs-on: ubuntu-latest
if: |
github.repository == 'IfcOpenShell/IfcOpenShell'
steps:
- name: Set env
run: echo ok go
build:
needs: activate
runs-on: ubuntu-latest
strategy:
fail-fast: false
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2 # https://github.com/actions/setup-python
with:
python-version: '3.11' # Version range or exact version of a Python version to use, using SemVer's version range syntax
- name: Compile
run: |
pip install build
cd src/ifccsv &&
make dist
- name: Publish a Python distribution to PyPI
uses: ortega2247/pypi-upload-action@master
with:
user: __token__
password: ${{ secrets.PYPI_API_TOKEN }}
packages_dir: src/ifccsv/dist
+3 -2
View File
@@ -1,6 +1,7 @@
name: ci-ifcopenshell-pypi
on:
workflow_dispatch:
schedule:
# ┌───────────── minute (0 - 59)
# │ ┌───────────── hour (0 - 23)
@@ -35,7 +36,7 @@ jobs:
strategy:
fail-fast: false
matrix:
pyver: [py36, py37, py38, py39, py310, py311]
pyver: [py39, py310, py311, py312]
config:
- {
name: "Windows Build",
@@ -57,7 +58,7 @@ jobs:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2 # https://github.com/actions/setup-python
with:
python-version: '3.10' # Version range or exact version of a Python version to use, using SemVer's version range syntax
python-version: '3.11' # Version range or exact version of a Python version to use, using SemVer's version range syntax
architecture: 'x64' # optional x64 or x86. Defaults to x64 if not specified
- run: echo ${{ env.DATE }}
- name: Get current date
+48
View File
@@ -0,0 +1,48 @@
name: ci-ifcpatch-pypi
on:
schedule:
# ┌───────────── minute (0 - 59)
# │ ┌───────────── hour (0 - 23)
# │ │ ┌───────────── day of the month (1 - 31)
# │ │ │ ┌───────────── month (1 - 12 or JAN-DEC)
# │ │ │ │ ┌───────────── day of the week (0 - 6 or SUN-SAT)
# * * * * *
- cron: "0 0 18 * *"
workflow_dispatch:
env:
major: 0
minor: 0
name: ifcopenshell
jobs:
activate:
runs-on: ubuntu-latest
if: |
github.repository == 'IfcOpenShell/IfcOpenShell'
steps:
- name: Set env
run: echo ok go
build:
needs: activate
runs-on: ubuntu-latest
strategy:
fail-fast: false
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2 # https://github.com/actions/setup-python
with:
python-version: '3.11' # Version range or exact version of a Python version to use, using SemVer's version range syntax
- name: Compile
run: |
pip install build
cd src/ifcpatch &&
make dist
- name: Publish a Python distribution to PyPI
uses: ortega2247/pypi-upload-action@master
with:
user: __token__
password: ${{ secrets.PYPI_API_TOKEN }}
packages_dir: src/ifcpatch/dist
@@ -0,0 +1,53 @@
name: Publish-ifcsverchok
on:
push:
paths:
- '.github/workflows/ci-ifcsverchok-build.yml'
- 'src/ifcsverchok/*'
branches:
- v0.7.0
env:
major: 0
minor: 0
name: ifcsverchok
jobs:
activate:
runs-on: ubuntu-latest
if: |
github.repository == 'IfcOpenShell/IfcOpenShell'
steps:
- name: Set env
run: echo ok go
build:
needs: activate
name: ifcsverchok
runs-on: ubuntu-latest
strategy:
fail-fast: false
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2 # https://github.com/actions/setup-python
with:
architecture: 'x64' # optional x64 or x86. Defaults to x64 if not specified
python-version: '3.11'
- run: echo ${{ env.DATE }}
- name: Get current date
id: date
run: echo "::set-output name=date::$(date +'%y%m%d')"
- name: Compile
run: |
cd src/ifcsverchok
make dist
- name: Upload Zip file to release
uses: svenstaro/upload-release-action@v2
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: src/ifcsverchok/dist/ifcsverchok-${{steps.date.outputs.date}}.zip
asset_name: ifcsverchok-${{steps.date.outputs.date}}.zip
tag: "ifcsverchok-${{steps.date.outputs.date}}"
overwrite: true
body: "ifcsverchok build for ${{steps.date.outputs.date}}"
+1 -10
View File
@@ -9,9 +9,6 @@ on:
# │ │ │ │ ┌───────────── day of the week (0 - 6 or SUN-SAT)
# * * * * *
- cron: "0 0 18 * *"
push:
paths:
- '.github/workflows/ci-ifctester-pypi.yml'
workflow_dispatch:
env:
@@ -30,7 +27,6 @@ jobs:
build:
needs: activate
name: ${{ matrix.config.name }}-${{ matrix.pyver }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
@@ -38,12 +34,7 @@ jobs:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2 # https://github.com/actions/setup-python
with:
python-version: '3.10' # Version range or exact version of a Python version to use, using SemVer's version range syntax
architecture: 'x64' # optional x64 or x86. Defaults to x64 if not specified
- run: echo ${{ env.DATE }}
- name: Get current date
id: date
run: echo "::set-output name=date::$(date +'%y%m%d')"
python-version: '3.11' # Version range or exact version of a Python version to use, using SemVer's version range syntax
- name: Compile
run: |
pip install build
+6
View File
@@ -76,8 +76,14 @@ src/ifcopenshell-python/test/build
# blenderbim libs
src/blenderbim/blenderbim/libs
# blenderbim i18n
src/blenderbim/blenderbim/translations.py
# blenderbim test temp files
src/blenderbim/test/files/temp
src/blenderbim/test/files/basic.ifc.cache.blend
src/blenderbim/test/files/basic.ifc.cache.sqlite
src/blenderbim/drawings
src/blenderbim/layouts
+7 -7
View File
@@ -18,14 +18,14 @@ and many other libraries, CLI apps, and more. Support is also provided for auxil
For more information, see:
* [IfcOpenShell Website](http://ifcopenshell.org)
* [IfcOpenShell Documentation](http://blenderbim.org/docs-python)
* [IfcOpenShell C++ Installation](https://blenderbim.org/docs-python/ifcopenshell/installation.html)
* [IfcOpenShell Python Installation](https://blenderbim.org/docs-python/ifcopenshell-python/installation.html)
* [IfcOpenShell Python Hello World Tutorial](https://blenderbim.org/docs-python/ifcopenshell-python/hello_world.html)
* [IfcOpenShell Documentation](https://docs.ifcopenshell.org)
* [IfcOpenShell C++ Installation](https://docs.ifcopenshell.org/ifcopenshell/installation.html)
* [IfcOpenShell Python Installation](https://docs.ifcopenshell.org/ifcopenshell-python/installation.html)
* [IfcOpenShell Python Hello World Tutorial](https://docs.ifcopenshell.org/ifcopenshell-python/hello_world.html)
* [BlenderBIM Add-on Website](https://blenderbim.org)
* [BlenderBIM Add-on Documentation](http://blenderbim.org/docs)
* [Add-on Installation](https://blenderbim.org/docs/users/installation.html)
* [Exploring an IFC model](https://blenderbim.org/docs/users/exploring_an_ifc_model.html)
* [BlenderBIM Add-on Documentation](https://docs.blenderbim.org/index.html)
* [Add-on Installation](https://docs.blenderbim.org/users/installation.html)
* [Exploring an IFC model](https://docs.blenderbim.org/users/exploring_an_ifc_model.html)
| Service | Status |
| ----------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
+1 -1
View File
@@ -12,7 +12,7 @@ RUN apt-get -y update && apt-get -y install unzip curl
# Install AWS Lambda runtime interface client
RUN pip install --target ${FUNCTION_DIR} awslambdaric
# Set the IfcOpenShell build version (check available builds at: https://blenderbim.org/docs-python/ifcopenshell-python/installation.html)
# Set the IfcOpenShell build version (check available builds at: https://docs.ifcopenshell.org/ifcopenshell-python/installation.html)
ARG IFC_OPENSHELL_BUILD="39-v0.7.0-476ab50"
# Download and extract IfcOpenShell
+1 -1
View File
@@ -15,7 +15,7 @@
<licenseUrl>https://github.com/IfcOpenShell/IfcOpenShell/blob/v0.7.0/COPYING</licenseUrl>
<requireLicenseAcceptance>true</requireLicenseAcceptance>
<projectSourceUrl>https://github.com/IfcOpenShell/IfcOpenShell</projectSourceUrl>
<docsUrl>https://blenderbim.org/docs/</docsUrl>
<docsUrl>https://docs.blenderbim.org/</docsUrl>
<!--<mailingListUrl></mailingListUrl>-->
<bugTrackerUrl>https://github.com/IfcOpenShell/IfcOpenShell/issues</bugTrackerUrl>
<tags>blender bim blenderbim ifc python opensource foss</tags>
+23 -2
View File
@@ -376,13 +376,34 @@ if(BUILD_IFCGEOM)
message(STATUS "Looking for Open CASCADE include files in: ${OCC_INCLUDE_DIR}")
endif()
if(OCC_INCLUDE_DIR)
file(STRINGS ${OCC_INCLUDE_DIR}/Standard_Version.hxx OCC_MAJOR
REGEX "#define OCC_VERSION_MAJOR.*"
)
string(REGEX MATCH "[0-9]+" OCC_MAJOR ${OCC_MAJOR})
file(STRINGS ${OCC_INCLUDE_DIR}/Standard_Version.hxx OCC_MINOR
REGEX "#define OCC_VERSION_MINOR.*"
)
string(REGEX MATCH "[0-9]+" OCC_MINOR ${OCC_MINOR})
file(STRINGS ${OCC_INCLUDE_DIR}/Standard_Version.hxx OCC_MAINT
REGEX "#define OCC_VERSION_MAINTENANCE.*"
)
string(REGEX MATCH "[0-9]+" OCC_MAINT ${OCC_MAINT})
set(OCC_VERSION_STRING "${OCC_MAJOR}.${OCC_MINOR}.${OCC_MAINT}")
endif(OCC_INCLUDE_DIR)
set(OPENCASCADE_LIBRARY_NAMES
TKernel TKMath TKBRep TKGeomBase TKGeomAlgo TKG3d TKG2d TKShHealing TKTopAlgo TKMesh TKPrim TKBool TKBO
TKFillet TKSTEP TKSTEPBase TKSTEPAttr TKXSBase TKSTEP209 TKIGES TKOffset TKHLR
TKFillet TKXSBase TKOffset TKHLR
# @todo investigate the exact conditions when this is necessary
TKBin
)
if(OCC_VERSION_STRING VERSION_LESS 7.8.0)
list(APPEND OPENCASCADE_LIBRARY_NAMES TKIGES TKSTEPBase TKSTEPAttr TKSTEP209 TKSTEP)
else(OCC_VERSION_STRING VERSION_LESS 7.8.0)
list(APPEND OPENCASCADE_LIBRARY_NAMES TKDESTEP TKDEIGES)
endif(OCC_VERSION_STRING VERSION_LESS 7.8.0)
if("${OCC_LIBRARY_DIR}" STREQUAL "")
find_library(OCC_LIBRARY TKernel
@@ -1151,7 +1172,7 @@ endforeach(COMPONENT)
set(CPACK_DEBIAN_PACKAGE_NAME "${PROJECT_NAME}")
set(CPACK_DEBIAN_PACKAGE_MAINTAINER "${CPACK_PACKAGE_CONTACT}")
set(CPACK_DEBIAN_PACKAGE_DEPENDS "python3, libxml2, libocct-foundation-dev, libocct-modeling-algorithms-dev, libocct-modeling-data-dev, libocct-ocaf-dev, libocct-visualization-dev, libocct-data-exchange-dev, libhdf5-serial-dev, libpython3.8, python3-pytest ${BOOST_DEPS}")
set(CPACK_DEBIAN_PACKAGE_DEPENDS "python3, libxml2, libocct-foundation-dev, libocct-modeling-algorithms-dev, libocct-modeling-data-dev, libocct-ocaf-dev, libocct-visualization-dev, libocct-data-exchange-dev, libhdf5-serial-dev, libpython3-dev, python3-pytest ${BOOST_DEPS}")
set(CPACK_DEBIAN_PACKAGE_DESCRIPTION_SUMMARY "${CPACK_PACKAGE_DESCRIPTION_SUMMARY}")
set(CPACK_DEBIAN_PACKAGE_DESCRIPTION "${CPACK_PACKAGE_DESCRIPTION}")
set(CPACK_DEBIAN_PACKAGE_PRIORITY "optional")
+9 -3
View File
@@ -76,7 +76,7 @@ PROJECT_NAME = "IfcOpenShell"
USE_CURRENT_PYTHON_VERSION = os.getenv("USE_CURRENT_PYTHON_VERSION")
ADD_COMMIT_SHA = os.getenv("ADD_COMMIT_SHA")
PYTHON_VERSIONS = ["3.7.13", "3.8.13", "3.9.11", "3.10.3", "3.11.7", "3.12.1"]
PYTHON_VERSIONS = ["3.9.11", "3.10.3", "3.11.8", "3.12.1"]
JSON_VERSION = "v3.6.1"
OCE_VERSION = "0.18.3"
OCCT_VERSION = "7.5.3"
@@ -254,6 +254,11 @@ if not os.path.exists(LOG_FILE):
open(LOG_FILE, "w").close()
logger.info(f"using command log file '{LOG_FILE}'")
# Causing havoc in python 3.11 build
try:
del os.environ['__PYVENV_LAUNCHER__']
except: pass
def run(cmds, cwd=None, can_fail=False):
"""
@@ -284,8 +289,9 @@ if platform.system() == "Darwin":
# Apparently not supported
PYTHON_VERSIONS = [pv for pv in PYTHON_VERSIONS if tuple(map(int, pv.split("."))) >= (3, 7)]
if run(["sw_vers", "-productVersion"]) < "10.16":
# Apparently not supported
PYTHON_VERSIONS = [pv for pv in PYTHON_VERSIONS if tuple(map(int, pv.split("."))) < (3, 11)]
# This is now solved with the '__PYVENV_LAUNCHER__' hack
# PYTHON_VERSIONS = [pv for pv in PYTHON_VERSIONS if tuple(map(int, pv.split("."))) < (3, 11)]
pass
BOOST_VERSION_UNDERSCORE = BOOST_VERSION.replace(".", "_")
+13 -260
View File
@@ -61,26 +61,6 @@ PYPI_PLATFORM:=--platform manylinux_2_17_x86_64
LXML_ARCH:=manylinux_2_28_x86_64
SHAPELY_ARCH:=manylinux_2_17_x86_64.manylinux2014_x86_64
PILLOW_ARCH:=manylinux_2_28_x86_64
ifeq ($(PYVERSION), py39)
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/2.4.0/download/linux-64/hpp-fcl-2.4.0-py39h359ec1e_0.conda
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/3.2.0/download/linux-64/eigenpy-3.2.0-py39hb1babde_0.conda
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/linux-64/boost-1.78.0-py39h7c9e3ff_4.tar.bz2
endif
ifeq ($(PYVERSION), py310)
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/2.4.0/download/linux-64/hpp-fcl-2.4.0-py310hd002f03_0.conda
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/3.2.0/download/linux-64/eigenpy-3.2.0-py310hcfe0b05_0.conda
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/linux-64/boost-1.78.0-py310hc4a4660_4.tar.bz2
endif
ifeq ($(PYVERSION), py311)
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/2.4.0/download/linux-64/hpp-fcl-2.4.0-py311h04f086a_0.conda
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/3.2.0/download/linux-64/eigenpy-3.2.0-py311h995a085_0.conda
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/linux-64/boost-1.78.0-py311h59ea3da_3.tar.bz2
endif
ASSIMP_URL:=https://anaconda.org/conda-forge/assimp/5.0.1/download/linux-64/assimp-5.0.1-hedfc422_6.tar.bz2
QHULL_URL:=https://anaconda.org/conda-forge/qhull/2020.2/download/linux-64/qhull-2020.2-h4bd325d_2.tar.bz2
OCTOMAP_URL:=https://anaconda.org/conda-forge/octomap/1.9.8/download/linux-64/octomap-1.9.8-h924138e_0.tar.bz2
BOOSTCPP_URL:=https://anaconda.org/conda-forge/boost-cpp/1.78.0/download/linux-64/boost-cpp-1.78.0-h6582d0a_3.conda
ZLIB_URL:=https://anaconda.org/conda-forge/zlib/1.2.11/download/linux-64/zlib-1.2.11-h516909a_1010.tar.bz2
endif
ifeq ($(PLATFORM), macos)
@@ -89,27 +69,13 @@ SHAPELY_ARCH:=macosx_10_9_x86_64
PILLOW_ARCH:=macosx_10_10_x86_64
ifeq ($(PYVERSION), py39)
LXML_ARCH:=macosx_11_0_x86_64
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/2.4.0/download/osx-64/hpp-fcl-2.4.0-py39h26350b9_0.conda
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/3.2.0/download/osx-64/eigenpy-3.2.0-py39h3323527_0.conda
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/osx-64/boost-1.78.0-py39h953a6b8_4.tar.bz2
endif
ifeq ($(PYVERSION), py310)
LXML_ARCH:=macosx_11_0_x86_64
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/2.4.0/download/osx-64/hpp-fcl-2.4.0-py310ha216469_0.conda
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/3.2.0/download/osx-64/eigenpy-3.2.0-py310h33a8c9b_0.conda
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/osx-64/boost-1.78.0-py310h3e792ce_4.tar.bz2
endif
ifeq ($(PYVERSION), py311)
LXML_ARCH:=macosx_11_0_universal2
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/2.4.0/download/osx-64/hpp-fcl-2.4.0-py311hebdd1a2_0.conda
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/3.2.0/download/osx-64/eigenpy-3.2.0-py311h62e90b6_0.conda
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/osx-64/boost-1.78.0-py311h349b758_4.tar.bz2
endif
ASSIMP_URL:=https://anaconda.org/conda-forge/assimp/5.0.1/download/osx-64/assimp-5.0.1-h1224e73_6.tar.bz2
QHULL_URL:=https://anaconda.org/conda-forge/qhull/2020.2/download/osx-64/qhull-2020.2-h940c156_2.tar.bz2
OCTOMAP_URL:=https://anaconda.org/conda-forge/octomap/1.9.8/download/osx-64/octomap-1.9.8-hb8565cd_0.tar.bz2
BOOSTCPP_URL:=https://anaconda.org/conda-forge/boost-cpp/1.78.0/download/osx-64/boost-cpp-1.78.0-hf5ba120_3.conda
ZLIB_URL:=https://anaconda.org/conda-forge/zlib/1.2.11/download/osx-64/zlib-1.2.11-h7795811_1010.tar.bz2
endif
ifeq ($(PLATFORM), macosm1)
@@ -117,26 +83,6 @@ PYPI_PLATFORM:=--platform macosx_11_0_arm64
LXML_ARCH:=macosx_11_0_universal2
SHAPELY_ARCH:=macosx_11_0_arm64
PILLOW_ARCH:=macosx_11_0_arm64
ifeq ($(PYVERSION), py39)
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/2.4.0/download/osx-arm64/hpp-fcl-2.4.0-py39h82b2ff2_0.conda
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/3.2.0/download/osx-arm64/eigenpy-3.2.0-py39h3a9e884_0.conda
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/osx-arm64/boost-1.78.0-py39h99de9ae_4.tar.bz2
endif
ifeq ($(PYVERSION), py310)
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/2.4.0/download/osx-arm64/hpp-fcl-2.4.0-py310hdb6c3eb_0.conda
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/3.2.0/download/osx-arm64/eigenpy-3.2.0-py310h96e9823_0.conda
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/osx-arm64/boost-1.78.0-py310h629746b_4.tar.bz2
endif
ifeq ($(PYVERSION), py311)
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/2.4.0/download/osx-arm64/hpp-fcl-2.4.0-py311hc0c29b9_0.conda
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/3.2.0/download/osx-arm64/eigenpy-3.2.0-py311h24fa84d_0.conda
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/osx-arm64/boost-1.78.0-py311h5c64717_4.tar.bz2
endif
ASSIMP_URL:=https://anaconda.org/conda-forge/assimp/5.0.1/download/osx-arm64/assimp-5.0.1-h0f81e16_7.tar.bz2
QHULL_URL:=https://anaconda.org/conda-forge/qhull/2020.2/download/osx-arm64/qhull-2020.2-hc021e02_2.tar.bz2
OCTOMAP_URL:=https://anaconda.org/conda-forge/octomap/1.9.8/download/osx-arm64/octomap-1.9.8-hffc8910_0.tar.bz2
BOOSTCPP_URL:=https://anaconda.org/conda-forge/boost-cpp/1.78.0/download/osx-arm64/boost-cpp-1.78.0-h9ed8d21_3.conda
ZLIB_URL:=https://anaconda.org/conda-forge/zlib/1.2.11/download/osx-arm64/zlib-1.2.11-h90dfc92_1014.tar.bz2
endif
ifeq ($(PLATFORM), win)
@@ -144,26 +90,6 @@ PYPI_PLATFORM:=--platform win_amd64
LXML_ARCH:=win_amd64
SHAPELY_ARCH:=win_amd64
PILLOW_ARCH:=win_amd64
ifeq ($(PYVERSION), py39)
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/2.4.0/download/win-64/hpp-fcl-2.4.0-py39hf3ebc58_0.conda
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/3.2.0/download/win-64/eigenpy-3.2.0-py39ha1f6a47_0.conda
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/win-64/boost-1.78.0-py39hea4d8d1_4.tar.bz2
endif
ifeq ($(PYVERSION), py310)
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/2.4.0/download/win-64/hpp-fcl-2.4.0-py310h1bf3f0a_0.conda
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/3.2.0/download/win-64/eigenpy-3.2.0-py310h7fb871d_0.conda
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/win-64/boost-1.78.0-py310h220cb41_4.tar.bz2
endif
ifeq ($(PYVERSION), py311)
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/2.4.0/download/win-64/hpp-fcl-2.4.0-py311hd263b2c_0.conda
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/3.2.0/download/win-64/eigenpy-3.2.0-py311hd354db1_0.conda
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/win-64/boost-1.78.0-py311h12feb9d_3.tar.bz2
endif
ASSIMP_URL:=https://anaconda.org/conda-forge/assimp/5.2.5/download/win-64/assimp-5.2.5-h4dcb625_0.tar.bz2
QHULL_URL:=https://anaconda.org/conda-forge/qhull/2020.2/download/win-64/qhull-2020.2-h70d2c02_2.tar.bz2
OCTOMAP_URL:=https://anaconda.org/conda-forge/octomap/1.9.8/download/win-64/octomap-1.9.8-h91493d7_0.tar.bz2
BOOSTCPP_URL:=https://anaconda.org/conda-forge/boost-cpp/1.78.0/download/win-64/boost-cpp-1.78.0-h9f4b32c_3.conda
ZLIB_URL:=https://anaconda.org/conda-forge/zlib/1.2.11/download/win-64/zlib-1.2.11-h62dcd97_1010.tar.bz2
endif
LXML_NAME:=lxml
@@ -211,7 +137,7 @@ endif
cp -r blenderbim/* dist/blenderbim/
# Provides IfcOpenShell Python functionality
cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-$(PYNUMBER)-v0.7.0-6c9e130-$(PLATFORM)64.zip
cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-$(PYNUMBER)-v0.7.0-f7c03db-$(PLATFORM)64.zip
cd dist/working && unzip ifcopenshell-python*
cp -r dist/working/ifcopenshell dist/blenderbim/libs/site/packages/
@@ -225,8 +151,10 @@ endif
cd dist/working && wget https://github.com/IfcOpenShell/IfcOpenShell/archive/v0.7.0.zip
cd dist/working && unzip v0.7.0.zip
# IfcOpenBot sometimes lags behind, so we hotfix the Python utilities
cp -r dist/working/IfcOpenShell-0.7.0/src/ifcopenshell-python/ifcopenshell/util/* dist/blenderbim/libs/site/packages/ifcopenshell/util/
cp -r dist/working/IfcOpenShell-0.7.0/src/ifcopenshell-python/ifcopenshell/api/* dist/blenderbim/libs/site/packages/ifcopenshell/api/
rm -rf dist/blenderbim/libs/site/packages/ifcopenshell/util/
rm -rf dist/blenderbim/libs/site/packages/ifcopenshell/api/
cp -r dist/working/IfcOpenShell-0.7.0/src/ifcopenshell-python/ifcopenshell/util dist/blenderbim/libs/site/packages/ifcopenshell/
cp -r dist/working/IfcOpenShell-0.7.0/src/ifcopenshell-python/ifcopenshell/api dist/blenderbim/libs/site/packages/ifcopenshell/
cp dist/working/IfcOpenShell-0.7.0/src/ifcopenshell-python/ifcopenshell/*.py dist/blenderbim/libs/site/packages/ifcopenshell/
cp dist/working/IfcOpenShell-0.7.0/src/ifcopenshell-python/ifcopenshell/express/rule_executor.py dist/blenderbim/libs/site/packages/ifcopenshell/express/
# Provides bcf functionality
@@ -427,6 +355,8 @@ endif
cd dist/working && mkdir site-packages
# Required by IfcCSV
cd dist/working && . env/bin/activate && $(PIP) install pandas $(PYPI_PLATFORM) --python-version $(PYPI_VERSION) --implementation $(PYPI_IMP) --only-binary=:all: --target=./site-packages
# Pandas ships with very heavy tests
cd dist/working/site-packages/pandas && rm -rf tests
cd dist/working && . env/bin/activate && $(PIP) install openpyxl --target=./site-packages
cd dist/working && . env/bin/activate && $(PIP) install odfpy --target=./site-packages
# Required by IFCCityJSON
@@ -434,161 +364,12 @@ endif
# Provides express rule validation for ifcopenshell.validate
cd dist/working && . env/bin/activate && $(PIP) install pytest --target=./site-packages
# Provides Brickschema functionality
cd dist/working && . env/bin/activate && $(PIP) install brickschema[persistence] --target=./site-packages
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
cp -r dist/working/site-packages/* dist/blenderbim/libs/site/packages/
rm -rf dist/working
# Required by IFCClash
mkdir dist/working
cd dist/working && wget $(HPPFCL_URL)
cd dist/working && unzip hpp-fcl*
cd dist/working && tar -I zstd -xvf pkg*
ifeq ($(PLATFORM), linux)
cp -r dist/working/lib/$(PYLIBDIR)/site-packages/hppfcl dist/blenderbim/libs/site/packages/
cp -r dist/working/lib/*.so* dist/blenderbim/libs/
endif
ifeq ($(PLATFORM), macos)
cp -r dist/working/lib/$(PYLIBDIR)/site-packages/hppfcl dist/blenderbim/libs/site/packages/
cp -r dist/working/lib/*.dylib dist/blenderbim/libs/
endif
ifeq ($(PLATFORM), macosm1)
cp -r dist/working/lib/$(PYLIBDIR)/site-packages/hppfcl dist/blenderbim/libs/site/packages/
cp -r dist/working/lib/*.dylib dist/blenderbim/libs/
endif
ifeq ($(PLATFORM), win)
cp -r dist/working/Lib/site-packages/hppfcl dist/blenderbim/libs/site/packages/
cp -r dist/working/Library/bin/*.dll dist/blenderbim/libs/site/packages/hppfcl/
endif
rm -rf dist/working
# Required by hpp-fcl
mkdir dist/working
cd dist/working && wget $(ASSIMP_URL)
cd dist/working && tar -xf assimp*
ifeq ($(PLATFORM), linux)
cp -r dist/working/lib/*.so* dist/blenderbim/libs/
endif
ifeq ($(PLATFORM), macos)
cp -r dist/working/lib/*.dylib dist/blenderbim/libs/
endif
ifeq ($(PLATFORM), macosm1)
cp -r dist/working/lib/*.dylib dist/blenderbim/libs/
endif
ifeq ($(PLATFORM), win)
cp -r dist/working/Library/bin/*.dll dist/blenderbim/libs/site/packages/hppfcl/
endif
rm -rf dist/working
# Required by hpp-fcl
mkdir dist/working
cd dist/working && wget $(QHULL_URL)
cd dist/working && tar -xf qhull*
ifeq ($(PLATFORM), linux)
cp -r dist/working/lib/*.so* dist/blenderbim/libs/
endif
ifeq ($(PLATFORM), macos)
cp -r dist/working/lib/*.dylib dist/blenderbim/libs/
endif
ifeq ($(PLATFORM), macosm1)
cp -r dist/working/lib/*.dylib dist/blenderbim/libs/
endif
ifeq ($(PLATFORM), win)
cp -r dist/working/Library/bin/*.dll dist/blenderbim/libs/site/packages/hppfcl/
endif
rm -rf dist/working
# Required by hpp-fcl
mkdir dist/working
cd dist/working && wget $(EIGENPY_URL)
cd dist/working && unzip eigenpy*
cd dist/working && tar -I zstd -xvf pkg*
ifeq ($(PLATFORM), linux)
cp -r dist/working/lib/*.so* dist/blenderbim/libs/
endif
ifeq ($(PLATFORM), macos)
cp -r dist/working/lib/*.dylib dist/blenderbim/libs/
endif
ifeq ($(PLATFORM), macosm1)
cp -r dist/working/lib/*.dylib dist/blenderbim/libs/
endif
ifeq ($(PLATFORM), win)
cp -r dist/working/Library/bin/*.dll dist/blenderbim/libs/site/packages/hppfcl/
endif
rm -rf dist/working
# Required by hpp-fcl
mkdir dist/working
cd dist/working && wget $(OCTOMAP_URL)
cd dist/working && tar -xf octomap*
ifeq ($(PLATFORM), linux)
cp -r dist/working/lib/*.so* dist/blenderbim/libs/
endif
ifeq ($(PLATFORM), macos)
cp -r dist/working/lib/*.dylib dist/blenderbim/libs/
endif
ifeq ($(PLATFORM), macosm1)
cp -r dist/working/lib/*.dylib dist/blenderbim/libs/
endif
ifeq ($(PLATFORM), win)
cp -r dist/working/Library/bin/*.dll dist/blenderbim/libs/site/packages/hppfcl/
endif
rm -rf dist/working
# Required by hpp-fcl
mkdir dist/working
cd dist/working && wget $(BOOST_URL)
cd dist/working && tar -xf boost*
ifeq ($(PLATFORM), linux)
cp -r dist/working/lib/*.so* dist/blenderbim/libs/
endif
ifeq ($(PLATFORM), macos)
cp -r dist/working/lib/*.dylib dist/blenderbim/libs/
endif
ifeq ($(PLATFORM), macosm1)
cp -r dist/working/lib/*.dylib dist/blenderbim/libs/
endif
ifeq ($(PLATFORM), win)
cp -r dist/working/Library/bin/*.dll dist/blenderbim/libs/site/packages/hppfcl/
endif
rm -rf dist/working
# Required by hpp-fcl
mkdir dist/working
cd dist/working && wget $(BOOSTCPP_URL)
cd dist/working && unzip boost-cpp*
cd dist/working && tar -I zstd -xvf pkg*
ifeq ($(PLATFORM), linux)
cp -r dist/working/lib/libboost_serialization.so* dist/blenderbim/libs/
endif
ifeq ($(PLATFORM), macos)
cp -r dist/working/lib/libboost_serialization.dylib dist/blenderbim/libs/
endif
ifeq ($(PLATFORM), macosm1)
cp -r dist/working/lib/libboost_serialization.dylib dist/blenderbim/libs/
endif
ifeq ($(PLATFORM), win)
cp -r dist/working/Library/bin/boost_serialization.dll dist/blenderbim/libs/site/packages/hppfcl/
endif
rm -rf dist/working
# Required by hpp-fcl
mkdir dist/working
cd dist/working && wget $(ZLIB_URL)
cd dist/working && tar -xf zlib*
ifeq ($(PLATFORM), linux)
cp -r dist/working/lib/*.so* dist/blenderbim/libs/
endif
ifeq ($(PLATFORM), macos)
cp -r dist/working/lib/*.dylib dist/blenderbim/libs/
endif
ifeq ($(PLATFORM), macosm1)
cp -r dist/working/lib/*.dylib dist/blenderbim/libs/
endif
ifeq ($(PLATFORM), win)
cp -r dist/working/Library/bin/*.dll dist/blenderbim/libs/site/packages/hppfcl/
endif
rm -rf dist/working
# Required by BIMTester
mkdir dist/working
cd dist/working && wget https://files.pythonhosted.org/packages/c8/4b/d0a8c23b6c8985e5544ea96d27105a273ea22051317f850c2cdbf2029fe4/behave-1.2.6.tar.gz
@@ -636,34 +417,6 @@ endif
cd dist/working/parse_type-0.5.2/ && cp -r parse_type ../../blenderbim/libs/site/packages/
rm -rf dist/working
# Required by augin
mkdir dist/working
cd dist/working && wget https://files.pythonhosted.org/packages/76/b4/b7baffbda025efd5dc8fcd8d2e953e3aa939c236a484084fa8f4c3588ee9/boto3-1.17.17.tar.gz
cd dist/working && tar -xzvf boto3*
cd dist/working/boto3-1.17.17/ && cp -r boto3 ../../blenderbim/libs/site/packages/
rm -rf dist/working
# Required by boto3
mkdir dist/working
cd dist/working && wget https://files.pythonhosted.org/packages/7f/2e/e02fdfd0f0377c4e44e61de27b05d5cfe93575770661ef9ded80ed90fa88/botocore-1.20.17.tar.gz
cd dist/working && tar -xzvf botocore*
cd dist/working/botocore-1.20.17/ && cp -r botocore ../../blenderbim/libs/site/packages/
rm -rf dist/working
# Required by boto3
mkdir dist/working
cd dist/working && wget https://files.pythonhosted.org/packages/3c/56/3f325b1eef9791759784aa5046a8f6a1aff8f7c898a2e34506771d3b99d8/jmespath-0.10.0.tar.gz
cd dist/working && tar -xzvf jmespath*
cd dist/working/jmespath-0.10.0/ && cp -r jmespath ../../blenderbim/libs/site/packages/
rm -rf dist/working
# Required by boto3
mkdir dist/working
cd dist/working && wget https://files.pythonhosted.org/packages/08/e1/3ee2096ebaeeb8c186d20ed16c8faf4a503913e5c9a0e14cd6b8ffc405a3/s3transfer-0.3.4.tar.gz
cd dist/working && tar -xzvf s3transfer*
cd dist/working/s3transfer-0.3.4/ && cp -r s3transfer ../../blenderbim/libs/site/packages/
rm -rf dist/working
# Provides IFCJSON functionality
mkdir dist/working
cd dist/working && wget https://github.com/IFCJSON-Team/IFC2JSON_python/archive/master.zip
@@ -681,7 +434,7 @@ endif
# Required by brickschema
# This is a bit of a dodgy one, it should be provided by setuptools which Blender doesn't ship.
mkdir dist/working
cd dist/working && wget https://blenderbim.org/builds/pkg_resources.tar.gz
cd dist/working && wget https://github.com/IfcOpenShell/IfcOpenShell/raw/v0.7.0/src/blenderbim/scripts/pkg_resources.tar.gz
cd dist/working && tar -xzvf pkg_resources*
cd dist/working/ && cp -r pkg_resources ../blenderbim/libs/site/packages/
rm -rf dist/working
@@ -696,9 +449,9 @@ endif
# Required for Desktop icon and file association
cp -r blenderbim/libs/desktop dist/blenderbim/libs/
# generate translations module for BBIM build
# Generate translations module for BBIM build
git clone https://github.com/IfcOpenShell/blenderbim-translations.git dist/working
python scripts/bbim_translations.py -i "dist/working" -o "dist/blenderbim"
$(PYTHON) scripts/bbim_translations.py -i "dist/working" -o "dist/blenderbim"
rm -rf dist/working
# Remove dependencies also bundled with Blender
+147 -7
View File
@@ -18,7 +18,13 @@
import os
import sys
import site
import bpy
import platform
import traceback
import subprocess
import webbrowser
import addon_utils
from collections import deque
bl_info = {
"name": "BlenderBIM",
@@ -27,21 +33,155 @@ bl_info = {
"blender": (3, 1, 0),
"version": (0, 0, 999999),
"location": "File Menu, Scene Properties Tab. See documentation for more.",
"doc_url": "https://blenderbim.org/docs",
"doc_url": "https://docs.blenderbim.org/",
"tracker_url": "https://github.com/IfcOpenShell/IfcOpenShell/issues",
"category": "System",
}
last_error = None
last_actions: deque = deque(maxlen=10)
def get_debug_info():
version = ".".join(
[
str(x)
for x in [
addon.bl_info.get("version", (-1, -1, -1))
for addon in addon_utils.modules()
if addon.bl_info["name"] == "BlenderBIM"
][0]
]
)
return {
"os": platform.system(),
"os_version": platform.version(),
"python_version": platform.python_version(),
"architecture": platform.architecture(),
"machine": platform.machine(),
"processor": platform.processor(),
"blender_version": bpy.app.version_string,
"blenderbim_version": version,
"last_actions": last_actions,
"last_error": last_error,
}
def format_debug_info(info: dict):
last_actions = ""
for action in info["last_actions"]:
last_actions += f"\n# {action['type']}: {action['name']}"
if settings := action.get("settings"):
last_actions += f"\n>>> {settings}"
info["last_actions"] = last_actions
text = "\n".join(f"{k}: {v}" for k, v in info.items())
return text.strip()
if sys.modules.get("bpy", None):
# 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"))
sys.path.insert(0, os.path.join(os.path.dirname(os.path.realpath(__file__)), "libs", "site", "packages"))
import blenderbim.bim
try:
import blenderbim.bim
import ifcopenshell.api
def register():
blenderbim.bim.register()
def log_api(usecase_path, ifc_file, settings):
last_actions.append(
{
"type": "ifcopenshell.api",
"name": usecase_path,
"settings": ifcopenshell.api.serialise_settings(settings),
}
)
def unregister():
blenderbim.bim.unregister()
ifcopenshell.api.add_pre_listener("*", "action_logger", log_api)
def register():
blenderbim.bim.register()
def unregister():
blenderbim.bim.unregister()
except:
last_error = traceback.format_exc()
print(last_error)
print(format_debug_info(get_debug_info()))
print("\nFATAL ERROR: Unable to load the BlenderBIM Add-on")
class BIM_PT_fatal_error(bpy.types.Panel):
bl_label = "BlenderBIM Fatal Error"
bl_idname = "SCENE_PT_error_message"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
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()
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")
box.label(text=f"Python {py} BBIM {info['blenderbim_version']}", icon="SCRIPTPLUGINS")
layout.operator("bim.copy_debug_information", text="Copy Error Message To Clipboard")
op = layout.operator("bim.open_uri", text="How Can I Fix This?")
op.uri = "https://docs.blenderbim.org/users/troubleshooting.html#installation-issues"
class OpenUri(bpy.types.Operator):
bl_idname = "bim.open_uri"
bl_label = "Open URI"
uri: bpy.props.StringProperty()
def execute(self, context):
webbrowser.open(self.uri)
return {"FINISHED"}
class CopyDebugInformation(bpy.types.Operator):
bl_idname = "bim.copy_debug_information"
bl_label = "Copy Debug Information"
bl_description = "Copies debugging information to your clipboard for use in bugreports"
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)
return {"FINISHED"}
class HiddenPanel:
@classmethod
def false_poll(cls, context):
return False
def register():
# Only show our error panel and nothing else in the scene tab
for item_name in dir(bpy.types):
item = getattr(bpy.types, item_name)
if not hasattr(item, "bl_rna") or not isinstance(item.bl_rna, bpy.types.Panel):
continue
if getattr(item, "bl_context", None) != "scene":
continue
# Reregister scene panel with a new poll to hide it
item.poll = HiddenPanel.false_poll
bpy.utils.unregister_class(item)
bpy.utils.register_class(item)
bpy.utils.register_class(BIM_PT_fatal_error)
bpy.utils.register_class(CopyDebugInformation)
bpy.utils.register_class(OpenUri)
def unregister():
bpy.utils.unregister_class(OpenUri)
bpy.utils.unregister_class(CopyDebugInformation)
bpy.utils.unregister_class(BIM_PT_fatal_error)
+15 -7
View File
@@ -17,12 +17,13 @@
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import os
from pathlib import Path
import bpy
import bpy.utils.previews
import blenderbim
import importlib
from pathlib import Path
from . import handler, ui, prop, operator, helper
from typing import Callable, Union
try:
from blenderbim.translations import translations_dict
@@ -96,9 +97,13 @@ for name in modules.keys():
classes = [
operator.AddIfcFile,
operator.BIM_OT_add_section_plane,
operator.BIM_OT_delete_object,
operator.BIM_OT_open_webbrowser,
operator.BIM_OT_remove_section_plane,
operator.BIM_OT_select_object,
operator.BIM_OT_show_description,
operator.ClippingPlaneCutWithCappings,
operator.CloseError,
operator.EditBlenderCollection,
operator.FileAssociate,
operator.FileUnassociate,
@@ -106,6 +111,7 @@ classes = [
operator.OpenUri,
operator.ReloadIfcFile,
operator.RemoveIfcFile,
operator.RevertClippingPlaneCut,
operator.SelectDataDir,
operator.SelectIfcFile,
operator.SelectSchemaDir,
@@ -128,6 +134,7 @@ classes = [
prop.BIMMeshProperties,
prop.BIMFacet,
prop.BIMFilterGroup,
ui.BIM_UL_clipping_plane,
ui.BIM_UL_generic,
ui.BIM_UL_topics,
ui.BIM_ADDON_preferences,
@@ -175,6 +182,7 @@ classes = [
ui.BIM_PT_tab_sandbox,
# TODO: move this somewhere else and clean it up
ui.BIM_PT_section_plane,
ui.BIM_PT_section_with_cappings,
]
for mod in modules.values():
@@ -184,7 +192,7 @@ addon_keymaps = []
icons = None
is_registering = False
last_commit_hash = "8888888"
overridden_scene_panels = dict()
original_scene_panels_polls: dict[bpy.types.Panel, Union[Callable, None]] = dict()
def on_register(scene):
@@ -292,10 +300,10 @@ def unregister():
km.keymap_items.remove(kmi)
addon_keymaps.clear()
for panel in tuple(overridden_scene_panels.keys()):
original_panel, override_panel = overridden_scene_panels[panel]
bpy.utils.unregister_class(override_panel)
bpy.utils.register_class(original_panel)
del overridden_scene_panels[panel]
import blenderbim.tool as tool
# use tuple() as method will be removing keys from dict
for panel in tuple(original_scene_panels_polls.keys()):
tool.Blender.remove_scene_panel_override(panel)
bpy.app.translations.unregister("blenderbim")
@@ -10,7 +10,7 @@
* (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
* 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.
*
@@ -10,7 +10,7 @@
* (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
* 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.
*
@@ -1,17 +1,17 @@
ISO-10303-21;
HEADER;
FILE_DESCRIPTION(('ViewDefinition[DesignTransferView]'),'2;1');
FILE_NAME('/dev/null','2023-09-07T13:40:06+10:00',(),(),'IfcOpenShell v0.7.0-fbd8ea1ed','IfcOpenShell v0.7.0-fbd8ea1ed','Nobody');
FILE_NAME('/dev/null','2024-03-03T16:03:58+11:00',(),(),'IfcOpenShell v0.7.0-eaa2aa05a','IfcOpenShell v0.7.0-eaa2aa05a','Nobody');
FILE_SCHEMA(('IFC4'));
ENDSEC;
DATA;
#1=IFCPROJECT('1arVsMni92iPUaaryGTUz2',$,'BlenderBIM Demo',$,$,$,$,(#12,#16),#7);
#2=IFCPROJECTLIBRARY('1FfDG5b21DuBDOO4eUE9rF',$,'BlenderBIM Demo Library',$,$,$,$,$,$);
#3=IFCRELDECLARES('3tfDIC3Tf9wP8EUOoFeycK',$,$,$,#1,(#2));
#1=IFCPROJECT('3NJUxHyTzEv96oezyDYEqf',$,'BlenderBIM Demo',$,$,$,$,(#12,#16),#7);
#2=IFCPROJECTLIBRARY('0D39k7hbT5ce$jWaCd2niG',$,'BlenderBIM Demo Library',$,$,$,$,$,$);
#3=IFCRELDECLARES('2RWVohyP57PBHT9zCs4H2O',$,$,$,#1,(#2));
#4=IFCSIUNIT(*,.LENGTHUNIT.,$,.METRE.);
#5=IFCSIUNIT(*,.AREAUNIT.,$,.SQUARE_METRE.);
#6=IFCSIUNIT(*,.VOLUMEUNIT.,$,.CUBIC_METRE.);
#7=IFCUNITASSIGNMENT((#5,#6,#4));
#7=IFCUNITASSIGNMENT((#6,#4,#5));
#8=IFCCARTESIANPOINT((0.,0.,0.));
#9=IFCDIRECTION((0.,0.,1.));
#10=IFCDIRECTION((1.,0.,0.));
@@ -23,84 +23,84 @@ DATA;
#16=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Plan',2,1.E-05,#15,$);
#17=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Body','Model',*,*,*,*,#12,$,.MODEL_VIEW.,$);
#18=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Body','Plan',*,*,*,*,#16,$,.PLAN_VIEW.,$);
#19=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Annotation','Plan',*,*,*,*,#16,$,.PLAN_VIEW.,$);
#19=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Annotation','Model',*,*,*,*,#16,$,.MODEL_VIEW.,$);
#20=IFCMATERIAL('Unknown',$,$);
#21=IFCWALLTYPE('2o8NqQQbHA5BeErrgwfr7d',$,'WAL50',$,$,$,$,$,$,.NOTDEFINED.);
#21=IFCWALLTYPE('2F1k78lI53fwS0rg3rqrRb',$,'WAL50',$,$,$,$,$,$,.NOTDEFINED.);
#22=IFCMATERIALLAYERSET((#24),$,$);
#23=IFCRELASSOCIATESMATERIAL('1Q$5DZKr98n82Eitz2dWey',$,$,$,(#21),#22);
#23=IFCRELASSOCIATESMATERIAL('0khP_Smdj12f2JrOTRbRKj',$,$,$,(#21),#22);
#24=IFCMATERIALLAYER(#20,0.05,$,$,$,$,$);
#25=IFCRELDECLARES('1KXKUEn8z0g8lvyEsCTWD3',$,$,$,#2,(#38,#82,#77,#42,#26,#48,#72,#59,#92,#30,#96,#63,#21,#54,#87,#153,#34,#67,#210));
#26=IFCWALLTYPE('3KlIOv_P9A79tkt8D_jq_m',$,'WAL100',$,$,$,$,$,$,.NOTDEFINED.);
#25=IFCRELDECLARES('31JriXdev6BP2sC9lcxdC1',$,$,$,#2,(#26,#48,#72,#59,#92,#30,#96,#63,#21,#54,#87,#210,#153,#34,#67,#38,#82,#77,#42));
#26=IFCWALLTYPE('3R5onkTtX1IxdCMPoThRaw',$,'WAL100',$,$,$,$,$,$,.NOTDEFINED.);
#27=IFCMATERIALLAYERSET((#29),$,$);
#28=IFCRELASSOCIATESMATERIAL('32B9Wx8Z1FxhIN7m77MiHG',$,$,$,(#26),#27);
#28=IFCRELASSOCIATESMATERIAL('0$mXRPCT16nP6Q70XFNvZv',$,$,$,(#26),#27);
#29=IFCMATERIALLAYER(#20,0.1,$,$,$,$,$);
#30=IFCWALLTYPE('2DqrSeel1AGw6h3b$ujafZ',$,'WAL200',$,$,$,$,$,$,.NOTDEFINED.);
#30=IFCWALLTYPE('1juanculbDJPqvqT2FCm8m',$,'WAL200',$,$,$,$,$,$,.NOTDEFINED.);
#31=IFCMATERIALLAYERSET((#33),$,$);
#32=IFCRELASSOCIATESMATERIAL('2FeYUpt3D4tAvwgGmwXWZn',$,$,$,(#30),#31);
#32=IFCRELASSOCIATESMATERIAL('0FsB2A$frFn9AZW2IG38ny',$,$,$,(#30),#31);
#33=IFCMATERIALLAYER(#20,0.2,$,$,$,$,$);
#34=IFCWALLTYPE('2jNdEOFIP1eQb3WZePlFGY',$,'WAL300',$,$,$,$,$,$,.NOTDEFINED.);
#34=IFCWALLTYPE('05UYduR9r1vRxZZgP64Rdw',$,'WAL300',$,$,$,$,$,$,.NOTDEFINED.);
#35=IFCMATERIALLAYERSET((#37),$,$);
#36=IFCRELASSOCIATESMATERIAL('2bFeM6xevAwucuwMlauX77',$,$,$,(#34),#35);
#36=IFCRELASSOCIATESMATERIAL('2FYhYrvmv4SBoZHVWDTTte',$,$,$,(#34),#35);
#37=IFCMATERIALLAYER(#20,0.3,$,$,$,$,$);
#38=IFCCOVERINGTYPE('3310gCgH59w9tu$DfmiCEN',$,'COV10',$,$,$,$,$,$,.NOTDEFINED.);
#38=IFCCOVERINGTYPE('0iLxgfHB9F2hRH2vMcw7Yv',$,'COV10',$,$,$,$,$,$,.NOTDEFINED.);
#39=IFCMATERIALLAYERSET((#41),$,$);
#40=IFCRELASSOCIATESMATERIAL('3pTNmWd_jBoOWyHc8Yf4qI',$,$,$,(#38),#39);
#40=IFCRELASSOCIATESMATERIAL('0yvFX0zgb9uvJ3ARnf4mfe',$,$,$,(#38),#39);
#41=IFCMATERIALLAYER(#20,0.01,$,$,$,$,$);
#42=IFCCOVERINGTYPE('2sksWFuPzCgeNk1ofXS6PZ',$,'COV20',$,$,(#46),$,$,$,.NOTDEFINED.);
#42=IFCCOVERINGTYPE('15Riw2WTrAUPwvfLyKXJ2g',$,'COV20',$,$,(#46),$,$,$,.NOTDEFINED.);
#43=IFCMATERIALLAYERSET((#45),$,$);
#44=IFCRELASSOCIATESMATERIAL('36rl5NddT1ux6ejkQAkT2z',$,$,$,(#42),#43);
#44=IFCRELASSOCIATESMATERIAL('1iAOG6NK9FBxitFtzN9qP5',$,$,$,(#42),#43);
#45=IFCMATERIALLAYER(#20,0.02,$,$,$,$,$);
#46=IFCPROPERTYSET('2VZJ9xfdn1NeOY6uEmnnYW',$,'EPset_Parametric',$,(#47));
#46=IFCPROPERTYSET('18ICCZ0fjDG9UXMyzrsFBA',$,'EPset_Parametric',$,(#47));
#47=IFCPROPERTYSINGLEVALUE('LayerSetDirection',$,IFCLABEL('AXIS2'),$);
#48=IFCCOVERINGTYPE('1uU6rxt95AC91lZ0jTRG6G',$,'COV30',$,$,(#52),$,$,$,.NOTDEFINED.);
#48=IFCCOVERINGTYPE('0gEOtYULD0F9KD9zPoA6cJ',$,'COV30',$,$,(#52),$,$,$,.NOTDEFINED.);
#49=IFCMATERIALLAYERSET((#51),$,$);
#50=IFCRELASSOCIATESMATERIAL('2lFv4xQYr5bwtiSsMTVCgz',$,$,$,(#48),#49);
#50=IFCRELASSOCIATESMATERIAL('0xd6dsjf13JA98q9fnAMpQ',$,$,$,(#48),#49);
#51=IFCMATERIALLAYER(#20,0.03,$,$,$,$,$);
#52=IFCPROPERTYSET('0HnrRhcd16Yfbgtci4sqCG',$,'EPset_Parametric',$,(#53));
#52=IFCPROPERTYSET('3qCbs1tuDCvuOXam5RrA$a',$,'EPset_Parametric',$,(#53));
#53=IFCPROPERTYSINGLEVALUE('LayerSetDirection',$,IFCLABEL('AXIS3'),$);
#54=IFCRAMPTYPE('3nonXCVn58jhVV08N8c97B',$,'RAM200',$,$,$,$,$,$,.NOTDEFINED.);
#54=IFCRAMPTYPE('2yW1ePlyb8cOR6HdEntpRh',$,'RAM200',$,$,$,$,$,$,.NOTDEFINED.);
#55=IFCMATERIALLAYERSET((#57),$,$);
#56=IFCRELASSOCIATESMATERIAL('2NdIrmbUP6bh4x$p_RY31C',$,$,$,(#54),#55);
#56=IFCRELASSOCIATESMATERIAL('1x9ZyVim19AQtJhfEE06XR',$,$,$,(#54),#55);
#57=IFCMATERIALLAYER(#20,0.2,$,$,$,$,$);
#58=IFCCIRCLEPROFILEDEF(.AREA.,$,$,0.3);
#59=IFCPILETYPE('2uuf5kq5TDDfO9jGN7XIHh',$,'P1',$,$,$,$,$,$,.NOTDEFINED.);
#59=IFCPILETYPE('25MTkMtaH5QeCf$gFbvSSw',$,'P1',$,$,$,$,$,$,.NOTDEFINED.);
#60=IFCMATERIALPROFILESET($,$,(#62),$);
#61=IFCRELASSOCIATESMATERIAL('3hi821Ffj8YR9yFjp226LU',$,$,$,(#59),#60);
#61=IFCRELASSOCIATESMATERIAL('09FXUZCnrFEffxBrtg16gx',$,$,$,(#59),#60);
#62=IFCMATERIALPROFILE($,$,#20,#58,$,$);
#63=IFCSLABTYPE('2janmz3nH4P9IHaOVvGY_B',$,'FLR150',$,$,$,$,$,$,.NOTDEFINED.);
#63=IFCSLABTYPE('1Akbal7tP5DAPW0ti$Qp30',$,'FLR150',$,$,$,$,$,$,.NOTDEFINED.);
#64=IFCMATERIALLAYERSET((#66),$,$);
#65=IFCRELASSOCIATESMATERIAL('0og5Zg4ib67g6XWgOgqBWy',$,$,$,(#63),#64);
#65=IFCRELASSOCIATESMATERIAL('0jzkXlkBL4HhmQ_FTWW6Cj',$,$,$,(#63),#64);
#66=IFCMATERIALLAYER(#20,0.2,$,$,$,$,$);
#67=IFCSLABTYPE('26UKpEaTb9fh4qrqi3Ymzj',$,'FLR250',$,$,$,$,$,$,.NOTDEFINED.);
#67=IFCSLABTYPE('0$gpYNi3T08BrxaR67CxgI',$,'FLR250',$,$,$,$,$,$,.NOTDEFINED.);
#68=IFCMATERIALLAYERSET((#70),$,$);
#69=IFCRELASSOCIATESMATERIAL('1BeBXPqen9TQEE42SxzhjF',$,$,$,(#67),#68);
#69=IFCRELASSOCIATESMATERIAL('3a6kjEM29DleYTlSkA8_DK',$,$,$,(#67),#68);
#70=IFCMATERIALLAYER(#20,0.3,$,$,$,$,$);
#71=IFCRECTANGLEPROFILEDEF(.AREA.,'500x600',$,0.5,0.6);
#72=IFCCOLUMNTYPE('1KnWzBBoLCNfJ3_J79LT7d',$,'C1',$,$,$,$,$,$,.NOTDEFINED.);
#72=IFCCOLUMNTYPE('278sjptdDDzgOZseERj390',$,'C1',$,$,$,$,$,$,.NOTDEFINED.);
#73=IFCMATERIALPROFILESET($,$,(#75),$);
#74=IFCRELASSOCIATESMATERIAL('1W8OWQjEL05gH5hEpve6Lh',$,$,$,(#72),#73);
#74=IFCRELASSOCIATESMATERIAL('1ppYXw39v6kBwi3rrIm4ZE',$,$,$,(#72),#73);
#75=IFCMATERIALPROFILE($,$,#20,#71,$,$);
#76=IFCCIRCLEHOLLOWPROFILEDEF(.AREA.,'500.0x5.0 CHS',$,0.25,0.005);
#77=IFCCOLUMNTYPE('33jPFERfL9bfTeWfTL5kZ7',$,'C2',$,$,$,$,$,$,.NOTDEFINED.);
#77=IFCCOLUMNTYPE('3aSZOvGmr7jggSNwsq5PJE',$,'C2',$,$,$,$,$,$,.NOTDEFINED.);
#78=IFCMATERIALPROFILESET($,$,(#80),$);
#79=IFCRELASSOCIATESMATERIAL('3czOrQw2v9BfS0PeQCrzWq',$,$,$,(#77),#78);
#79=IFCRELASSOCIATESMATERIAL('1AtNJPaD14DgINbAjXvbU4',$,$,$,(#77),#78);
#80=IFCMATERIALPROFILE($,$,#20,#76,$,$);
#81=IFCRECTANGLEHOLLOWPROFILEDEF(.AREA.,'150x75x2.0 RHS',$,0.075,0.15,0.002,0.005,0.005);
#82=IFCCOLUMNTYPE('21YcNdKNj2$95U55yNx1Nw',$,'C3',$,$,$,$,$,$,.NOTDEFINED.);
#82=IFCCOLUMNTYPE('28iv3Kru12yQ7R7RRwNjvD',$,'C3',$,$,$,$,$,$,.NOTDEFINED.);
#83=IFCMATERIALPROFILESET($,$,(#85),$);
#84=IFCRELASSOCIATESMATERIAL('3n6Cru3xr6jvDptU9u7QEF',$,$,$,(#82),#83);
#84=IFCRELASSOCIATESMATERIAL('2M_oL$n3j6rBRnOW5RSk87',$,$,$,(#82),#83);
#85=IFCMATERIALPROFILE($,$,#20,#81,$,$);
#86=IFCISHAPEPROFILEDEF(.AREA.,'DEMO-I',$,0.1,0.2,0.005,0.01,0.005,$,$);
#87=IFCBEAMTYPE('2CsGpD$6nDCxWv9jXTtvq9',$,'B1',$,$,$,$,$,$,.NOTDEFINED.);
#87=IFCBEAMTYPE('2E$V5l4b54dxuToJ1A6IHp',$,'B1',$,$,$,$,$,$,.NOTDEFINED.);
#88=IFCMATERIALPROFILESET($,$,(#90),$);
#89=IFCRELASSOCIATESMATERIAL('2LJExc74j9rgBSBx51HDPK',$,$,$,(#87),#88);
#89=IFCRELASSOCIATESMATERIAL('3PUNnY7cj8LhyXbHKVqZnf',$,$,$,(#87),#88);
#90=IFCMATERIALPROFILE($,$,#20,#86,$,$);
#91=IFCCSHAPEPROFILEDEF(.AREA.,'DEMO-C',$,0.2,0.1,0.0015,0.03,0.005);
#92=IFCBEAMTYPE('32pHN2b0P0awGw$U8PpneO',$,'B2',$,$,$,$,$,$,.NOTDEFINED.);
#92=IFCBEAMTYPE('3qutoZTvP0lvLWNXyjPhPm',$,'B2',$,$,$,$,$,$,.NOTDEFINED.);
#93=IFCMATERIALPROFILESET($,$,(#95),$);
#94=IFCRELASSOCIATESMATERIAL('2wg8R1Drb2xhQ2Y_pRSh3c',$,$,$,(#92),#93);
#94=IFCRELASSOCIATESMATERIAL('3dGyQf42H2ahch2bSYPPHP',$,$,$,(#92),#93);
#95=IFCMATERIALPROFILE($,$,#20,#91,$,$);
#96=IFCWINDOWTYPE('1Gg9HfZEr69u8SdWfDs2J3',$,'WT01',$,$,$,(#135,#152),$,$,.NOTDEFINED.,.NOTDEFINED.,$,$);
#96=IFCWINDOWTYPE('0bK3c4PWL3eOMNwkPN$rlg',$,'WT01',$,$,$,(#135,#152),$,$,.NOTDEFINED.,.NOTDEFINED.,$,$);
#97=IFCINDEXEDPOLYGONALFACE((13,17,18,14));
#98=IFCINDEXEDPOLYGONALFACE((5,6,3,4));
#99=IFCINDEXEDPOLYGONALFACE((7,8,2,1));
@@ -157,7 +157,7 @@ DATA;
#150=IFCDIRECTION((0.,0.,1.));
#151=IFCAXIS2PLACEMENT3D(#148,#150,#149);
#152=IFCREPRESENTATIONMAP(#151,#147);
#153=IFCDOORTYPE('0NBUmPKyT9WecsIeYJrEqg',$,'DT01',$,$,$,(#196,#209),$,$,.NOTDEFINED.,.NOTDEFINED.,$,$);
#153=IFCDOORTYPE('2j0vIBgkH0ERnBCP0cpcCz',$,'DT01',$,$,$,(#196,#209),$,$,.NOTDEFINED.,.NOTDEFINED.,$,$);
#154=IFCINDEXEDPOLYGONALFACE((17,16,15,14));
#155=IFCINDEXEDPOLYGONALFACE((2,3,29,28));
#156=IFCINDEXEDPOLYGONALFACE((27,28,29,30,32,31));
@@ -214,7 +214,7 @@ DATA;
#207=IFCDIRECTION((0.,0.,1.));
#208=IFCAXIS2PLACEMENT3D(#205,#207,#206);
#209=IFCREPRESENTATIONMAP(#208,#204);
#210=IFCFURNITURETYPE('3Kyc6IyarAUw3_8fkNtXIg',$,'BUN01',$,$,$,(#931,#952),$,$,.NOTDEFINED.,.NOTDEFINED.);
#210=IFCFURNITURETYPE('02XxQ_3oT0SPrmFPATrt7o',$,'BUN01',$,$,$,(#931,#952),$,$,.NOTDEFINED.,.NOTDEFINED.);
#211=IFCINDEXEDPOLYGONALFACE((187,278,44));
#212=IFCINDEXEDPOLYGONALFACE((21,52,60));
#213=IFCINDEXEDPOLYGONALFACE((91,100,31));
@@ -957,35 +957,35 @@ DATA;
#950=IFCDIRECTION((0.,0.,1.));
#951=IFCAXIS2PLACEMENT3D(#948,#950,#949);
#952=IFCREPRESENTATIONMAP(#951,#947);
#953=IFCTYPEPRODUCT('0TBMBnD_b66QLUWKD9HLsc',$,'SETOUT-POINT',$,'IfcAnnotation/SYMBOL',(#954),$,$);
#954=IFCPROPERTYSET('2dQzX_K4r1Xet6dz9zBlgs',$,'EPset_Annotation',$,(#955));
#953=IFCTYPEPRODUCT('0UrP0fLdD5OwzwD41aRKao',$,'SETOUT-POINT',$,'IfcAnnotation/SYMBOL',(#954),$,$);
#954=IFCPROPERTYSET('27lmSbeAXC08EWkEq8XdUG',$,'EPset_Annotation',$,(#955));
#955=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('setout-point'),$);
#956=IFCTYPEPRODUCT('0vo_7PU3H9ygTnrIipqPRI',$,'CONTROL-POINT',$,'IfcAnnotation/SYMBOL',(#957),$,$);
#957=IFCPROPERTYSET('3APQOw$FP1ivxp08UxHsl6',$,'EPset_Annotation',$,(#958));
#956=IFCTYPEPRODUCT('3IQrQOSFP0WfkuLk0ak7_0',$,'CONTROL-POINT',$,'IfcAnnotation/SYMBOL',(#957),$,$);
#957=IFCPROPERTYSET('23aN9DsdjFcfWq8KGSIVoN',$,'EPset_Annotation',$,(#958));
#958=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('control-point'),$);
#959=IFCTYPEPRODUCT('32V27G3$T1OO3TbXg6948F',$,'TRAVERSE-POINT',$,'IfcAnnotation/SYMBOL',(#960),$,$);
#960=IFCPROPERTYSET('0cTSYXMc9B$PTXVDrhBYYW',$,'EPset_Annotation',$,(#961));
#959=IFCTYPEPRODUCT('3wvbaiaIz8agQgDRzt01vz',$,'TRAVERSE-POINT',$,'IfcAnnotation/SYMBOL',(#960),$,$);
#960=IFCPROPERTYSET('1j7aFM1Rb9BRuKgUEn1U10',$,'EPset_Annotation',$,(#961));
#961=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('traverse-point'),$);
#962=IFCTYPEPRODUCT('2PXIC7Bg914gJhRh2XeGnN',$,'DASHED',$,'IfcAnnotation/LINEWORK',(#963),$,$);
#963=IFCPROPERTYSET('0RiYsOxp529gXnCkw44wF8',$,'EPset_Annotation',$,(#964));
#962=IFCTYPEPRODUCT('0p5ZTfTnX9ZOywroa7Ffql',$,'DASHED',$,'IfcAnnotation/LINEWORK',(#963),$,$);
#963=IFCPROPERTYSET('27smSHyiv39vhBRSwfDVCD',$,'EPset_Annotation',$,(#964));
#964=IFCPROPERTYSINGLEVALUE('Classes',$,IFCLABEL('dashed'),$);
#965=IFCTYPEPRODUCT('1Ou1kA3Vb4HhuNBvM0uGe0',$,'FINE',$,'IfcAnnotation/LINEWORK',(#966),$,$);
#966=IFCPROPERTYSET('2gzly9D0L1qPK2MExeXgRO',$,'EPset_Annotation',$,(#967));
#965=IFCTYPEPRODUCT('2EvrG9Vuf6t9etgMeWFuJ2',$,'FINE',$,'IfcAnnotation/LINEWORK',(#966),$,$);
#966=IFCPROPERTYSET('0gVTuDcZ5ByRdR3sZnEZUR',$,'EPset_Annotation',$,(#967));
#967=IFCPROPERTYSINGLEVALUE('Classes',$,IFCLABEL('fine'),$);
#968=IFCTYPEPRODUCT('3lx$KQPRbEZwbQ7Xdfm5gw',$,'THIN',$,'IfcAnnotation/LINEWORK',(#969),$,$);
#969=IFCPROPERTYSET('0vsVpqs6zArvA2bfBtvQew',$,'EPset_Annotation',$,(#970));
#968=IFCTYPEPRODUCT('0IeM1ywXn1qhR_6NhB6N4s',$,'THIN',$,'IfcAnnotation/LINEWORK',(#969),$,$);
#969=IFCPROPERTYSET('0t6c$uCeT9092GYkbm8hDS',$,'EPset_Annotation',$,(#970));
#970=IFCPROPERTYSINGLEVALUE('Classes',$,IFCLABEL('thin'),$);
#971=IFCTYPEPRODUCT('00j$y97p903w2HOb35lAQ2',$,'MEDIUM',$,'IfcAnnotation/LINEWORK',(#972),$,$);
#972=IFCPROPERTYSET('240rEBDOn8PAvfnm_43s55',$,'EPset_Annotation',$,(#973));
#971=IFCTYPEPRODUCT('1jZVbCwrTCGhZdKbH4uqTP',$,'MEDIUM',$,'IfcAnnotation/LINEWORK',(#972),$,$);
#972=IFCPROPERTYSET('2uTJ11lF98GeN8pPIf340N',$,'EPset_Annotation',$,(#973));
#973=IFCPROPERTYSINGLEVALUE('Classes',$,IFCLABEL('medium'),$);
#974=IFCTYPEPRODUCT('2tVdFGorj6dfrL4uA1kyW8',$,'THICK',$,'IfcAnnotation/LINEWORK',(#975),$,$);
#975=IFCPROPERTYSET('2IEGHncr1D$R8fKA9zCEJP',$,'EPset_Annotation',$,(#976));
#974=IFCTYPEPRODUCT('38zW9E1uH2ae$zat9KrieS',$,'THICK',$,'IfcAnnotation/LINEWORK',(#975),$,$);
#975=IFCPROPERTYSET('2XQxgO16v9vxjvuIJpaPpG',$,'EPset_Annotation',$,(#976));
#976=IFCPROPERTYSINGLEVALUE('Classes',$,IFCLABEL('thick'),$);
#977=IFCTYPEPRODUCT('1T5C$$ONTBB8A9a7vv0Gn6',$,'STRONG',$,'IfcAnnotation/LINEWORK',(#978),$,$);
#978=IFCPROPERTYSET('1gRomAS1L2WguA51ZI0E40',$,'EPset_Annotation',$,(#979));
#977=IFCTYPEPRODUCT('3G2s7ZLzfDJh5J$2iIzHw9',$,'STRONG',$,'IfcAnnotation/LINEWORK',(#978),$,$);
#978=IFCPROPERTYSET('0TcB8Gal96vebTrLWa5CEw',$,'EPset_Annotation',$,(#979));
#979=IFCPROPERTYSINGLEVALUE('Classes',$,IFCLABEL('strong'),$);
#980=IFCTYPEPRODUCT('2LmKYrAEr2K8EwJyCAsyc4',$,'SETOUT-TAG',$,'IfcAnnotation/TEXT',(#981),(#1000),$);
#981=IFCPROPERTYSET('0nF9du8qDAF9aldqjCUsbX',$,'EPset_Annotation',$,(#982));
#980=IFCTYPEPRODUCT('3pq2B$0k52ZOv2bFuRYkxO',$,'SETOUT-TAG',$,'IfcAnnotation/TEXT',(#981),(#1000),$);
#981=IFCPROPERTYSET('39$oNFI052cBhtpZCVVLfj',$,'EPset_Annotation',$,(#982));
#982=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('setout-tag'),$);
#983=IFCCARTESIANPOINT((0.,0.,0.));
#984=IFCDIRECTION((0.,0.,1.));
@@ -1005,8 +1005,8 @@ DATA;
#998=IFCDIRECTION((0.,0.,1.));
#999=IFCAXIS2PLACEMENT3D(#996,#998,#997);
#1000=IFCREPRESENTATIONMAP(#999,#995);
#1001=IFCTYPEPRODUCT('1cdhVmqEPF6e13_TFOdlQw',$,'DOOR-TAG',$,'IfcAnnotation/TEXT',(#1002),(#1021),$);
#1002=IFCPROPERTYSET('1MUPGQolnDEgp0NxcMN56E',$,'EPset_Annotation',$,(#1003));
#1001=IFCTYPEPRODUCT('11M3ahhrr9NBdB29YEazzw',$,'DOOR-TAG',$,'IfcAnnotation/TEXT',(#1002),(#1021),$);
#1002=IFCPROPERTYSET('3xUNoLKPT9kB4LMdoctm5k',$,'EPset_Annotation',$,(#1003));
#1003=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('door-tag'),$);
#1004=IFCCARTESIANPOINT((0.,0.,0.));
#1005=IFCDIRECTION((0.,0.,1.));
@@ -1026,8 +1026,8 @@ DATA;
#1019=IFCDIRECTION((0.,0.,1.));
#1020=IFCAXIS2PLACEMENT3D(#1017,#1019,#1018);
#1021=IFCREPRESENTATIONMAP(#1020,#1016);
#1022=IFCTYPEPRODUCT('0OuDi3gRH2CxW9mrtE0vXw',$,'WINDOW-TAG',$,'IfcAnnotation/TEXT',(#1023),(#1036),$);
#1023=IFCPROPERTYSET('2X7dAo_1P5ruRnlKA4kIl6',$,'EPset_Annotation',$,(#1024));
#1022=IFCTYPEPRODUCT('1eE8Y$BVDFDgG8Fj6d9wiV',$,'WINDOW-TAG',$,'IfcAnnotation/TEXT',(#1023),(#1036),$);
#1023=IFCPROPERTYSET('2MqzSGkXDBcPSolaAQvYO4',$,'EPset_Annotation',$,(#1024));
#1024=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('window-tag'),$);
#1025=IFCCARTESIANPOINT((0.,0.,0.));
#1026=IFCDIRECTION((0.,0.,1.));
@@ -1041,8 +1041,8 @@ DATA;
#1034=IFCDIRECTION((0.,0.,1.));
#1035=IFCAXIS2PLACEMENT3D(#1032,#1034,#1033);
#1036=IFCREPRESENTATIONMAP(#1035,#1031);
#1037=IFCTYPEPRODUCT('3WEV_9wQn6AQTESgjW36PH',$,'SPACE-TAG',$,'IfcAnnotation/TEXT',(#1038),(#1063),$);
#1038=IFCPROPERTYSET('02FAX8dIzAlunWD5ak$6sU',$,'EPset_Annotation',$,(#1039));
#1037=IFCTYPEPRODUCT('0vvfHSiaPBxewA$32C4LdW',$,'SPACE-TAG',$,'IfcAnnotation/TEXT',(#1038),(#1063),$);
#1038=IFCPROPERTYSET('2zKuFdTPj3DhNdQ0U2kd5u',$,'EPset_Annotation',$,(#1039));
#1039=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('space-tag'),$);
#1040=IFCCARTESIANPOINT((0.,0.,0.));
#1041=IFCDIRECTION((0.,0.,1.));
@@ -1068,8 +1068,8 @@ DATA;
#1061=IFCDIRECTION((0.,0.,1.));
#1062=IFCAXIS2PLACEMENT3D(#1059,#1061,#1060);
#1063=IFCREPRESENTATIONMAP(#1062,#1058);
#1064=IFCTYPEPRODUCT('1evqvQLLL1zxdsIWqEn0lK',$,'MATERIAL-TAG',$,'IfcAnnotation/TEXT',(#1065),(#1078),$);
#1065=IFCPROPERTYSET('2KPJlGyer0UBmphfR7952k',$,'EPset_Annotation',$,(#1066));
#1064=IFCTYPEPRODUCT('36lTcd9yT8UBNDejQUNWrq',$,'MATERIAL-TAG',$,'IfcAnnotation/TEXT',(#1065),(#1078),$);
#1065=IFCPROPERTYSET('1x86VNVk1Drwe5XOj$GZX3',$,'EPset_Annotation',$,(#1066));
#1066=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('rectangle-tag'),$);
#1067=IFCCARTESIANPOINT((0.,0.,0.));
#1068=IFCDIRECTION((0.,0.,1.));
@@ -1083,8 +1083,8 @@ DATA;
#1076=IFCDIRECTION((0.,0.,1.));
#1077=IFCAXIS2PLACEMENT3D(#1074,#1076,#1075);
#1078=IFCREPRESENTATIONMAP(#1077,#1073);
#1079=IFCTYPEPRODUCT('3Nem6d4xX87O3deyWDi3AW',$,'TYPE-TAG',$,'IfcAnnotation/TEXT',(#1080),(#1093),$);
#1080=IFCPROPERTYSET('15bspgnA9CEuFox6xFjoTL',$,'EPset_Annotation',$,(#1081));
#1079=IFCTYPEPRODUCT('1rxhEX6I16oPqglXaGZ7Sw',$,'TYPE-TAG',$,'IfcAnnotation/TEXT',(#1080),(#1093),$);
#1080=IFCPROPERTYSET('3BeqEbDzX0xORamqqSx_kZ',$,'EPset_Annotation',$,(#1081));
#1081=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('capsule-tag'),$);
#1082=IFCCARTESIANPOINT((0.,0.,0.));
#1083=IFCDIRECTION((0.,0.,1.));
@@ -1098,8 +1098,8 @@ DATA;
#1091=IFCDIRECTION((0.,0.,1.));
#1092=IFCAXIS2PLACEMENT3D(#1089,#1091,#1090);
#1093=IFCREPRESENTATIONMAP(#1092,#1088);
#1094=IFCTYPEPRODUCT('0klFX9AjnEnPBkdIURv8XD',$,'NAME-TAG',$,'IfcAnnotation/TEXT',(#1095),(#1108),$);
#1095=IFCPROPERTYSET('1d53tifbv2rwcoFFJosiHf',$,'EPset_Annotation',$,(#1096));
#1094=IFCTYPEPRODUCT('0sws1hxNb2Og1etXqyoEh1',$,'NAME-TAG',$,'IfcAnnotation/TEXT',(#1095),(#1108),$);
#1095=IFCPROPERTYSET('11L$PtdnX8LfYigzRZ$g0a',$,'EPset_Annotation',$,(#1096));
#1096=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('capsule-tag'),$);
#1097=IFCCARTESIANPOINT((0.,0.,0.));
#1098=IFCDIRECTION((0.,0.,1.));
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -5,7 +5,7 @@ FILE_NAME('EPset_Drawing.ifc','2020-01-01T00:00:00',(),(),'EPset_Drawing','EPset
FILE_SCHEMA(('IFC4'));
ENDSEC;
DATA;
#1=IFCPROPERTYSETTEMPLATE('2JhNIvqZrFnAgxfhK0XVQX',$,'EPset_Drawing','',.PSET_OCCURRENCEDRIVEN.,'IfcAnnotation',(#2,#3,#4,#5,#6,#7,#8,#9,#10,#11,#12,#13,#14,#15,#16,#17,#18,#19,#20,#21,#22));
#1=IFCPROPERTYSETTEMPLATE('2JhNIvqZrFnAgxfhK0XVQX',$,'EPset_Drawing','',.PSET_OCCURRENCEDRIVEN.,'IfcAnnotation/DRAWING',(#2,#3,#4,#5,#6,#7,#8,#9,#10,#11,#12,#13,#14,#15,#16,#17,#18,#19,#20,#21,#22,#23));
#2=IFCSIMPLEPROPERTYTEMPLATE('23JavTMk98ZxXhrUEnjAcf',$,'TargetView','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
#3=IFCSIMPLEPROPERTYTEMPLATE('1yVWUt5H9DAOuu0OaMMLpe',$,'Scale','The scale of this drawing represented as a numerator and denominator, such as 1/100',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
#4=IFCSIMPLEPROPERTYTEMPLATE('3gsuPBtU93b8f0gg1pjkq6',$,'HumanScale','The scale of this drawing in human readable format, such as 1:100',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
@@ -27,5 +27,6 @@ DATA;
#20=IFCSIMPLEPROPERTYTEMPLATE('0joEq0Rd10cxweEh0NeHT6',$,'JoinCriteria','Comma separated selection keys which determine what cut objects are to be joined.',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
#21=IFCSIMPLEPROPERTYTEMPLATE('0nYMT3OSj5gArVniCWZRtv',$,'ShadingStyles','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
#22=IFCSIMPLEPROPERTYTEMPLATE('3VWG22eZXBdQwdKlzMeVQH',$,'CurrentShadingStyle','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
#23=IFCSIMPLEPROPERTYTEMPLATE('28mJ$GDxb7kBbKFH_rACMQ',$,'AngleDecimalPlaces','',.P_SINGLEVALUE.,'IfcInteger',$,$,$,$,$,.READWRITE.);
ENDSEC;
END-ISO-10303-21;
@@ -5,20 +5,20 @@ FILE_NAME('Psets_BBIM_Annotation.ifc','2020-01-01T00:00:00',(),(),'Psets_BBIM_An
FILE_SCHEMA(('IFC4'));
ENDSEC;
DATA;
#1=IFCPROPERTYSETTEMPLATE('3VuPUwdCD2Qx3XDDRs0R1N',$,'EPset_Annotation','',.PSET_OCCURRENCEDRIVEN.,'IfcAnnotation,IfcTypeProduct',(#2,#3,#4));
#1=IFCPROPERTYSETTEMPLATE('3VuPUwdCD2Qx3XDDRs0R1N',$,'EPset_Annotation','',.PSET_TYPEDRIVENOVERRIDE.,'IfcAnnotation,IfcTypeProduct',(#2,#3,#4));
#2=IFCSIMPLEPROPERTYTEMPLATE('2P7JN79n96Q9pElZ83LKe4',$,'ZIndex','',.P_SINGLEVALUE.,'IfcInteger',$,$,$,$,$,.READWRITE.);
#3=IFCSIMPLEPROPERTYTEMPLATE('1Wpx_r2xj1_9w5JpI0QRJy',$,'Symbol','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
#4=IFCSIMPLEPROPERTYTEMPLATE('3q0oxMUKP47vZ4jnyG$dDb',$,'Classes','Classes separarated by spaces that end up in classes for this element in svg. Can be used to specify the text font size: small - 1.8mm; regular - 2.5mm; large - 3.5mm; header - 5mm; title - 7mm. By default regular size is used.',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
#5=IFCPROPERTYSETTEMPLATE('0iKwujnQL9IevVQato8f7Z',$,'BBIM_Batting','',.PSET_OCCURRENCEDRIVEN.,'IfcAnnotation,IfcTypeProduct',(#6,#7));
#5=IFCPROPERTYSETTEMPLATE('0iKwujnQL9IevVQato8f7Z',$,'BBIM_Batting','',.PSET_TYPEDRIVENOVERRIDE.,'IfcAnnotation/BATTING,IfcTypeProduct',(#6,#7));
#6=IFCSIMPLEPROPERTYTEMPLATE('0t2LEesGT1QRQtrIZUAR8L',$,'Thickness','Batting thickness',.P_SINGLEVALUE.,'IfcPositiveLengthMeasure',$,$,$,$,$,.READWRITE.);
#7=IFCSIMPLEPROPERTYTEMPLATE('082PndS6v2kBOiJoSboMnh',$,'Reverse pattern direction','Reverse batting pattern (swap starting and ending points)',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
#8=IFCPROPERTYSETTEMPLATE('1Dx2EiZnP67xotInXpwz90',$,'BBIM_Section','',.PSET_OCCURRENCEDRIVEN.,'IfcAnnotation,IfcTypeProduct',(#9,#10,#11,#12,#13));
#8=IFCPROPERTYSETTEMPLATE('1Dx2EiZnP67xotInXpwz90',$,'BBIM_Section','',.PSET_TYPEDRIVENOVERRIDE.,'IfcAnnotation/SECTION,IfcTypeProduct',(#9,#10,#11,#12,#13));
#9=IFCSIMPLEPROPERTYTEMPLATE('2a_9s8spHDc9dZHtgg71XL',$,'ShowStartArrow','Display start arrow.',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
#10=IFCSIMPLEPROPERTYTEMPLATE('3i6SH_GbT7zhKea$wVE56A',$,'StartArrowSymbol','Custom symbol for the start of the section marker arrow. Need to make sure it''s present in "symbols.svg".',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
#11=IFCSIMPLEPROPERTYTEMPLATE('1DtsPn5a9FG8$zXHDDMavY',$,'ShowEndArrow','Display end arrow.',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
#12=IFCSIMPLEPROPERTYTEMPLATE('2$6U0mLI9AiPRWeBabdY3u',$,'EndArrowSymbol','Custom symbol for the end of the section marker arrow. Need to make sure it''s present in "symbols.svg".',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
#13=IFCSIMPLEPROPERTYTEMPLATE('1naFqntIL7igCY7hCaE7kq',$,'HasConnectedSectionLine','Connect or disconnect section markers with line (by default = True).',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
#14=IFCPROPERTYSETTEMPLATE('3V8oZ8YRD3_O7uR5vcUleS',$,'BBIM_Documentation','',.PSET_OCCURRENCEDRIVEN.,'IfcProject',(#15,#16,#17,#18,#19,#20,#21,#22,#23));
#14=IFCPROPERTYSETTEMPLATE('3V8oZ8YRD3_O7uR5vcUleS',$,'BBIM_Documentation','',.PSET_TYPEDRIVENOVERRIDE.,'IfcProject',(#15,#16,#17,#18,#19,#20,#21,#22,#23));
#15=IFCSIMPLEPROPERTYTEMPLATE('0ulAhgk3v9qfGlDILauR6J',$,'SheetsDir','Default sheets directory',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
#16=IFCSIMPLEPROPERTYTEMPLATE('2yvlVKiQXASucfH40deCvu',$,'LayoutsDir','Default layouts directory',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
#17=IFCSIMPLEPROPERTYTEMPLATE('2kXZqXicL3jRwsOnLM_0ho',$,'TitleblocksDir','Default titleblocks directory',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
@@ -28,7 +28,7 @@ DATA;
#21=IFCSIMPLEPROPERTYTEMPLATE('1UDakJ5_f7kBhggNSW4$h5',$,'SymbolsPath','Default symbols SVG',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
#22=IFCSIMPLEPROPERTYTEMPLATE('0d53LEtgLDQxnv__NfgH7i',$,'PatternsPath','Default patterns SVG',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
#23=IFCSIMPLEPROPERTYTEMPLATE('26qFNMv7nCHgU6Jd7Anga5',$,'ShadingStylesPath','Default shading styles',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
#24=IFCPROPERTYSETTEMPLATE('0I9merLinF5Ap$aZwaclgm',$,'BBIM_Dimension','',.PSET_OCCURRENCEDRIVEN.,'IfcAnnotation,IfcTypeProduct',(#25,#26,#27,#28));
#24=IFCPROPERTYSETTEMPLATE('0I9merLinF5Ap$aZwaclgm',$,'BBIM_Dimension','',.PSET_TYPEDRIVENOVERRIDE.,'IfcAnnotation/DIMENSION,IfcTypeProduct',(#25,#26,#27,#28));
#25=IFCSIMPLEPROPERTYTEMPLATE('1rL2AbQsXD8RbpoWH5pYOV',$,'ShowDescriptionOnly','Hide the measurement values and show only annotation description',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
#26=IFCSIMPLEPROPERTYTEMPLATE('0SVyOfB0rC2xNfdRYf3XvY',$,'SuppressZeroInches','Suppress 0 inch values in dimension annotation text (for example: 12'' - 0" -> 12'')',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
#27=IFCSIMPLEPROPERTYTEMPLATE('2bUmj458PBqPAtUoI3MXsb',$,'TextPrefix','Text to add before annotation measurement value',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
File diff suppressed because one or more lines are too long
+21 -14
View File
@@ -16,6 +16,7 @@
# 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
import os
import bpy
import json
@@ -27,6 +28,7 @@ import addon_utils
import ifcopenshell
import ifcopenshell.api
import ifcopenshell.util.placement
import ifcopenshell.util.unit
import blenderbim.tool as tool
import blenderbim.core.geometry
import blenderbim.core.aggregate
@@ -34,6 +36,8 @@ import blenderbim.core.spatial
import blenderbim.core.style
from blenderbim.bim.ifc import IfcStore
from mathutils import Vector
from typing import Union
from logging import Logger
class IfcExporter:
@@ -85,8 +89,8 @@ class IfcExporter:
self.get_application_name(), tool.Blender.get_blenderbim_version()
)
def sync_all_objects(self):
results = []
def sync_all_objects(self, skip_unlinking=False) -> list[ifcopenshell.entity_instance]:
results: list[ifcopenshell.entity_instance] = []
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(self.file)
for ifc_definition_id in list(IfcStore.id_map.keys()):
obj = IfcStore.id_map[ifc_definition_id]
@@ -95,19 +99,21 @@ class IfcExporter:
continue
if obj.library:
continue
tool.Collector.sync(obj)
tool.Collector.sync(obj, skip_unlinking)
result = self.sync_object_placement(obj)
if result:
results.append(result)
result = self.sync_object_material(obj)
# TODO: sync_object_material always returns None
# so it's never really appended
if result:
results.append(result)
except ReferenceError:
pass # The object is likely deleted
return results
def sync_edited_objects(self):
results = []
def sync_edited_objects(self) -> list[ifcopenshell.entity_instance]:
results: list[ifcopenshell.entity_instance] = []
for obj in IfcStore.edited_objs.copy():
if not obj:
continue
@@ -125,7 +131,7 @@ class IfcExporter:
IfcStore.edited_objs.clear()
return results
def sync_object_material(self, obj):
def sync_object_material(self, obj: bpy.types.Object) -> None:
if not obj.data or not isinstance(obj.data, bpy.types.Mesh):
return
if not self.has_changed_materials(obj):
@@ -136,11 +142,10 @@ class IfcExporter:
checksum = obj.data.BIMMeshProperties.material_checksum
return checksum != str([s.id() for s in tool.Geometry.get_styles(obj) if s])
def sync_object_placement(self, obj):
def sync_object_placement(self, obj: bpy.types.Object) -> Union[ifcopenshell.entity_instance, None]:
element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
if not tool.Ifc.is_moved(obj):
return
blender_matrix = np.array(obj.matrix_world)
if (obj.scale - Vector((1.0, 1.0, 1.0))).length > 1e-4:
bpy.ops.bim.update_representation(obj=obj.name)
return element
@@ -151,7 +156,7 @@ class IfcExporter:
blenderbim.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj)
return element
def sync_grid_axis_object_placement(self, obj, element):
def sync_grid_axis_object_placement(self, obj: bpy.types.Object, element: ifcopenshell.entity_instance) -> None:
grid = (element.PartOfU or element.PartOfV or element.PartOfW)[0]
grid_obj = tool.Ifc.get_object(grid)
if grid_obj:
@@ -160,10 +165,10 @@ class IfcExporter:
bpy.ops.bim.update_representation(obj=obj.name)
tool.Geometry.record_object_position(obj)
def get_application_name(self):
def get_application_name(self) -> str:
return "BlenderBIM"
def get_application_version(self):
def get_application_version(self) -> str:
version = ".".join(
[
str(x)
@@ -181,11 +186,13 @@ class IfcExporter:
class IfcExportSettings:
def __init__(self):
self.logger = None
self.output_file = None
self.logger: Logger = None
self.output_file: str = None
self.json_version: str = None
self.json_compact: bool = None
@staticmethod
def factory(context, output_file, logger):
def factory(context: bpy.types.Context, output_file: str, logger: Logger) -> IfcExportSettings:
settings = IfcExportSettings()
settings.output_file = output_file
settings.logger = logger
+26 -68
View File
@@ -31,6 +31,7 @@ from blenderbim.bim.module.model.data import AuthoringData
from blenderbim.bim.module.model.workspace import LIST_OF_TOOLS, TOOLS_TO_CLASSES_MAP
from mathutils import Vector
from math import cos, degrees
from typing import Union
cwd = os.path.dirname(os.path.realpath(__file__))
@@ -191,6 +192,8 @@ def refresh_ui_data():
if isinstance(tool.Ifc.get(), ifcopenshell.sqlite):
tool.Ifc.get().clear_cache()
bpy.context.scene.DocProperties.should_draw_decorations = bpy.context.scene.DocProperties.should_draw_decorations
@persistent
def loadIfcStore(scene):
@@ -220,7 +223,7 @@ def redo_post(scene):
tool.Ifc.rebuild_element_maps()
def get_application(ifc):
def get_application(ifc: ifcopenshell.file) -> ifcopenshell.entity_instance:
# TODO: cache this for even faster application retrieval. It honestly makes a difference on long scripts.
version = get_application_version()
for element in ifc.by_type("IfcApplication"):
@@ -235,7 +238,20 @@ def get_application(ifc):
)
def get_application_version():
def get_user(ifc: ifcopenshell.file) -> Union[ifcopenshell.entity_instance, None]:
# TODO: cache this for even faster application retrieval. It honestly makes a difference on long scripts.
if pao := next(iter(ifc.by_type("IfcPersonAndOrganization")), None):
return pao
elif ifc.schema == "IFC2X3":
if (person := next(iter(ifc.by_type("IfcPerson")), None)) is None:
person = tool.Ifc.run("owner.add_person")
if (organization := next(iter(ifc.by_type("IfcOrganization")), None)) is None:
organization = tool.Ifc.run("owner.add_organisation")
pao = tool.Ifc.run("owner.add_person_and_organisation", person=person, organisation=organization)
return pao
def get_application_version() -> str:
return ".".join(
[
str(x)
@@ -254,60 +270,6 @@ def viewport_shading_changed_callback(area):
bpy.context.scene.BIMStylesProperties.active_style_type = "External"
@classmethod
def poll_check_blender_tab(cls, context):
return tool.Blender.is_tab(context, "BLENDER")
def get_override_scene_panel(panel_name):
original_panel = getattr(bpy.types, panel_name)
override_panel = type(f"Override_{panel_name}", (original_panel,), {})
override_panel.bl_idname = f"{panel_name}_override"
# some blender panel depend on other and register_class will throw an error
# if we won't override their `bl_parent_id`
bl_parent_id = getattr(override_panel, "bl_parent_id", None)
if bl_parent_id is not None:
override_panel.bl_parent_id = f"{bl_parent_id}_override"
# override poll method
poll = getattr(override_panel, "poll", None)
if poll is None:
override_panel.poll = poll_check_blender_tab
else:
@classmethod
def wrapped_poll(cls, context):
return super(override_panel, cls).poll(context) and poll_check_blender_tab.__func__(cls, context)
override_panel.poll = wrapped_poll
return override_panel
# TODO: possibly override scene panels from other addons too?
# list can be updated from
# https://projects.blender.org/blender/blender/src/branch/main/scripts/startup/bl_ui/properties_scene.py#L421
OVERRIDE_SCENE_PANELS = (
"SCENE_PT_scene",
"SCENE_PT_unit",
"SCENE_PT_physics",
"SCENE_PT_rigid_body_world",
"SCENE_PT_rigid_body_world_settings",
"SCENE_PT_rigid_body_cache",
"SCENE_PT_rigid_body_field_weights",
"SCENE_PT_audio",
"SCENE_PT_keying_sets",
"SCENE_PT_custom_props",
# after SCENE_PT_keying_sets
"SCENE_PT_keying_set_paths",
"SCENE_PT_keyframing_settings",
)
if bpy.app.version >= (4, 0):
OVERRIDE_SCENE_PANELS += ("SCENE_PT_simulation",)
@persistent
def load_post(scene):
global global_subscription_owner
@@ -330,7 +292,7 @@ def load_post(scene):
key=key, owner=global_subscription_owner, args=(area,), notify=viewport_shading_changed_callback
)
ifcopenshell.api.owner.settings.get_user = lambda ifc: core_owner.get_user(tool.Owner)
ifcopenshell.api.owner.settings.get_user = get_user
ifcopenshell.api.owner.settings.get_application = get_application
AuthoringData.type_thumbnails = {}
@@ -343,15 +305,11 @@ def load_post(scene):
else:
bpy.ops.workspace.append_activate(idname="BIM", filepath=os.path.join(cwd, "data", "workspace.blend"))
# To improve usability for new users, we hijack the scene properties
# tab. We override default scene properties panels with our own poll
# to hide them unless the user has chosen to view Blender properties.
for panel in OVERRIDE_SCENE_PANELS:
if panel in blenderbim.bim.overridden_scene_panels:
continue
override_panel = get_override_scene_panel(panel)
original_panel = getattr(bpy.types, panel)
bpy.utils.register_class(override_panel)
bpy.utils.unregister_class(original_panel)
blenderbim.bim.overridden_scene_panels[panel] = (original_panel, override_panel)
# To improve usability for new users, we hijack the scene properties
# tab. We override default scene properties panels with our own poll
# to hide them unless the user has chosen to view Blender properties.
for panel in tool.Blender.get_scene_panels_list():
if panel in blenderbim.bim.original_scene_panels_polls:
continue
tool.Blender.override_scene_panel(panel)
tool.Blender.setup_tabs()
+10 -40
View File
@@ -29,6 +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
def draw_attributes(props, layout, copy_operator=None, popup_active_attribute=None):
@@ -75,6 +76,10 @@ def draw_attribute(attribute, layout, copy_operator=None):
op.data_path = attribute.path_from_id("string_value")
if attribute.is_optional:
layout.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
if attribute.name == "GlobalId":
layout.operator("bim.generate_global_id", icon="FILE_REFRESH", text="")
if copy_operator:
op = layout.operator(f"{copy_operator}", text="", icon="COPYDOWN")
op.name = attribute.name
@@ -116,7 +121,7 @@ def import_attribute(attribute, props, data, callback=None):
elif is_handled_by_callback is False:
props.remove(len(props) - 1)
elif data_type == "string":
new.string_value = "" if new.is_null else str(data[attribute.name()])
new.string_value = "" if new.is_null else str(data[attribute.name()]).replace("\n", "\\n")
if attribute.type_of_attribute().declared_type().name() == "IfcURIReference":
new.is_uri = True
elif data_type == "boolean":
@@ -178,7 +183,7 @@ def add_attribute_description(attribute_blender, attribute_ifc=None):
attribute_blender.description = description
def export_attributes(props, callback=None):
def export_attributes(props, callback: Optional[Callable] = None) -> dict[str, Any]:
attributes = {}
for prop in props:
is_handled_by_callback = callback(attributes, prop) if callback else False
@@ -216,39 +221,6 @@ def get_enum_items(data, prop_name, context=None):
return items
def get_obj_ifc_definition_id(context, obj, obj_type):
if obj_type == "Object":
return bpy.data.objects.get(obj).BIMObjectProperties.ifc_definition_id
elif obj_type == "Material":
return bpy.data.materials.get(obj).BIMObjectProperties.ifc_definition_id
elif obj_type == "MaterialSet":
return ifcopenshell.util.element.get_material(
tool.Ifc.get_entity(bpy.data.objects.get(obj)), should_skip_usage=True
).id()
elif obj_type == "MaterialSetItem":
return bpy.data.objects.get(obj).BIMObjectMaterialProperties.active_material_set_item_id
elif obj_type == "Task":
return context.scene.BIMTaskTreeProperties.tasks[
context.scene.BIMWorkScheduleProperties.active_task_index
].ifc_definition_id
elif obj_type == "Cost":
return context.scene.BIMCostProperties.cost_items[
context.scene.BIMCostProperties.active_cost_item_index
].ifc_definition_id
elif obj_type == "Resource":
return context.scene.BIMResourceTreeProperties.resources[
context.scene.BIMResourceProperties.active_resource_index
].ifc_definition_id
elif obj_type == "Profile":
return context.scene.BIMProfileProperties.profiles[
context.scene.BIMProfileProperties.active_profile_index
].ifc_definition_id
elif obj_type == "WorkSchedule":
return context.scene.BIMWorkScheduleProperties.active_work_schedule_id
elif obj_type == "Group":
prop = context.scene.BIMGroupProperties
return prop.groups[prop.active_group_index].ifc_definition_id
# hack to close popup
# https://blender.stackexchange.com/a/202576/130742
def close_operator_panel(event):
@@ -304,6 +276,9 @@ def draw_filter(layout, filter_groups, data, module):
op.type = sprops.facet
op.index = i
op.module = module
op = row.operator("bim.remove_filter_group", text="", icon="X")
op.index = i
op.module = module
for j, ifc_filter in enumerate(filter_group.filters):
if ifc_filter.type == "entity":
@@ -350,11 +325,6 @@ def draw_filter(layout, filter_groups, data, module):
op.index = j
op.module = module
row = box.row()
op = row.operator("bim.remove_filter_group", icon="X")
op.index = i
op.module = module
# TODO this should move into ifcopenshell.util
+56 -34
View File
@@ -19,37 +19,46 @@
import os
import bpy
import uuid
import shutil
import hashlib
import zipfile
import tempfile
import traceback
import ifcopenshell
import ifcopenshell.geom
import ifcopenshell.ifcopenshell_wrapper
import blenderbim
import blenderbim.bim.handler
import blenderbim.tool as tool
from pathlib import Path
from blenderbim.tool.brick import BrickStore
from typing import Set, Union, Optional
IFC_CONNECTED_TYPE = Union[bpy.types.Material, bpy.types.Object]
class IfcStore:
path = ""
file = None
schema = None
cache = None
cache_path = None
id_map = {}
guid_map = {}
edited_objs = set()
pset_template_path = ""
pset_template_file = None
classification_path = ""
classification_file = None
library_path = ""
library_file = None
path: str = ""
file: Optional[ifcopenshell.file] = None
schema: Optional[ifcopenshell.ifcopenshell_wrapper.schema_definition] = None
cache: Optional[ifcopenshell.ifcopenshell_wrapper.HdfSerializer] = None
cache_path: Optional[str] = None
id_map: dict[int, IFC_CONNECTED_TYPE] = {}
guid_map: dict[str, IFC_CONNECTED_TYPE] = {}
edited_objs: Set[bpy.types.Object] = set()
pset_template_path: str = ""
pset_template_file: Optional[ifcopenshell.file] = None
classification_path: str = ""
classification_file: Optional[ifcopenshell.file] = None
library_path: str = ""
library_file: Optional[ifcopenshell.file] = None
current_transaction = ""
last_transaction = ""
history = []
future = []
schema_identifiers = ["IFC4", "IFC2X3", "IFC4X3"]
session_files = {}
session_files: dict[str, ifcopenshell.file] = {}
@staticmethod
def purge():
@@ -113,11 +122,17 @@ class IfcStore:
ifc_hash = hashlib.md5(ifc_key.encode("utf-8")).hexdigest()
new_cache_path = os.path.join(bpy.context.scene.BIMProperties.data_dir, "cache", f"{ifc_hash}.h5")
IfcStore.cache = None
os.replace(IfcStore.cache_path, new_cache_path)
try:
shutil.move(IfcStore.cache_path, new_cache_path)
except PermissionError:
try:
shutil.copy2(IfcStore.cache_path, new_cache_path)
except PermissionError:
pass # Well we tried. No cache for you!
IfcStore.get_cache()
@staticmethod
def load_file(path):
def load_file(path) -> None:
if not os.path.isfile(path):
return
extension = path.split(".")[-1]
@@ -146,7 +161,7 @@ class IfcStore:
return IfcStore.schema
@staticmethod
def get_element(id_or_guid):
def get_element(id_or_guid: Union[int, str]) -> IFC_CONNECTED_TYPE:
if isinstance(id_or_guid, int):
map_object = IfcStore.id_map
else:
@@ -159,7 +174,7 @@ class IfcStore:
return obj
@staticmethod
def relink_all_objects():
def relink_all_objects() -> None:
if not IfcStore.get_file():
return
for obj in bpy.data.objects:
@@ -172,7 +187,7 @@ class IfcStore:
IfcStore.relink_object(obj)
@staticmethod
def relink_object(obj):
def relink_object(obj: IFC_CONNECTED_TYPE) -> None:
if not obj:
return
if obj.BIMObjectProperties.ifc_definition_id:
@@ -193,7 +208,7 @@ class IfcStore:
IfcStore.commit_link_element(data)
@staticmethod
def link_element(element, obj):
def link_element(element: ifcopenshell.entity_instance, obj: IFC_CONNECTED_TYPE) -> None:
# Please use tool.Ifc.link() instead of this method. We want to
# refactor this class and deprecate usage of IfcStore in favour of
# tools.
@@ -259,7 +274,7 @@ class IfcStore:
# TODO We're handling id_map and guid_map, but what about edited_objs? This might cause big problems.
@staticmethod
def rollback_unlink_element(data):
def rollback_unlink_element(data) -> None:
if "id" not in data or "obj" not in data:
return
obj = bpy.data.objects.get(data["obj"])
@@ -268,13 +283,15 @@ class IfcStore:
IfcStore.guid_map[data["guid"]] = obj
@staticmethod
def commit_unlink_element(data):
def commit_unlink_element(data) -> None:
del IfcStore.id_map[data["id"]]
if data["guid"]:
del IfcStore.guid_map[data["guid"]]
@staticmethod
def unlink_element(element=None, obj=None):
def unlink_element(
element: Optional[ifcopenshell.entity_instance] = None, obj: Optional[IFC_CONNECTED_TYPE] = None
) -> None:
if element is None:
try:
element = tool.Ifc.get_entity(obj)
@@ -320,7 +337,8 @@ class IfcStore:
)
@staticmethod
def execute_ifc_operator(operator, context, is_invoke=False):
def execute_ifc_operator(operator: bpy.types.Operator, context: bpy.types.Context, is_invoke=False):
blenderbim.last_actions.append({"type": "operator", "name": operator.bl_idname})
bpy.context.scene.BIMProperties.is_dirty = True
is_top_level_operator = not bool(IfcStore.current_transaction)
@@ -335,10 +353,14 @@ class IfcStore:
else:
operator.transaction_key = IfcStore.current_transaction
if is_invoke:
result = getattr(operator, "_invoke")(context, None)
else:
result = getattr(operator, "_execute")(context)
try:
if is_invoke:
result = getattr(operator, "_invoke")(context, None)
else:
result = getattr(operator, "_execute")(context)
except:
blenderbim.last_error = traceback.format_exc()
raise
if is_top_level_operator:
if tool.Ifc.get():
@@ -354,17 +376,17 @@ class IfcStore:
return result
@staticmethod
def begin_transaction(operator):
def begin_transaction(operator: bpy.types.Operator) -> None:
IfcStore.current_transaction = str(uuid.uuid4())
operator.transaction_key = IfcStore.current_transaction
@staticmethod
def end_transaction(operator):
def end_transaction(operator: bpy.types.Operator) -> None:
IfcStore.current_transaction = ""
operator.transaction_key = ""
@staticmethod
def add_transaction_operation(operator, rollback=None, commit=None):
def add_transaction_operation(operator: bpy.types.Operator, rollback=None, commit=None) -> None:
key = getattr(operator, "transaction_key", None)
data = getattr(operator, "transaction_data", None)
bpy.context.scene.BIMProperties.last_transaction = key
@@ -380,7 +402,7 @@ class IfcStore:
IfcStore.future = []
@staticmethod
def undo(until_key=None):
def undo(until_key=None) -> None:
BrickStore.undo()
if not IfcStore.history:
return
@@ -395,7 +417,7 @@ class IfcStore:
IfcStore.future.append(event)
@staticmethod
def redo(until_key=None):
def redo(until_key=None) -> None:
BrickStore.redo()
if not IfcStore.future:
+174 -89
View File
@@ -32,24 +32,29 @@ import ifcopenshell.geom
import ifcopenshell.util.unit
import ifcopenshell.util.element
import ifcopenshell.util.geolocation
import ifcopenshell.util.placement
import ifcopenshell.util.representation
import ifcopenshell.util.shape
import blenderbim.tool as tool
import ifcopenshell.ifcopenshell_wrapper as ifcopenshell_wrapper
from itertools import chain, accumulate
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.ifc import IfcStore, IFC_CONNECTED_TYPE
from blenderbim.tool.loader import OBJECT_DATA_TYPE
from blenderbim.bim.module.drawing.prop import ANNOTATION_TYPES_DATA
from typing import Dict
from typing import Dict, Union, Optional, Any
class MaterialCreator:
def __init__(self, ifc_import_settings: IfcImportSettings, ifc_importer: IfcImporter):
self.mesh = None
self.obj = None
self.mesh: bpy.types.Mesh = None
self.obj: bpy.types.Object = None
self.materials: Dict[int, bpy.types.Material] = {}
self.styles: Dict[int, bpy.types.Material] = {}
self.parsed_meshes = set()
self.parsed_meshes: set[str] = set()
self.ifc_import_settings = ifc_import_settings
self.ifc_importer = ifc_importer
def create(self, element, obj, mesh):
def create(self, element: ifcopenshell.entity_instance, obj: bpy.types.Object, mesh: bpy.types.Mesh) -> None:
self.mesh = mesh
# as ifcopenshell triangulates the mesh, we need to merge it to quads again
self.obj = obj
@@ -65,7 +70,7 @@ class MaterialCreator:
self.assign_material_slots_to_faces()
tool.Geometry.record_object_materials(obj)
def add_default_material(self, element):
def add_default_material(self, element: ifcopenshell.entity_instance) -> None:
element_material = ifcopenshell.util.element.get_material(element)
if not element_material:
return
@@ -83,14 +88,17 @@ class MaterialCreator:
self.mesh.materials.append(self.materials[material.id()])
return
def load_existing_materials(self):
def load_existing_materials(self) -> None:
for material in bpy.data.materials:
if material.BIMObjectProperties.ifc_definition_id:
self.materials[material.BIMObjectProperties.ifc_definition_id] = material
if material.BIMMaterialProperties.ifc_style_id:
self.styles[material.BIMMaterialProperties.ifc_style_id] = material
def parse_representations(self, element):
def parse_representations(self, element: ifcopenshell.entity_instance) -> bool:
"""Search for styles in in all `element`'s representation items
and adds them to `self.mesh.materials`.\n
Returns `True` if any styles were found and added, returns `False` otherwise."""
has_parsed = False
if hasattr(element, "Representation"):
for representation in element.Representation.Representations:
@@ -104,7 +112,7 @@ class MaterialCreator:
has_parsed = True
return has_parsed
def parse_representation(self, representation):
def parse_representation(self, representation: ifcopenshell.entity_instance) -> bool:
has_parsed = False
representation_items = self.resolve_all_stylable_representation_items(representation)
for item in representation_items:
@@ -112,9 +120,9 @@ class MaterialCreator:
has_parsed = True
return has_parsed
def parse_representation_item(self, item):
def parse_representation_item(self, item: ifcopenshell.entity_instance) -> bool:
if not item.StyledByItem:
return
return False
style_ids = []
styles = list(item.StyledByItem[0].Styles)
while styles:
@@ -124,11 +132,12 @@ class MaterialCreator:
elif style.is_a("IfcPresentationStyleAssignment"):
styles.extend(style.Styles)
if not style_ids:
return
return False
for style_id in style_ids:
material = self.styles[style_id]
def get_ifc_coordinate(material):
def get_ifc_coordinate(material: ifcopenshell.entity_instance) -> Union[ifcopenshell.entity_instance, None]:
"""returns IfcTextureCoordinate"""
texture_style = tool.Style.get_texture_style(material)
if not texture_style:
return
@@ -149,7 +158,7 @@ class MaterialCreator:
self.mesh.materials.append(material)
return True
def assign_material_slots_to_faces(self):
def assign_material_slots_to_faces(self) -> None:
if "ios_materials" not in self.mesh or not self.mesh["ios_materials"]:
return
if len(self.mesh.materials) == 1:
@@ -157,8 +166,12 @@ class MaterialCreator:
material_to_slot = {}
if len(self.mesh.polygons) == len(self.mesh["ios_material_ids"]):
for i, style_id in enumerate(self.mesh["ios_materials"]):
slot_index = self.mesh.materials.find(self.styles[style_id].name)
for i, style_or_material_id in enumerate(self.mesh["ios_materials"]):
if style_or_material_id in self.styles:
blender_material = self.styles[style_or_material_id]
else:
blender_material = self.materials[style_or_material_id]
slot_index = self.mesh.materials.find(blender_material.name)
material_to_slot[i] = slot_index
material_index = [
@@ -166,7 +179,10 @@ class MaterialCreator:
]
self.mesh.polygons.foreach_set("material_index", material_index)
def resolve_all_stylable_representation_items(self, representation):
def resolve_all_stylable_representation_items(
self, representation: ifcopenshell.entity_instance
) -> list[ifcopenshell.entity_instance]:
"""returns list of resolved IfcRepresentationItems"""
items = []
for item in representation.Items:
if item.is_a("IfcMappedItem"):
@@ -184,24 +200,30 @@ class MaterialCreator:
class IfcImporter:
def __init__(self, ifc_import_settings):
def __init__(self, ifc_import_settings: IfcImportSettings):
self.ifc_import_settings = ifc_import_settings
self.diff = None
self.file = None
self.context_settings = []
self.gross_context_settings = []
self.file: ifcopenshell.file = None
self.context_settings: list[ifcopenshell.geom.main.settings] = []
self.gross_context_settings: list[ifcopenshell.geom.main.settings] = []
self.contexts = []
self.project = None
self.has_existing_project = False
self.collections = {}
self.elements = set()
self.type_collection = None
# element guids to blender collections mapping
self.collections: dict[str, bpy.types.Collection] = {}
self.elements: set[ifcopenshell.entity_instance] = set()
self.annotations: set[ifcopenshell.entity_instance] = set()
self.gross_elements: set[ifcopenshell.entity_instance] = set()
self.element_types: set[ifcopenshell.entity_instance] = set()
self.spatial_elements: set[ifcopenshell.entity_instance] = set()
self.type_collection: bpy.types.Collection = None
self.type_products = {}
self.meshes = {}
self.mesh_shapes = {}
self.time = 0
self.unit_scale = 1
self.added_data = {}
# ifc definition ids to blender elements mapping
self.added_data: dict[int, IFC_CONNECTED_TYPE] = {}
self.native_elements = set()
self.native_data = {}
self.progress = 0
@@ -277,6 +299,7 @@ class IfcImporter:
if self.ifc_import_settings.should_setup_viewport_camera:
self.setup_viewport_camera()
self.setup_arrays()
self.profile_code("Setup arrays")
self.update_progress(100)
bpy.context.window_manager.progress_end()
@@ -409,7 +432,7 @@ class IfcImporter:
self.annotations = set([a for a in self.file.by_type("IfcAnnotation")])
self.annotations -= drawing_annotations
self.elements = [e for e in self.elements if not e.is_a("IfcFeatureElement")]
self.elements = [e for e in self.elements if not e.is_a("IfcFeatureElement") or e.is_a("IfcSurfaceFeature")]
if self.ifc_import_settings.is_coordinating:
self.elements = [e for e in self.elements if e.Representation]
@@ -440,7 +463,9 @@ class IfcImporter:
for element in self.gross_elements:
print(element)
def get_spatial_elements_filtered_by_elements(self, elements):
def get_spatial_elements_filtered_by_elements(
self, elements: set[ifcopenshell.entity_instance]
) -> set[ifcopenshell.entity_instance]:
leaf_spatial_elements = set([ifcopenshell.util.element.get_container(e) for e in elements])
results = set()
for spatial_element in leaf_spatial_elements:
@@ -578,6 +603,9 @@ class IfcImporter:
return products
def predict_dense_mesh(self):
if self.ifc_import_settings.should_use_native_meshes:
return
threshold = 10000 # Just from experience.
faces = [len(e.CfsFaces) for e in self.file.by_type("IfcClosedShell")]
@@ -674,15 +702,17 @@ class IfcImporter:
mat = np.array(
([m[0], m[3], m[6], m[9]], [m[1], m[4], m[7], m[10]], [m[2], m[5], m[8], m[11]], [0, 0, 0, 1])
)
point = np.array(
point = mat @ np.array(
(
shape.geometry.verts[0] / self.unit_scale,
shape.geometry.verts[1] / self.unit_scale,
shape.geometry.verts[2] / self.unit_scale,
shape.geometry.verts[0],
shape.geometry.verts[1],
shape.geometry.verts[2],
0.0,
)
)
return mat @ point
point = point / self.unit_scale
if self.is_point_far_away(point, is_meters=False):
return point
def does_element_likely_have_geometry_far_away(self, element):
for representation in element.Representation.Representations:
@@ -702,7 +732,7 @@ class IfcImporter:
if len(subelement.Coordinates) == 3 and self.is_point_far_away(subelement, is_meters=False):
return True
def apply_blender_offset_to_matrix_world(self, obj, matrix):
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):
@@ -776,7 +806,7 @@ class IfcImporter:
continue
self.create_element_type(element_type)
def create_element_type(self, element):
def create_element_type(self, element: ifcopenshell.entity_instance) -> None:
self.ifc_import_settings.logger.info("Creating object %s", element)
mesh = None
if self.ifc_import_settings.should_load_geometry:
@@ -833,17 +863,22 @@ class IfcImporter:
self.create_product(element, mesh=mesh)
print("Done creating geometry")
def create_spatial_elements(self):
self.create_generic_elements(self.spatial_elements)
def create_spatial_elements(self) -> None:
if bpy.context.preferences.addons["blenderbim"].preferences.spatial_elements_unselectable:
self.create_generic_elements(self.spatial_elements, unselectable=True)
else:
self.create_generic_elements(self.spatial_elements, unselectable=False)
def create_elements(self):
def create_elements(self) -> None:
self.create_generic_elements(self.elements)
tmp = self.context_settings
self.context_settings = self.gross_context_settings
self.create_generic_elements(self.gross_elements)
self.context_settings = tmp
def create_generic_shape(self, element):
def create_generic_shape(
self, element: ifcopenshell.entity_instance
) -> Union[ifcopenshell_wrapper.TriangulationElement, None]:
for settings in self.context_settings:
try:
result = ifcopenshell.geom.create_shape(settings, element)
@@ -852,7 +887,7 @@ class IfcImporter:
except:
pass
def create_generic_elements(self, elements):
def create_generic_elements(self, elements: set[ifcopenshell.entity_instance], unselectable=False) -> None:
if isinstance(self.file, ifcopenshell.sqlite):
return self.create_generic_sqlite_elements(elements)
@@ -866,12 +901,17 @@ class IfcImporter:
elements -= products
total = len(elements)
objects = set()
for i, element in enumerate(elements):
if i % 250 == 0:
print("{} / {} elements processed ...".format(i, total))
self.create_product(element)
objects.add(self.create_product(element))
def create_generic_sqlite_elements(self, elements):
if unselectable:
for obj in objects:
obj.hide_select = True
def create_generic_sqlite_elements(self, elements: set[ifcopenshell.entity_instance]) -> None:
self.geometry_cache = self.file.get_geometry([e.id() for e in elements])
for geometry_id, geometry in self.geometry_cache["geometry"].items():
mesh_name = tool.Loader.get_mesh_name(type("Geometry", (), {"id": geometry_id}))
@@ -918,7 +958,11 @@ class IfcImporter:
mesh = self.meshes.get(mesh_name)
self.create_product(element, mesh=mesh)
def create_products(self, products, settings=None):
def create_products(
self,
products: set[ifcopenshell.entity_instance],
settings: Optional[ifcopenshell.geom.main.settings] = None,
) -> set[ifcopenshell.entity_instance]:
results = set()
if not products:
return results
@@ -1041,7 +1085,7 @@ class IfcImporter:
return mapped_representation
return None
def create_pointclouds(self, products):
def create_pointclouds(self, products: set[ifcopenshell.entity_instance]) -> set[ifcopenshell.entity_instance]:
result = set()
for product in products:
representation = self.get_pointcloud_representation(product)
@@ -1052,7 +1096,9 @@ class IfcImporter:
return result
def create_pointcloud(self, product, representation):
def create_pointcloud(
self, product: ifcopenshell.entity_instance, representation: ifcopenshell.entity_instance
) -> Union[ifcopenshell.entity_instance, None]:
placement_matrix = self.get_element_matrix(product)
vertex_list = []
for item in representation.Items:
@@ -1080,7 +1126,12 @@ class IfcImporter:
self.link_element(product, obj)
return product
def create_product(self, element, shape=None, mesh=None):
def create_product(
self,
element: ifcopenshell.entity_instance,
shape: Optional[Any] = None,
mesh: Optional[OBJECT_DATA_TYPE] = None,
) -> Union[bpy.types.Object, None]:
if element is None:
return
@@ -1125,6 +1176,9 @@ class IfcImporter:
return obj
def load_existing_meshes(self) -> None:
self.meshes.update({m.name: m for m in bpy.data.meshes})
def get_representation_item_material_name(self, item):
if not item.StyledByItem:
return
@@ -1446,7 +1500,10 @@ class IfcImporter:
# Occurs when reloading a project
pass
project_collection = bpy.context.view_layer.layer_collection.children[self.project["blender"].name]
project_collection.children[self.type_collection.name].hide_viewport = True
types_collection = project_collection.children[self.type_collection.name]
types_collection.hide_viewport = False
for obj in types_collection.collection.objects: # turn off all objects inside Types collection.
obj.hide_set(True)
def clean_mesh(self):
obj = None
@@ -1531,18 +1588,19 @@ class IfcImporter:
"{}/{}".format(self.project["ifc"].is_a(), self.project["ifc"].Name)
)
obj = self.create_product(self.project["ifc"])
obj.hide_select = True
self.project["blender"].objects.link(obj)
self.project["blender"].BIMCollectionProperties.obj = obj
obj.BIMObjectProperties.collection = self.collections[project.GlobalId] = self.project["blender"]
def create_collections(self):
def create_collections(self) -> None:
self.create_spatial_decomposition_collections()
if self.ifc_import_settings.collection_mode == "DECOMPOSITION":
self.create_aggregate_and_nest_collections()
elif self.ifc_import_settings.collection_mode == "SPATIAL_DECOMPOSITION":
pass
def create_spatial_decomposition_collections(self):
def create_spatial_decomposition_collections(self) -> None:
for rel_aggregate in self.project["ifc"].IsDecomposedBy or []:
self.create_spatial_decomposition_collection(self.project["blender"], rel_aggregate.RelatedObjects)
@@ -1555,7 +1613,9 @@ class IfcImporter:
tool.Loader.create_project_collection("Views")
self.type_collection = tool.Loader.create_project_collection("Types")
def create_spatial_decomposition_collection(self, parent, related_objects):
def create_spatial_decomposition_collection(
self, parent: Union[bpy.types.Collection, bpy.types.Object], related_objects: list[ifcopenshell.entity_instance]
) -> None:
for element in related_objects:
if element not in self.spatial_elements:
continue
@@ -1578,26 +1638,34 @@ class IfcImporter:
if self.ifc_import_settings.has_filter:
rel_aggregates = set()
for element in self.elements:
if element.IsDecomposedBy:
rel_aggregates.add(element.IsDecomposedBy[0])
elif element.Decomposes:
rel_aggregates.add(element.Decomposes[0])
elif getattr(element, "IsNestedBy", []): # IFC2X3 does not have IsNestedBy
if [e for e in element.IsNestedBy[0].RelatedObjects if not e.is_a("IfcPort")]:
rel_aggregates.add(element.IsNestedBy[0])
elif getattr(element, "Nests", []):
rel_aggregates.add(element.Nests[0])
if decomposed_by := element.IsDecomposedBy:
rel_aggregates.add(decomposed_by[0])
elif decomposes := element.Decomposes:
rel_aggregates.add(decomposes[0])
elif nested_by := getattr(element, "IsNestedBy", []): # IFC2X3 does not have IsNestedBy
if next((e for e in nested_by[0].RelatedObjects if not e.is_a("IfcPort")), None):
rel_aggregates.add(nested_by[0])
elif nests := getattr(element, "Nests", []):
rel_aggregates.add(nests[0])
elif element.is_a("IfcSurfaceFeature") and self.file.schema == "IFC4X3":
rel_aggregates.add(element.AdheresToElement[0])
else:
rel_aggregates = [
r
for r in self.file.by_type("IfcRelAggregates")
if r.RelatingObject.is_a("IfcElement") or r.RelatingObject.is_a("IfcElementType")
if (relating_obj := r.RelatingObject).is_a("IfcElement") or relating_obj.is_a("IfcElementType")
] + [
r
for r in self.file.by_type("IfcRelNests")
if (r.RelatingObject.is_a("IfcElement") or r.RelatingObject.is_a("IfcElementType"))
if (
(relating_obj := r.RelatingObject).is_a("IfcElement")
or relating_obj.is_a("IfcElementType")
or (relating_obj.is_a("IfcPositioningElement") and not relating_obj.is_a("IfcGrid"))
)
and [e for e in r.RelatedObjects if not e.is_a("IfcPort")]
]
if self.file.schema == "IFC4X3":
rel_aggregates += [r for r in self.file.by_type("IfcRelAdheresToElement")]
if len(rel_aggregates) > 10000:
# More than 10,000 collections makes Blender unhappy
@@ -1605,9 +1673,11 @@ class IfcImporter:
self.ifc_import_settings.collection_mode = "SPATIAL_DECOMPOSITION"
return
aggregates = {}
aggregates: dict[str, dict] = {}
for rel_aggregate in rel_aggregates:
element = rel_aggregate.RelatingObject
element: ifcopenshell.entity_instance = getattr(rel_aggregate, "RelatingObject", None) or getattr(
rel_aggregate, "RelatingElement"
)
collection = bpy.data.collections.new(tool.Loader.get_name(element))
aggregates[element.GlobalId] = {"element": element, "collection": collection}
self.collections[element.GlobalId] = collection
@@ -1659,7 +1729,9 @@ class IfcImporter:
continue
self.create_style(style)
def create_style(self, style, blender_material=None):
def create_style(
self, style: ifcopenshell.entity_instance, blender_material: Optional[bpy.types.Material] = None
) -> None:
if not blender_material:
name = style.Name or str(style.id())
blender_material = bpy.data.materials.new(name)
@@ -1676,18 +1748,20 @@ class IfcImporter:
else:
blender_material.BIMStyleProperties.active_style_type = "Shading"
def place_objects_in_collections(self):
def place_objects_in_collections(self) -> None:
for ifc_definition_id, obj in self.added_data.items():
if isinstance(obj, bpy.types.Object):
self.place_object_in_collection(self.file.by_id(ifc_definition_id), obj)
def place_object_in_collection(self, element, obj):
def place_object_in_collection(self, element: ifcopenshell.entity_instance, obj: bpy.types.Object) -> None:
if self.ifc_import_settings.collection_mode == "DECOMPOSITION":
self.place_object_in_decomposition_collection(element, obj)
elif self.ifc_import_settings.collection_mode == "SPATIAL_DECOMPOSITION":
self.place_object_in_spatial_decomposition_collection(element, obj)
def place_object_in_decomposition_collection(self, element, obj):
def place_object_in_decomposition_collection(
self, element: ifcopenshell.entity_instance, obj: bpy.types.Object
) -> None:
if element.is_a("IfcProject"):
return
elif element.is_a("IfcGridAxis"):
@@ -1704,10 +1778,15 @@ class IfcImporter:
elif getattr(element, "Nests", None) and not element.is_a("IfcPort"):
nest = ifcopenshell.util.element.get_nest(element)
return self.collections[nest.GlobalId].objects.link(obj)
else:
return self.place_object_in_spatial_decomposition_collection(element, obj)
elif element.is_a("IfcSurfaceFeature") and self.file.schema == "IFC4X3":
adherend = element.AdheresToElement[0].RelatingElement
return self.collections[adherend.GlobalId].objects.link(obj)
def place_object_in_spatial_decomposition_collection(self, element, obj):
return self.place_object_in_spatial_decomposition_collection(element, obj)
def place_object_in_spatial_decomposition_collection(
self, element: ifcopenshell.entity_instance, obj: bpy.types.Object
) -> None:
if element.is_a("IfcProject"):
return
elif element.is_a("IfcGridAxis"):
@@ -1751,7 +1830,7 @@ class IfcImporter:
if rel.is_a("IfcRelAssignsToGroup") and rel.RelatingGroup.ObjectType == "DRAWING":
return rel.RelatingGroup
def get_element_matrix(self, element):
def get_element_matrix(self, element: ifcopenshell.entity_instance) -> np.ndarray:
if isinstance(element, ifcopenshell.sqlite_entity):
result = self.geometry_cache["shapes"][element.id()]["matrix"]
else:
@@ -1761,7 +1840,7 @@ class IfcImporter:
result[2][3] *= self.unit_scale
return result
def scale_matrix(self, matrix):
def scale_matrix(self, matrix: np.array) -> np.array:
matrix[0][3] *= self.unit_scale
matrix[1][3] *= self.unit_scale
matrix[2][3] *= self.unit_scale
@@ -1793,7 +1872,7 @@ class IfcImporter:
):
return representation.Items[0].MappingTarget
def create_curve(self, element, shape):
def create_curve(self, element: ifcopenshell.entity_instance, shape) -> bpy.types.Curve:
if hasattr(shape, "geometry"):
geometry = shape.geometry
else:
@@ -1818,12 +1897,13 @@ class IfcImporter:
polyline.points[-1].co = mathutils.Vector(v2)
return curve
def create_mesh(self, element, shape):
def create_mesh(self, element: ifcopenshell.entity_instance, shape) -> bpy.types.Mesh:
try:
if hasattr(shape, "geometry"):
geometry = shape.geometry
# shape is ifcopenshell_wrapper.TriangulationElement
geometry: ifcopenshell_wrapper.Triangulation = shape.geometry
else:
geometry = shape
geometry: ifcopenshell_wrapper.Triangulation = shape
mesh = bpy.data.meshes.new(tool.Loader.get_mesh_name(geometry))
@@ -1833,11 +1913,7 @@ class IfcImporter:
and geometry.verts
and self.is_point_far_away((geometry.verts[0], geometry.verts[1], geometry.verts[2]))
):
m = shape.transformation.matrix.data
mat = np.array(
([m[0], m[3], m[6], m[9]], [m[1], m[4], m[7], m[10]], [m[2], m[5], m[8], m[11]], [0, 0, 0, 1])
)
offset_point = np.linalg.inv(mat) @ np.array(
offset_point = np.array(
(
float(props.blender_eastings),
float(props.blender_northings),
@@ -1845,8 +1921,15 @@ class IfcImporter:
0.0,
)
)
if geometry != shape:
m = shape.transformation.matrix.data
mat = np.array(
([m[0], m[3], m[6], m[9]], [m[1], m[4], m[7], m[10]], [m[2], m[5], m[8], m[11]], [0, 0, 0, 1])
)
offset_point = np.linalg.inv(mat) @ offset_point
verts = [None] * len(geometry.verts)
for i in range(0, len(geometry.verts), 3):
# Note: this enh2xyz call is crazy slow.
verts[i], verts[i + 1], verts[i + 2] = ifcopenshell.util.geolocation.enh2xyz(
geometry.verts[i],
geometry.verts[i + 1],
@@ -1872,7 +1955,9 @@ class IfcImporter:
# See bug 3546
# ios_edges holds true edges that aren't triangulated.
mesh["ios_edges"] = list(set(tuple(e) for e in ifcopenshell.util.shape.get_edges(geometry)))
#
# we do `.tolist()` because Blender can't assign `np.int32` to it's custom attributes
mesh["ios_edges"] = list(set(tuple(e) for e in ifcopenshell.util.shape.get_edges(geometry).tolist()))
mesh.vertices.add(num_vertices)
mesh.vertices.foreach_set("co", verts)
@@ -1899,14 +1984,14 @@ class IfcImporter:
print(traceback.format_exc())
def a2p(self, o, z, x):
def a2p(self, o: mathutils.Vector, z: mathutils.Vector, x: mathutils.Vector) -> mathutils.Matrix:
y = z.cross(x)
r = mathutils.Matrix((x, y, z, o))
r.resize_4x4()
r.transpose()
return r
def get_axis2placement(self, plc):
def get_axis2placement(self, plc: ifcopenshell.entity_instance) -> mathutils.Matrix:
if plc.is_a("IfcAxis2Placement3D"):
z = mathutils.Vector(plc.Axis.DirectionRatios if plc.Axis else (0, 0, 1))
x = mathutils.Vector(plc.RefDirection.DirectionRatios if plc.RefDirection else (1, 0, 0))
@@ -1926,7 +2011,7 @@ class IfcImporter:
o = plc.LocalOrigin.Coordinates
return self.a2p(o, z, x)
def get_local_placement(self, plc):
def get_local_placement(self, plc: Optional[ifcopenshell.entity_instance] = None) -> mathutils.Matrix:
if plc is None:
return mathutils.Matrix()
if plc.PlacementRelTo is None:
@@ -1941,11 +2026,11 @@ class IfcImporter:
bpy.context.scene.BIMRootProperties.contexts = str(subcontext.id())
break
def link_element(self, element, obj):
def link_element(self, element: ifcopenshell.entity_instance, obj: IFC_CONNECTED_TYPE) -> None:
self.added_data[element.id()] = obj
tool.Ifc.link(element, obj)
def set_matrix_world(self, obj, matrix_world):
def set_matrix_world(self, obj: bpy.types.Object, matrix_world: mathutils.Matrix) -> None:
obj.matrix_world = matrix_world
tool.Geometry.record_object_position(obj)
@@ -1968,7 +2053,7 @@ class IfcImporter:
class IfcImportSettings:
def __init__(self):
self.logger = None
self.logger: logging.Logger = None
self.input_file = None
self.diff_file = None
self.should_use_cpu_multiprocessing = True
@@ -1989,7 +2074,7 @@ class IfcImportSettings:
self.has_filter = None
self.should_filter_spatial_elements = True
self.should_setup_viewport_camera = True
self.elements = set()
self.elements: set[ifcopenshell.entity_instance] = set()
self.collection_mode = "DECOMPOSITION"
@staticmethod
@@ -44,7 +44,12 @@ class BIM_OT_aggregate_assign_object(bpy.types.Operator, Operator):
def _execute(self, context):
relating_obj = None
if self.relating_object:
relating_obj = tool.Ifc.get_object(tool.Ifc.get().by_id(self.relating_object))
element = tool.Ifc.get().by_id(self.relating_object)
if element.IsDecomposedBy:
relating_obj = tool.Ifc.get_object(element)
else:
assembly = element.Decomposes[0].RelatingObject
relating_obj = tool.Ifc.get_object(assembly)
elif context.active_object:
relating_obj = context.active_object
if not relating_obj:
@@ -89,14 +94,15 @@ class BIM_OT_aggregate_unassign_object(bpy.types.Operator, Operator):
relating_obj=tool.Ifc.get_object(aggregate),
related_obj=tool.Ifc.get_object(element),
)
# Removes Pset related to Linked Aggregates
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)
if not element.is_a('IfcElementAssembly'):
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)
class BIM_OT_enable_editing_aggregate(bpy.types.Operator, Operator):
"""Enable editing aggregation relationship"""
@@ -127,6 +133,7 @@ class BIM_OT_add_aggregate(bpy.types.Operator, tool.Ifc.Operator):
bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
ifc_class: bpy.props.StringProperty(name="IFC Class", default="IfcElementAssembly")
aggregate_name: bpy.props.StringProperty(name="Name", default="Default_Name")
def invoke(self, context, event):
return context.window_manager.invoke_props_dialog(self)
@@ -134,13 +141,15 @@ class BIM_OT_add_aggregate(bpy.types.Operator, tool.Ifc.Operator):
def draw(self, context):
row = self.layout
row.prop(self, "ifc_class")
row = self.layout
row.prop(self, "aggregate_name")
def _execute(self, context):
try:
ifc_class = tool.Ifc.schema().declaration_by_name(self.ifc_class).name()
except:
return
aggregate = self.create_aggregate(context, ifc_class)
aggregate = self.create_aggregate(context, ifc_class, self.aggregate_name)
for obj in context.selected_objects:
element = tool.Ifc.get_entity(obj)
@@ -168,8 +177,8 @@ class BIM_OT_add_aggregate(bpy.types.Operator, tool.Ifc.Operator):
)
core.assign_object(tool.Ifc, tool.Aggregate, tool.Collector, relating_obj=aggregate, related_obj=obj)
def create_aggregate(self, context, ifc_class):
aggregate = bpy.data.objects.new("Assembly", None)
def create_aggregate(self, context, ifc_class, aggregate_name):
aggregate = bpy.data.objects.new(aggregate_name, None)
aggregate.location = context.scene.cursor.location
bpy.ops.bim.assign_class(obj=aggregate.name, ifc_class=ifc_class)
return aggregate
@@ -185,12 +194,17 @@ class BIM_OT_select_parts(bpy.types.Operator):
def execute(self, context):
self.file = IfcStore.get_file()
obj = bpy.data.objects.get(self.obj) or context.active_object
parts = ifcopenshell.util.element.get_parts(tool.Ifc.get_entity(obj))
parts_objs = set(tool.Ifc.get_object(part) for part in parts)
selectable_parts_objs = set(context.selectable_objects).intersection(parts_objs)
for selectable_part_obj in selectable_parts_objs:
selectable_part_obj.select_set(True)
# obj = bpy.data.objects.get(self.obj) or context.active_object
for obj in context.selected_objects:
parts = ifcopenshell.util.element.get_parts(tool.Ifc.get_entity(obj))
if parts:
parts_objs = set(tool.Ifc.get_object(part) for part in parts)
selectable_parts_objs = set(context.selectable_objects).intersection(parts_objs)
for selectable_part_obj in selectable_parts_objs:
selectable_part_obj.select_set(True)
else:
obj.select_set(False)
return {"FINISHED"}
@@ -201,15 +215,54 @@ class BIM_OT_select_aggregate(bpy.types.Operator):
bl_label = "Select Aggregate"
bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
select_parts: bpy.props.BoolProperty(default=False)
@classmethod
def description(cls, context, properties):
if properties.select_parts:
return "Select Aggregate and Parts"
else:
return "Select Aggregate"
def execute(self, context):
self.file = IfcStore.get_file()
obj = bpy.data.objects.get(self.obj) or context.active_object
aggregate = ifcopenshell.util.element.get_aggregate(tool.Ifc.get_entity(obj))
aggregate_obj = tool.Ifc.get_object(aggregate)
if aggregate_obj in context.selectable_objects:
aggregate_obj.select_set(True)
bpy.context.view_layer.objects.active = aggregate_obj
# obj = bpy.data.objects.get(self.obj) or context.active_object
# aggregate = ifcopenshell.util.element.get_aggregate(tool.Ifc.get_entity(obj))
# aggregate_obj = tool.Ifc.get_object(aggregate)
all_parts = []
for obj in context.selected_objects:
element = tool.Ifc.get_entity(obj)
if element:
aggregate = ifcopenshell.util.element.get_aggregate(element)
if aggregate:
all_parts.append(aggregate)
obj.select_set(False)
else:
pass
if not element:
obj.select_set(False)
if self.select_parts:
all_objs = []
for part in all_parts:
if part.IsDecomposedBy:
for subpart in part.IsDecomposedBy[0].RelatedObjects:
all_parts.append(subpart)
all_objs.append(part)
for element in all_objs:
obj = tool.Ifc.get_object(element)
if obj:
obj.select_set(True)
else:
for aggregate_element in all_parts:
aggregate_obj = tool.Ifc.get_object(aggregate_element)
aggregate_obj.select_set(True)
bpy.context.view_layer.objects.active = aggregate_obj
return {"FINISHED"}
@@ -237,6 +290,7 @@ class BIM_OT_add_part_to_object(bpy.types.Operator, Operator):
part_name=self.part_name,
)
class BIM_OT_break_link_to_other_aggregates(bpy.types.Operator, Operator):
bl_idname = "bim.break_link_to_other_aggregates"
bl_label = "Break link to other aggregates"
@@ -251,48 +305,72 @@ class BIM_OT_break_link_to_other_aggregates(bpy.types.Operator, Operator):
return []
parts = ifcopenshell.util.element.get_parts(aggregate)
for part in parts:
pset = ifcopenshell.util.element.get_pset(part, 'BBIM_Linked_Aggregate')
pset = ifcopenshell.util.element.get_pset(part, "BBIM_Linked_Aggregate")
pset = tool.Ifc.get().by_id(pset["id"])
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), pset=pset)
linked_aggregate_group = [
r.RelatingGroup
for r in getattr(aggregate, "HasAssignments", []) or []
if r.is_a("IfcRelAssignsToGroup")
if "BBIM_Linked_Aggregate" in r.RelatingGroup.Name
]
tool.Ifc.run("group.unassign_group", group=linked_aggregate_group[0], product=aggregate)
tool.Ifc.run("group.unassign_group", group=linked_aggregate_group[0], products=[aggregate])
return {"FINISHED"}
class BIM_OT_select_linked_aggregates(bpy.types.Operator, Operator):
bl_idname = "bim.select_linked_aggregates"
bl_label = "Select linked aggregates"
bl_options = {"REGISTER", "UNDO"}
select_parts: bpy.props.BoolProperty(default=False)
@classmethod
def description(cls, context, properties):
if properties.select_parts:
return "Select all aggregates, subaggregates and all their parts"
else:
return "Select all aggregates"
def _execute(self, context):
element = tool.Ifc.get_entity(bpy.context.active_object)
aggregate = ifcopenshell.util.element.get_aggregate(element)
if not aggregate:
return []
if not element:
return []
linked_aggregate_group = [
r.RelatingGroup
for r in getattr(aggregate, "HasAssignments", []) or []
if r.is_a("IfcRelAssignsToGroup")
if "BBIM_Linked_Aggregate" in r.RelatingGroup.Name
]
for obj in context.selected_objects:
obj.select_set(False)
for rel in linked_aggregate_group[0].IsGroupedBy or []:
for element in rel.RelatedObjects:
obj = tool.Ifc.get_object(element)
obj.select_set(True)
element = tool.Ifc.get_entity(obj)
aggregate = ifcopenshell.util.element.get_aggregate(element)
if not aggregate:
continue
if not element:
continue
linked_aggregate_group = [
r.RelatingGroup
for r in getattr(aggregate, "HasAssignments", []) or []
if r.is_a("IfcRelAssignsToGroup")
if "BBIM_Linked_Aggregate" in r.RelatingGroup.Name
]
group_rel = linked_aggregate_group[0].IsGroupedBy or []
for group_link in group_rel:
parts = list(group_link.RelatedObjects)
if self.select_parts:
parts_objs = []
for part in parts:
if part.IsDecomposedBy:
for subpart in part.IsDecomposedBy[0].RelatedObjects:
parts.append(subpart)
parts_objs.append(part)
for element in parts_objs:
obj = tool.Ifc.get_object(element)
if obj:
obj.select_set(True)
else:
for element in parts:
obj = tool.Ifc.get_object(element)
obj.select_set(True)
return {"FINISHED"}
@@ -20,6 +20,7 @@ from bpy.types import Panel
from blenderbim.bim.module.aggregate.data import AggregateData
from blenderbim.bim.module.group.data import GroupsData, ObjectGroupsData
from blenderbim.bim.ifc import IfcStore
import blenderbim.tool as tool
class BIM_PT_aggregate(Panel):
@@ -62,13 +63,17 @@ class BIM_PT_aggregate(Panel):
row = layout.row(align=True)
if AggregateData.data["has_relating_object"]:
row.label(text=AggregateData.data["relating_object_label"], icon="TRIA_UP")
op = row.operator("bim.select_aggregate", icon="OBJECT_DATA", text="")
op.obj = context.active_object.name
op.select_parts = False
op = row.operator("bim.select_aggregate", icon="RESTRICT_SELECT_OFF", text="")
op.obj = context.active_object.name
op.select_parts = True
row.operator("bim.enable_editing_aggregate", icon="GREASEPENCIL", text="")
row.operator("bim.add_aggregate", icon="ADD", text="")
op = row.operator("bim.aggregate_unassign_object", icon="X", text="")
else:
row.label(text="No Aggregate", icon="TRIA_UP")
row.label(text="No Whole relation defined", icon="TRIA_UP")
row.operator("bim.enable_editing_aggregate", icon="GREASEPENCIL", text="")
row.operator("bim.add_aggregate", icon="ADD", text="")
@@ -127,19 +132,29 @@ class BIM_PT_linked_aggregate(Panel):
if not AggregateData.is_loaded:
AggregateData.load()
props = context.active_object.BIMObjectAggregateProperties
obj = context.active_object
element = tool.Ifc.get_entity(obj)
props = obj.BIMObjectAggregateProperties
row = layout.row(align=True)
row.label(text="Advanced Users Only", icon="ERROR")
row = layout.row(align=True)
if type(AggregateData.data['total_linked_aggregate']) is int:
if AggregateData.data['total_linked_aggregate'] > 0:
row.label(text=f"{AggregateData.data['total_linked_aggregate']} Linked Aggregates")
row.operator("bim.select_linked_aggregates", text="", icon="RESTRICT_SELECT_OFF")
row.operator("bim.refresh_linked_aggregate", text="", icon="FILE_REFRESH")
op = row.operator("bim.break_link_to_other_aggregates", text="", icon="X")
if element.Decomposes:
Number_Linked_Aggregates = AggregateData.data['total_linked_aggregate']
if not Number_Linked_Aggregates:
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")
if type(Number_Linked_Aggregates) is int:
if Number_Linked_Aggregates > 0:
op = row.operator("bim.select_linked_aggregates", text="", icon="OUTLINER_DATA_POINTCLOUD")
op.select_parts = False
op = row.operator("bim.select_linked_aggregates", text="", icon="OUTLINER_OB_POINTCLOUD")
op.select_parts = True
row.operator("bim.refresh_linked_aggregate", text="", icon="FILE_REFRESH")
op = row.operator("bim.break_link_to_other_aggregates", text="", icon="X")
else:
row.label(text="No Linked Aggregates")
row.label(text="Not an Aggregate")
@@ -20,6 +20,7 @@ import bpy
import json
import ifcopenshell
import ifcopenshell.api
import ifcopenshell.guid
import blenderbim.bim.helper
import blenderbim.bim.handler
import blenderbim.tool as tool
@@ -47,10 +48,18 @@ class EnableEditingAttributes(bpy.types.Operator):
obj = bpy.data.objects.get(self.obj)
elif self.obj_type == "Material":
obj = bpy.data.materials.get(self.obj)
oprops = obj.BIMObjectProperties
props = obj.BIMAttributeProperties
props.attributes.clear()
element = tool.Ifc.get_entity(obj)
has_inherited_predefined_type = False
if not element.is_a("IfcTypeObject") and (element_type := ifcopenshell.util.element.get_type(element)):
# Allow for None due to https://github.com/buildingSMART/IFC4.3.x-development/issues/818
has_inherited_predefined_type = ifcopenshell.util.element.get_predefined_type(element_type) not in (
"NOTDEFINED",
None,
)
def callback(name, prop, data):
if name in ("RefLatitude", "RefLongitude"):
new = props.attributes.add()
@@ -62,10 +71,11 @@ class EnableEditingAttributes(bpy.types.Operator):
new.string_value = "" if new.is_null else json.dumps(data[name])
blenderbim.bim.helper.add_attribute_description(new)
new.description += " The degrees, minutes and seconds should follow this format : [12,34,56]"
if name in ("PredefinedType", "ObjectType") and has_inherited_predefined_type:
props.attributes.remove(len(props.attributes) - 1)
return True
blenderbim.bim.helper.import_attributes2(
tool.Ifc.get().by_id(oprops.ifc_definition_id), props.attributes, callback=callback
)
blenderbim.bim.helper.import_attributes2(element, props.attributes, callback=callback)
props.is_editing_attributes = True
return {"FINISHED"}
@@ -120,20 +130,46 @@ class EditAttributes(bpy.types.Operator, Operator):
return {"FINISHED"}
class GenerateGlobalId(bpy.types.Operator):
class GenerateGlobalId(bpy.types.Operator, Operator):
bl_idname = "bim.generate_global_id"
bl_label = "Regenerate GlobalId"
bl_description = "Regenerate GlobalId\n\nSHIFT+CLICK to regenerate GlobalIds for all selected objects"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
index = context.active_object.BIMAttributeProperties.attributes.find("GlobalId")
if index >= 0:
global_id = context.active_object.BIMAttributeProperties.attributes[index]
use_selected: bpy.props.BoolProperty(name="Use All Selected Objects", default=False, options={"SKIP_SAVE"})
def invoke(self, context, event):
# using all selected objects on shift+click
# make sure to use SKIP_SAVE on property, otherwise it might get stuck
if event.type == "LEFTMOUSE" and event.shift:
self.use_selected = True
return self.execute(context)
def _execute(self, context):
if self.use_selected:
for obj in context.selected_objects:
element = tool.Ifc.get_entity(obj)
if not element or not element.is_a("IfcRoot"):
continue
element.GlobalId = ifcopenshell.guid.new()
obj = context.active_object
if not obj or not obj.BIMAttributeProperties.is_editing_attributes:
return {"FINISHED"}
props = obj.BIMAttributeProperties
element = tool.Ifc.get_entity(obj)
if not element.is_a("IfcRoot"):
return {"FINISHED"}
if self.use_selected and obj in context.selected_objects:
# guid value was already regenerated, just update the ui prop
guid_value = element.GlobalId
else:
global_id = context.active_object.BIMAttributeProperties.attributes.add()
global_id.name = "GlobalId"
global_id.data_type = "string"
global_id.string_value = ifcopenshell.guid.new()
guid_value = ifcopenshell.guid.new()
props.attributes["GlobalId"].string_value = guid_value
return {"FINISHED"}
@@ -22,6 +22,7 @@ from . import ui, operator, prop
classes = (
operator.AddBoundary,
operator.ColourByRelatedBuildingElement,
operator.DecorateBoundaries,
operator.DisableEditingBoundary,
operator.DisableEditingBoundaryGeometry,
operator.EditBoundaryAttributes,
@@ -20,10 +20,12 @@ import bpy
import bmesh
import logging
import shapely
import shapely.ops
import mathutils
import numpy as np
import multiprocessing
import ifcopenshell.api
import ifcopenshell.geom
import ifcopenshell.util.unit
import ifcopenshell.util.shape
import ifcopenshell.util.element
@@ -37,6 +39,7 @@ from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.module.model.decorator import ProfileDecorator
from blenderbim.bim.module.boundary.decorator import BoundaryDecorator
import blenderbim.core
import blenderbim.core.geometry
def get_boundaries_collection(blender_space):
@@ -147,7 +150,9 @@ class Loader:
self.ifc_importer = import_ifc.IfcImporter(ifc_import_settings)
self.ifc_importer.file = self.ifc_file
def load_boundary(self, boundary, blender_space=None):
def load_boundary(
self, boundary: ifcopenshell.entity_instance, blender_space: bpy.types.Object = None
) -> bpy.types.Object:
if not blender_space:
if not boundary.RelatingSpace:
self.operator.report(
@@ -509,10 +514,10 @@ class DisableEditingBoundaryGeometry(bpy.types.Operator, tool.Ifc.Operator):
class ShowBoundaries(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.show_boundaries"
bl_label = "Show Boundaries"
bl_description = "Load selected objects boundaries if they are not loaded"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
props = bpy.context.scene.BIMBoundaryProperties
loader = Loader(self)
for obj in context.selected_objects:
element = tool.Ifc.get_entity(obj)
@@ -524,7 +529,7 @@ class ShowBoundaries(bpy.types.Operator, tool.Ifc.Operator):
for rel in element.BoundedBy or []:
boundary_obj = loader.load_boundary(rel, obj)
tool.Boundary.decorate_boundary(boundary_obj)
BoundaryDecorator.install(bpy.context)
BoundaryDecorator.install(context)
return {"FINISHED"}
@@ -557,6 +562,36 @@ class HideBoundaries(bpy.types.Operator, tool.Ifc.Operator):
return {"FINISHED"}
class DecorateBoundaries(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.decorate_boundaries"
bl_label = "Toggle Boundaries Decorations"
bl_description = "Add special decorations for all boundaries in the scene or hide them if they are already added"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
props = context.scene.BIMBoundaryProperties
# filter not decorated boundaries and add decorations for them
decorated_boundaries = set([i.obj for i in props.boundaries])
active_boundaries = set()
for element in tool.Ifc.get().by_type("IfcRelSpaceBoundary"):
obj = tool.Ifc.get_object(element)
if obj is not None:
active_boundaries.add(obj)
boundaries_to_decorate = active_boundaries - decorated_boundaries
if boundaries_to_decorate:
for obj in boundaries_to_decorate:
tool.Boundary.decorate_boundary(obj)
BoundaryDecorator.install(context)
else:
for obj in decorated_boundaries:
tool.Boundary.undecorate_boundary(obj)
props.boundaries.clear()
BoundaryDecorator.uninstall()
tool.Blender.update_viewport()
return {"FINISHED"}
class AddBoundary(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.add_boundary"
bl_label = "Add Boundary"
@@ -37,7 +37,7 @@ classes = (
def register():
if not bpy.app.background:
bpy.utils.register_tool(workspace.CadTool, after={"builtin.transform"}, separator=True, group=True)
bpy.utils.register_tool(workspace.CadTool, after={"builtin.transform"}, separator=True, group=False)
bpy.types.Scene.BIMCadProperties = bpy.props.PointerProperty(type=prop.BIMCadProperties)
@@ -24,10 +24,10 @@ from math import pi
class BIMCadProperties(PropertyGroup):
resolution: bpy.props.IntProperty(name="Arc Resolution", min=1, default=1)
radius: bpy.props.FloatProperty(name="Radius", default=0.1)
distance: bpy.props.FloatProperty(name="Distance", default=0.1)
x: bpy.props.FloatProperty(name="X", default=0.2)
y: bpy.props.FloatProperty(name="Y", default=0.1)
radius: bpy.props.FloatProperty(name="Radius", default=0.1, subtype="DISTANCE")
distance: bpy.props.FloatProperty(name="Distance", default=0.1, subtype="DISTANCE")
x: bpy.props.FloatProperty(name="X", default=0.2, subtype="DISTANCE")
y: bpy.props.FloatProperty(name="Y", default=0.1, subtype="DISTANCE")
gable_roof_edge_angle: bpy.props.FloatProperty(
name="Gable Roof Edge Angle", default=pi / 2, soft_min=0, soft_max=pi / 2, subtype="ANGLE"
)
@@ -20,6 +20,7 @@ import os
import bpy
import blenderbim.tool as tool
import blenderbim.bim.module.type.prop as type_prop
import ifcopenshell.util.unit
from bpy.types import WorkSpaceTool
from blenderbim.bim.module.model.data import AuthoringData, RailingData, RoofData
@@ -239,8 +240,9 @@ class CadHotkey(bpy.types.Operator):
row.prop(props, "resolution")
def hotkey_S_C(self):
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
if self.is_profile():
bpy.ops.bim.add_ifccircle(radius=self.props.radius)
bpy.ops.bim.add_ifccircle(radius=self.props.radius / si_conversion)
else:
bpy.ops.bim.cad_arc_from_2_points()
@@ -248,13 +250,15 @@ class CadHotkey(bpy.types.Operator):
bpy.ops.bim.cad_trim_extend()
def hotkey_S_F(self):
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
if self.is_profile():
bpy.ops.bim.add_ifcarcindex_fillet(radius=self.props.radius)
bpy.ops.bim.add_ifcarcindex_fillet(radius=self.props.radius / si_conversion)
else:
bpy.ops.bim.cad_fillet(resolution=self.props.resolution, radius=self.props.radius)
bpy.ops.bim.cad_fillet(resolution=self.props.resolution, radius=self.props.radius / si_conversion)
def hotkey_S_O(self):
bpy.ops.bim.cad_offset(distance=self.props.distance)
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
bpy.ops.bim.cad_offset(distance=self.props.distance / si_conversion)
def hotkey_S_Q(self):
element = tool.Ifc.get_entity(bpy.context.active_object)
@@ -270,7 +274,8 @@ class CadHotkey(bpy.types.Operator):
def hotkey_S_R(self):
if self.is_profile():
bpy.ops.bim.add_rectangle(x=self.props.x, y=self.props.y)
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
bpy.ops.bim.add_rectangle(x=self.props.x / si_conversion, y=self.props.y / si_conversion)
elif (
(RoofData.is_loaded or not RoofData.load())
and RoofData.data["pset_data"]
@@ -25,15 +25,12 @@ classes = (
operator.ExecuteIfcClash,
operator.ExportClashSets,
operator.ImportClashSets,
operator.LoadIfcClashes,
operator.SaveIfcClashes,
operator.LoadSmartGroupsForActiveClashSet,
operator.RemoveClashSet,
operator.RemoveClashSource,
operator.SelectClash,
operator.SelectClashResults,
operator.SelectClashSource,
operator.SelectIfcClashResults,
operator.SelectSmartGroup,
operator.SelectSmartGroupedClashesPath,
operator.SmartClashGroup,
@@ -44,7 +41,6 @@ classes = (
prop.BIMClashProperties,
ui.BIM_PT_ifcclash,
ui.BIM_PT_clash_manager,
ui.BIM_PT_dumb_clash_manager,
ui.BIM_PT_smart_clash_manager,
ui.BIM_UL_clashes,
ui.BIM_UL_clash_sets,
@@ -0,0 +1,52 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2024 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BlenderBIM Add-on.
#
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BlenderBIM Add-on is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
import json
import ifcopenshell.util.element
import blenderbim.tool as tool
def refresh():
ClashData.is_loaded = False
class ClashData:
data = {}
is_loaded = False
@classmethod
def load(cls):
cls.is_loaded = True
cls.data = {}
cls.data["saved_searches"] = cls.saved_searches()
@classmethod
def saved_searches(cls):
if not tool.Ifc.get():
return []
groups = tool.Ifc.get().by_type("IfcGroup")
results = []
for group in groups:
try:
data = json.loads(group.Description)
if isinstance(data, dict) and data.get("type", None) == "BBIM_Search" and data.get("query", None):
results.append(group)
except:
pass
return [(str(g.id()), g.Name or "Unnamed", "") for g in sorted(results, key=lambda x: x.Name or "Unnamed")]
@@ -0,0 +1,102 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2024 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BlenderBIM Add-on.
#
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BlenderBIM Add-on is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import blf
import gpu
import bmesh
import blenderbim.tool as tool
from bpy.types import SpaceView3D
from mathutils import Vector
from gpu_extras.batch import batch_for_shader
from bpy_extras.view3d_utils import location_3d_to_region_2d
class ClashDecorator:
is_installed = False
handlers = []
@classmethod
def install(cls, context):
if cls.is_installed:
cls.uninstall()
handler = cls()
cls.handlers.append(SpaceView3D.draw_handler_add(handler.draw_text, (context,), "WINDOW", "POST_PIXEL"))
cls.handlers.append(SpaceView3D.draw_handler_add(handler.draw_geometry, (context,), "WINDOW", "POST_VIEW"))
cls.is_installed = True
@classmethod
def uninstall(cls):
for handler in cls.handlers:
try:
SpaceView3D.draw_handler_remove(handler, "WINDOW")
except ValueError:
pass
cls.is_installed = False
def draw_batch(self, shader_type, content_pos, color, indices=None):
shader = self.line_shader if shader_type == "LINES" else self.shader
batch = batch_for_shader(shader, shader_type, {"pos": content_pos}, indices=indices)
shader.uniform_float("color", color)
batch.draw(shader)
def draw_text(self, context):
self.addon_prefs = context.preferences.addons["blenderbim"].preferences
selected_elements_color = self.addon_prefs.decorator_color_selected
unselected_elements_color = self.addon_prefs.decorator_color_unselected
special_elements_color = self.addon_prefs.decorator_color_special
text = context.scene.BIMClashProperties.active_clash_text
p = context.scene.BIMClashProperties.p1.lerp(context.scene.BIMClashProperties.p2, 0.5)
font_id = 0
blf.size(font_id, 12)
coords_2d = location_3d_to_region_2d(context.region, context.region_data, p)
color = self.addon_prefs.decorations_colour
blf.color(font_id, *color)
if coords_2d:
w, h = blf.dimensions(font_id, text)
coords_2d -= Vector((w * .5, 0))
blf.position(font_id, coords_2d[0], coords_2d[1], 0)
blf.draw(font_id, text) # Set your text here
def draw_geometry(self, context):
self.addon_prefs = context.preferences.addons["blenderbim"].preferences
selected_elements_color = self.addon_prefs.decorator_color_selected
unselected_elements_color = self.addon_prefs.decorator_color_unselected
special_elements_color = self.addon_prefs.decorator_color_special
gpu.state.point_size_set(6)
gpu.state.blend_set("ALPHA")
self.line_shader = gpu.shader.from_builtin("POLYLINE_UNIFORM_COLOR")
self.line_shader.bind() # required to be able to change uniforms of the shader
# POLYLINE_UNIFORM_COLOR specific uniforms
self.line_shader.uniform_float("viewportSize", (context.region.width, context.region.height))
self.line_shader.uniform_float("lineWidth", 2.0)
# general shader
self.shader = gpu.shader.from_builtin("UNIFORM_COLOR")
selected_vertices = [context.scene.BIMClashProperties.p1, context.scene.BIMClashProperties.p2]
selected_edges = []
if selected_vertices[0] != selected_vertices[1]:
selected_edges = [[0, 1]]
self.draw_batch("POINTS", selected_vertices, special_elements_color)
if selected_edges:
self.draw_batch("LINES", selected_vertices, special_elements_color, selected_edges)
@@ -23,10 +23,11 @@ import bmesh
import logging
import numpy as np
import ifcopenshell
from mathutils import Matrix, Vector
from math import radians
from blenderbim.bim.ifc import IfcStore
import blenderbim.tool as tool
from math import radians
from mathutils import Matrix, Vector
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.module.clash.decorator import ClashDecorator
class ExportClashSets(bpy.types.Operator):
@@ -45,18 +46,7 @@ class ExportClashSets(bpy.types.Operator):
def execute(self, context):
self.filepath = bpy.path.ensure_ext(self.filepath, ".json")
clash_sets = []
for clash_set in context.scene.BIMClashProperties.clash_sets:
self.a = []
self.b = []
for ab in ["a", "b"]:
for data in getattr(clash_set, ab):
clash_source = {"file": data.name}
if data.selector:
clash_source["selector"] = data.selector
clash_source["mode"] = data.mode
getattr(self, ab).append(clash_source)
clash_sets.append({"name": clash_set.name, "tolerance": clash_set.tolerance, "a": self.a, "b": self.b})
clash_sets = tool.Clash.export_clash_sets()
with open(self.filepath, "w") as destination:
destination.write(json.dumps(clash_sets, indent=4))
return {"FINISHED"}
@@ -78,25 +68,34 @@ class ImportClashSets(bpy.types.Operator):
return {"RUNNING_MODAL"}
def execute(self, context):
with open(self.filepath) as f:
clash_sets = json.load(f)
for clash_set in clash_sets:
tool.Clash.load_clash_sets(self.filepath)
context.scene.BIMClashProperties.clash_sets.clear()
for clash_set in tool.Clash.get_clash_sets():
new = context.scene.BIMClashProperties.clash_sets.add()
new.name = clash_set["name"]
new.tolerance = clash_set["tolerance"]
new.mode = clash_set["mode"]
if new.mode == "intersection":
new.tolerance = clash_set["tolerance"]
new.check_all = clash_set["check_all"]
elif new.mode == "collision":
new.allow_touching = clash_set["allow_touching"]
elif new.mode == "clearance":
new.clearance = clash_set["clearance"]
new.check_all = clash_set["check_all"]
for clash_source in clash_set["a"]:
new_source = new.a.add()
new_source.name = clash_source["file"]
if "selector" in clash_source:
new_source.selector = clash_source["selector"]
tool.Search.import_filter_query(clash_source["selector"], new_source.filter_groups)
new_source.mode = clash_source["mode"]
if clash_set["b"]:
if "b" in clash_set and clash_set["b"]:
for clash_source in clash_set["b"]:
new_source = new.b.add()
new_source.name = clash_source["file"]
if "selector" in clash_source:
new_source.selector = clash_source["selector"]
tool.Search.import_filter_query(clash_source["selector"], new_source.filter_groups)
new_source.mode = clash_source["mode"]
tool.Clash.import_active_clashes()
return {"FINISHED"}
@@ -109,7 +108,6 @@ class AddClashSet(bpy.types.Operator):
def execute(self, context):
new = context.scene.BIMClashProperties.clash_sets.add()
new.name = "New Clash Set"
new.tolerance = 0.01
return {"FINISHED"}
@@ -211,8 +209,11 @@ class ExecuteIfcClash(bpy.types.Operator):
def invoke(self, context, event):
_, extension = os.path.splitext(self.filepath)
if extension != ".json":
self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".bcf")
if extension != ".bcf":
self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".json")
# TODO Temporarily until BCF support comes back
# if extension != ".json":
# self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".bcf")
WindowManager = context.window_manager
WindowManager.fileselect_add(self)
return {"RUNNING_MODAL"}
@@ -220,7 +221,12 @@ class ExecuteIfcClash(bpy.types.Operator):
def execute(self, context):
from ifcclash import ifcclash
self.props = context.scene.BIMClashProperties
_, extension = os.path.splitext(self.filepath)
if extension != ".bcf":
self.filepath = bpy.path.ensure_ext(self.filepath, ".json")
# TODO Temporarily until BCF support comes back
if extension != ".json":
self.filepath = bpy.path.ensure_ext(self.filepath, ".bcf")
@@ -230,7 +236,7 @@ class ExecuteIfcClash(bpy.types.Operator):
settings.logger.setLevel(logging.DEBUG)
clasher = ifcclash.Clasher(settings)
if context.scene.BIMClashProperties.should_create_clash_snapshots:
if self.props.should_create_clash_snapshots:
def get_viewpoint_snapshot(viewpoint):
camera = bpy.data.objects.get("IFC Clash Camera")
@@ -271,23 +277,13 @@ class ExecuteIfcClash(bpy.types.Operator):
clasher.get_viewpoint_snapshot = get_viewpoint_snapshot
clasher.clash_sets = []
for clash_set in context.scene.BIMClashProperties.clash_sets:
self.a = []
self.b = []
for ab in ["a", "b"]:
for data in getattr(clash_set, ab):
clash_source = {"file": data.name}
if data.selector:
clash_source["selector"] = data.selector
clash_source["mode"] = data.mode
getattr(self, ab).append(clash_source)
clash_set_data = {"name": clash_set.name, "tolerance": clash_set.tolerance, "a": self.a}
if self.b:
clash_set_data["b"] = self.b
clasher.clash_sets.append(clash_set_data)
clasher.clash_sets = tool.Clash.export_clash_sets()
clasher.clash()
clasher.export()
if extension == ".json":
tool.Clash.load_clash_sets(self.filepath)
tool.Clash.import_active_clashes()
return {"FINISHED"}
@@ -306,6 +302,7 @@ class SelectIfcClashResults(bpy.types.Operator):
return {"RUNNING_MODAL"}
def execute(self, context):
# TODO refactor into new clash results system
self.file = IfcStore.get_file()
self.filepath = bpy.path.ensure_ext(self.filepath, ".json")
with open(self.filepath) as f:
@@ -352,83 +349,6 @@ class SelectIfcClashResults(bpy.types.Operator):
return {"FINISHED"}
class LoadIfcClashes(bpy.types.Operator):
bl_idname = "bim.load_ifc_clashes"
bl_label = "Load IFC Clashes"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Load the clashing IFC geometry stored in a file"
filename_ext = ".json"
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def invoke(self, context, event):
self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".json")
WindowManager = context.window_manager
WindowManager.fileselect_add(self)
return {"RUNNING_MODAL"}
def execute(self, context):
self.file = IfcStore.get_file()
self.filepath = bpy.path.ensure_ext(self.filepath, ".json")
with open(self.filepath) as f:
clash_sets_json = json.load(f)
active_clash_set = context.scene.BIMClashProperties.active_clash_set
for clash_set in clash_sets_json:
if not "clashes" in clash_set.keys():
self.report({"WARNING"}, "No clashes found for the selected Clash Set.")
return {"CANCELLED"}
active_clash_set.clashes.clear()
for clash in clash_set["clashes"].values():
blender_clash = active_clash_set.clashes.add()
blender_clash.a_global_id = clash["a_global_id"]
blender_clash.b_global_id = clash["b_global_id"]
blender_clash.a_name = "{}/{}".format(clash["a_ifc_class"], clash["a_name"])
blender_clash.b_name = "{}/{}".format(clash["b_ifc_class"], clash["b_name"])
blender_clash.status = False if not "status" in clash.keys() else clash["status"]
return {"FINISHED"}
class SaveIfcClashes(bpy.types.Operator):
bl_idname = "bim.save_ifc_clashes"
bl_label = "Save IFC Clashes"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Save the clashing IFC geometry stored in a file"
filename_ext = ".json"
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def invoke(self, context, event):
self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".json")
WindowManager = context.window_manager
WindowManager.fileselect_add(self)
return {"RUNNING_MODAL"}
def execute(self, context):
self.file = IfcStore.get_file()
self.filepath = bpy.path.ensure_ext(self.filepath, ".json")
clash_sets_json = self.load_json()
active_clash_set = context.scene.BIMClashProperties.active_clash_set
self.update_clash_sets(clash_sets_json, active_clash_set)
self.save_json(clash_sets_json)
return {"FINISHED"}
def load_json(self):
with open(self.filepath) as f:
return json.load(f)
def update_clash_sets(self, clash_sets_json, active_clash_set):
for clash_set in clash_sets_json:
if clash_set["name"] != active_clash_set.name or "clashes" not in clash_set.keys():
continue
for clash in active_clash_set.clashes:
clash_key = clash.a_global_id + "-" + clash.b_global_id
if clash_set.get("clashes", {}).get(clash_key, None) is not None:
clash_set["clashes"][clash_key]["status"] = clash.status
def save_json(self, clash_sets_json):
with open(self.filepath, "w") as destination:
json.dump(clash_sets_json, destination, indent=4)
class SelectClash(bpy.types.Operator):
bl_idname = "bim.select_clash"
bl_label = "Select Clash"
@@ -437,31 +357,29 @@ class SelectClash(bpy.types.Operator):
index: bpy.props.IntProperty()
def execute(self, context):
clash = context.scene.BIMClashProperties.active_clash
self.props = context.scene.BIMClashProperties
clash_set = tool.Clash.get_clash_set(self.props.active_clash_set.name)
active_clash = self.props.active_clash
clash = tool.Clash.get_clash(clash_set, active_clash.a_global_id, active_clash.b_global_id)
if not clash:
return {"FINISHED"}
products = []
linked_objects = []
try:
products.append(tool.Ifc.get().by_guid(clash.a_global_id))
except:
linked_objects.append(clash.a_name)
try:
products.append(tool.Ifc.get().by_guid(clash.b_global_id))
except:
linked_objects.append(clash.b_name)
for global_id in (clash["a_global_id"], clash["b_global_id"]):
try:
products.append(tool.Ifc.get().by_guid(global_id))
except:
pass
tool.Spatial.select_products(products, unhide=True)
print(linked_objects)
for obj_name in linked_objects:
obj = bpy.data.objects.get(obj_name)
if obj:
obj.select_set(True)
else:
print("Object not found:", obj_name)
if context.scene.BIMClashProperties.sould_focus_on_clash:
context_override = tool.Blender.get_viewport_context()
with bpy.context.temp_override(**context_override):
bpy.ops.view3d.view_selected()
ClashDecorator.install(bpy.context)
target = Vector(clash["p1"])
tool.Clash.look_at(target, target + Vector((5, 5, 5)))
self.props.p1 = clash["p1"]
self.props.p2 = clash["p2"]
self.props.active_clash_text = clash["type"].title() + " " + str(round(clash["distance"] * 1000)) + "mm"
return {"FINISHED"}
@@ -17,7 +17,7 @@
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
from blenderbim.bim.prop import StrProperty, Attribute
from blenderbim.bim.prop import StrProperty, Attribute, BIMFilterGroup
from bpy.types import PropertyGroup
from bpy.props import (
PointerProperty,
@@ -33,11 +33,12 @@ from bpy.props import (
class ClashSource(PropertyGroup):
name: StringProperty(name="File")
selector: StringProperty(name="Selector")
filter_groups: CollectionProperty(type=BIMFilterGroup, name="Filter Groups")
mode: EnumProperty(
items=[
("i", "Include", "Only the selected objects are included for clashing"),
("e", "Exclude", "All objects except the selected objects are included for clashing"),
("a", "All Elements", "All elements will be used for clashing"),
("i", "Include", "Only the selected elements are included for clashing"),
("e", "Exclude", "All elements except the selected elements are included for clashing"),
],
name="Mode",
)
@@ -53,7 +54,24 @@ class Clash(PropertyGroup):
class ClashSet(PropertyGroup):
name: StringProperty(name="Name")
tolerance: FloatProperty(name="Tolerance")
mode: EnumProperty(
items=[
(
"intersection",
"Intersection",
"Detect objects that protrude or pierce another object",
"PIVOT_MEDIAN",
1,
),
("collision", "Collision", "Detect touching objects with any surface collision", "PIVOT_INDIVIDUAL", 2),
("clearance", "Clearance", "Detect objects within a proximity threshold", "PIVOT_ACTIVE", 3),
],
name="Mode",
)
tolerance: FloatProperty(name="Tolerance", default=0.002)
clearance: FloatProperty(name="Clearance", default=0.01)
allow_touching: BoolProperty(name="Allow Touching", default=False)
check_all: BoolProperty(name="Check All", default=False)
a: CollectionProperty(name="Group A", type=ClashSource)
b: CollectionProperty(name="Group B", type=ClashSource)
clashes: CollectionProperty(name="Clashes", type=Clash)
@@ -78,22 +96,21 @@ class BIMClashProperties(PropertyGroup):
smart_clash_grouping_max_distance: IntProperty(
name="Smart Clash Grouping Max Distance", default=3, soft_min=1, soft_max=10
)
sould_focus_on_clash: BoolProperty(name="Show Focus Clash", default=False)
p1: FloatVectorProperty(name="P1", default=(0.0, 0.0, 0.0), subtype="XYZ")
p2: FloatVectorProperty(name="P2", default=(0.0, 0.0, 0.0), subtype="XYZ")
active_clash_text: StringProperty(name="Active Clash Text")
@property
def active_clash_set(self):
if not self.clash_sets:
return None
return self.clash_sets[self.active_clash_set_index]
if self.active_clash_set_index < len(self.clash_sets):
return self.clash_sets[self.active_clash_set_index]
@property
def active_smart_group(self):
if not self.smart_clash_groups:
return None
return self.smart_clash_groups[self.active_smart_group_index]
if self.active_smart_group_index < len(self.smart_clash_groups):
return self.smart_clash_groups[self.active_smart_group_index]
@property
def active_clash(self):
if not self.active_clash_set:
return None
return self.active_clash_set.clashes[self.active_clash_index]
if self.active_clash_index < len(self.active_clash_set.clashes):
return self.active_clash_set.clashes[self.active_clash_index]
@@ -17,7 +17,9 @@
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
import blenderbim.bim.helper
from bpy.types import Panel
from blenderbim.bim.module.clash.data import ClashData
class BIM_PT_ifcclash(Panel):
@@ -30,6 +32,9 @@ class BIM_PT_ifcclash(Panel):
bl_parent_id = "BIM_PT_tab_clash_detection"
def draw(self, context):
if not ClashData.is_loaded:
ClashData.load()
layout = self.layout
scene = context.scene
@@ -45,57 +50,83 @@ class BIM_PT_ifcclash(Panel):
layout.template_list("BIM_UL_clash_sets", "", props, "clash_sets", props, "active_clash_set_index")
if props.active_clash_set_index < len(props.clash_sets):
clash_set = props.active_clash_set
if not props.active_clash_set:
return
row = layout.row(align=True)
row.prop(clash_set, "name")
row.operator("bim.remove_clash_set", icon="X", text="").index = props.active_clash_set_index
clash_set = props.active_clash_set
row = layout.row(align=True)
row = layout.row(align=True)
row.prop(clash_set, "name")
row.operator("bim.remove_clash_set", icon="X", text="").index = props.active_clash_set_index
row = layout.row()
row.prop(clash_set, "mode")
if clash_set.mode == "intersection":
row = layout.row()
row.prop(clash_set, "tolerance")
layout.label(text="Group A:")
row = layout.row()
row.operator("bim.add_clash_source").group = "a"
for index, source in enumerate(clash_set.a):
row = layout.row(align=True)
row.prop(source, "name", text="")
op = row.operator("bim.select_clash_source", icon="FILE_FOLDER", text="")
op.index = index
op.group = "a"
op = row.operator("bim.remove_clash_source", icon="X", text="")
op.index = index
op.group = "a"
row = layout.row(align=True)
row.prop(source, "mode", text="")
row.prop(source, "selector", text="")
layout.label(text="Group B:")
row.prop(clash_set, "check_all")
elif clash_set.mode == "collision":
row = layout.row()
row.operator("bim.add_clash_source").group = "b"
for index, source in enumerate(clash_set.b):
row = layout.row(align=True)
row.prop(source, "name", text="")
op = row.operator("bim.select_clash_source", icon="FILE_FOLDER", text="")
op.index = index
op.group = "b"
op = row.operator("bim.remove_clash_source", icon="X", text="")
op.index = index
op.group = "b"
row = layout.row(align=True)
row.prop(source, "mode", text="")
row.prop(source, "selector", text="")
row.prop(clash_set, "allow_touching")
elif clash_set.mode == "clearance":
row = layout.row()
row.prop(props, "should_create_clash_snapshots")
row.prop(clash_set, "clearance")
row = layout.row()
row.prop(clash_set, "check_all")
row = layout.row(align=True)
row.label(text="Group A:", icon="OUTLINER_OB_POINTCLOUD")
row.operator("bim.add_clash_source", icon="ADD", text="").group = "a"
for index, source in enumerate(clash_set.a):
row = layout.row(align=True)
row.operator("bim.execute_ifc_clash")
row.operator("bim.select_ifc_clash_results")
row.prop(source, "name", text="")
row.prop(source, "mode", text="")
op = row.operator("bim.select_clash_source", icon="FILE_FOLDER", text="")
op.index = index
op.group = "a"
op = row.operator("bim.remove_clash_source", icon="X", text="")
op.index = index
op.group = "a"
if source.mode != "a":
blenderbim.bim.helper.draw_filter(
layout, source.filter_groups, ClashData, f"clash_{props.active_clash_set_index}_a_{index}"
)
row = layout.row(align=True)
row.label(text="Group B:", icon="OUTLINER_OB_POINTCLOUD")
row.operator("bim.add_clash_source", icon="ADD", text="").group = "b"
for index, source in enumerate(clash_set.b):
row = layout.row(align=True)
row.prop(source, "name", text="")
row.prop(source, "mode", text="")
op = row.operator("bim.select_clash_source", icon="FILE_FOLDER", text="")
op.index = index
op.group = "b"
op = row.operator("bim.remove_clash_source", icon="X", text="")
op.index = index
op.group = "b"
if source.mode != "a":
blenderbim.bim.helper.draw_filter(
layout, source.filter_groups, ClashData, f"clash_{props.active_clash_set_index}_b_{index}"
)
row = layout.row()
row.prop(props, "should_create_clash_snapshots")
row = layout.row()
row.operator("bim.execute_ifc_clash")
row = layout.row()
row.label(text=f"{len(clash_set.clashes)} Clashes Found", icon="PIVOT_CURSOR")
layout.template_list("BIM_UL_clashes", "", props.active_clash_set, "clashes", props, "active_clash_index")
row = layout.row()
row.operator("bim.select_clash")
class BIM_PT_clash_manager(Panel):
@@ -111,29 +142,6 @@ class BIM_PT_clash_manager(Panel):
pass
class BIM_PT_dumb_clash_manager(Panel):
bl_idname = "BIM_PT_dumb_clash_manager"
bl_label = "Dumb Manager"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_clash_manager"
def draw(self, context):
layout = self.layout
props = context.scene.BIMClashProperties
row = layout.row(align=True)
row.operator("bim.load_ifc_clashes", text="LOAD IFC CLASHES", icon="IMPORT")
row.operator("bim.save_ifc_clashes", text="SAVE CLASH RESULTS", icon="EXPORT")
layout.template_list("BIM_UL_clashes", "", props.active_clash_set, "clashes", props, "active_clash_index")
# bim.select_clash
row = layout.row(align=True)
row.operator("bim.select_clash", text="SELECT CLASH", icon="IMPORT")
row.prop(props, "sould_focus_on_clash", icon="RESTRICT_VIEW_OFF")
class BIM_PT_smart_clash_manager(Panel):
bl_idname = "BIM_PT_smart_clash_manager"
bl_label = "Smart Clash Manager"
@@ -20,6 +20,8 @@ import bpy
import json
import ifcopenshell
import ifcopenshell.api
import ifcopenshell.util.classification
import ifcopenshell.util.element
import blenderbim.tool as tool
import blenderbim.bim.helper
from blenderbim.bim.ifc import IfcStore
@@ -87,7 +89,7 @@ class AddManualClassificationReference(bpy.types.Operator, tool.Ifc.Operator):
reference = ifcopenshell.api.run(
"classification.add_reference",
tool.Ifc.get(),
product=product,
products=[product],
classification=classification,
identification="X",
name="Unnamed",
@@ -207,6 +209,10 @@ class DisableEditingClassification(bpy.types.Operator):
class RemoveClassification(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.remove_classification"
bl_label = "Remove Classification"
bl_description = (
"The classification and all of its relationships, children references, "
"and relationships between objects and child references will be completely removed from a project"
)
bl_options = {"REGISTER", "UNDO"}
classification: bpy.props.IntProperty()
@@ -292,20 +298,25 @@ class RemoveClassificationReference(bpy.types.Operator, tool.Ifc.Operator):
active_reference = tool.Ifc.get().by_id(self.reference)
identification = active_reference[1]
elements_by_references: dict[ifcopenshell.entity_instance, list[ifcopenshell.entity_instance]] = {}
for obj in objects:
ifc_definition_id = blenderbim.bim.helper.get_obj_ifc_definition_id(context, 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)
references = ifcopenshell.util.classification.get_references(element, should_inherit=False)
for reference in references:
if (identification and reference[1] == identification) or (
not identification and reference == active_reference
):
ifcopenshell.api.run(
"classification.remove_reference",
tool.Ifc.get(),
reference=reference,
product=element,
)
elements_by_references.setdefault(reference, []).append(element)
if elements_by_references:
for reference, products in elements_by_references.items():
ifcopenshell.api.run(
"classification.remove_reference",
tool.Ifc.get(),
reference=reference,
products=products,
)
class EditClassificationReference(bpy.types.Operator, tool.Ifc.Operator):
@@ -357,15 +368,18 @@ class AddClassificationReference(bpy.types.Operator, tool.Ifc.Operator):
classification = element
break
for obj in objects:
ifc_definition_id = blenderbim.bim.helper.get_obj_ifc_definition_id(context, obj, self.obj_type)
if not ifc_definition_id:
continue
ifc_file = tool.Ifc.get()
products = [
ifc_file.by_id(ifc_definition_id)
for obj in objects
if (ifc_definition_id := tool.Blender.get_obj_ifc_definition_id(obj, self.obj_type, context))
]
if products:
ifcopenshell.api.run(
"classification.add_reference",
tool.Ifc.get(),
reference=IfcStore.classification_file.by_id(self.reference),
product=tool.Ifc.get().by_id(ifc_definition_id),
products=products,
classification=classification,
)
@@ -406,14 +420,14 @@ class AddClassificationReferenceFromBSDD(bpy.types.Operator, tool.Ifc.Operator):
classification.Location = bsdd_classification.domain_namespace_uri
for obj in objects:
ifc_definition_id = blenderbim.bim.helper.get_obj_ifc_definition_id(context, obj, self.obj_type)
ifc_definition_id = tool.Blender.get_obj_ifc_definition_id(obj, self.obj_type, context)
if not ifc_definition_id:
continue
element = tool.Ifc.get().by_id(ifc_definition_id)
reference = ifcopenshell.api.run(
"classification.add_reference",
tool.Ifc.get(),
product=element,
products=[element],
classification=classification,
identification=bsdd_classification.reference_code,
name=bsdd_classification.name,
@@ -114,7 +114,7 @@ class EditObjective(bpy.types.Operator):
ifcopenshell.api.run(
"constraint.edit_objective",
self.file,
**{"objective": self.file.by_id(props.active_constraint_id), "attributes": attributes}
**{"objective": self.file.by_id(props.active_constraint_id), "attributes": attributes},
)
bpy.ops.bim.load_objectives()
return {"FINISHED"}
@@ -179,19 +179,17 @@ class AssignConstraint(bpy.types.Operator):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
self.file = IfcStore.get_file()
self.file = tool.Ifc.get()
objs = [bpy.data.objects.get(self.obj)] if self.obj else context.selected_objects
for obj in objs:
obj_id = obj.BIMObjectProperties.ifc_definition_id
if not obj_id:
continue
products = [self.file.by_id(obj_id) for obj in objs if (obj_id := obj.BIMObjectProperties.ifc_definition_id)]
if products:
ifcopenshell.api.run(
"constraint.assign_constraint",
self.file,
**{
"product": self.file.by_id(obj_id),
"products": products,
"constraint": self.file.by_id(self.constraint),
}
},
)
return {"FINISHED"}
@@ -207,18 +205,16 @@ class UnassignConstraint(bpy.types.Operator):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
self.file = IfcStore.get_file()
self.file = tool.Ifc.get()
objs = [bpy.data.objects.get(self.obj)] if self.obj else context.selected_objects
for obj in objs:
obj_id = obj.BIMObjectProperties.ifc_definition_id
if not obj_id:
continue
products = [self.file.by_id(obj_id) for obj in objs if (obj_id := obj.BIMObjectProperties.ifc_definition_id)]
if products:
ifcopenshell.api.run(
"constraint.unassign_constraint",
self.file,
**{
"product": self.file.by_id(obj_id),
"products": products,
"constraint": self.file.by_id(self.constraint),
}
},
)
return {"FINISHED"}
@@ -53,6 +53,10 @@ class AddContext(bpy.types.Operator, Operator):
class RemoveContext(bpy.types.Operator, Operator):
bl_idname = "bim.remove_context"
bl_label = "Remove Context"
bl_description = (
"Remove representation context. Any representation geometry that is assigned to the context is also removed. "
"If a context is removed, then any subcontexts are also removed"
)
bl_options = {"REGISTER", "UNDO"}
context: bpy.props.IntProperty()
@@ -107,59 +107,88 @@ class CoveringToolUI:
# elif element and bpy.context.selected_objects and element.is_a("IfcSpace"):
# op. = row.operator("bim.add_istance_flooring_from_spaces"):
if (type_material_usage == "IfcMaterialLayerSet" and
not bpy.context.selected_objects):
row = cls.layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="", icon="EVENT_A")
if tool.Ifc.get_entity(collection_obj):
if AuthoringData.data["predefined_type"] == "FLOORING":
op = row.operator("bim.add_instance_flooring_covering_from_cursor")
elif AuthoringData.data["predefined_type"] == "CEILING":
op = row.operator("bim.add_instance_ceiling_covering_from_cursor")
else:
op = row.operator("bim.add_constr_type_instance", text="Add")
op.from_invoke = True
if cls.props.relating_type_id.isnumeric():
op.relating_type_id = int(cls.props.relating_type_id)
else:
op = row.operator("bim.add_constr_type_instance", text="Add")
op.from_invoke = True
if cls.props.relating_type_id.isnumeric():
op.relating_type_id = int(cls.props.relating_type_id)
elif (AuthoringData.data["predefined_type"] == "FLOORING" and
type_material_usage == "IfcMaterialLayerSet" and
element and
bpy.context.selected_objects and
element.is_a("IfcWall")):
row = cls.layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="", icon="EVENT_A")
op = row.operator("bim.add_instance_flooring_coverings_from_walls")
elif (AuthoringData.data["predefined_type"] == "CEILING" and
type_material_usage == "IfcMaterialLayerSet" and
element and
bpy.context.selected_objects and
element.is_a("IfcWall")):
row = cls.layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="", icon="EVENT_A")
op = row.operator("bim.add_instance_ceiling_coverings_from_walls")
elif (element and
bpy.context.selected_objects and
element.is_a("IfcCovering") and
# AuthoringData.data["predefined_type"] == "FLOORING" and
AuthoringData.data["active_material_usage"] == "LAYER3"):
row = cls.layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="", icon="EVENT_G")
op = row.operator("bim.regen_selected_covering_object")
# if (type_material_usage == "IfcMaterialLayerSet" and
# not bpy.context.selected_objects):
# row = cls.layout.row(align=True)
# row.label(text="", icon="EVENT_SHIFT")
# row.label(text="", icon="EVENT_A")
# if tool.Ifc.get_entity(collection_obj):
# if AuthoringData.data["predefined_type"] == "FLOORING":
# op = row.operator("bim.add_instance_flooring_covering_from_cursor")
# elif AuthoringData.data["predefined_type"] == "CEILING":
# op = row.operator("bim.add_instance_ceiling_covering_from_cursor")
# else:
# op = row.operator("bim.add_constr_type_instance", text="Add")
# op.from_invoke = True
# if cls.props.relating_type_id.isnumeric():
# op.relating_type_id = int(cls.props.relating_type_id)
#
# else:
# op = row.operator("bim.add_constr_type_instance", text="Add")
# op.from_invoke = True
# if cls.props.relating_type_id.isnumeric():
# op.relating_type_id = int(cls.props.relating_type_id)
#
# elif (AuthoringData.data["predefined_type"] == "FLOORING" and
# type_material_usage == "IfcMaterialLayerSet" and
# element and
# bpy.context.selected_objects and
# element.is_a("IfcWall")):
# row = cls.layout.row(align=True)
# row.label(text="", icon="EVENT_SHIFT")
# row.label(text="", icon="EVENT_A")
# op = row.operator("bim.add_instance_flooring_coverings_from_walls")
#
# elif (AuthoringData.data["predefined_type"] == "CEILING" and
# type_material_usage == "IfcMaterialLayerSet" and
# element and
# bpy.context.selected_objects and
# element.is_a("IfcWall")):
# row = cls.layout.row(align=True)
# row.label(text="", icon="EVENT_SHIFT")
# row.label(text="", icon="EVENT_A")
# op = row.operator("bim.add_instance_ceiling_coverings_from_walls")
#
# elif (element and
# bpy.context.selected_objects and
# element.is_a("IfcCovering") and
## AuthoringData.data["predefined_type"] == "FLOORING" and
# AuthoringData.data["active_material_usage"] == "LAYER3"):
# row = cls.layout.row(align=True)
# row.label(text="", icon="EVENT_SHIFT")
# row.label(text="", icon="EVENT_G")
# op = row.operator("bim.regen_selected_covering_object")
row = cls.layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="", icon="EVENT_A")
op = row.operator("bim.add_constr_type_instance", text="Add")
row = cls.layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="", icon="EVENT_A")
op = row.operator("bim.add_instance_flooring_covering_from_cursor")
row = cls.layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="", icon="EVENT_A")
op = row.operator("bim.add_instance_ceiling_covering_from_cursor")
row = cls.layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="", icon="EVENT_A")
op = row.operator("bim.add_instance_flooring_coverings_from_walls")
row = cls.layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="", icon="EVENT_A")
op = row.operator("bim.add_instance_ceiling_coverings_from_walls")
row = cls.layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="", icon="EVENT_G")
op = row.operator("bim.regen_selected_covering_object")
# elif AuthoringData.data["predefined_type"] == "CEILING":
# row = cls.layout.row(align=True)
@@ -174,14 +203,14 @@ class CoveringToolUI:
# op.from_invoke = True
# if cls.props.relating_type_id.isnumeric():
# op.relating_type_id = int(cls.props.relating_type_id)
else:
row = cls.layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="", icon="EVENT_A")
op = row.operator("bim.add_constr_type_instance", text="Add")
op.from_invoke = True
if cls.props.relating_type_id.isnumeric():
op.relating_type_id = int(cls.props.relating_type_id)
# else:
# row = cls.layout.row(align=True)
# row.label(text="", icon="EVENT_SHIFT")
# row.label(text="", icon="EVENT_A")
# op = row.operator("bim.add_constr_type_instance", text="Add")
# op.from_invoke = True
# if cls.props.relating_type_id.isnumeric():
# op.relating_type_id = int(cls.props.relating_type_id)
@classmethod
def draw_type_selection_interface(cls):
@@ -191,11 +220,11 @@ class CoveringToolUI:
row = cls.layout.row(align=True)
row.label(text="", icon="FILE_3D")
prop_with_search(row, cls.props, "relating_type_id", text="")
row.operator("bim.launch_type_manager", icon="LIGHTPROBE_GRID", text="")
row.operator("bim.launch_type_manager", icon=tool.Blender.TYPE_MANAGER_ICON, text="")
else:
row.label(text=f"No {AuthoringData.data['ifc_element_type']} Found", icon="ERROR")
row = cls.layout.row()
row.operator("bim.launch_type_manager", icon="LIGHTPROBE_GRID", text="Launch Type Manager")
row.operator("bim.launch_type_manager", icon=tool.Blender.TYPE_MANAGER_ICON, text="Launch Type Manager")
@classmethod
def draw_basic_bim_tool_interface(cls):
@@ -72,7 +72,7 @@ class ReorderCsvAttribute(bpy.types.Operator):
def execute(self, context):
old = context.scene.CsvProperties.csv_attributes[self.old_index]
new = context.scene.CsvProperties.csv_attributes[self.new_index]
props = ["name", "header", "sort", "group", "varies_value", "summary"]
props = ["name", "header", "sort", "group", "varies_value", "summary", "formatting"]
for prop in props:
value = getattr(new, prop)
setattr(new, prop, getattr(old, prop))
@@ -246,6 +246,7 @@ class ExportIfcCsv(bpy.types.Operator):
if props.format != "csv" and props.should_generate_svg:
schedule_creator = scheduler.Scheduler()
schedule_creator.schedule(self.filepath, tool.Drawing.get_path_with_ext(self.filepath, "svg"))
self.report({"INFO"}, f"Data is exported to {props.format.upper()}.")
return {"FINISHED"}
@@ -285,6 +286,7 @@ class ImportIfcCsv(bpy.types.Operator):
if not props.should_load_from_memory:
ifc_file.write(props.csv_ifc_file)
refresh_ui_data()
self.report({"INFO"}, "Data is imported to IFC.")
return {"FINISHED"}
@@ -24,22 +24,23 @@ classes = (
operator.CopyDebugInformation,
operator.CreateAllShapes,
operator.CreateShapeFromStepId,
operator.DebugActiveDrawing,
operator.InspectFromObject,
operator.InspectFromStepId,
operator.OverrideDisplayType,
operator.ParseExpress,
operator.PipInstall,
operator.PrintIfcFile,
operator.PrintObjectPlacement,
operator.PrintUnusedElementStats,
operator.ProfileImportIFC,
operator.PurgeHdf5Cache,
operator.PurgeIfcLinks,
operator.PurgeUnusedElementsByClass,
operator.RewindInspector,
operator.SelectExpressFile,
operator.SelectHighPolygonMeshes,
operator.SelectHighestPolygonMeshes,
operator.ValidateIfcFile,
operator.PrintUnusedElementStats,
operator.PurgeUnusedElementsByClass,
prop.BIMDebugProperties,
ui.BIM_PT_debug,
)
@@ -17,20 +17,23 @@
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import os
import sys
import bpy
import time
import random
import logging
import platform
import subprocess
import addon_utils
import ifcopenshell
import ifcopenshell.api
import ifcopenshell.util.element
import ifcopenshell.util.placement
import ifcopenshell.util.representation
import blenderbim.tool as tool
import blenderbim.core.debug as core
import blenderbim.bim.handler
import blenderbim.bim.import_ifc as import_ifc
import blenderbim.tool as tool
from blenderbim import get_debug_info, format_debug_info
from blenderbim.bim.ifc import IfcStore
@@ -40,28 +43,7 @@ class CopyDebugInformation(bpy.types.Operator):
bl_description = "Copies debugging information to your clipboard for use in bugreports"
def execute(self, context):
version = ".".join(
[
str(x)
for x in [
addon.bl_info.get("version", (-1, -1, -1))
for addon in addon_utils.modules()
if addon.bl_info["name"] == "BlenderBIM"
][0]
]
)
info = {
"os": platform.system(),
"os_version": platform.version(),
"python_version": platform.python_version(),
"architecture": platform.architecture(),
"machine": platform.machine(),
"processor": platform.processor(),
"blender_version": bpy.app.version_string,
"blenderbim_version": version,
"ifc": False,
}
info = get_debug_info()
if tool.Ifc.get():
info.update(
{
@@ -72,16 +54,18 @@ class CopyDebugInformation(bpy.types.Operator):
}
)
# Format it in a readable way
text = "\n".join(f"{k}: {v}" for k, v in info.items())
text = format_debug_info(info)
print("-" * 80)
print(text)
print("-" * 80)
if platform.system() == "Windows":
command = "echo | set /p nul=" + text.strip()
command = "echo | set /p nul=" + text
elif platform.system() == "Darwin": # for MacOS
command = 'printf "' + text.strip().replace("\n", "\\n").replace('"', "") + '" | pbcopy'
command = 'printf "' + text.replace("\n", "\\n").replace('"', "") + '" | pbcopy'
else: # Linux
command = 'printf "' + text.strip().replace("\n", "\\n").replace('"', "") + '" | xclip -selection clipboard'
command = 'printf "' + text.replace("\n", "\\n").replace('"', "") + '" | xclip -selection clipboard'
subprocess.run(command, shell=True, check=True)
return {"FINISHED"}
@@ -100,10 +84,11 @@ class PrintIfcFile(bpy.types.Operator):
return {"FINISHED"}
class PurgeIfcLinks(bpy.types.Operator):
bl_idname = "bim.purge_ifc_links"
bl_label = "Purge IFC Links"
bl_description = "Purge all definitions and references from the file.\nWarning : Cannot be undone."
class ConvertToBlender(bpy.types.Operator):
bl_idname = "bim.convert_to_blender"
bl_label = "Convert To Blender File"
bl_description = "Removes all IFC data and revert to basic Blender objects.\nWarning : Cannot be undone."
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
for obj in bpy.data.objects:
@@ -120,26 +105,6 @@ class PurgeIfcLinks(bpy.types.Operator):
return {"FINISHED"}
class ConvertToBlender(bpy.types.Operator):
bl_idname = "bim.convert_to_blender"
bl_label = "Convert To Blender File"
bl_description = "Removes all IFC data, and converts the file to a simple Blender file."
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
for o in bpy.data.objects:
if o.type in {"MESH", "EMPTY"}:
o.BIMObjectProperties.ifc_definition_id = 0
if o.data:
o.data.BIMMeshProperties.ifc_definition_id = 0
for m in bpy.data.materials:
m.BIMMaterialProperties.ifc_style_id = False
bpy.context.scene.BIMProperties.ifc_file = ""
IfcStore.purge()
blenderbim.bim.handler.refresh_ui_data()
return {"FINISHED"}
class ValidateIfcFile(bpy.types.Operator):
bl_idname = "bim.validate_ifc_file"
bl_label = "Validate IFC File"
@@ -623,3 +588,140 @@ class PurgeUnusedElementsByClass(bpy.types.Operator, tool.Ifc.Operator):
print("Finished 20 batches. Manually stopping in case of infinite loop.")
self.report({"INFO"}, f"Auto purged {total_purged} orphaned elements")
tool.Ifc.get().write(self.filepath)
class PipInstall(bpy.types.Operator):
bl_idname = "bim.pip_install"
bl_label = "Pip Install"
bl_description = "Installs a package from PyPI"
name: bpy.props.StringProperty()
def execute(self, context):
import blenderbim.bim
target = [p for p in sys.path if p.endswith("packages") and "addons" in p][0]
py_exec = str(sys.executable)
print("Detected executable:", py_exec)
print("Installing to:", target)
subprocess.call([py_exec, "-m", "ensurepip", "--user"])
subprocess.call([py_exec, "-m", "pip", "install", "--upgrade", "pip"])
# Trusted host to make things simpler due to some enterprise network restrictions
subprocess.call(
[
py_exec,
"-m",
"pip",
"install",
f"--target={target}",
"--upgrade",
self.name,
"--trusted-host",
"pypi.org",
"--trusted-host",
"files.pythonhosted.org",
]
)
return {"FINISHED"}
class DebugActiveDrawing(bpy.types.Operator):
bl_idname = "bim.debug_active_drawing"
bl_label = "Search Active Drawing For Failing Elements"
bl_description = (
"Will iterate over all visible drawing's objects, trying to narrow down the list of possible failing objects"
)
def execute(self, context: bpy.types.Context):
props = context.scene.DocProperties
drawing_item = props.drawings[props.active_drawing_index]
drawing = tool.Ifc.get().by_id(drawing_item.ifc_definition_id)
GREEN = "\033[92m"
CYAN = "\033[96m"
END = "\033[0m"
ATTEMPS = 10
# run create drawing with sync for once
# to make sure everything is actually in sync
try:
bpy.ops.bim.create_drawing(sync=True)
except:
pass
else:
self.report({"INFO"}, "No errors creating drawing, nothing to investigate.")
return {"FINISHED"}
all_elements = [e for obj in context.visible_objects if (e := tool.Ifc.get_entity(obj))]
all_elements = set(all_elements)
original_exclude = ifcopenshell.util.element.get_pset(drawing, "EPset_Drawing", "Exclude")
pset = tool.Pset.get_element_pset(drawing, "EPset_Drawing")
def drawing_fails_to_load(chunk_to_include: set) -> bool:
current_elements = all_elements - chunk_to_include
excluded_guids = ", ".join([e.GlobalId for e in current_elements if hasattr(e, "GlobalId")])
tool.Ifc.run("pset.edit_pset", pset=pset, properties={"Exclude": f"{original_exclude}, {excluded_guids}"})
try:
bpy.ops.bim.create_drawing(sync=False)
result = False
except Exception as e:
# print(e)
result = True
tool.Ifc.run("pset.edit_pset", pset=pset, properties={"Exclude": original_exclude})
return result
def test_elements(elements: list[ifcopenshell.entity_instance], attempts: int = ATTEMPS) -> None:
print(f"{CYAN}processing {len(elements)} elements{END}")
if not elements:
print(f"Empty list of elements, will stop...")
return
n_elements = len(elements)
middle = int(n_elements / 2)
chunk1, chunk2 = elements[:middle], elements[middle:]
test_chunk_1 = drawing_fails_to_load(set(chunk1))
test_chunk_2 = drawing_fails_to_load(set(chunk2))
if (
# both chunks do not fail anymore
(not test_chunk_1 and not test_chunk_2)
# or we have 1 element chunk that is still failing
or (test_chunk_1 and not chunk2)
or (test_chunk_2 and not chunk1)
):
if attempts == 0 or n_elements in (1, 2):
print(f"{GREEN}Couldn't narrow it down any further.{END}")
print(f"It's some of the {n_elements} elements:")
print(elements)
print("Let's test excluding them...")
for element in elements:
test = drawing_fails_to_load(all_elements - {element})
if test:
print(f"{CYAN}Excluding element didn't fixed the drawing: {END}")
print(element)
else:
print(f"{GREEN}Excluding element fixed the drawing: {END}")
print(element)
else:
print(f"{CYAN}Will try to reshuffle elements and try again, attempt {ATTEMPS-attempts+1}/{ATTEMPS}")
attempts -= 1
random.shuffle(elements)
test_elements(elements, attempts)
return
# if chunk fails we need to investigate it further
if test_chunk_1:
test_elements(chunk1)
if test_chunk_2:
test_elements(chunk2)
test_elements(list(all_elements))
self.report({"INFO"}, "See system console for the results")
return {"FINISHED"}
@@ -51,3 +51,4 @@ class BIMDebugProperties(PropertyGroup):
default="BOUNDS",
)
ifc_class_purge: StringProperty(name="Unused Elements IFC Class", default="")
package_name: StringProperty(name="Package Name", default="")
@@ -44,6 +44,10 @@ class BIM_PT_debug(Panel):
row.operator("bim.parse_express", icon="IMPORT", text="")
row.operator("bim.select_express_file", icon="FILE_FOLDER", text="")
row = self.layout.row(align=True)
row.prop(props, "package_name", text="")
row.operator("bim.pip_install", icon="EVENT_PAGEDOWN").name = props.package_name
row = layout.row()
row.operator("bim.reload_ifc_file", text="Incrementally Reload Changes")
@@ -56,9 +60,6 @@ class BIM_PT_debug(Panel):
row = layout.row()
row.operator("bim.purge_hdf5_cache")
row = layout.row()
row.operator("bim.purge_ifc_links")
row = layout.row()
row.operator("bim.update_representation", text="Manually Save Representation")
@@ -71,6 +72,9 @@ class BIM_PT_debug(Panel):
row = layout.row()
row.operator("bim.profile_import_ifc")
row = layout.row()
row.operator("bim.debug_active_drawing")
row = layout.split(factor=0.5, align=True)
row.operator("bim.create_shape_from_step_id").should_include_curves = False
row.operator("bim.create_shape_from_step_id", text="", icon="IPO_ELASTIC").should_include_curves = True
@@ -38,6 +38,7 @@ class DiffData:
cls.data["total_deleted"] = cls.total_deleted()
cls.data["total_changed"] = cls.total_changed()
cls.data["changes"] = cls.changes()
cls.data["saved_searches"] = cls.saved_searches()
cls.is_loaded = True
@classmethod
@@ -84,3 +85,18 @@ class DiffData:
elif element.GlobalId in diff["deleted"]:
changes["Deleted"] = True
return changes
@classmethod
def saved_searches(cls):
if not tool.Ifc.get():
return []
groups = tool.Ifc.get().by_type("IfcGroup")
results = []
for group in groups:
try:
data = json.loads(group.Description)
if isinstance(data, dict) and data.get("type", None) == "BBIM_Search" and data.get("query", None):
results.append(group)
except:
pass
return [(str(g.id()), g.Name or "Unnamed", "") for g in sorted(results, key=lambda x: x.Name or "Unnamed")]
@@ -135,6 +135,7 @@ class ExecuteIfcDiff(bpy.types.Operator):
bl_label = "Execute IFC Diff"
filename_ext = ".json"
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
filter_glob: bpy.props.StringProperty(default="*.json", options={"HIDDEN"})
def invoke(self, context, event):
self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".json")
@@ -162,7 +163,7 @@ class ExecuteIfcDiff(bpy.types.Operator):
new = ifcopenshell.open(self.props.new_file)
relationships = [r.relationship for r in self.props.diff_relationships]
query = self.props.diff_filter_elements
query = tool.Search.export_filter_query(self.props.filter_groups) or None
ifc_diff = ifcdiff.IfcDiff(old, new, relationships=relationships, filter_elements=query)
ifc_diff.diff()
@@ -17,7 +17,7 @@
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
from blenderbim.bim.prop import StrProperty
from blenderbim.bim.prop import StrProperty, BIMFilterGroup
from blenderbim.bim.module.diff.data import DiffData
from bpy.types import PropertyGroup
from bpy.props import (
@@ -48,7 +48,7 @@ class DiffProperties(PropertyGroup):
old_file: StringProperty(default="", name="Old IFC File")
new_file: StringProperty(default="", name="New IFC File")
diff_relationships: CollectionProperty(type=Relationships, name="Relationships")
diff_filter_elements: StringProperty(default="", name="Filter")
filter_groups: CollectionProperty(type=BIMFilterGroup, name="Filter Groups")
should_load_changed_elements: BoolProperty(name="Load Changed Elements", default=True)
active_file: EnumProperty(
items=[
@@ -19,8 +19,8 @@
from bpy.types import Panel
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.module.diff.data import DiffData
import blenderbim.bim.helper
import blenderbim.tool as tool
import json
class BIM_PT_diff(Panel):
@@ -93,9 +93,7 @@ class BIM_PT_diff(Panel):
remove.collection = "diff_relationships"
remove.index = index
row = layout.row(align=True)
row.prop(props, "diff_filter_elements")
row.operator("bim.ifc_selector", icon="FILTER", text="")
blenderbim.bim.helper.draw_filter(self.layout, props.filter_groups, DiffData, "diff")
row = layout.row()
row.operator("bim.execute_ifc_diff")
@@ -41,6 +41,7 @@ classes = (
operator.CleanWireframes,
operator.ContractSheet,
operator.ContractTargetView,
operator.ConvertSVGToDXF,
operator.CreateDrawing,
operator.CreateSheets,
operator.DisableAddAnnotationType,
@@ -126,7 +127,7 @@ def register():
bpy.types.Object.BIMAssignedProductProperties = bpy.props.PointerProperty(type=prop.BIMAssignedProductProperties)
bpy.types.Object.BIMTextProperties = bpy.props.PointerProperty(type=prop.BIMTextProperties)
bpy.types.TextCurve.BIMTextProperties = bpy.props.PointerProperty(type=prop.BIMTextProperties)
bpy.app.handlers.load_post.append(handler.toggleDecorationsOnLoad)
bpy.app.handlers.load_post.append(handler.load_post)
bpy.app.handlers.depsgraph_update_pre.append(handler.depsgraph_update_pre_handler)
bpy.types.VIEW3D_MT_image_add.append(ui.add_object_button)
@@ -140,6 +141,6 @@ def unregister():
del bpy.types.Object.BIMAssignedProductProperties
del bpy.types.Object.BIMTextProperties
del bpy.types.TextCurve.BIMTextProperties
bpy.app.handlers.load_post.remove(handler.toggleDecorationsOnLoad)
bpy.app.handlers.load_post.remove(handler.load_post)
bpy.app.handlers.depsgraph_update_pre.remove(handler.depsgraph_update_pre_handler)
bpy.types.VIEW3D_MT_image_add.remove(ui.add_object_button)
@@ -23,6 +23,7 @@ import ifcopenshell.util.element
import ifcopenshell.util.representation
import blenderbim.tool as tool
from pathlib import Path
from typing import Any, Union
def refresh():
@@ -234,9 +235,9 @@ class DecoratorData:
cut_cache = {}
layerset_cache = {}
# used by Ifc Annotations with ObjectType = "BATTING"
@classmethod
def get_batting_thickness(cls, obj):
"""used by IfcAnnotations with ObjectType = "BATTING" """
result = cls.data.get(obj.name, None)
if result is not None:
return result
@@ -248,9 +249,9 @@ class DecoratorData:
cls.data[obj.name] = thickness
return thickness
# used by Ifc Annotations with ObjectType = "SECTION"
@classmethod
def get_section_markers_display_data(cls, obj):
"""used by IfcAnnotations with ObjectType = "SECTION" """
result = cls.data.get(obj.name, None)
if result is not None:
return result
@@ -290,10 +291,10 @@ class DecoratorData:
cls.data[obj.name] = display_data
return display_data
# used by Ifc Annotations with ObjectType = "TEXT" / "TEXT_LEADER"
@classmethod
def get_ifc_text_data(cls, obj):
"""returns font size in mm for current ifc text object"""
def get_ifc_text_data(cls, obj: bpy.types.Object) -> dict[str, Any]:
"""used by Ifc Annotations with ObjectType = "TEXT" / "TEXT_LEADER"\n
returns font size in mm for current ifc text object"""
result = cls.data.get(obj.name, None)
if result is not None:
return result
@@ -317,7 +318,7 @@ class DecoratorData:
font_size = FONT_SIZES[font_size_type]
# get symbol
symbol = pset_data.get("Symbol", None)
symbol = tool.Drawing.get_annotation_symbol(element)
# other attributes
props_literals = props.literals
@@ -340,6 +341,11 @@ class DecoratorData:
cls.data[obj.name] = text_data
return text_data
@classmethod
def get_symbol(cls, obj: bpy.types.Object) -> Union[str, None]:
"""used by IfcAnnotations with ObjectType MULTI_SYMBOL"""
return tool.Drawing.get_annotation_symbol(tool.Ifc.get_entity(obj))
@classmethod
def get_dimension_data(cls, obj):
"""used by Ifc Annotations with ObjectType:
@@ -412,10 +418,12 @@ class AnnotationData:
relating_types = []
for relating_type in tool.Ifc.get().by_type("IfcTypeProduct"):
if tool.Drawing.is_annotation_object_type(relating_type, object_type):
relating_types.append({
"id": relating_type.id(),
"name": relating_type.Name or "Unnamed",
"description": relating_type.Description or "No Description",
})
relating_types.append(
{
"id": relating_type.id(),
"name": relating_type.Name or "Unnamed",
"description": relating_type.Description or "No Description",
}
)
return sorted(relating_types, key=lambda x: x["name"])
@@ -37,6 +37,7 @@ from blenderbim.bim.module.drawing.shaders import add_verts_sequence, add_offset
from blenderbim.bim.module.drawing.helper import format_distance
from timeit import default_timer as timer
from functools import lru_cache
from typing import Optional, Iterator, Type, Union
UNSPECIAL_ELEMENT_COLOR = (0.2, 0.2, 0.2, 1) # GREY
@@ -69,7 +70,7 @@ class profile_consequential:
cls.lines = []
def worldspace_to_winspace(verts, context):
def worldspace_to_winspace(verts: list[Vector], context: bpy.types.Context) -> list[Vector]:
"""Convert world space verts to window space"""
region = context.region
region3d = context.region_data
@@ -80,7 +81,7 @@ def worldspace_to_winspace(verts, context):
return winspace_verts
def winspace_to_worldspace(verts, context):
def winspace_to_worldspace(verts: list[Vector], context: bpy.types.Context) -> list[Vector]:
"""Convert winspace verts to world space"""
region = context.region
region3d = context.region_data
@@ -121,17 +122,18 @@ def get_callout_head(edge_dir, edge_side, callout_size, callout_gap):
return head
def get_circle_head(size, segments=20):
def get_circle_head(size: float, segments: int = 20, rotation: float = 0.0) -> list[Vector]:
"""`rotation` value in radians"""
angle_d = 2 * pi / segments
head = []
for i in range(segments):
angle = angle_d * i
angle = angle_d * i + rotation
head.append(Vector([cos(angle), sin(angle), 0]) * size)
return head
def get_circle_head_asterisk(size, segments=6):
circle_head = get_circle_head(size, segments)
def get_circle_head_asterisk(size: float, segments: int = 6, rotation: float = 0.0) -> Iterator[tuple[Vector, Vector]]:
circle_head = get_circle_head(size, segments, rotation)
middle = segments // 2
return zip(circle_head[:middle], circle_head[middle:])
@@ -264,7 +266,7 @@ class BaseDecorator:
vertices = [obj.matrix_world @ v.co for v in bm.verts]
return vertices, indices
def decorate(self, context, obj):
def decorate(self, context: bpy.types.Context, obj: bpy.types.Object):
"""perform actual drawing stuff"""
raise NotImplementedError()
@@ -310,13 +312,28 @@ class BaseDecorator:
self.draw_lines(context, obj, output_verts, output_edges)
def draw_batch(self, shader_type, content_pos, color, indices=None):
def draw_batch(
self,
shader_type: str,
content_pos: list[Vector],
color: tuple[float, float, float, float],
indices: Optional[list[tuple[int, int]]] = None,
) -> None:
shader = self.line_shader if shader_type == "LINES" else self.base_shader
batch = batch_for_shader(shader, shader_type, {"pos": content_pos}, indices=indices)
shader.uniform_float("color", color)
batch.draw(shader)
def draw_lines(self, context, obj, vertices, indices, color=None, line_width=1.0):
def draw_lines(
self,
context: bpy.types.Context,
obj: Optional[bpy.types.Object],
vertices: list[Vector],
indices: list[tuple[int, int]],
color: Optional[tuple[float, float, float, float]] = None,
line_width: float = 1.0,
) -> None:
# TODO: `obj` is actually never used, need to remove it
"""`verts` should be in winspace with `(0,0,0)` in the screen left bottom corner, not in the center"""
region = context.region
if not color:
@@ -329,7 +346,7 @@ class BaseDecorator:
gpu.state.blend_set("ALPHA")
self.draw_batch("LINES", vertices, color, indices)
def get_viewport_drawing_scale(self, context):
def get_viewport_drawing_scale(self, context: bpy.types.Context) -> float:
# Horrific prototype code
factor = self.camera_zoom_to_factor(context.space_data.region_3d.view_camera_zoom)
camera_width_px = factor * context.region.width
@@ -485,14 +502,16 @@ class BaseDecorator:
decimal_places = drawing_pset_data.get("DecimalPlaces", None)
return format_distance(value, precision=precision, decimal_places=decimal_places)
def draw_asterisk(self, context, obj):
def draw_asterisk(self, context: bpy.types.Context, pos: Vector, rotation: float = 0.0, scale: float = 1.0) -> None:
"""`pos` is a world space position\n
`rotation` value in radians"""
# gather geometry data and convert to winspace
verts = [obj.location]
verts = [pos]
viewportDrawingScale = self.get_viewport_drawing_scale(context)
winspace_verts = worldspace_to_winspace(verts, context)
# setup geometry parameters
circle_size = viewportDrawingScale * 4
circle_size = viewportDrawingScale * 4 * scale
output_verts = []
output_edges = []
@@ -502,20 +521,13 @@ class BaseDecorator:
}
v0 = winspace_verts[0]
asterisk_head = get_circle_head_asterisk(circle_size)
asterisk_head = get_circle_head_asterisk(circle_size, rotation=rotation)
start_i = 0
for segment in asterisk_head:
start_i = add_verts_sequence(add_offsets(v0, segment), start_i, **out_kwargs)
self.draw_lines(context, obj, output_verts, output_edges)
self.draw_lines(context, None, output_verts, output_edges)
def draw_text(self, context, obj, text_world_position=None, reverse_lines_order=False):
"""if `text_world_position` is not provided, the object's location will be used"""
if not text_world_position:
text_world_position = obj.location
region = context.region
region3d = context.region_data
def get_annotation_direction(self, context: bpy.types.Context, obj: bpy.types.Object) -> Vector:
camera = context.scene.camera
def get_basis_vector(matrix, i=0):
@@ -523,10 +535,57 @@ class BaseDecorator:
return matrix.inverted()[i].to_3d().normalized()
text_dir_world_x_axis = get_basis_vector(obj.matrix_world)
camera_matrix = tool.Drawing.get_camera_matrix(camera)
text_dir = (camera_matrix.inverted().to_quaternion() @ text_dir_world_x_axis).to_2d().normalized()
return text_dir
def draw_symbol(self, context: bpy.types.Context, obj: bpy.types.Object, annotation_dir: Vector) -> None:
if obj.type not in ("MESH", "EMPTY"):
return
if obj.type == "MESH":
mesh: bpy.types.Mesh = obj.data
if len(mesh.vertices) == 0:
return
if mesh.edges:
MiscDecorator.decorate(self, context, obj)
return
symbol = DecoratorData.get_symbol(obj)
if not symbol:
return
for vert in mesh.vertices:
self.draw_asterisk(context, obj.matrix_world @ vert.co)
return
# EMPTY objects
symbol = DecoratorData.get_symbol(obj)
if not symbol:
return
rotation = -Vector((1, 0)).angle_signed(annotation_dir)
# NOTE: for now we assume that scale is uniform
self.draw_asterisk(context, obj.location, rotation, obj.scale.x)
def draw_text(
self,
context: bpy.types.Context,
obj: bpy.types.Object,
text_world_position: Optional[Vector] = None,
reverse_lines_order=False,
) -> None:
"""if `text_world_position` is not provided, the object's location will be used"""
if not text_world_position:
text_world_position = obj.location
region = context.region
region3d = context.region_data
text_dir = self.get_annotation_direction(context, obj)
pos = location_3d_to_region_2d(region, region3d, text_world_position)
props = obj.BIMTextProperties
@@ -539,8 +598,7 @@ class BaseDecorator:
# draw asterisk symbol to indicate that there is some symbol that's not shown in viewport
if symbol:
self.draw_asterisk(context, obj)
# NOTE: for now we assume that scale is uniform
self.draw_symbol(context, obj, text_dir)
text_scale = obj.scale.x
line_i = 0
@@ -548,7 +606,7 @@ class BaseDecorator:
for literal_data in literals_data:
box_alignment = literal_data["BoxAlignment"]
for line in literal_data["CurrentValue"].split("\\n"):
for line in literal_data["CurrentValue"].split("\n"):
self.draw_label(
context,
line,
@@ -812,8 +870,9 @@ class AngleDecorator(BaseDecorator):
base_edge = edge0 if tool.Cad.is_counter_clockwise_order(p0, p1, p2) else edge1
text_offset = (Matrix.Rotation(-angle_rad / 2, 2) @ base_edge).normalized() * (radius + ANGLE_LABEL_OFFSET)
label_position = p1 + text_offset
text = f"{int(angle)}deg"
drawing_pset_data = DrawingsData.data["active_drawing_pset_data"]
decimal_places = drawing_pset_data.get("AngleDecimalPlaces", None)
text = f"{round(angle, decimal_places)}°"
label_dir = Vector((1, 0))
self.draw_label(context, text, label_position, label_dir, box_alignment="center")
@@ -1003,7 +1062,7 @@ class StairDecorator(BaseDecorator):
class MiscDecorator(BaseDecorator):
objecttype = "MISC"
def decorate(self, context, obj):
def decorate(self, context: bpy.types.Context, obj: bpy.types.Object):
if obj.data.is_editmode:
verts, idxs = self.get_editmesh_geom(obj)
else:
@@ -1505,6 +1564,14 @@ class TextDecorator(BaseDecorator):
self.draw_text(context, obj)
class SymbolDecorator(BaseDecorator):
objecttype = "SYMBOL"
def decorate(self, context: bpy.types.Context, obj: bpy.types.Object) -> None:
annotation_dir = self.get_annotation_direction(context, obj)
self.draw_symbol(context, obj, annotation_dir)
class CutDecorator:
installed = None
cache = {}
@@ -1901,7 +1968,7 @@ class CutDecorator:
class DecorationsHandler:
decorators_classes = [
decorators_classes: list[Type[BaseDecorator]] = [
DimensionDecorator,
AngleDecorator,
GridDecorator,
@@ -1918,6 +1985,7 @@ class DecorationsHandler:
SectionDecorator,
ElevationDecorator,
TextDecorator,
SymbolDecorator,
BattingDecorator,
FallDecorator,
]
@@ -1945,6 +2013,7 @@ class DecorationsHandler:
self.decorators = {cls.objecttype: cls() for cls in self.decorators_classes}
for object_type in ("SLOPE_ANGLE", "SLOPE_FRACTION", "SLOPE_PERCENT"):
self.decorators[object_type] = self.decorators["FALL"]
self.decorators["MULTI_SYMBOL"] = self.decorators["SYMBOL"]
def get_objects_and_decorators(self, collection):
# TODO: do it in data instead of the handler for performance?
@@ -1962,7 +2031,7 @@ class DecorationsHandler:
if not element.is_a("IfcAnnotation"):
continue
object_type = element.ObjectType
object_type: Union[str, None] = element.ObjectType
if object_type == "DRAWING":
continue
@@ -23,7 +23,7 @@ from bpy.app.handlers import persistent
@persistent
def toggleDecorationsOnLoad(*args):
def load_post(*args):
if bpy.context.scene.DocProperties.should_draw_decorations:
decoration.DecorationsHandler.install(bpy.context)
else:
@@ -188,7 +188,6 @@ def format_distance(
feet += 1
inches = 0
if not isArea:
add_inches = bool(inches) or not suppress_zero_inches
tx_dist = ""
@@ -299,7 +298,9 @@ def parse_diagram_scale(camera):
return float(numerator) / float(denominator)
def ortho_view_frame(camera, margin=0.015):
def ortho_view_frame(
camera: bpy.types.Camera, margin: float = 0.015
) -> tuple[float, float, float, float, float, float]:
"""Calculates 2d bounding box of camera view area.
Similar to `bpy.types.Camera.view_frame`
@@ -29,6 +29,7 @@ import subprocess
import numpy as np
import multiprocessing
import ifcopenshell
import ifcopenshell.ifcopenshell_wrapper
import ifcopenshell.geom
import ifcopenshell.util.selector
import ifcopenshell.util.representation
@@ -44,7 +45,7 @@ import blenderbim.bim.module.drawing.helper as helper
import blenderbim.bim.export_ifc
from blenderbim.bim.module.drawing.decoration import CutDecorator
from blenderbim.bim.module.drawing.data import DecoratorData, DrawingsData
from typing import NamedTuple, List
from typing import NamedTuple, List, Union, Optional
from lxml import etree
from mathutils import Vector, Color, Matrix
from timeit import default_timer as timer
@@ -199,6 +200,11 @@ class CreateDrawing(bpy.types.Operator):
+ "SHIFT+CLICK to print all selected drawings"
)
print_all: bpy.props.BoolProperty(name="Print All", default=False, options={"SKIP_SAVE"})
sync: bpy.props.BoolProperty(
name="Sync Before Creating Drawing",
description="Could save some time if you're sure IFC and current Blender session are already in sync",
default=True,
)
@classmethod
def poll(cls, context):
@@ -214,13 +220,15 @@ class CreateDrawing(bpy.types.Operator):
def execute(self, context):
self.props = context.scene.DocProperties
active_drawing_id = context.scene.camera.BIMObjectProperties.ifc_definition_id
if self.print_all:
original_drawing_id = self.props.active_drawing_id
original_drawing_id = active_drawing_id
drawings_to_print = [d.ifc_definition_id for d in self.props.drawings if d.is_selected and d.is_drawing]
else:
drawings_to_print = [self.props.active_drawing_id]
drawings_to_print = [active_drawing_id]
for drawing_id in drawings_to_print:
for drawing_i, drawing_id in enumerate(drawings_to_print):
self.drawing_index = drawing_i
if self.print_all:
bpy.ops.bim.activate_drawing(drawing=drawing_id, camera_view_point=False)
@@ -335,6 +343,13 @@ class CreateDrawing(bpy.types.Operator):
if not ifcopenshell.util.element.get_pset(self.drawing, "EPset_Drawing", "HasUnderlay"):
return
svg_path = self.get_svg_path(cache_type="underlay")
if os.path.isfile(svg_path) and self.props.should_use_underlay_cache:
return svg_path
visible_object_names = {obj.name for obj in bpy.context.visible_objects}
for obj in bpy.context.view_layer.objects:
obj.hide_render = obj.name not in visible_object_names
context.scene.render.filepath = svg_path[0:-4] + ".png"
drawing_style = context.scene.DocProperties.drawing_styles[self.cprops.active_drawing_style_index]
@@ -445,8 +460,14 @@ class CreateDrawing(bpy.types.Operator):
return LineworkContexts(body_contexts, annotation_contexts)
def serialize_contexts_elements(
self, ifc, tree: ifcopenshell.geom.tree, contexts: LineworkContexts, context_type, drawing_elements, target_view
):
self,
ifc: ifcopenshell.file,
tree: ifcopenshell.geom.tree,
contexts: LineworkContexts,
context_type: str,
drawing_elements: set[ifcopenshell.entity_instance],
target_view: str,
) -> None:
drawing_elements = drawing_elements.copy()
contexts = getattr(contexts, context_type)
for context in contexts:
@@ -472,20 +493,24 @@ class CreateDrawing(bpy.types.Operator):
tree.add_element(elem)
drawing_elements -= processed
def generate_linework(self, context):
def generate_linework(self, context: bpy.types.Context) -> Union[str, None]:
if not ifcopenshell.util.element.get_pset(self.drawing, "EPset_Drawing", "HasLinework"):
return
svg_path = self.get_svg_path(cache_type="linework")
if os.path.isfile(svg_path) and self.props.should_use_linework_cache:
return svg_path
with profile("sync"):
# All very hackish whilst prototyping
exporter = blenderbim.bim.export_ifc.IfcExporter(None)
exporter.file = tool.Ifc.get()
invalidated_elements = exporter.sync_all_objects()
invalidated_elements += exporter.sync_edited_objects()
invalidated_guids = [e.GlobalId for e in invalidated_elements if hasattr(e, "GlobalId")]
# in case of printing multiple drawings we need to sync just once
if self.sync and self.drawing_index == 0:
with profile("sync"):
# All very hackish whilst prototyping
exporter = blenderbim.bim.export_ifc.IfcExporter(None)
exporter.file = tool.Ifc.get()
invalidated_elements = exporter.sync_all_objects(skip_unlinking=True)
invalidated_elements += exporter.sync_edited_objects()
invalidated_guids = [e.GlobalId for e in invalidated_elements if hasattr(e, "GlobalId")]
cache = IfcStore.get_cache()
[cache.remove(guid) for guid in invalidated_guids]
# If we have already calculated it in the SVG in the past, don't recalculate
edited_guids = set()
@@ -506,6 +531,7 @@ class CreateDrawing(bpy.types.Operator):
for ifc_path, ifc in files.items():
# Don't use draw.main() just whilst we're prototyping and experimenting
# TODO: hash paths are never used
ifc_hash = hashlib.md5(ifc_path.encode("utf-8")).hexdigest()
ifc_cache_path = os.path.join(context.scene.BIMProperties.data_dir, "cache", f"{ifc_hash}.h5")
@@ -518,8 +544,6 @@ class CreateDrawing(bpy.types.Operator):
drawing_elements = tool.Drawing.get_drawing_elements(self.camera_element)
self.setup_serialiser(ifc, target_view)
cache = IfcStore.get_cache()
[cache.remove(guid) for guid in invalidated_guids]
tree = ifcopenshell.geom.tree()
tree.enable_face_styles(True)
@@ -579,8 +603,7 @@ class CreateDrawing(bpy.types.Operator):
if global_id not in elements_with_faces:
continue
for path in projection.findall("./{http://www.w3.org/2000/svg}path"):
# Rounding is necessary to ensure coincident points are coincident
start, end = [[round(float(o), 1) for o in co[1:].split(",")] for co in path.attrib["d"].split()]
start, end = [[float(o) for o in co[1:].split(",")] for co in path.attrib["d"].split()]
if start == end:
continue
# Extension by 0.5mm is necessary to ensure lines overlap with other diagonal lines
@@ -954,7 +977,7 @@ class CreateDrawing(bpy.types.Operator):
for path in el.findall("{http://www.w3.org/2000/svg}path"):
for subpath in path.attrib["d"].split("M")[1:]:
subpath = "M" + subpath.strip()
coords = [[round(float(o), 1) for o in co[1:].split(",")] for co in subpath.split()]
coords = [[float(o) for o in co[1:].split(",")] for co in subpath.split()]
if coords[0] != coords[-1]:
has_open_paths = True
line_strings.append(shapely.LineString(coords))
@@ -1018,7 +1041,7 @@ class CreateDrawing(bpy.types.Operator):
for path in el.findall("{http://www.w3.org/2000/svg}path"):
for subpath in path.attrib["d"].split("M")[1:]:
subpath_co = "M" + subpath.strip(" Z")
coords = [[round(float(o), 1) for o in co[1:].split(",")] for co in subpath_co.split()]
coords = [[float(o) for o in co[1:].split(",")] for co in subpath_co.split()]
if subpath.strip().lower().endswith("z"):
coords.append(coords[0])
if len(coords) > 2 and coords[0] == coords[-1]:
@@ -1116,14 +1139,14 @@ class CreateDrawing(bpy.types.Operator):
continue
return space
def get_material_name(self, element):
def get_material_name(self, element: ifcopenshell.entity_instance) -> str:
if hasattr(element, "Name") and element.Name:
return element.Name
elif hasattr(element, "LayerSetName") and element.LayerSetName:
return element.LayerSetName
return "mat-" + str(element.id())
def get_svg_path(self, cache_type=None):
def get_svg_path(self, cache_type: Optional[str] = None) -> str:
drawing_path = tool.Drawing.get_document_uri(self.camera_document)
drawings_dir = os.path.dirname(drawing_path)
@@ -1220,12 +1243,14 @@ class AddDrawingToSheet(bpy.types.Operator, Operator):
return
reference = tool.Ifc.run("document.add_reference", information=sheet)
id_attr = "ItemReference" if tool.Ifc.get_schema() == "IFC2X3" else "Identification"
attributes = {
id_attr: str(len([r for r in references if r.Description in ("DRAWING", "SCHEDULE")]) + 1),
"Location": drawing_reference.Location,
"Description": "DRAWING",
}
attributes = tool.Drawing.generate_reference_attributes(
reference,
Identification=str(
len([r for r in references if tool.Drawing.get_reference_description(r) in ("DRAWING", "SCHEDULE")]) + 1
),
Location=drawing_reference.Location,
Description="DRAWING",
)
tool.Ifc.run("document.edit_reference", reference=reference, attributes=attributes)
sheet_builder = sheeter.SheetBuilder()
sheet_builder.data_dir = context.scene.BIMProperties.data_dir
@@ -1293,9 +1318,10 @@ class CreateSheets(bpy.types.Operator, Operator):
has_sheet_reference = False
for reference in tool.Drawing.get_document_references(sheet):
if reference.Description == "SHEET":
reference_description = tool.Drawing.get_reference_description(reference)
if reference_description == "SHEET":
has_sheet_reference = True
elif reference.Description == "RASTER":
elif reference_description == "RASTER":
if reference.Location in raster_references:
raster_references.remove(reference.Location)
else:
@@ -1306,7 +1332,9 @@ class CreateSheets(bpy.types.Operator, Operator):
tool.Ifc.run(
"document.edit_reference",
reference=reference,
attributes={"Location": tool.Ifc.get_relative_uri(svg), "Description": "SHEET"},
attributes=tool.Drawing.generate_reference_attributes(
reference, Location=tool.Ifc.get_relative_uri(svg), Description="SHEET"
),
)
for raster_reference in raster_references:
@@ -1314,7 +1342,9 @@ class CreateSheets(bpy.types.Operator, Operator):
tool.Ifc.run(
"document.edit_reference",
reference=reference,
attributes={"Location": tool.Ifc.get_relative_uri(raster_reference), "Description": "RASTER"},
attributes=tool.Drawing.generate_reference_attributes(
reference, Location=tool.Ifc.get_relative_uri(raster_reference), Description="RASTER"
),
)
svg2pdf_command = context.preferences.addons["blenderbim"].preferences.svg2pdf_command
@@ -1346,7 +1376,7 @@ class SelectAllDrawings(bpy.types.Operator):
bl_label = "Select All Drawings"
view: bpy.props.StringProperty()
bl_description = "Select all drawings in the drawing list.\n\n" + "SHIFT+CLICK to deselect all drawings"
select_all: bpy.props.BoolProperty(name="Open All", default=False, options={"SKIP_SAVE"})
select_all: bpy.props.BoolProperty(name="Open All", default=True, options={"SKIP_SAVE"})
def invoke(self, context, event):
# deselect all drawings on shift+click
@@ -1423,13 +1453,19 @@ class ActivateModel(bpy.types.Operator):
CutDecorator.uninstall()
# save current visibility statuses for Views and Types collections
visibility_status: dict[bpy.types.Object, bool] = {}
for col in bpy.data.collections["Views"].children:
for obj in col.objects:
visibility_status[obj] = obj.hide_get()
for obj in bpy.data.collections["Types"].objects:
visibility_status[obj] = obj.hide_get()
if not bpy.app.background:
with context.temp_override(**tool.Blender.get_viewport_context()):
bpy.ops.object.hide_view_clear()
bpy.ops.bim.activate_status_filters()
subcontext = ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model", "Body", "MODEL_VIEW")
for obj in context.visible_objects:
element = tool.Ifc.get_entity(obj)
if not element:
@@ -1447,15 +1483,16 @@ class ActivateModel(bpy.types.Operator):
is_global=True,
should_sync_changes_first=True,
)
# restore visibility after hide_view_clear()
for obj, hide_status in visibility_status.items():
obj.hide_set(hide_status)
tool.Blender.update_viewport()
return {"FINISHED"}
class ActivateDrawing(bpy.types.Operator):
"""
Activates the selected drawing view
"""
bl_idname = "bim.activate_drawing"
bl_label = "Activate Drawing"
bl_options = {"REGISTER", "UNDO"}
@@ -1478,7 +1515,14 @@ class ActivateDrawing(bpy.types.Operator):
if not self.camera_view_point:
viewport_position = tool.Blender.get_viewport_position()
core.activate_drawing_view(tool.Ifc, tool.Drawing, drawing=drawing)
try:
core.activate_drawing_view(tool.Ifc, tool.Blender, tool.Drawing, drawing=drawing)
except core.CameraNotAvailableError:
self.report(
{"ERROR"},
"The drawing view is not available. Ensure you have not excluded it in the active view layer.",
)
return {"CANCELLED"}
if not self.camera_view_point:
tool.Blender.set_viewport_position(viewport_position)
@@ -1601,8 +1645,14 @@ class ReloadDrawingStyles(bpy.types.Operator):
json_path = Path(tool.Ifc.resolve_uri(rel_path))
if not json_path.exists():
self.report({"ERROR"}, "Shading styles file not found: {}".format(json_path))
return {"CANCELLED"}
ootb_resource = Path(context.scene.BIMProperties.data_dir) / "assets" / "shading_styles.json"
print(
f"WARNING. Couldn't find shading_styles for the drawing by the path: {json_path}. "
f"Default BBIM resource will be copied from {ootb_resource}"
)
if ootb_resource.exists():
os.makedirs(json_path.parent, exist_ok=True)
shutil.copy(ootb_resource, json_path)
with open(json_path, "r") as fi:
shading_styles_json = json.load(fi)
@@ -1965,12 +2015,14 @@ class AddScheduleToSheet(bpy.types.Operator, Operator):
return
reference = tool.Ifc.run("document.add_reference", information=sheet)
id_attr = "ItemReference" if tool.Ifc.get_schema() == "IFC2X3" else "Identification"
attributes = {
id_attr: str(len([r for r in references if r.Description in ("DRAWING", "SCHEDULE")]) + 1),
"Location": schedule_location,
"Description": "SCHEDULE",
}
attributes = tool.Drawing.generate_reference_attributes(
reference,
Identification=str(
len([r for r in references if tool.Drawing.get_reference_description(r) in ("DRAWING", "SCHEDULE")]) + 1
),
Location=schedule_location,
Description="SCHEDULE",
)
tool.Ifc.run("document.edit_reference", reference=reference, attributes=attributes)
sheet_builder = sheeter.SheetBuilder()
@@ -2019,12 +2071,15 @@ class AddReferenceToSheet(bpy.types.Operator, Operator):
return
reference = tool.Ifc.run("document.add_reference", information=sheet)
id_attr = "ItemReference" if tool.Ifc.get_schema() == "IFC2X3" else "Identification"
attributes = {
id_attr: str(len([r for r in references if r.Description in ("DRAWING", "REFERENCE")]) + 1),
"Location": extref_location,
"Description": "REFERENCE",
}
attributes = tool.Drawing.generate_reference_attributes(
reference,
Identification=str(
len([r for r in references if tool.Drawing.get_reference_description(r) in ("DRAWING", "REFERENCE")])
+ 1
),
Location=extref_location,
Description="REFERENCE",
)
tool.Ifc.run("document.edit_reference", reference=reference, attributes=attributes)
sheet_builder = sheeter.SheetBuilder()
@@ -2350,6 +2405,7 @@ class LoadSheets(bpy.types.Operator, Operator):
if not filepath.is_file():
sheet_name = f"{sheet_prop.identification} - {sheet_prop.name}"
sheets_not_found.append(f'"{sheet_name}" - {document_uri}')
core.regenerate_sheet(tool.Drawing, sheet)
if sheets_not_found:
self.report({"ERROR"}, "Some sheets svg files are missing:\n" + "\n".join(sheets_not_found))
@@ -2368,8 +2424,8 @@ class EditSheet(bpy.types.Operator, Operator):
if sheet.is_a("IfcDocumentInformation"):
self.document_type = "SHEET"
self.name = sheet.Name
self.identification = sheet.Identification
elif sheet.is_a("IfcDocumentReference") and sheet.Description == "TITLEBLOCK":
self.identification = sheet.DocumentId if tool.Ifc.get_schema() == "IFC2X3" else sheet.Identification
elif sheet.is_a("IfcDocumentReference") and tool.Drawing.get_reference_description(sheet) == "TITLEBLOCK":
self.document_type = "TITLEBLOCK"
else:
self.document_type = "EMBEDDED"
@@ -2395,7 +2451,7 @@ class EditSheet(bpy.types.Operator, Operator):
if self.document_type == "SHEET":
core.rename_sheet(tool.Ifc, tool.Drawing, sheet=sheet, identification=self.identification, name=self.name)
elif self.document_type == "EMBEDDED":
core.rename_reference(tool.Ifc, reference=sheet, identification=self.identification)
core.rename_reference(tool.Ifc, tool.Drawing, reference=sheet, identification=self.identification)
elif self.document_type == "TITLEBLOCK":
titleblock = self.props.titleblock
reference = sheet
@@ -2547,8 +2603,16 @@ class EnableEditingElementFilter(bpy.types.Operator, Operator):
def _execute(self, context):
obj = bpy.context.scene.camera
if obj:
obj.data.BIMCameraProperties.filter_mode = self.filter_mode
if not obj:
return
obj.data.BIMCameraProperties.filter_mode = self.filter_mode
element = tool.Ifc.get_entity(obj)
if query := ifcopenshell.util.element.get_pset(element, "EPset_Drawing", self.filter_mode.title()):
filter_groups = tool.Search.get_filter_groups(f"drawing_{self.filter_mode.lower()}")
try:
tool.Search.import_filter_query(query, filter_groups)
except:
pass
class EditElementFilter(bpy.types.Operator, Operator):
@@ -2690,3 +2754,50 @@ class AddReferenceImage(bpy.types.Operator, Operator):
)
tool.Style.reload_material_from_ifc(material)
tool.Geometry.record_object_materials(obj)
class ConvertSVGToDXF(bpy.types.Operator):
bl_idname = "bim.convert_svg_to_dxf"
bl_label = "Convert SVG to DXF"
bl_options = {"REGISTER", "UNDO"}
view: bpy.props.StringProperty()
bl_description = "Convert current drawing's .svg to .dxf.\n\nSHIFT+CLICK to convert all selected drawings"
convert_all: bpy.props.BoolProperty(name="Convert All", default=False, options={"SKIP_SAVE"})
def invoke(self, context, event):
# convert all drawings on shift+click
# make sure to use SKIP_SAVE on property, otherwise it might get stuck
if event.type == "LEFTMOUSE" and event.shift:
self.open_all = True
return self.execute(context)
def execute(self, context):
if self.convert_all:
drawings = [
tool.Ifc.get().by_id(d.ifc_definition_id) for d in context.scene.DocProperties.drawings if d.is_selected
]
else:
drawings = [tool.Ifc.get().by_id(context.scene.DocProperties.drawings.get(self.view).ifc_definition_id)]
drawing_uris: list[Path] = []
drawings_not_found: list[str] = []
for drawing in drawings:
drawing_uri = tool.Drawing.get_document_uri(tool.Drawing.get_drawing_document(drawing))
if drawing_uri is None or not os.path.exists(drawing_uri):
drawings_not_found.append(drawing.Name)
else:
drawing_uris.append(Path(drawing_uri))
if drawings_not_found:
msg = "Some drawings .svg files were not found, need to print them first: \n{}.".format(
"\n".join(drawings_not_found)
)
self.report({"ERROR"}, msg)
return {"CANCELLED"}
for drawing_uri in drawing_uris:
tool.Drawing.convert_svg_to_dxf(drawing_uri, drawing_uri.with_suffix(".dxf"))
self.report({"INFO"}, f"{len(drawing_uris)} drawings were converted to .dxf.")
return {"FINISHED"}
@@ -66,6 +66,11 @@ def get_location_hint(self, context):
def update_diagram_scale(self, context):
if not context.scene.camera or context.scene.camera.data != self.id_data:
return
element = tool.Ifc.get_entity(context.scene.camera)
if not element:
return
try:
element = (
tool.Ifc.get()
@@ -87,6 +92,11 @@ def update_diagram_scale(self, context):
def update_is_nts(self, context):
if not context.scene.camera or context.scene.camera.data != self.id_data:
return
element = tool.Ifc.get_entity(context.scene.camera)
if not element:
return
try:
element = (
tool.Ifc.get()
@@ -194,7 +204,7 @@ def update_document_name(self, context):
def update_has_underlay(self, context):
update_layer(self, context, "HasUnderlay", self.has_underlay)
# making sure that camera is active
if self.has_underlay and (context.active_object and context.active_object.data == self.id_data):
if self.has_underlay and (context.scene.camera and context.scene.camera.data == self.id_data):
bpy.ops.bim.reload_drawing_styles()
bpy.ops.bim.activate_drawing_style()
@@ -208,10 +218,12 @@ def update_has_annotation(self, context):
def update_layer(self, context, name, value):
element = tool.Ifc.get_entity(context.active_object)
if not context.scene.camera or context.scene.camera.data != self.id_data:
return
element = tool.Ifc.get_entity(context.scene.camera)
if not element:
return
pset = ifcopenshell.util.element.get_psets(element).get("EPset_Drawing")
pset = ifcopenshell.util.element.get_pset(element, "EPset_Drawing")
if pset:
pset = tool.Ifc.get().by_id(pset["id"])
else:
@@ -229,9 +241,8 @@ def update_titleblocks(self, context):
SheetsData.data["titleblocks"] = SheetsData.titleblocks()
def toggleDecorations(self, context):
toggle = self.should_draw_decorations
if toggle:
def update_should_draw_decorations(self, context):
if self.should_draw_decorations:
# TODO: design a proper text variable templating renderer
collection = context.scene.camera.BIMObjectProperties.collection
for obj in collection.objects:
@@ -343,7 +354,7 @@ class DocProperties(PropertyGroup):
active_sheet_index: IntProperty(name="Active Sheet Index")
ifc_files: CollectionProperty(name="IFCs", type=StrProperty)
drawing_styles: CollectionProperty(name="Drawing Styles", type=DrawingStyle)
should_draw_decorations: BoolProperty(name="Should Draw Decorations", update=toggleDecorations)
should_draw_decorations: BoolProperty(name="Should Draw Decorations", update=update_should_draw_decorations)
sheets_dir: StringProperty(default=os.path.join("sheets") + os.path.sep, name="Default Sheets Directory")
layouts_dir: StringProperty(default=os.path.join("layouts") + os.path.sep, name="Default Layouts Directory")
titleblocks_dir: StringProperty(
@@ -41,7 +41,7 @@ class SheetBuilder:
self.data_dir = None
self.scale = "NTS"
def create(self, layout_path, titleblock_name):
def create(self, layout_path: str, titleblock_name: str) -> None:
root = ET.Element("svg")
root.attrib["xmlns"] = "http://www.w3.org/2000/svg"
root.attrib["xmlns:xlink"] = "http://www.w3.org/1999/xlink"
@@ -76,7 +76,12 @@ class SheetBuilder:
with open(layout_path, "w") as f:
f.write(minidom.parseString(ET.tostring(root)).toprettyxml(indent=" "))
def add_drawing(self, reference, drawing, sheet):
def add_drawing(
self,
reference: ifcopenshell.entity_instance,
drawing: ifcopenshell.entity_instance,
sheet: ifcopenshell.entity_instance,
) -> None:
filename = drawing.Name
layout_path = tool.Drawing.get_document_uri(sheet, "LAYOUT")
layout_dir = os.path.dirname(layout_path)
@@ -131,7 +136,7 @@ class SheetBuilder:
)
layout_tree.write(layout_path)
def update_sheet_drawing_sizes(self, sheet):
def update_sheet_drawing_sizes(self, sheet: ifcopenshell.entity_instance) -> None:
ET.register_namespace("", "http://www.w3.org/2000/svg")
layout_path = tool.Drawing.get_document_uri(sheet, "LAYOUT")
@@ -171,7 +176,7 @@ class SheetBuilder:
layout_tree.write(layout_path)
def remove_drawing(self, reference, sheet):
def remove_drawing(self, reference: ifcopenshell.entity_instance, sheet: ifcopenshell.entity_instance) -> None:
ET.register_namespace("", "http://www.w3.org/2000/svg")
layout_path = tool.Drawing.get_document_uri(sheet, "LAYOUT")
@@ -187,7 +192,12 @@ class SheetBuilder:
layout_tree.write(layout_path)
def add_document(self, reference, document, sheet):
def add_document(
self,
reference: ifcopenshell.entity_instance,
document: ifcopenshell.entity_instance,
sheet: ifcopenshell.entity_instance,
) -> None:
view_path = tool.Drawing.get_path_with_ext(tool.Drawing.get_document_uri(document), "svg")
if not os.path.exists(view_path):
tool.Drawing.create_svg_document(document)
@@ -224,7 +234,7 @@ class SheetBuilder:
)
layout_tree.write(layout_path)
def add_view_title(self, x, y, parent, layout_dir):
def add_view_title(self, x: float, y: float, parent: ET.Element, layout_dir: str) -> None:
title_path = os.path.join(layout_dir, "assets", "view-title.svg")
os.makedirs(os.path.dirname(title_path), exist_ok=True)
if not os.path.exists(title_path):
@@ -241,7 +251,7 @@ class SheetBuilder:
title.attrib["width"] = str(self.convert_to_mm(title_root.attrib.get("width")))
title.attrib["height"] = str(self.convert_to_mm(title_root.attrib.get("height")))
def build(self, sheet):
def build(self, sheet: ifcopenshell.entity_instance) -> dict:
self.references = {"SHEET": None, "RASTER": []}
layout_path = tool.Drawing.get_document_uri(sheet, "LAYOUT")
@@ -272,7 +282,7 @@ class SheetBuilder:
return self.references
def build_titleblock(self, root, sheet):
def build_titleblock(self, root: ET.Element, sheet: ifcopenshell.entity_instance) -> None:
titleblock = root.findall('{http://www.w3.org/2000/svg}g[@data-type="titleblock"]')[0]
image = titleblock.findall("{http://www.w3.org/2000/svg}image")[0]
g = self.parse_embedded_svg(image, sheet.get_info())
@@ -285,7 +295,7 @@ class SheetBuilder:
titleblock.append(g)
titleblock.remove(image)
def ensure_drawing_unique_styles(self, svg, drawing_id):
def ensure_drawing_unique_styles(self, svg: ET.Element, drawing_id: int) -> ET.Element:
"""ensures all drawing's classes and ids will be unique for the whole sheet
by adding `drawing_id` based prefix
"""
@@ -313,7 +323,7 @@ class SheetBuilder:
brackets_level -= 1
text += l
def replace_urls(text):
def replace_urls(text: str) -> str:
"""replace urls `url(#marker)` with `url(#prefix-marker)`
since `url(#marker.prefix)` doesn't seem to work
"""
@@ -343,7 +353,7 @@ class SheetBuilder:
return svg
def build_drawings(self, root, sheet):
def build_drawings(self, root: ET.Element, sheet: ifcopenshell.entity_instance):
for view in root.findall('{http://www.w3.org/2000/svg}g[@data-type="drawing"]'):
drawing_id = int(view.attrib["data-id"])
try:
@@ -390,7 +400,7 @@ class SheetBuilder:
for image in images:
view.remove(image)
def build_documents(self, root, sheet):
def build_documents(self, root: ET.Element, sheet: ifcopenshell.entity_instance) -> None:
schedules = root.findall('{http://www.w3.org/2000/svg}g[@data-type="schedule"]')
references = root.findall('{http://www.w3.org/2000/svg}g[@data-type="reference"]')
documents = schedules + references
@@ -427,10 +437,10 @@ class SheetBuilder:
for image in images:
view.remove(image)
def get_href(self, element):
return urllib.parse.unquote(element.attrib.get("{http://www.w3.org/1999/xlink}href")).replace('\\','/')
def get_href(self, element: ET.Element) -> str:
return urllib.parse.unquote(element.attrib.get("{http://www.w3.org/1999/xlink}href")).replace("\\", "/")
def parse_embedded_svg(self, image, data):
def parse_embedded_svg(self, image: ET.Element, data: dict) -> ET.Element:
group = ET.Element("g")
group.attrib["transform"] = "translate({},{})".format(
self.convert_to_mm(image.attrib.get("x")), self.convert_to_mm(image.attrib.get("y"))
@@ -466,7 +476,7 @@ class SheetBuilder:
group.append(child)
return group
def change_titleblock(self, sheet, titleblock_name):
def change_titleblock(self, sheet: ifcopenshell.entity_instance, titleblock_name: str) -> None:
ootb_titleblock_path = os.path.join(self.data_dir, "templates", "titleblocks", titleblock_name + ".svg")
titleblock_path = tool.Drawing.get_default_titleblock_path(titleblock_name)
sheet_path = tool.Drawing.get_document_uri(sheet, "LAYOUT")
@@ -499,7 +509,7 @@ class SheetBuilder:
sheet_tree.write(sheet_path)
def convert_to_mm(self, value):
def convert_to_mm(self, value: str) -> float:
# CSS is what defines these possibilities
# https://www.w3.org/TR/SVG/refs.html#ref-css-values-3
# https://www.w3.org/TR/css-values-3/#absolute-lengths
@@ -520,5 +530,5 @@ class SheetBuilder:
return float(value[0:-2]) * (1 / 96) * 2.54 * 10
return float(value)
def mm_to_px(self, value):
def mm_to_px(self, value: float) -> float:
return (value / 25.4) * 96
@@ -29,9 +29,11 @@ import svgwrite
import ifcopenshell
import ifcopenshell.util.element
import ifcopenshell.util.representation
import ifcopenshell.util.selector
import blenderbim.tool as tool
import blenderbim.bim.module.drawing.helper as helper
import blenderbim.bim.module.drawing.annotation as annotation
from blenderbim.bim.module.drawing.data import DrawingsData
from blenderbim.bim.module.drawing.data import DecoratorData
from blenderbim.bim.ifc import IfcStore
@@ -39,6 +41,7 @@ from blenderbim.bim.ifc import IfcStore
from math import pi, ceil, atan, degrees, acos
from mathutils import geometry, Vector
from bpy_extras import view3d_utils
from typing import Optional
class External(svgwrite.container.Group):
@@ -90,7 +93,7 @@ class SvgWriter:
def setup_drawing_resource_paths(self, element):
pset = ifcopenshell.util.element.get_pset(element, "EPset_Drawing")
for resource in ("Stylesheet", "Markers", "Symbols", "Patterns"):
for resource in ("Stylesheet", "Markers", "Symbols", "Patterns", "ShadingStyles"):
resource_path = pset.get(resource)
if not resource_path:
self.resource_paths[resource] = None
@@ -322,7 +325,7 @@ class SvgWriter:
)
)
def draw_misc_annotation(self, obj):
def draw_misc_annotation(self, obj: bpy.types.Object) -> None:
# We have to decide whether this should come from Blender or from IFC.
# For the moment, for convenience of experimenting with ideas, it comes
# from Blender. In the future, it should probably come from IFC.
@@ -356,14 +359,14 @@ class SvgWriter:
d = "M{}".format(d[1:])
path = self.svg.add(self.svg.path(d=d, class_=" ".join(classes)))
def get_attribute_classes(self, obj):
def get_attribute_classes(self, obj: bpy.types.Object) -> list[str]:
element = tool.Ifc.get_entity(obj)
global_id = "GlobalId-{}".format(element.GlobalId)
predefined_type = "PredefinedType-" + tool.Drawing.canonicalise_class_name(
str(ifcopenshell.util.element.get_predefined_type(element))
)
classes = [global_id, element.is_a(), predefined_type]
custom_classes = ifcopenshell.util.element.get_pset(element, "EPset_Annotation", "Classes")
custom_classes: str = ifcopenshell.util.element.get_pset(element, "EPset_Annotation", "Classes")
if custom_classes:
classes.extend(custom_classes.split())
for key in self.metadata:
@@ -716,14 +719,15 @@ class SvgWriter:
self.svg.text(sheet_id, insert=(text_position[0], text_position[1] + 2.5), class_="ELEVATION", **text_style)
)
def get_reference_and_sheet_id_from_annotation(self, element):
def get_reference_and_sheet_id_from_annotation(self, element: ifcopenshell.entity_instance) -> tuple[str, str]:
reference_id = "-"
sheet_id = "-"
drawing = tool.Drawing.get_annotation_element(element)
reference = tool.Drawing.get_drawing_reference(drawing)
if reference:
for sheet_reference in tool.Ifc.get().by_type("IfcDocumentReference"):
if sheet_reference.Description != "DRAWING" or sheet_reference.Location != reference.Location:
reference_description = tool.Drawing.get_reference_description(sheet_reference)
if reference_description != "DRAWING" or sheet_reference.Location != reference.Location:
continue
sheet = tool.Drawing.get_reference_document(sheet_reference)
if sheet:
@@ -787,7 +791,7 @@ class SvgWriter:
attrib["filter"] = "url(#fill-background)"
return element
def draw_text_annotation(self, text_obj, position):
def draw_text_annotation(self, text_obj: bpy.types.Object, position: Vector) -> None:
x_offset = self.raw_width / 2
y_offset = self.raw_height / 2
element = tool.Ifc.get_entity(text_obj)
@@ -799,17 +803,7 @@ class SvgWriter:
text_position_svg = text_position * self.svg_scale
text_position_svg_str = ", ".join(map(str, text_position_svg))
def get_basis_vector(matrix, i=0):
"""returns basis vector for i in world space, unaffected by object scale"""
return matrix.inverted()[i].to_3d().normalized()
text_dir_world_x_axis = get_basis_vector(text_obj.matrix_world)
# RCP cameras may be scaled, so reset scales.
camera_matrix = tool.Drawing.get_camera_matrix(self.camera)
text_dir = (camera_matrix.inverted().to_quaternion() @ text_dir_world_x_axis).to_2d().normalized()
angle = math.degrees(-text_dir.angle_signed(Vector((1, 0))))
angle = self.get_empty_object_angle(text_obj)
classes = self.get_attribute_classes(text_obj)
classes_str = " ".join(classes)
fill_bg = "fill-bg" in classes
@@ -817,8 +811,7 @@ class SvgWriter:
symbol = tool.Drawing.get_annotation_symbol(element)
template_text_fields = []
if symbol:
# NOTE: for now we assume that scale is uniform
symbol_transform = f"translate({text_position_svg_str}) rotate({angle}) scale({text_obj.scale.x})"
symbol_transform = self.get_symbol_transform(text_position_svg_str, angle, text_obj)
symbol_svg = self.find_xml_symbol_by_id(symbol)
if symbol_svg:
@@ -847,7 +840,7 @@ class SvgWriter:
return None
if not symbol_svg or not template_text_fields:
self.svg.add(self.svg.use(f"#{symbol}", transform=symbol_transform))
self.draw_symbol(symbol, symbol_transform)
line_number = 0
for text_literal in text_literals:
@@ -865,17 +858,14 @@ class SvgWriter:
self.svg.add(tag)
line_number += len(tag.elements)
def draw_empty_annotation(self, obj, classes):
def draw_empty_annotation(self, obj: bpy.types.Object, classes: list[str]) -> None:
x_offset = self.raw_width / 2
y_offset = self.raw_height / 2
point = self.project_point_onto_camera(obj.matrix_world.translation)
element = tool.Ifc.get_entity(obj)
svg_id = ifcopenshell.util.element.get_pset(element, "EPset_Annotation", "Symbol")
if not svg_id:
# EPset_AnnotationSurveyArea is not standard! See bSI-4.3 proposal #660.
svg_id = ifcopenshell.util.element.get_pset(element, "EPset_AnnotationSurveyArea", "PointType")
svg_id = tool.Drawing.get_annotation_symbol(element)
if not svg_id:
svg_id = str(ifcopenshell.util.element.get_predefined_type(element))
if not svg_id:
@@ -883,9 +873,37 @@ class SvgWriter:
point = Vector(((x_offset + point.x), (y_offset - point.y)))
symbol_position_svg = point * self.svg_scale
self.svg.add(self.svg.use(f"#{svg_id}", insert=symbol_position_svg))
svg_position_str = ", ".join(map(str, symbol_position_svg))
def draw_point_annotation(self, obj, classes):
angle = self.get_empty_object_angle(obj)
symbol_transform = self.get_symbol_transform(svg_position_str, angle, obj)
self.draw_symbol(svg_id, symbol_transform)
def get_empty_object_angle(self, obj: bpy.types.Object) -> float:
def get_basis_vector(matrix: bpy.types.Object, i: int = 0) -> Vector:
"""returns basis vector for i in world space, unaffected by object scale"""
return matrix.inverted()[i].to_3d().normalized()
text_dir_world_x_axis = get_basis_vector(obj.matrix_world)
# RCP cameras may be scaled, so reset scales.
camera_matrix = tool.Drawing.get_camera_matrix(self.camera)
text_dir = (camera_matrix.inverted().to_quaternion() @ text_dir_world_x_axis).to_2d().normalized()
return math.degrees(-text_dir.angle_signed(Vector((1, 0))))
def get_symbol_transform(
self, svg_position_str: str, angle: float = 0.0, obj: Optional[bpy.types.Object] = None
) -> str:
"""`obj` will be used to identify symbol scale,
ignore it if object scale doesn't match the symbol scale"""
# NOTE: for now we assume that scale is uniform
scale = 1.0 if not obj else obj.scale.x
return f"translate({svg_position_str}) rotate({angle}) scale({scale})"
def draw_symbol(self, symbol_id: str, symbol_transform: str) -> None:
self.svg.add(self.svg.use(f"#{symbol_id}", transform=symbol_transform))
def draw_point_annotation(self, obj: bpy.types.Object, classes: list[str]) -> None:
x_offset = self.raw_width / 2
y_offset = self.raw_height / 2
@@ -893,10 +911,7 @@ class SvgWriter:
projected_points = [self.project_point_onto_camera(matrix_world @ v.co) for v in obj.data.vertices]
element = tool.Ifc.get_entity(obj)
svg_id = ifcopenshell.util.element.get_pset(element, "EPset_Annotation", "Symbol")
if not svg_id:
# EPset_AnnotationSurveyArea is not standard! See bSI-4.3 proposal #660.
svg_id = ifcopenshell.util.element.get_pset(element, "EPset_AnnotationSurveyArea", "PointType")
svg_id = tool.Drawing.get_annotation_symbol(element)
if not svg_id:
svg_id = str(ifcopenshell.util.element.get_predefined_type(element))
if not svg_id:
@@ -905,7 +920,10 @@ class SvgWriter:
for symbol_position in projected_points:
symbol_position = Vector(((x_offset + symbol_position.x), (y_offset - symbol_position.y)))
symbol_position_svg = symbol_position * self.svg_scale
self.svg.add(self.svg.use(f"#{svg_id}", insert=symbol_position_svg))
svg_position_str = ", ".join(map(str, symbol_position_svg))
symbol_transform = self.get_symbol_transform(svg_position_str)
self.draw_symbol(svg_id, symbol_transform)
def draw_break_annotations(self, obj):
x_offset = self.raw_width / 2
@@ -1066,7 +1084,9 @@ class SvgWriter:
# calculating text parameters and adding text
text_position = arc_mid_point + arc_mid_dir * 5
text_style = SvgWriter.get_box_alignment_parameters("center")
angle_text = f"{int(angle)}deg"
drawing_pset_data = DrawingsData.data["active_drawing_pset_data"]
decimal_places = drawing_pset_data.get("AngleDecimalPlaces", None)
angle_text = f"{round(angle, decimal_places)}°"
self.svg.add(self.svg.text(angle_text, insert=text_position, class_="ANGLE", **text_style))
# Draw SVG arc, see for details: http://xahlee.info/js/svg_circle_arc.html
@@ -1402,7 +1422,7 @@ class SvgWriter:
return text_tags
def project_point_onto_camera(self, point):
def project_point_onto_camera(self, point: Vector) -> Vector:
# TODO is this needlessly complex?
return self.camera.matrix_world.inverted() @ geometry.intersect_line_plane(
point.xyz,
@@ -268,6 +268,11 @@ class BIM_PT_drawings(Panel):
col = row.column()
col.alignment = "RIGHT"
convert_to_dxf = row.row(align=True)
op = convert_to_dxf.operator("bim.convert_svg_to_dxf", text="", icon="IMAGE_DATA")
op.view = active_drawing.name
convert_to_dxf.enabled = active_drawing.ifc_definition_id > 0
op = row.operator("bim.select_all_drawings", icon="SELECT_SUBTRACT", text="")
open_drawing_button = row.row(align=True)
@@ -250,7 +250,7 @@ class AnnotationToolUI:
row = cls.layout.row(align=True)
row.label(text="", icon="FILE_3D")
prop_with_search(row, cls.props, "relating_type_id", text="")
row.operator("bim.launch_annotation_type_manager", icon="LIGHTPROBE_GRID", text="")
row.operator("bim.launch_annotation_type_manager", icon=tool.Blender.TYPE_MANAGER_ICON, text="")
add_layout_hotkey_operator(cls.layout, "Add", "S_A", "Create a new annotation")
@@ -17,6 +17,7 @@
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
import ifcopenshell.util.element
import blenderbim.tool as tool
import ifcopenshell.util.placement
from mathutils import Vector
@@ -96,6 +97,8 @@ class RepresentationsData:
@classmethod
def shape_aspects(cls):
obj = bpy.context.active_object
if not obj.data:
return []
element = tool.Ifc.get_entity(obj)
active_representation_id = obj.data.BIMMeshProperties.ifc_definition_id
base_representation = tool.Ifc.get().by_id(active_representation_id)
@@ -220,7 +223,7 @@ class ConnectionsData:
@classmethod
def is_connection_realization(cls):
element = tool.Ifc.get_entity(bpy.context.active_object)
connections = element.IsConnectionRealization
connections = getattr(element, "IsConnectionRealization", None)
if not connections:
return
@@ -21,12 +21,14 @@ import bmesh
import mathutils
import ifcopenshell
import ifcopenshell.util.unit
import blenderbim.tool as tool
from math import pi, pow
from mathutils import Vector, Matrix, geometry
from typing import Union
class Helper:
def __init__(self, file):
def __init__(self, file: ifcopenshell.file):
self.file = file
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(self.file)
@@ -34,7 +36,7 @@ class Helper:
# edge that shares a single vertex only with that face to find the extrusion
# edge. A face with the normal facing down is prioritised. A limited
# dissolve ensure that faces are quads and not tris.
def auto_detect_rectangle_profile_extruded_area_solid(self, mesh):
def auto_detect_rectangle_profile_extruded_area_solid(self, mesh: bpy.types.Mesh) -> dict:
bm = bmesh.new()
bm.from_mesh(mesh)
bmesh.ops.dissolve_limit(bm, angle_limit=pi / 180 * 1, verts=bm.verts, edges=bm.edges)
@@ -115,7 +117,9 @@ class Helper:
return {"profile": profile, "extrusion": extrusion}
def auto_detect_arbitrary_profile_with_voids(self, obj, mesh):
def auto_detect_arbitrary_profile_with_voids(
self, obj: bpy.types.Object, mesh: bpy.types.Mesh
) -> Union[tuple, dict]:
groups = {"IFCARCINDEX": [], "IFCCIRCLE": []}
for i, group in enumerate(obj.vertex_groups):
if "IFCARCINDEX" in group.name:
@@ -138,28 +142,31 @@ class Helper:
total_groups = 0
is_circle = False
for group_type, group_indices in groups.items():
for group_index in group_indices:
if group_index in vert[deform_layer]:
if group_type == "IFCCIRCLE":
is_circle = True
group_verts[group_type].setdefault(group_index, 0)
group_verts[group_type][group_index] += 1
total_groups += 0
if not group_indices:
continue
is_special, group_index = tool.Blender.bmesh_check_vertex_in_groups(vert, deform_layer, group_indices)
if not is_special:
continue
if group_type == "IFCCIRCLE":
is_circle = True
group_verts[group_type].setdefault(group_index, 0)
group_verts[group_type][group_index] += 1
total_groups += 0
if total_groups > 1: # A vert can only belong to one group
return (False, "AMBIGUOUS_SPECIAL_VERTEX")
elif is_circle:
pass # Circles are allowed to be unclosed
pass # Circles are allowed to be unclosed
elif total_groups == 0 and len(vert.link_edges) != 2: # Unclosed loop or forked loop
return (False, "UNCLOSED_LOOP")
for group_type, group_counts in group_verts.items():
if group_type == "IFCARCINDEX":
for group_count in group_counts.values():
if group_count != 3: # Each arc needs 3 verts
if group_count != 3: # Each arc needs 3 verts
return (False, "3POINT_ARC")
elif group_type == "IFCCIRCLE":
for group_count in group_counts.values():
if group_count != 2: # Each circle needs 2 verts
if group_count != 2: # Each circle needs 2 verts
return (False, "CIRCLE")
loop_edges = set(bm.edges)
@@ -269,7 +276,7 @@ class Helper:
inner_loops.remove(outer_loop)
# Copy vectors to prevent random data mangling after bmesh is freed.
points = [Vector(list(v.co)) for v in bm.verts]
points = [v.co.copy() for v in bm.verts]
bm.to_mesh(mesh)
mesh.update()
@@ -531,7 +538,7 @@ class Helper:
)
position = None
if self.file.schema == "IFC2X3":
position = self.file.createIfcAxis2Placement2D(self.file.createIfcCartesianPoint([0.0, 0.0, 0.0]))
position = self.file.createIfcAxis2Placement2D(self.file.createIfcCartesianPoint([0.0, 0.0]))
curve = self.file.createIfcRectangleProfileDef("AREA", None, position, xdim, ydim)
return {"curve_ucs": curve_ucs, "curve": curve}
@@ -26,6 +26,7 @@ import ifcopenshell
import ifcopenshell.util.unit
import ifcopenshell.util.element
import ifcopenshell.util.representation
import ifcopenshell.util.placement
import ifcopenshell.api
import blenderbim.core.geometry as core
import blenderbim.core.aggregate
@@ -37,6 +38,7 @@ import blenderbim.bim.handler
from mathutils import Vector, Matrix
from time import time
from blenderbim.bim.ifc import IfcStore
from ifcopenshell.util.shape_builder import ShapeBuilder
class Operator:
@@ -134,19 +136,23 @@ class AddRepresentation(bpy.types.Operator, Operator):
"for Profile - 2D bounding box by local XZ axes.\n"
"For other contexts - bounding box is 3d.",
),
("PROJECT", "Full Representation", ""),
("OBJECT", "From Object", "Copies geometry from another object"),
("PROJECT", "Full Representation", "Reuses the current representation"),
],
name="Representation Conversion Method",
)
def _execute(self, context):
obj = context.active_object
props = obj.BIMGeometryProperties
ifc_context = int(props.contexts or "0") or None
props = context.scene.BIMGeometryProperties
oprops = obj.BIMGeometryProperties
ifc_context = int(oprops.contexts or "0") or None
if not ifc_context:
return
ifc_context = tool.Ifc.get().by_id(ifc_context)
original_data = obj.data
if self.representation_conversion_method == "OUTLINE":
if ifc_context.ContextType == "Plan":
data = tool.Geometry.generate_outline_mesh(obj, axis="+Z")
@@ -163,17 +169,31 @@ class AddRepresentation(bpy.types.Operator, Operator):
else:
data = tool.Geometry.generate_3d_box_mesh(obj)
tool.Geometry.change_object_data(obj, data, is_global=True)
elif (
self.representation_conversion_method == "OBJECT"
and props.representation_from_object
and props.representation_from_object.data
):
data = tool.Geometry.duplicate_object_data(props.representation_from_object)
tool.Geometry.change_object_data(obj, data, is_global=True)
core.add_representation(
tool.Ifc,
tool.Geometry,
tool.Style,
tool.Surveyor,
obj=obj,
context=ifc_context,
ifc_representation_class=None,
profile_set_usage=None,
)
try:
core.add_representation(
tool.Ifc,
tool.Geometry,
tool.Style,
tool.Surveyor,
obj=obj,
context=ifc_context,
ifc_representation_class=None,
profile_set_usage=None,
)
except core.IncompatibleRepresentationError:
if obj.data != original_data:
tool.Geometry.change_object_data(obj, original_data, is_global=True)
bpy.data.meshes.remove(data)
self.report({"ERROR"}, "No compatible representation for the context could be created.")
return {"CANCELLED"}
def invoke(self, context, event):
return context.window_manager.invoke_props_dialog(self)
@@ -181,6 +201,9 @@ class AddRepresentation(bpy.types.Operator, Operator):
def draw(self, context):
row = self.layout.row()
row.prop(self, "representation_conversion_method", text="")
if self.representation_conversion_method == "OBJECT":
row = self.layout.row()
row.prop(context.scene.BIMGeometryProperties, "representation_from_object", text="")
class SelectConnection(bpy.types.Operator, Operator):
@@ -213,6 +236,13 @@ class SwitchRepresentation(bpy.types.Operator, Operator):
disable_opening_subtractions: bpy.props.BoolProperty()
should_switch_all_meshes: bpy.props.BoolProperty()
@classmethod
def poll(cls, context):
if context.active_object.mode == "OBJECT":
return True
cls.poll_message_set("Only available in OBJECT mode - Press TAB in the viewport")
return False
def _execute(self, context):
target_representation = tool.Ifc.get().by_id(self.ifc_definition_id)
target = target_representation.ContextOfItems
@@ -221,6 +251,8 @@ class SwitchRepresentation(bpy.types.Operator, Operator):
element = tool.Ifc.get_entity(obj)
if not element:
continue
if not obj.mode == "OBJECT":
continue
if obj == context.active_object:
representation = target_representation
else:
@@ -322,8 +354,8 @@ class UpdateRepresentation(bpy.types.Operator, Operator):
# We are explicitly casting to a tessellation, so remove all parametric materials.
element_type = ifcopenshell.util.element.get_type(product)
if element_type: # Some invalid IFCs use material sets without a type.
ifcopenshell.api.run("material.unassign_material", tool.Ifc.get(), product=element_type)
ifcopenshell.api.run("material.unassign_material", tool.Ifc.get(), product=product)
ifcopenshell.api.run("material.unassign_material", tool.Ifc.get(), products=[element_type])
ifcopenshell.api.run("material.unassign_material", tool.Ifc.get(), products=[product])
else:
# These objects are parametrically based on an axis and should not be modified as a mesh
return
@@ -536,6 +568,8 @@ class OverrideDelete(bpy.types.Operator):
row.prop(self, "is_batch", text="Enable Faster Deletion")
def _execute(self, context):
start_time = time()
if self.is_batch:
ifcopenshell.util.element.batch_remove_deep2(tool.Ifc.get())
@@ -560,6 +594,11 @@ class OverrideDelete(bpy.types.Operator):
IfcStore.add_transaction_operation(self)
# Required otherwise gizmos are still visible
context.view_layer.objects.active = None
operator_time = time() - start_time
if operator_time > 10:
self.report({"INFO"}, "IFC Delete was finished in {:.2f} seconds".format(operator_time))
return {"FINISHED"}
def rollback(self, data):
@@ -841,7 +880,7 @@ class OverrideDuplicateMove(bpy.types.Operator):
# Recreate decompositions
tool.Root.recreate_decompositions(decomposition_relationships, old_to_new)
OverrideDuplicateMove.handle_linked_aggregates(old_to_new)
OverrideDuplicateMove.remove_linked_aggregate_data(old_to_new)
blenderbim.bim.handler.refresh_ui_data()
return old_to_new
@@ -894,25 +933,21 @@ class OverrideDuplicateMove(bpy.types.Operator):
if entity in old_to_new.keys():
core.remove_connection(tool.Geometry, connection=connection)
@staticmethod
def handle_linked_aggregates(old_to_new):
def remove_linked_aggregate_data(old_to_new):
for old, new in old_to_new.items():
pset = ifcopenshell.util.element.get_pset(new[0], "BBIM_Linked_Aggregate")
if pset:
old_aggregate = ifcopenshell.util.element.get_aggregate(old)
new_aggregate = ifcopenshell.util.element.get_aggregate(new[0])
if old_aggregate == new_aggregate:
parts = ifcopenshell.util.element.get_parts(new_aggregate)
if parts:
index = DuplicateMoveLinkedAggregate.get_max_index(parts)
index += 1
pset = tool.Ifc.get().by_id(pset['id'])
ifcopenshell.api.run(
"pset.edit_pset",
tool.Ifc.get(),
pset=pset,
properties={"Index": index},
)
pset = tool.Ifc.get().by_id(pset["id"])
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), pset=pset)
if new[0].is_a("IfcElementAssembly"):
linked_aggregate_group = [
r.RelatingGroup
for r in getattr(new[0], "HasAssignments", []) or []
if r.is_a("IfcRelAssignsToGroup")
if "BBIM_Linked_Aggregate" in r.RelatingGroup.Name
]
tool.Ifc.run("group.unassign_group", group=linked_aggregate_group[0], products=[new[0]])
class OverrideDuplicateMoveLinkedMacro(bpy.types.Macro):
@@ -938,6 +973,7 @@ class OverrideDuplicateMoveLinked(bpy.types.Operator):
class DuplicateMoveLinkedAggregateMacro(bpy.types.Macro):
bl_description = "Create a new linked aggregate"
bl_idname = "bim.object_duplicate_move_linked_aggregate_macro"
bl_label = "IFC Duplicate Linked Aggregate"
bl_options = {"REGISTER", "UNDO"}
@@ -972,34 +1008,33 @@ class DuplicateMoveLinkedAggregate(bpy.types.Operator):
obj.select_set(True)
parts = ifcopenshell.util.element.get_parts(element)
if parts:
index = DuplicateMoveLinkedAggregate.get_max_index(parts)
index = get_max_index(parts)
add_linked_aggregate_pset(element, index)
index +=1
index += 1
for part in parts:
if part.is_a("IfcElementAssembly"):
select_objects_and_add_data(part)
else:
add_linked_aggregate_pset(part, index)
index += 1
index = add_linked_aggregate_pset(part, index)
# index += 1
obj = tool.Ifc.get_object(part)
obj.select_set(True)
def add_linked_aggregate_pset(part, index):
pset = ifcopenshell.util.element.get_pset(part, self.pset_name)
if not pset:
pset = ifcopenshell.api.run(
"pset.add_pset", tool.Ifc.get(), product=part, name=self.pset_name
)
pset = ifcopenshell.api.run("pset.add_pset", tool.Ifc.get(), product=part, name=self.pset_name)
ifcopenshell.api.run(
"pset.edit_pset",
tool.Ifc.get(),
pset=pset,
properties={"Index": index},
)
index += 1
else:
pass
@@ -1027,21 +1062,53 @@ class DuplicateMoveLinkedAggregate(bpy.types.Operator):
if r.is_a("IfcRelAssignsToGroup")
if "BBIM_Linked_Aggregate" in r.RelatingGroup.Name
][0]
number = len(group_elements) - 1
number = f"{number:02d}"
new_obj = tool.Ifc.get_object(new[0])
pattern1 = r'_\d'
pattern1 = r"_\d"
if re.findall(pattern1, new_obj.name):
split_name = new_obj.name.split("_")
new_obj.name = split_name[0] + "_" + number
continue
pattern2 = r'\.\d{3}'
pattern2 = r"\.\d{3}"
if re.findall(pattern2, new_obj.name):
split_name = new_obj.name.split(".")
new_obj.name = split_name[0] + "_" + number
def get_max_index(parts):
psets = [ifcopenshell.util.element.get_pset(p, "BBIM_Linked_Aggregate") for p in parts]
index = [i["Index"] for i in psets if i]
if len(index) > 0:
index = max(index)
return index
else:
return 0
def copy_linked_aggregate_data(old_to_new):
for old, new in old_to_new.items():
pset = ifcopenshell.util.element.get_pset(old, "BBIM_Linked_Aggregate")
if pset:
new_pset = ifcopenshell.api.run(
"pset.add_pset", tool.Ifc.get(), product=new[0], name=self.pset_name
)
ifcopenshell.api.run(
"pset.edit_pset",
tool.Ifc.get(),
pset=new_pset,
properties={"Index": pset["Index"]},
)
if new[0].is_a("IfcElementAssembly"):
linked_aggregate_group = [
r.RelatingGroup
for r in getattr(old, "HasAssignments", []) or []
if r.is_a("IfcRelAssignsToGroup")
if "BBIM_Linked_Aggregate" in r.RelatingGroup.Name
]
tool.Ifc.run("group.assign_group", group=linked_aggregate_group[0], products=new)
if len(context.selected_objects) != 1:
return {"FINISHED"}
@@ -1062,29 +1129,16 @@ class DuplicateMoveLinkedAggregate(bpy.types.Operator):
old_to_new = OverrideDuplicateMove.execute_ifc_duplicate_operator(self, context, linked=True)
tool.Root.recreate_aggregate(old_to_new)
copy_linked_aggregate_data(old_to_new)
custom_incremental_naming_for_element_assembly(old_to_new)
# Recreate aggregate relationship
for old in old_to_new.keys():
if old.is_a("IfcElementAssembly"):
tool.Root.recreate_aggregate(old_to_new)
blenderbim.bim.handler.refresh_ui_data()
return old_to_new
@staticmethod
def get_max_index(parts):
psets = [ifcopenshell.util.element.get_pset(p, "BBIM_Linked_Aggregate") for p in parts]
index = [i['Index'] for i in psets if i]
if len(index) > 0:
index = max(index)
return index
else:
return 0
class RefreshLinkedAggregate(bpy.types.Operator):
bl_idname = "bim.refresh_linked_aggregate"
@@ -1132,9 +1186,9 @@ class RefreshLinkedAggregate(bpy.types.Operator):
original_names[group] = {}
pset = ifcopenshell.util.element.get_pset(element, self.pset_name)
index = pset['Index']
index = pset["Index"]
original_names[group][index] = tool.Ifc.get_object(element).name
parts = ifcopenshell.util.element.get_parts(element)
if parts:
for part in parts:
@@ -1142,20 +1196,22 @@ class RefreshLinkedAggregate(bpy.types.Operator):
original_names | get_original_names(part)
else:
try:
pset = ifcopenshell.util.element.get_pset(part, self.pset_name)
pset = ifcopenshell.util.element.get_pset(part, self.pset_name)
except:
continue
index = pset['Index']
continue
index = pset["Index"]
original_names[group][index] = tool.Ifc.get_object(part).name
return original_names
def set_original_name(obj, original_names):
element = tool.Ifc.get_entity(obj)
aggregate = ifcopenshell.util.element.get_aggregate(element)
if ifcopenshell.util.element.get_parts(element): # if element has parts it means it is the base of and aggregate or sub-aggregate
if ifcopenshell.util.element.get_parts(
element
): # if element has parts it means it is the base of and aggregate or sub-aggregate
aggregate = element
group = [
r.RelatingGroup
for r in getattr(aggregate, "HasAssignments", []) or []
@@ -1164,18 +1220,16 @@ class RefreshLinkedAggregate(bpy.types.Operator):
]
if not group:
return
group = group[0].id()
pset = ifcopenshell.util.element.get_pset(element, self.pset_name)
index = pset['Index']
index = pset["Index"]
try:
obj.name = original_names[group][index]
except:
return
def get_element_assembly(element):
if element.is_a("IfcElementAssembly"):
@@ -1218,6 +1272,22 @@ class RefreshLinkedAggregate(bpy.types.Operator):
return list(set(linked_aggregate_groups)), selected_parents
def get_original_matrix(element, base_instance):
selected_obj = tool.Ifc.get_object(base_instance)
selected_matrix = selected_obj.matrix_world
object_duplicate = tool.Ifc.get_object(element)
duplicate_matrix = object_duplicate.matrix_world.decompose()
return selected_matrix, duplicate_matrix
def set_new_matrix(selected_matrix, duplicate_matrix, old_to_new):
for old, new in old_to_new.items():
new_obj = tool.Ifc.get_object(new[0])
new_base_matrix = Matrix.LocRotScale(*duplicate_matrix)
matrix_diff = Matrix.inverted(selected_matrix) @ new_obj.matrix_world
new_obj_matrix = new_base_matrix @ matrix_diff
new_obj.matrix_world = new_obj_matrix
active_element = tool.Ifc.get_entity(context.active_object)
if not active_element:
self.report({"INFO"}, "Object has no Ifc metadata.")
@@ -1257,13 +1327,10 @@ class RefreshLinkedAggregate(bpy.types.Operator):
element_aggregate = ifcopenshell.util.element.get_aggregate(element)
selected_obj = tool.Ifc.get_object(base_instance)
selected_matrix = selected_obj.matrix_world
object_duplicate = tool.Ifc.get_object(element)
duplicate_matrix = object_duplicate.matrix_world.decompose()
selected_matrix, duplicate_matrix = get_original_matrix(element, base_instance)
original_names = get_original_names(element)
delete_objects(element)
for obj in context.selected_objects:
@@ -1272,30 +1339,26 @@ class RefreshLinkedAggregate(bpy.types.Operator):
tool.Ifc.get_object(base_instance).select_set(True)
old_to_new = DuplicateMoveLinkedAggregate.execute_ifc_duplicate_linked_aggregate_operator(self, context)
for old, new in old_to_new.items():
new_obj = tool.Ifc.get_object(new[0])
new_base_matrix = Matrix.LocRotScale(*duplicate_matrix)
matrix_diff = Matrix.inverted(selected_matrix) @ new_obj.matrix_world
new_obj_matrix = new_base_matrix @ matrix_diff
new_obj.matrix_world = new_obj_matrix
set_new_matrix(selected_matrix, duplicate_matrix, old_to_new)
for old, new in old_to_new.items():
if element_aggregate and new[0].is_a("IfcElementAssembly"):
new_aggregate = ifcopenshell.util.element.get_aggregate(new[0])
if not new_aggregate:
blenderbim.core.aggregate.assign_object(
tool.Ifc,
tool.Aggregate,
tool.Collector,
relating_obj=tool.Ifc.get_object(element_aggregate),
related_obj=tool.Ifc.get_object(new[0]),
)
tool.Ifc,
tool.Aggregate,
tool.Collector,
relating_obj=tool.Ifc.get_object(element_aggregate),
related_obj=tool.Ifc.get_object(new[0]),
)
for old, new in old_to_new.items():
new_obj = tool.Ifc.get_object(new[0])
set_original_name(new_obj, original_names)
blenderbim.bim.handler.refresh_ui_data()
operator_time = time() - refresh_start_time
@@ -1328,18 +1391,23 @@ class OverrideJoin(bpy.types.Operator, Operator):
return self.join_blender_obj()
def join_ifc_obj(self):
representation = tool.Ifc.get().by_id(self.target.data.BIMMeshProperties.ifc_definition_id)
ifc_file = tool.Ifc.get()
builder = ShapeBuilder(ifc_file)
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(ifc_file)
representation = ifc_file.by_id(self.target.data.BIMMeshProperties.ifc_definition_id)
if representation.RepresentationType in ("Tessellation", "Brep"):
for obj in bpy.context.selected_objects:
if obj == self.target:
continue
element = tool.Ifc.get_entity(obj)
if element:
ifcopenshell.api.run("root.remove_product", tool.Ifc.get(), product=element)
ifcopenshell.api.run("root.remove_product", ifc_file, product=element)
bpy.ops.object.join()
bpy.ops.bim.update_representation(obj=self.target.name, ifc_representation_class="")
elif representation.RepresentationType == "SweptSolid":
target_placement = np.array(self.target.matrix_world)
target_placement[:, 3][:3] /= si_conversion
items = list(representation.Items)
for obj in bpy.context.selected_objects:
if obj == self.target:
@@ -1352,31 +1420,28 @@ class OverrideJoin(bpy.types.Operator, Operator):
continue
# Only objects of the same representation type can be joined
obj_rep = tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id)
obj_rep = ifc_file.by_id(obj.data.BIMMeshProperties.ifc_definition_id)
if obj_rep.RepresentationType != "SweptSolid":
obj.select_set(False)
continue
placement = np.array(obj.matrix_world)
placement[:, 3][:3] /= si_conversion
for item in obj_rep.Items:
copied_item = ifcopenshell.util.element.copy_deep(tool.Ifc.get(), item)
copied_item = ifcopenshell.util.element.copy_deep(ifc_file, item)
for style in item.StyledByItem:
copied_style = ifcopenshell.util.element.copy(tool.Ifc.get(), style)
copied_style = ifcopenshell.util.element.copy(ifc_file, style)
copied_style.Item = copied_item
if copied_item.Position:
position = ifcopenshell.util.placement.get_axis2placement(copied_item.Position)
else:
position = np.eye(4)
position = np.eye(4, dtype=float)
position = placement @ position
position = np.linalg.inv(target_placement) @ position
copied_item.Position = tool.Ifc.get().createIfcAxis2Placement3D(
tool.Ifc.get().createIfcCartesianPoint([float(n) for n in position[:, 3][:3]]),
tool.Ifc.get().createIfcDirection([float(n) for n in position[:, 2][:3]]),
tool.Ifc.get().createIfcDirection([float(n) for n in position[:, 0][:3]]),
)
copied_item.Position = builder.create_axis2_placement_3d_from_matrix(position)
items.append(copied_item)
ifcopenshell.api.run("root.remove_product", tool.Ifc.get(), product=element)
ifcopenshell.api.run("root.remove_product", ifc_file, product=element)
representation.Items = items
bpy.ops.object.join()
core.switch_representation(
@@ -1407,14 +1472,13 @@ class OverridePasteBuffer(bpy.types.Operator):
def execute(self, context):
bpy.ops.view3d.pastebuffer()
if IfcStore.get_file():
for obj in context.selected_objects:
# Pasted objects may come from another Blender session, or even
# from the same session where the original object has since
# been deleted. As the source element may not exist, paste will
# always unlink the element. If you want to duplicate an
# element, use the duplicate commands.
tool.Root.unlink_object(obj)
for obj in context.selected_objects:
# Pasted objects may come from another Blender session, or even
# from the same session where the original object has since
# been deleted. As the source element may not exist, paste will
# always unlink the element. If you want to duplicate an
# element, use the duplicate commands.
tool.Root.unlink_object(obj)
return {"FINISHED"}
@@ -1441,7 +1505,9 @@ class OverrideModeSetEdit(bpy.types.Operator):
for obj in selected_objs:
if not obj:
continue
if not obj.data:
obj_supports_edit_mode, message = tool.Blender.object_supports_edit_mode(obj)
if not obj_supports_edit_mode:
self.report({"INFO"}, message)
obj.select_set(False)
continue
element = tool.Ifc.get_entity(obj)
@@ -138,3 +138,4 @@ class BIMGeometryProperties(PropertyGroup):
name="IFC Interaction Mode",
update=update_mode,
)
representation_from_object: PointerProperty(type=bpy.types.Object)
@@ -212,7 +212,7 @@ class BIM_PT_representation_items(Panel):
if props.is_editing_item_style:
# NOTE: we currently support 1 item having just 1 style
# when IfcStyledItem can actually have multiple styles
row.prop(props, "representation_item_style", icon="SHADING_RENDERED", text="")
prop_with_search(row, props, "representation_item_style", icon="SHADING_RENDERED", text="")
row.operator("bim.edit_representation_item_style", icon="CHECKMARK", text="")
row.operator("bim.disable_editing_representation_item_style", icon="CANCEL", text="")
else:
@@ -212,7 +212,7 @@ class UnassignGroup(bpy.types.Operator, tool.Ifc.Operator):
"group.unassign_group",
self.file,
**{
"product": self.file.by_id(product.BIMObjectProperties.ifc_definition_id),
"products": [self.file.by_id(product.BIMObjectProperties.ifc_definition_id)],
"group": self.file.by_id(self.group),
}
)
@@ -151,7 +151,10 @@ class AssignPresentationLayer(bpy.types.Operator):
ifcopenshell.api.run(
"layer.assign_layer",
self.file,
**{"item": self.file.by_id(item.BIMMeshProperties.ifc_definition_id), "layer": self.file.by_id(self.layer)}
**{
"items": [self.file.by_id(item.BIMMeshProperties.ifc_definition_id)],
"layer": self.file.by_id(self.layer),
}
)
return {"FINISHED"}
@@ -173,7 +176,10 @@ class UnassignPresentationLayer(bpy.types.Operator):
ifcopenshell.api.run(
"layer.unassign_layer",
self.file,
**{"item": self.file.by_id(item.BIMMeshProperties.ifc_definition_id), "layer": self.file.by_id(self.layer)}
**{
"items": [self.file.by_id(item.BIMMeshProperties.ifc_definition_id)],
"layer": self.file.by_id(self.layer),
}
)
return {"FINISHED"}
@@ -24,6 +24,7 @@ classes = (
operator.AddLayer,
operator.AddListItem,
operator.AddMaterial,
operator.DuplicateMaterial,
operator.AddMaterialSet,
operator.AddProfile,
operator.AssignMaterial,
@@ -19,6 +19,8 @@
import os
import bpy
import ifcopenshell
import ifcopenshell.util.element
import ifcopenshell.util.doc
import ifcopenshell.util.schema
import blenderbim.tool as tool
@@ -109,29 +111,41 @@ class MaterialsData:
def active_styles(cls):
props = bpy.context.scene.BIMMaterialProperties
results = []
if props.materials and props.active_material_index < len(props.materials):
material = props.materials[props.active_material_index].ifc_definition_id
if not material:
return results
material = tool.Ifc.get().by_id(material)
for definition in material.HasRepresentation:
for representation in definition.Representations:
if not representation.is_a("IfcStyledRepresentation"):
if not props.materials or props.active_material_index >= len(props.materials):
return results
material = props.materials[props.active_material_index].ifc_definition_id
if not material:
return results
material = tool.Ifc.get_entity_by_id(material)
# NOTE: it's possible that data will be refreshed during material removal
# (when it will try to fetch active material type and will reach for material_types)
# and props.materials[i].ifc_definition_id will contain already removed id
if not material or not material.is_a("IfcMaterial"):
return results
for definition in material.HasRepresentation:
for representation in definition.Representations:
if not representation.is_a("IfcStyledRepresentation"):
continue
context = representation.ContextOfItems
for item in representation.Items:
if not item.is_a("IfcStyledItem"):
continue
context = representation.ContextOfItems
for item in representation.Items:
if not item.is_a("IfcStyledItem"):
continue
for style in item.Styles:
if style.is_a("IfcSurfaceStyle"):
results.append({
for style in item.Styles:
if style.is_a("IfcSurfaceStyle"):
results.append(
{
"context_type": context.ContextType,
"context_identifier": getattr(context, "ContextIdentifier", ""),
"target_view": getattr(context, "TargetView", ""),
"name": style.Name or "Unnamed",
"id": style.id(),
"context_id": context.id(),
})
}
)
return results
@@ -153,6 +167,9 @@ class ObjectMaterialData:
cls.data["type_material"] = cls.type_material()
cls.data["material_type"] = cls.material_type()
cls.data["active_material_constituents"] = cls.active_material_constituents()
# after material_name and type_material
cls.data["is_type_material_overridden"] = cls.is_type_material_overridden()
cls.is_loaded = True
@classmethod
@@ -281,8 +298,7 @@ class ObjectMaterialData:
@classmethod
def material_name(cls):
element = tool.Ifc.get_entity(bpy.context.active_object)
material = ifcopenshell.util.element.get_material(element)
material = cls.material
if material:
return getattr(material, "Name", None) or "Unnamed"
@@ -326,3 +342,18 @@ class ObjectMaterialData:
if not cls.material or not material.is_a("IfcMaterialConstituentSet"):
return []
return [m.Name for m in material.MaterialConstituents if m.Name]
@classmethod
def is_type_material_overridden(cls) -> bool:
if not cls.data["type_material"]:
return False
# try to avoid accessing ifc
if cls.data["material_name"] != cls.data["type_material"]:
return True
# in theory material can be overridden by the same material
# so we check occurrence material explicitly
element = tool.Ifc.get_entity(bpy.context.active_object)
occurrence_material = ifcopenshell.util.element.get_material(element, should_inherit=False)
return bool(occurrence_material)
@@ -34,6 +34,7 @@ from blenderbim.bim.ifc import IfcStore
class LoadMaterials(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.load_materials"
bl_label = "Load Materials"
bl_description = "Display list of named materials"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
@@ -117,7 +118,7 @@ class AddMaterial(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.add_material"
bl_label = "Add Material"
bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
obj: bpy.props.StringProperty(name="Material Name")
name: bpy.props.StringProperty(default="Default")
def invoke(self, context, event):
@@ -133,6 +134,19 @@ class AddMaterial(bpy.types.Operator, tool.Ifc.Operator):
material_prop_purge()
class DuplicateMaterial(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.duplicate_material"
bl_label = "Diplicate Material"
bl_options = {"REGISTER", "UNDO"}
material: bpy.props.IntProperty(name="Material ID")
def _execute(self, context):
ifc_file = tool.Ifc.get()
tool.Material.duplicate_material(ifc_file.by_id(self.material))
material_prop_purge()
bpy.ops.bim.load_materials()
class AddMaterialSet(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.add_material_set"
bl_label = "Add Material Set"
@@ -273,6 +287,7 @@ class RemoveProfile(bpy.types.Operator, tool.Ifc.Operator):
class AddLayer(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.add_layer"
bl_label = "Add Layer"
bl_description = "The List is Ordered From Origin Point"
bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
layer_set: bpy.props.IntProperty()
@@ -297,6 +312,7 @@ class AddLayer(bpy.types.Operator, tool.Ifc.Operator):
class ReorderMaterialSetItem(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.reorder_material_set_item"
bl_label = "Reorder Material Set Item"
bl_description = "The List is Ordered From Origin Point"
bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
old_index: bpy.props.IntProperty()
@@ -760,6 +776,11 @@ class EditMaterialStyle(bpy.types.Operator, tool.Ifc.Operator):
ifcopenshell.api.run("style.assign_material_style", ifc_file, material=material, style=style, context=context)
tool.Material.disable_editing_material()
core.load_materials(tool.Material, props.material_type)
# NOTE: Update all elements that has this material and
# not just the ones that are using it in IfcMaterialConstituent,
# to handle cases where Style is connected to geometry implicitly:
# e.g. RepItem->IfcElement->IfcMaterial->Style
# instead of RepItem->ShapeAspect->Constituent->Style.
tool.Material.update_elements_using_material(material)
@@ -61,6 +61,8 @@ class BIM_PT_materials(Panel):
if self.props.materials and self.props.active_material_index < len(self.props.materials):
material = self.props.materials[self.props.active_material_index]
if material.ifc_definition_id:
op = row.operator("bim.duplicate_material", text="", icon="DUPLICATE")
op.material = material.ifc_definition_id
op = row.operator("bim.select_by_material", text="", icon="RESTRICT_SELECT_OFF")
op.material = material.ifc_definition_id
op = row.operator("bim.enable_editing_material", text="", icon="GREASEPENCIL")
@@ -175,7 +177,13 @@ class BIM_PT_object_material(Panel):
if ObjectMaterialData.data["type_material"]:
row = self.layout.row(align=True)
row.label(text="Inherited Material: " + ObjectMaterialData.data["type_material"], icon="FILE_PARENT")
if ObjectMaterialData.data["is_type_material_overridden"]:
row.label(
text=f"Inherited Material Is Occurrence Overridden",
icon="CON_CHILDOF",
)
else:
row.label(text="Inherited Material: " + ObjectMaterialData.data["type_material"], icon="CON_CHILDOF")
if ObjectMaterialData.data["material_class"]:
return self.draw_material_ui()
@@ -230,7 +238,7 @@ class BIM_PT_object_material(Panel):
row = self.layout.row(align=True)
row.label(text="No Profiles Available")
row.operator("bim.add_profile_def", icon="ADD", text="")
else:
else:
row = self.layout.row(align=True)
if ObjectMaterialData.data["set_item_name"] == "profile":
prop_with_search(row, self.mprops, "profiles", icon="ITALIC", text="")
@@ -194,15 +194,18 @@ addon_keymaps = []
def register():
if not bpy.app.background:
bpy.utils.register_tool(workspace.WallTool, after={"builtin.transform"}, separator=True, group=False)
bpy.utils.register_tool(workspace.WallTool, after={"bim.explore_tool"}, separator=True, group=False)
bpy.utils.register_tool(workspace.SlabTool, after={"bim.wall_tool"}, separator=False, group=False)
bpy.utils.register_tool(workspace.DoorTool, after={"bim.slab_tool"}, separator=False, group=False)
bpy.utils.register_tool(workspace.WindowTool, after={"bim.door_tool"}, separator=False, group=False)
bpy.utils.register_tool(workspace.ColumnTool, after={"bim.window_tool"}, separator=False, group=False)
bpy.utils.register_tool(workspace.BeamTool, after={"bim.column_tool"}, separator=False, group=False)
bpy.utils.register_tool(workspace.DuctTool, after={"bim.beam_tool"}, separator=False, group=False)
bpy.utils.register_tool(workspace.BimTool, after={"bim.beam_tool"}, separator=False, group=False)
bpy.utils.register_tool(workspace.DuctTool, after={"bim.beam_tool"}, separator=False, group=True)
bpy.utils.register_tool(workspace.PipeTool, after={"bim.duct_tool"}, separator=False, group=False)
bpy.utils.register_tool(workspace.BimTool, after={"bim.pipe_tool"}, separator=False, group=False)
bpy.utils.register_tool(workspace.CableCarrierTool, after={"bim.pipe_tool"}, separator=False, group=False)
bpy.utils.register_tool(workspace.CableTool, after={"bim.cable_carrier_tool"}, separator=False, group=False)
bpy.types.Scene.BIMModelProperties = bpy.props.PointerProperty(type=prop.BIMModelProperties)
bpy.types.Object.BIMArrayProperties = bpy.props.PointerProperty(type=prop.BIMArrayProperties)
bpy.types.Object.BIMStairProperties = bpy.props.PointerProperty(type=prop.BIMStairProperties)
@@ -211,6 +214,7 @@ def register():
bpy.types.Object.BIMDoorProperties = bpy.props.PointerProperty(type=prop.BIMDoorProperties)
bpy.types.Object.BIMRailingProperties = bpy.props.PointerProperty(type=prop.BIMRailingProperties)
bpy.types.Object.BIMRoofProperties = bpy.props.PointerProperty(type=prop.BIMRoofProperties)
bpy.types.VIEW3D_MT_mesh_add.append(ui.add_mesh_object_menu)
bpy.types.VIEW3D_MT_add.append(ui.add_menu)
bpy.app.handlers.load_post.append(handler.load_post)
@@ -232,6 +236,8 @@ def unregister():
bpy.utils.unregister_tool(workspace.BeamTool)
bpy.utils.unregister_tool(workspace.DuctTool)
bpy.utils.unregister_tool(workspace.PipeTool)
bpy.utils.unregister_tool(workspace.CableCarrierTool)
bpy.utils.unregister_tool(workspace.CableTool)
bpy.utils.unregister_tool(workspace.BimTool)
del bpy.types.Scene.BIMModelProperties
del bpy.types.Object.BIMArrayProperties
@@ -83,8 +83,17 @@ 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
@@ -93,7 +102,9 @@ 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"}
@@ -137,6 +148,10 @@ 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"}
@@ -32,7 +32,9 @@ class AddInstanceFlooringCoveringFromCursor(bpy.types.Operator, tool.Ifc.Operato
def poll(cls, context):
collection = context.view_layer.active_layer_collection.collection
collection_obj = collection.BIMCollectionProperties.obj
return tool.Ifc.get_entity(collection_obj)
relating_type_id = int(bpy.data.scenes["Scene"].BIMModelProperties.relating_type_id)
relating_type = ifcopenshell.util.element.get_predefined_type(tool.Ifc.get().by_id(relating_type_id))
return tool.Ifc.get_entity(collection_obj) and relating_type == "FLOORING"
def _execute(self, context):
@@ -61,7 +63,9 @@ class AddInstanceCeilingCoveringFromCursor(bpy.types.Operator, tool.Ifc.Operator
def poll(cls, context):
collection = context.view_layer.active_layer_collection.collection
collection_obj = collection.BIMCollectionProperties.obj
return tool.Ifc.get_entity(collection_obj)
relating_type_id = int(bpy.data.scenes["Scene"].BIMModelProperties.relating_type_id)
relating_type = ifcopenshell.util.element.get_predefined_type(tool.Ifc.get().by_id(relating_type_id))
return tool.Ifc.get_entity(collection_obj) and relating_type == "CEILING"
def _execute(self, context):
@@ -116,9 +120,11 @@ class AddInstanceFlooringCoveringsFromWalls(bpy.types.Operator, tool.Ifc.Operato
def poll(cls, context):
active_obj = bpy.context.active_object
element = tool.Ifc.get_entity(active_obj)
relating_type_id = int(bpy.data.scenes["Scene"].BIMModelProperties.relating_type_id)
relating_type = ifcopenshell.util.element.get_predefined_type(tool.Ifc.get().by_id(relating_type_id))
if element:
if element.is_a("IfcWall") and tool.Model.get_usage_type(element) == "LAYER2":
return context.selected_objects
return context.selected_objects and relating_type == "FLOORING"
def _execute(self, context):
# This only works based on a 2D plan only considering the standard
@@ -147,7 +153,7 @@ class AddInstanceFlooringCoveringsFromWalls(bpy.types.Operator, tool.Ifc.Operato
class AddInstanceCeilingCoveringsFromWalls(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.add_instance_ceiling_coverings_from_walls"
bl_label = "Add Ceilings From Walls"
bl_label = "Add Ceiling From Walls"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Add instance ceiling coverings from selected walls. The active object must be a wall and layered vertically"
@@ -155,9 +161,11 @@ class AddInstanceCeilingCoveringsFromWalls(bpy.types.Operator, tool.Ifc.Operator
def poll(cls, context):
active_obj = bpy.context.active_object
element = tool.Ifc.get_entity(active_obj)
relating_type_id = int(bpy.data.scenes["Scene"].BIMModelProperties.relating_type_id)
relating_type = ifcopenshell.util.element.get_predefined_type(tool.Ifc.get().by_id(relating_type_id))
if element:
if element.is_a("IfcWall") and tool.Model.get_usage_type(element) == "LAYER2":
return context.selected_objects
return context.selected_objects and relating_type == "CEILING"
def _execute(self, context):
# This only works based on a 2D plan only considering the standard
@@ -23,6 +23,7 @@ from math import sin, cos, radians
from bpy.types import SpaceView3D
from mathutils import Vector, Matrix
from gpu_extras.batch import batch_for_shader
from typing import Union
ERROR_ELEMENTS_COLOR = (1, 0.2, 0.322, 1) # RED
@@ -35,15 +36,6 @@ def transparent_color(color, alpha=0.1):
return color
def bm_check_vertex_in_groups(vertex, deform_layer, groups):
"""returns tuple boolean (whether vertex is in any of the groups)
and related group index"""
for group_index in vertex[deform_layer].keys():
if group_index in groups:
return True, group_index
return False, None
class ProfileDecorator:
installed = None
@@ -148,12 +140,12 @@ class ProfileDecorator:
# deform_layer is None if there are no verts assigned to vertex groups
# even if there are vertex groups in the obj.vertex_groups
if deform_layer:
is_arc, group_index = bm_check_vertex_in_groups(vertex, deform_layer, arc_groups)
is_arc, group_index = tool.Blender.bmesh_check_vertex_in_groups(vertex, deform_layer, arc_groups)
if is_arc:
arcs.setdefault(group_index, []).append(vertex)
special_vertex_indices[vertex.index] = group_index
is_circle, group_index = bm_check_vertex_in_groups(vertex, deform_layer, circle_groups)
is_circle, group_index = tool.Blender.bmesh_check_vertex_in_groups(vertex, deform_layer, circle_groups)
if is_circle:
circles.setdefault(group_index, []).append(vertex)
special_vertex_indices[vertex.index] = group_index
@@ -542,7 +542,7 @@ class MEPGenerator:
ifc_representation_class=None,
)
rel = ifcopenshell.api.run("material.assign_material", ifc_file, product=element, type="IfcMaterialProfileSet")
rel = ifcopenshell.api.run("material.assign_material", ifc_file, products=[element], type="IfcMaterialProfileSet")
profile_set = rel.RelatingMaterial
material_profile = ifcopenshell.api.run(
"material.add_profile", ifc_file, profile_set=profile_set, material=material
@@ -24,16 +24,20 @@ import shapely
import logging
import numpy as np
import ifcopenshell
import ifcopenshell.api
import ifcopenshell.geom
import ifcopenshell.util.shape
import ifcopenshell.util.element
import ifcopenshell.util.shape_builder
import ifcopenshell.util.placement
import ifcopenshell.util.representation
import ifcopenshell.util.unit
import blenderbim.tool as tool
import blenderbim.core.geometry
import blenderbim.bim.import_ifc as import_ifc
import blenderbim.bim.handler
from blenderbim.bim.ifc import IfcStore
from math import pi
from math import pi, radians
from mathutils import Vector, Matrix
from bpy.types import Operator
from bpy.types import SpaceView3D
@@ -87,20 +91,28 @@ class FilledOpeningGenerator:
# In this prototype, we assume openings are only added to axis-based elements
layers = tool.Model.get_material_layer_parameters(element)
axis = tool.Model.get_wall_axis(voided_obj, layers=layers)["base"]
if layers["layer_set_direction"] == "AXIS2":
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)
new_matrix.translation.x = point_on_axis.x
new_matrix.translation.y = point_on_axis.y
new_matrix = voided_obj.matrix_world.copy()
point_on_axis = tool.Cad.point_on_edge(target, axis)
new_matrix.translation.x = point_on_axis.x
new_matrix.translation.y = point_on_axis.y
if should_set_z_level:
if filling.is_a("IfcDoor"):
new_matrix.translation.z = voided_obj.matrix_world.translation.z
if should_set_z_level:
if filling.is_a("IfcDoor"):
new_matrix.translation.z = voided_obj.matrix_world.translation.z
else:
new_matrix.translation.z = voided_obj.matrix_world.translation.z + props.rl2
else:
new_matrix.translation.z = voided_obj.matrix_world.translation.z + props.rl2
else:
new_matrix.translation.z = filling_obj.matrix_world.copy().translation.z
new_matrix.translation.z = filling_obj.matrix_world.copy().translation.z
elif layers["layer_set_direction"] == "AXIS3":
new_matrix = voided_obj.matrix_world.copy()
local_position_on_voided_obj = raycast[1]
# Equivalent to "side Z" for a wall axis, so that stuff like skylights appear on the top.
local_position_on_voided_obj.z = layers["offset"] + layers["thickness"]
new_matrix.translation.xyz = voided_obj.matrix_world @ local_position_on_voided_obj
rotation_matrix = Matrix.Rotation(radians(-90), 4, "X")
new_matrix @= rotation_matrix
filling_obj.matrix_world = new_matrix
bpy.context.view_layer.update()
@@ -176,7 +188,13 @@ class FilledOpeningGenerator:
)
def regenerate_from_type(self, usecase_path, ifc_file, settings):
filling = settings["related_object"]
relating_type = settings["relating_type"]
for related_object in settings["related_objects"]:
self._regenerate_from_type(related_object)
def _regenerate_from_type(self, related_object: ifcopenshell.entity_instance) -> None:
filling = related_object
if not getattr(filling, "FillsVoids", None):
return
@@ -776,20 +794,28 @@ class EditOpenings(Operator, tool.Ifc.Operator):
building_objs.add(obj)
similar_openings = blenderbim.core.geometry.get_similar_openings(tool.Ifc, opening)
similar_openings_building_objs = blenderbim.core.geometry.get_similar_openings_building_objs(tool.Ifc, similar_openings)
similar_openings_building_objs = blenderbim.core.geometry.get_similar_openings_building_objs(
tool.Ifc, similar_openings
)
building_objs.update(similar_openings_building_objs)
if opening_obj:
if tool.Ifc.is_edited(opening_obj):
tool.Geometry.run_geometry_update_representation(obj=opening_obj)
blenderbim.core.geometry.edit_similar_opening_placement(tool.Geometry, opening, similar_openings)
blenderbim.core.geometry.edit_similar_opening_placement(
tool.Geometry, opening, similar_openings
)
elif tool.Ifc.is_moved(opening_obj):
blenderbim.core.geometry.edit_object_placement(
tool.Ifc, tool.Geometry, tool.Surveyor, obj=opening_obj
)
blenderbim.core.geometry.edit_similar_opening_placement(tool.Geometry, opening, similar_openings)
blenderbim.core.geometry.edit_similar_opening_placement(
tool.Geometry, opening, similar_openings
)
building_objs.update(self.get_all_building_objects_of_similar_openings(opening)) #NB this has nothing to do with clone similar_opening
building_objs.update(
self.get_all_building_objects_of_similar_openings(opening)
) # NB this has nothing to do with clone similar_opening
tool.Ifc.unlink(element=opening, obj=opening_obj)
bpy.data.objects.remove(opening_obj)
@@ -25,9 +25,13 @@ import ifcopenshell.util.system
import ifcopenshell.util.element
import ifcopenshell.util.placement
import ifcopenshell.util.representation
import ifcopenshell.util.type
import ifcopenshell.util.unit
import blenderbim.tool as tool
import blenderbim.core.aggregate
import blenderbim.core.type
import blenderbim.core.geometry
import blenderbim.core.spatial
from . import wall, slab, profile, mep
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.module.model.data import AuthoringData
@@ -35,6 +39,7 @@ from mathutils import Vector, Matrix
from bpy_extras.object_utils import AddObjectHelper
from . import prop
import json
from typing import Any, Union
class EnableAddType(bpy.types.Operator, tool.Ifc.Operator):
@@ -273,7 +278,7 @@ class AddConstrTypeInstance(bpy.types.Operator):
obj.location[2] = collection_obj.location[2] + bpy.context.scene.BIMModelProperties.rl2
@staticmethod
def generate_layered_element(ifc_class, relating_type):
def generate_layered_element(ifc_class: str, relating_type: ifcopenshell.entity_instance) -> bool:
layer_set_direction = None
parametric = ifcopenshell.util.element.get_psets(relating_type).get("EPset_Parametric")
@@ -297,6 +302,7 @@ class AddConstrTypeInstance(bpy.types.Operator):
material = ifcopenshell.util.element.get_material(tool.Ifc.get_entity(obj))
material.LayerSetDirection = layer_set_direction
return True
return False
class ChangeTypePage(bpy.types.Operator, tool.Ifc.Operator):
@@ -506,11 +512,11 @@ def regenerate_profile_usage(usecase_path, ifc_file, settings):
)
def ensure_material_assigned(usecase_path, ifc_file, settings):
def ensure_material_assigned(usecase_path: str, ifc_file: ifcopenshell.file, settings: dict[str, Any]) -> None:
if usecase_path == "material.assign_material":
if not settings.get("material", None):
return
elements = [settings["product"]]
elements = settings["products"]
else:
elements = []
for rel in ifc_file.by_type("IfcRelAssociatesMaterial"):
@@ -519,53 +525,76 @@ def ensure_material_assigned(usecase_path, ifc_file, settings):
]:
elements.extend(rel.RelatedObjects)
for element in elements:
obj = IfcStore.get_element(element.GlobalId)
if not obj or not obj.data:
continue
element_material = ifcopenshell.util.element.get_material(element)
material = [m for m in ifc_file.traverse(element_material) if m.is_a("IfcMaterial")]
object_material_ids = [
om.BIMObjectProperties.ifc_definition_id
for om in obj.data.materials
if om is not None and om.BIMObjectProperties.ifc_definition_id
]
if material and material[0].id() in object_material_ids:
continue
if len(obj.data.materials) == 1:
obj.data.materials.clear()
if not material:
continue
obj.data.materials.append(IfcStore.get_element(material[0].id()))
update_blender_ifc_materials(elements)
def ensure_material_unassigned(usecase_path, ifc_file, settings):
elements = [settings["product"]]
def ensure_material_unassigned(usecase_path: str, ifc_file: ifcopenshell.file, settings: dict[str, Any]) -> None:
elements = settings["products"]
if elements[0].is_a("IfcElementType"):
elements.extend(ifcopenshell.util.element.get_types(elements[0]))
update_blender_ifc_materials(elements)
def update_blender_ifc_materials(elements: list[ifcopenshell.entity_instance]) -> None:
"""update mesh blender materials that have ifc material connected to them
by replacing them with `blender_material`"""
# since different elements can share meshes (e.g. occurrecnes without openings)
# we need to make sure not to affect them accidentally
meshes_users: dict[bpy.types.Mesh, set[bpy.types.Object]] = dict()
for obj in bpy.data.objects:
if not obj.data:
continue
meshes_users.setdefault(obj.data, set()).add(obj)
objects: set[bpy.types.Object] = set()
for element in elements:
obj = tool.Ifc.get_object(element)
obj: bpy.types.Object = tool.Ifc.get_object(element)
if not obj or not obj.data:
continue
element_material = ifcopenshell.util.element.get_material(element)
if element_material:
objects.add(obj)
meshes: set[bpy.types.Mesh] = {obj.data for obj in objects}
for mesh in meshes:
mesh_users = meshes_users[mesh]
if not mesh_users.issubset(objects):
continue
to_remove = []
for i, slot in enumerate(obj.material_slots):
if not slot.material:
# NOTE: we need `obj` as removing materials and appending them to `mesh.materials`
# will mess up mesh faces material indices
# NOTE: we make an assumption here that all mesh users
# have the same material - they either inherit it from the type
# or type doesn't have a material.
#
# If we add option to UI to add materials overriding type materials
# then this assumption won't be safe anymore
obj = next(iter(mesh_users))
element = tool.Ifc.get_entity(obj)
current_material = ifcopenshell.util.element.get_material(element)
if current_material:
current_material = tool.Ifc.get_object(current_material)
material_replaced = False
for material_slot in obj.material_slots:
material = material_slot.material
if material is None:
continue
material = tool.Ifc.get_entity(slot.material)
if material:
to_remove.append(i)
total_removed = 0
for i in to_remove:
obj.active_material_index = i - total_removed
with bpy.context.temp_override(object=obj):
bpy.ops.object.material_slot_remove()
total_removed += 1
ifc_material = tool.Ifc.get_entity(material)
# it's blender material for style, so ignore it
if not ifc_material:
continue
if ifc_material == current_material:
continue
material_slot.material = current_material
material_replaced = True
if not material_replaced and current_material:
mesh.materials.append(current_material)
# clear empty slots
for i, material in reversed(list(enumerate(mesh.materials[:]))):
if material is None:
mesh.materials.pop(index=i)
@@ -21,14 +21,18 @@ import copy
import bmesh
import mathutils.geometry
import ifcopenshell
import ifcopenshell.api
import ifcopenshell.util.type
import ifcopenshell.util.unit
import ifcopenshell.util.element
import ifcopenshell.util.placement
import ifcopenshell.util.representation
import blenderbim.bim.handler
import blenderbim.tool as tool
import blenderbim.core.type
import blenderbim.core.geometry
import blenderbim.core.material
import blenderbim.core.root
from math import pi, degrees, inf
from mathutils import Vector, Matrix, Quaternion
from blenderbim.bim.module.geometry.helper import Helper
@@ -92,7 +96,7 @@ class DumbProfileGenerator:
should_add_representation=False,
context=self.body_context,
)
ifcopenshell.api.run("type.assign_type", self.file, related_object=element, relating_type=self.relating_type)
ifcopenshell.api.run("type.assign_type", self.file, related_objects=[element], relating_type=self.relating_type)
material = ifcopenshell.util.element.get_material(element)
material.CardinalPoint = self.cardinal_point
@@ -207,12 +211,19 @@ class DumbProfileRegenerator:
return results
def regenerate_from_type(self, usecase_path, ifc_file, settings):
obj = tool.Ifc.get_object(settings["related_object"])
if not obj or not obj.data or not obj.data.BIMMeshProperties.ifc_definition_id:
return
new_material = ifcopenshell.util.element.get_material(settings["relating_type"])
relating_type = settings["relating_type"]
new_material = ifcopenshell.util.element.get_material(relating_type)
if not new_material or not new_material.is_a("IfcMaterialProfileSet"):
return
for related_object in settings["related_objects"]:
self._regenerate_from_type(related_object)
def _regenerate_from_type(self, related_object: ifcopenshell.entity_instance) -> None:
obj = tool.Ifc.get_object(related_object)
if not obj or not obj.data or not obj.data.BIMMeshProperties.ifc_definition_id:
return
DumbProfileRecalculator().recalculate([obj])
@@ -1013,17 +1024,30 @@ class EditExtrusionAxis(bpy.types.Operator, tool.Ifc.Operator):
obj = context.active_object
element = tool.Ifc.get_entity(obj)
start = obj.matrix_world @ obj.data.vertices[0].co.copy()
end = obj.matrix_world @ obj.data.vertices[1].co.copy()
matrix = obj.matrix_world
previous_z_axis = matrix.col[2].to_3d().normalized()
start = matrix @ obj.data.vertices[0].co
end = matrix @ obj.data.vertices[1].co
depth = (end - start).length
z_axis = (end - start).normalized()
y_axis = Vector((0, 0, 1))
# making sure z_axis != y_axis
if z_axis == y_axis:
y_axis = Vector((0, 1, 0))
x_axis = y_axis.cross(z_axis).normalized()
y_axis = z_axis.cross(x_axis).normalized()
# if z-axis didn't changed we can just reuse the previous rotation
if not tool.Cad.are_vectors_equal(previous_z_axis, z_axis):
y_axis = Vector((0, 0, 1))
# making sure z_axis != y_axis
if z_axis == y_axis:
y_axis = Vector((0, 1, 0))
x_axis = y_axis.cross(z_axis).normalized()
y_axis = z_axis.cross(x_axis).normalized()
# update basises
matrix.col[0].xyz = x_axis
matrix.col[1].xyz = y_axis
matrix.col[2].xyz = z_axis
matrix.translation = start
body = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
blenderbim.core.geometry.switch_representation(
@@ -1036,16 +1060,6 @@ class EditExtrusionAxis(bpy.types.Operator, tool.Ifc.Operator):
should_sync_changes_first=False,
)
matrix = Matrix(
(
[x_axis[0], y_axis[0], z_axis[0], start.x],
[x_axis[1], y_axis[1], z_axis[1], start.y],
[x_axis[2], y_axis[2], z_axis[2], start.z],
[0, 0, 0, 1],
)
)
obj.matrix_world = matrix
bpy.context.view_layer.update()
joiner = DumbProfileJoiner()
@@ -87,6 +87,21 @@ 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(
@@ -203,6 +218,14 @@ 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,17 +20,22 @@ import bpy
import json
import bmesh
import ifcopenshell
import ifcopenshell.util.type
import ifcopenshell.util.unit
import ifcopenshell.api
import ifcopenshell.util.element
import ifcopenshell.util.placement
import ifcopenshell.util.representation
import ifcopenshell.util.unit
import ifcopenshell.util.type
import blenderbim.bim.handler
import blenderbim.core.type
import blenderbim.core.geometry
import blenderbim.core.root
import blenderbim.tool as tool
from math import radians
from mathutils import Vector, Matrix
from blenderbim.bim.module.geometry.helper import Helper
from blenderbim.bim.module.model.decorator import ProfileDecorator
from typing import Optional
def calculate_quantities(usecase_path, ifc_file, settings):
@@ -110,7 +115,7 @@ def calculate_quantities(usecase_path, ifc_file, settings):
class DumbSlabGenerator:
def __init__(self, relating_type):
def __init__(self, relating_type: ifcopenshell.entity_instance):
self.relating_type = relating_type
def generate(self):
@@ -173,7 +178,7 @@ class DumbSlabGenerator:
should_add_representation=False,
context=self.body_context,
)
ifcopenshell.api.run("type.assign_type", self.file, related_object=element, relating_type=self.relating_type)
ifcopenshell.api.run("type.assign_type", self.file, related_objects=[element], relating_type=self.relating_type)
blenderbim.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj)
representation = ifcopenshell.api.run(
@@ -246,27 +251,38 @@ class DumbSlabPlaner:
self.change_thickness(element, total_thickness)
def regenerate_from_type(self, usecase_path, ifc_file, settings):
obj = tool.Ifc.get_object(settings["related_object"])
if not obj or not obj.data or not obj.data.BIMMeshProperties.ifc_definition_id:
return
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(ifc_file)
new_material = ifcopenshell.util.element.get_material(settings["relating_type"])
relating_type = settings["relating_type"]
new_material = ifcopenshell.util.element.get_material(relating_type)
if not new_material or not new_material.is_a("IfcMaterialLayerSet"):
return
parametric = ifcopenshell.util.element.get_psets(settings["relating_type"]).get("EPset_Parametric")
layer_set_direction = None
if parametric:
layer_set_direction = parametric.get("LayerSetDirection", layer_set_direction)
new_thickness = sum([l.LayerThickness for l in new_material.MaterialLayers])
material = ifcopenshell.util.element.get_material(settings["related_object"])
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(ifc_file)
for related_object in settings["related_objects"]:
self._regenerate_from_type(related_object, layer_set_direction, new_thickness)
def _regenerate_from_type(
self, related_object: ifcopenshell.entity_instance, layer_set_direction: Optional[str], new_thickness: float
) -> None:
obj = tool.Ifc.get_object(related_object)
if not obj or not obj.data or not obj.data.BIMMeshProperties.ifc_definition_id:
return
material = ifcopenshell.util.element.get_material(related_object)
if not material or not material.is_a("IfcMaterialLayerSetUsage"):
return
if layer_set_direction:
material.LayerSetDirection = layer_set_direction
if material.LayerSetDirection == "AXIS3":
self.change_thickness(settings["related_object"], new_thickness)
self.change_thickness(related_object, new_thickness)
def change_thickness(self, element, thickness):
def change_thickness(self, element: ifcopenshell.entity_instance, thickness: float) -> None:
body_context = ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model", "Body", "MODEL_VIEW")
obj = tool.Ifc.get_object(element)
if not obj:
@@ -698,9 +714,7 @@ class EditExtrusionProfile(bpy.types.Operator, tool.Ifc.Operator):
new_footprint = ifcopenshell.api.run(
"geometry.add_footprint_representation", tool.Ifc.get(), context=footprint_context, curves=curves
)
old_footprint = ifcopenshell.util.representation.get_representation(
element, "Plan", "FootPrint", "SKETCH_VIEW"
)
old_footprint = ifcopenshell.util.representation.get_representation(element, "Plan", "FootPrint", "SKETCH_VIEW")
if old_footprint:
for inverse in tool.Ifc.get().get_inverse(old_footprint):
ifcopenshell.util.element.replace_attribute(inverse, old_footprint, new_footprint)
@@ -27,36 +27,42 @@ import blenderbim.core.type
class GenerateSpace(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.generate_space"
bl_label = "Generate Space"
bl_label = "Generate Space from Cursor"
bl_options = {"REGISTER"}
bl_description = "Create a space from the cursor position. Move the cursor position into the desired position, select the right space collection and run the operator"
bl_description = (
"Create a space from the cursor position. "
"Move the cursor position into the desired position, "
"select the right space collection and run the operator"
)
@classmethod
def poll(cls, context):
collection = context.view_layer.active_layer_collection.collection
collection_obj = collection.BIMCollectionProperties.obj
active_obj = bpy.context.active_object
element = tool.Ifc.get_entity(active_obj)
return tool.Ifc.get_entity(collection_obj) and not element.is_a("IfcWall")
# @classmethod
# def poll(cls, context):
# print(context)
# collection = context.view_layer.active_layer_collection.collection
# collection_obj = collection.BIMCollectionProperties.obj
# active_obj = context.active_object
# element = tool.Ifc.get_entity(active_obj)
# return tool.Ifc.get_entity(collection_obj) and not element.is_a("IfcWall")
def _execute(self, context):
# This works as a 2.5 extruded polygon based on a cutting plane. Note
# that rooms exclude walls (i.e. not to wall midpoint or exterior /
# exterior edge.
def msg(self, context):
self.layout.label(text="NO ACTIVE STOREY")
def msg_no_collection(self, context):
self.layout.label(text="NO ACTIVE COLLECTION. PLEASE SELECT A SPATIAL COLLECTION OBJECT OR A WALL")
# props = context.scene.BIMModelProperties
def msg_no_active_storey(self, context):
self.layout.label(text="NO ACTIVE STOREY. PLEASE SELECT A SPATIAL COLLECTION OBJECT")
collection = context.view_layer.active_layer_collection.collection
collection_obj = collection.BIMCollectionProperties.obj
if not collection_obj:
bpy.context.window_manager.popup_menu(msg, title="Error", icon="ERROR")
context.window_manager.popup_menu(msg_no_collection, title="Error", icon="ERROR")
return
spatial_element = tool.Ifc.get_entity(collection_obj)
if not spatial_element:
bpy.context.window_manager.popup_menu(msg, title="Error", icon="ERROR")
if not spatial_element or not spatial_element.is_a("IfcBuildingStorey"):
context.window_manager.popup_menu(msg_no_active_storey, title="Error", icon="ERROR")
return
core.generate_space(tool.Ifc, tool.Spatial, tool.Model, tool.Type)
@@ -68,12 +74,12 @@ class GenerateSpacesFromWalls(bpy.types.Operator, tool.Ifc.Operator):
bl_options = {"REGISTER", "UNDO"}
bl_description = "Generate spaces from selected walls. The active object must be a wall"
@classmethod
def poll(cls, context):
active_obj = bpy.context.active_object
element = tool.Ifc.get_entity(active_obj)
if element:
return context.selected_objects and element.is_a("IfcWall")
# @classmethod
# def poll(cls, context):
# active_obj = context.active_object
# element = tool.Ifc.get_entity(active_obj)
# if element:
# return context.selected_objects and element.is_a("IfcWall")
def _execute(self, context):
# This only works based on a 2D plan only considering the standard
@@ -81,43 +87,56 @@ class GenerateSpacesFromWalls(bpy.types.Operator, tool.Ifc.Operator):
# In order to run, the active object must be a wall and
# there must be selected walls
active_obj = bpy.context.active_object
active_obj = context.active_object
element = tool.Ifc.get_entity(active_obj)
container = tool.Spatial.get_container(element)
def msg_no_active_object(self, context):
self.layout.label(text="No active object. Please select a wall")
def msg_no_active_wall(self, context):
self.layout.label(text="The active object is not a wall. Please select a wall.")
def msg_no_container(self, context):
self.layout.label(text="The wall is not contained. Please the selected wall in a building container")
def msg_no_selected_objects(self, context):
self.layout.label(text="No selected objects found. Please select walls.")
if not active_obj:
self.report({"ERROR"}, "No active object. Please select a wall")
context.window_manager.popup_menu(msg_no_active_object, title="Error", icon="ERROR")
return
element = tool.Ifc.get_entity(active_obj)
if element and not element.is_a("IfcWall"):
return self.report({"ERROR"}, "The active object is not a wall. Please select a wall.")
context.window_manager.popup_menu(msg_no_active_wall, title="Error", icon="ERROR")
return
if not container:
self.report({"ERROR"}, "The wall is not contained.")
context.window_manager.popup_menu(msg_no_container, title="Error", icon="ERROR")
return
if not bpy.context.selected_objects:
self.report({"ERROR"}, "No selected objects found. Please select walls.")
if not context.selected_objects:
context.window_manager.popup_menu(msg_no_selected_objects, title="Error", icon="ERROR")
return
core.generate_spaces_from_walls(tool.Ifc, tool.Spatial, tool.Collector)
class ToggleSpaceVisibility(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.toggle_space_visibility"
bl_label = "Toggle Space Visibility"
bl_options = {"REGISTER"}
bl_description = "Change the space visibility"
def execute(cls, context):
def execute(self, context):
core.toggle_space_visibility(tool.Ifc, tool.Spatial)
return {"FINISHED"}
class ToggleHideSpaces(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.toggle_hide_spaces"
bl_label = "Toggle Hide Spaces"
bl_options = {"REGISTER"}
bl_description = "Hide or Unhide all spaces"
def execute(cls, context):
def execute(self, context):
core.toggle_hide_spaces(tool.Ifc, tool.Spatial)
return {"FINISHED"}
@@ -54,6 +54,11 @@ class LaunchTypeManager(bpy.types.Operator):
props.type_page = 1
if get_ifc_class(None, context):
ifc_class = props.ifc_class or AuthoringData.data["ifc_element_type"]
else:
ifc_class = AuthoringData.data["ifc_element_type"]
# will be None if project has no types
if ifc_class is not None:
props.type_class = ifc_class
bpy.ops.bim.load_type_thumbnails(ifc_class=ifc_class, offset=0, limit=9)
return context.window_manager.invoke_popup(self, width=550)
@@ -218,6 +223,8 @@ 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()})"
@@ -24,7 +24,9 @@ import ifcopenshell
import ifcopenshell.api
import ifcopenshell.util.unit
import ifcopenshell.util.element
import ifcopenshell.util.placement
import ifcopenshell.util.representation
import ifcopenshell.util.type
import mathutils.geometry
import blenderbim.bim.handler
import blenderbim.core.type
@@ -35,6 +37,7 @@ from blenderbim.bim.ifc import IfcStore
from math import pi, sin, cos, degrees, radians
from mathutils import Vector, Matrix
from blenderbim.bim.module.model.opening import FilledOpeningGenerator
from typing import Optional
class JoinWall(bpy.types.Operator, tool.Ifc.Operator):
@@ -555,7 +558,10 @@ class DumbWallGenerator:
for face in sibling_obj.data.polygons:
normal = (sibling_obj.matrix_world.to_quaternion() @ face.normal).normalized()
face_center = sibling_obj.matrix_world @ face.center
if normal.z != 0 or abs(mathutils.geometry.distance_point_to_plane(self.location, face_center, normal)) > 0.01:
if (
normal.z != 0
or abs(mathutils.geometry.distance_point_to_plane(self.location, face_center, normal)) > 0.01
):
continue
rotation = math.atan2(normal[1], normal[0])
@@ -603,7 +609,7 @@ class DumbWallGenerator:
should_add_representation=False,
context=self.body_context,
)
ifcopenshell.api.run("type.assign_type", self.file, related_object=element, relating_type=self.relating_type)
ifcopenshell.api.run("type.assign_type", self.file, related_objects=[element], relating_type=self.relating_type)
if self.axis_context:
representation = ifcopenshell.api.run(
"geometry.add_axis_representation",
@@ -724,18 +730,29 @@ class DumbWallPlaner:
DumbWallRecalculator().recalculate([w for w in set(walls) if w])
def regenerate_from_type(self, usecase_path, ifc_file, settings):
obj = tool.Ifc.get_object(settings["related_object"])
if not obj or not obj.data or not obj.data.BIMMeshProperties.ifc_definition_id:
return
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(ifc_file)
new_material = ifcopenshell.util.element.get_material(settings["relating_type"])
relating_type = settings["relating_type"]
new_material = ifcopenshell.util.element.get_material(relating_type)
if not new_material or not new_material.is_a("IfcMaterialLayerSet"):
return
parametric = ifcopenshell.util.element.get_psets(settings["relating_type"]).get("EPset_Parametric")
parametric = ifcopenshell.util.element.get_psets(relating_type).get("EPset_Parametric")
layer_set_direction = None
if parametric:
layer_set_direction = parametric.get("LayerSetDirection", layer_set_direction)
material = ifcopenshell.util.element.get_material(settings["related_object"])
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(ifc_file)
for related_object in settings["related_objects"]:
self._regenerate_from_type(related_object, layer_set_direction)
def _regenerate_from_type(
self, related_object: ifcopenshell.entity_instance, layer_set_direction: Optional[str]
) -> None:
obj = tool.Ifc.get_object(related_object)
if not obj or not obj.data or not obj.data.BIMMeshProperties.ifc_definition_id:
return
material = ifcopenshell.util.element.get_material(related_object)
if not material or not material.is_a("IfcMaterialLayerSetUsage"):
return
if layer_set_direction:
@@ -854,11 +871,11 @@ class DumbWallJoiner:
if (location_on_base - location).length < (location_on_side - location).length:
axis_offset = location_on_side - location_on_base
offset_from_axis = location_on_base - location
opening_matrix.translation = (location_on_base - axis_offset - offset_from_axis)
opening_matrix.translation = location_on_base - axis_offset - offset_from_axis
else:
axis_offset = location_on_side - location_on_base
offset_from_axis = location_on_side - location
opening_matrix.translation = (location_on_side - axis_offset - offset_from_axis)
opening_matrix.translation = location_on_side - axis_offset - offset_from_axis
opening_matrixes[opening] = opening_matrix
for filling in [r.RelatedBuildingElement for r in opening.HasFillings]:
@@ -871,11 +888,11 @@ class DumbWallJoiner:
if (location_on_base - location).length < (location_on_side - location).length:
axis_offset = location_on_side - location_on_base
offset_from_axis = location_on_base - location
filling_matrix.translation = (location_on_base - axis_offset - offset_from_axis)
filling_matrix.translation = location_on_base - axis_offset - offset_from_axis
else:
axis_offset = location_on_side - location_on_base
offset_from_axis = location_on_side - location
filling_matrix.translation = (location_on_side - axis_offset - offset_from_axis)
filling_matrix.translation = location_on_side - axis_offset - offset_from_axis
filling_matrixes[filling] = filling_matrix
self.recreate_wall(element1, wall1, axis1["reference"], axis1["reference"])
@@ -1120,13 +1137,8 @@ class DumbWallJoiner:
other = tool.Ifc.get_object(rel.RelatedElement)
if connection not in ["ATPATH", "NOTDEFINED"]:
self.join(
obj,
other,
connection,
rel.RelatedConnectionType,
is_relating=True,
description=rel.Description
)
obj, other, connection, rel.RelatedConnectionType, is_relating=True, description=rel.Description
)
for rel in element.ConnectedFrom:
if rel.is_a("IfcRelConnectsPathElements"):
connection = rel.RelatedConnectionType
@@ -176,6 +176,21 @@ class DuctTool(BimTool):
BimToolUI.draw(context, layout, ifc_element_type=cls.ifc_element_type)
class CableCarrierTool(BimTool):
bl_space_type = "VIEW_3D"
bl_context_mode = "OBJECT"
bl_idname = "bim.cable_carrier_tool"
bl_label = "Create Cable Carrier"
bl_description = "Create and edit cable carriers"
bl_icon = os.path.join(os.path.dirname(__file__), "ops.authoring.cablecarrier")
bl_widget = None
ifc_element_type = "IfcCableCarrierSegmentType"
@classmethod
def draw_settings(cls, context, layout, ws_tool):
BimToolUI.draw(context, layout, ifc_element_type=cls.ifc_element_type)
class PipeTool(BimTool):
bl_space_type = "VIEW_3D"
bl_context_mode = "OBJECT"
@@ -191,6 +206,21 @@ class PipeTool(BimTool):
BimToolUI.draw(context, layout, ifc_element_type=cls.ifc_element_type)
class CableTool(BimTool):
bl_space_type = "VIEW_3D"
bl_context_mode = "OBJECT"
bl_idname = "bim.cable_tool"
bl_label = "Create Cable"
bl_description = "Create and edit cables"
bl_icon = os.path.join(os.path.dirname(__file__), "ops.authoring.cable")
bl_widget = None
ifc_element_type = "IfcCableSegmentType"
@classmethod
def draw_settings(cls, context, layout, ws_tool):
BimToolUI.draw(context, layout, ifc_element_type=cls.ifc_element_type)
def add_layout_hotkey_operator(layout, text, hotkey, description):
modifiers = {
"A": "EVENT_ALT",
@@ -348,6 +378,7 @@ class BimToolUI:
op.depth = cls.props.extrusion_depth
add_layout_hotkey_operator(cls.layout, "Extend", "S_E", "")
add_layout_hotkey_operator(cls.layout, "Flip", "S_F", bpy.ops.bim.flip_object.__doc__)
if AuthoringData.data["active_class"] in (
"IfcCableCarrierSegment",
@@ -355,8 +386,8 @@ class BimToolUI:
"IfcDuctSegment",
"IfcPipeSegment",
):
add_layout_hotkey_operator(cls.layout, "Add Fitting", "S_F", "")
add_layout_hotkey_operator(cls.layout, "Add Fitting", "S_Y", "")
if context.region.type != "TOOL_HEADER":
cls.layout.operator("bim.mep_add_bend")
cls.layout.operator("bim.mep_add_transition")
@@ -364,7 +395,6 @@ class BimToolUI:
else:
add_layout_hotkey_operator(cls.layout, "Edit Axis", "A_E", "")
add_layout_hotkey_operator(cls.layout, "Flip", "S_F", bpy.ops.bim.flip_object.__doc__)
add_layout_hotkey_operator(cls.layout, "Butt", "S_T", "")
add_layout_hotkey_operator(cls.layout, "Mitre", "S_Y", "")
add_layout_hotkey_operator(cls.layout, "Rotate 90", "S_R", bpy.ops.bim.rotate_90.__doc__)
@@ -486,20 +516,21 @@ class BimToolUI:
prop_with_search(row, cls.props, "relating_type_id", text="")
else:
row.label(text="No Construction Type", icon="FILE_3D")
row.operator("bim.launch_type_manager", icon="LIGHTPROBE_GRID", text="")
row.operator("bim.launch_type_manager", icon=tool.Blender.TYPE_MANAGER_ICON, text="")
else:
if AuthoringData.data["ifc_element_type"]:
row.label(text=f"No {AuthoringData.data['ifc_element_type']} Found", icon="ERROR")
row = cls.layout.row()
row.prop(cls.props, "type_name")
row = cls.layout.row()
row = cls.layout.row(align=True)
op = row.operator(
"bim.add_default_type", icon="ADD", text=f"Add {AuthoringData.data['ifc_element_type']}"
)
op.ifc_element_type = AuthoringData.data["ifc_element_type"]
row.operator("bim.launch_type_manager", icon=tool.Blender.TYPE_MANAGER_ICON, text="")
else:
row.label(text="No Element Types Found", icon="ERROR")
row.operator("bim.launch_type_manager", icon="LIGHTPROBE_GRID", text="Launch Type Manager")
row.operator("bim.launch_type_manager", icon=tool.Blender.TYPE_MANAGER_ICON, text="Launch Type Manager")
@classmethod
def draw_basic_bim_tool_interface(cls):
@@ -688,10 +719,9 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator):
bpy.ops.bim.flip_wall()
elif self.active_class in ("IfcWindow", "IfcWindowStandardCase", "IfcDoor", "IfcDoorStandardCase"):
bpy.ops.bim.flip_fill()
elif self.active_class in ("IfcBeam", "IfcColumn"):
elif self.active_material_usage == "PROFILE":
bpy.ops.bim.flip_object(flip_local_axes="XZ")
elif self.active_class in ("IfcDuctSegment", "IfcPipeSegment", "IfcCableCarrierSegment", "IfcCableSegment"):
bpy.ops.bim.fit_flow_segments()
def hotkey_S_G(self):
obj = bpy.context.active_object
@@ -777,9 +807,12 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator):
return
if self.active_material_usage == "LAYER2":
bpy.ops.bim.join_wall(join_type="V")
elif self.active_class in ("IfcDuctSegment", "IfcPipeSegment", "IfcCableCarrierSegment", "IfcCableSegment"):
bpy.ops.bim.fit_flow_segments()
elif self.active_material_usage == "PROFILE":
bpy.ops.bim.extend_profile(join_type="V")
def hotkey_S_B(self):
bpy.ops.bim.add_boundary()
@@ -18,6 +18,7 @@
import bpy
import importlib
import importlib.util
from pathlib import Path
import ifcpatch
from blenderbim.bim.prop import StrProperty, Attribute
@@ -21,6 +21,7 @@ from . import ui, prop, operator, data
classes = (
operator.AddProfileDef,
operator.DuplicateProfileDef,
operator.DisableEditingArbitraryProfile,
operator.DisableEditingProfile,
operator.DisableProfileEditingUI,
@@ -66,9 +66,15 @@ class RemoveProfileDef(bpy.types.Operator, tool.Ifc.Operator):
profile: bpy.props.IntProperty()
def _execute(self, context):
props = context.scene.BIMProfileProperties
current_index = props.active_profile_index
ifcopenshell.api.run("profile.remove_profile", tool.Ifc.get(), profile=tool.Ifc.get().by_id(self.profile))
bpy.ops.bim.load_profiles()
# preserve selected index if possible
if props.profiles:
props.active_profile_index = min(current_index, len(props.profiles) - 1)
class EnableEditingProfile(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.enable_editing_profile"
@@ -126,6 +132,27 @@ class AddProfileDef(bpy.types.Operator, tool.Ifc.Operator):
bpy.ops.bim.load_profiles()
class DuplicateProfileDef(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.duplicate_profile_def"
bl_label = "Duplicate Profile"
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
props = context.scene.BIMProfileProperties
if len(props.profiles) > props.active_profile_index:
return True
cls.poll_message_set("No profile selected to duplicate.")
return False
def _execute(self, context):
props = context.scene.BIMProfileProperties
ifc_file = tool.Ifc.get()
profile = ifc_file.by_id(props.profiles[props.active_profile_index].ifc_definition_id)
tool.Profile.duplicate_profile(profile)
bpy.ops.bim.load_profiles()
class EnableEditingArbitraryProfile(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.enable_editing_arbitrary_profile"
bl_label = "Enable Editing Arbitrary Profile"
@@ -70,6 +70,7 @@ class BIM_PT_profiles(Panel):
row = self.layout.row(align=True)
row.prop(self.props, "profile_classes", text="")
row.operator("bim.add_profile_def", text="", icon="ADD")
row.operator("bim.duplicate_profile_def", icon="DUPLICATE", text="")
self.layout.template_list(
"BIM_UL_profiles",
@@ -17,26 +17,37 @@
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
from . import ui, prop, operator
from . import ui, prop, operator, workspace, gizmo, decorator
classes = (
operator.AppendEntireLibrary,
operator.AppendInspectedLinkedElement,
operator.AppendLibraryElement,
operator.AppendLibraryElementByQuery,
operator.AssignLibraryDeclaration,
operator.BIM_OT_load_clipping_planes,
operator.BIM_OT_save_clipping_planes,
operator.ChangeLibraryElement,
operator.CreateClippingPlane,
operator.CreateProject,
operator.DisableCulling,
operator.DisableEditingHeader,
operator.EditHeader,
operator.EnableCulling,
operator.EnableEditingHeader,
operator.ExportIFC,
operator.FlipClippingPlane,
operator.ImportIFC,
operator.LinkIfc,
operator.LoadLink,
operator.LoadLinkedProject,
operator.LoadProject,
operator.LoadProjectElements,
operator.NewProject,
operator.QueryLinkedElement,
operator.RefreshClippingPlanes,
operator.RefreshLibrary,
operator.ReloadLink,
operator.RevertProject,
operator.RewindLibrary,
operator.SaveLibraryFile,
@@ -48,7 +59,7 @@ classes = (
operator.UnlinkIfc,
operator.UnloadLink,
operator.UnloadProject,
operator.ReloadLink,
workspace.ExploreHotkey,
prop.LibraryElement,
prop.FilterCategory,
prop.Link,
@@ -62,6 +73,7 @@ classes = (
ui.BIM_UL_library,
ui.BIM_UL_filter_categories,
ui.BIM_UL_links,
gizmo.ClippingPlane,
)
@@ -69,7 +81,10 @@ addon_keymaps = []
def register():
if not bpy.app.background:
bpy.utils.register_tool(workspace.ExploreTool, after={"builtin.transform"}, separator=True, group=False)
bpy.types.Scene.BIMProjectProperties = bpy.props.PointerProperty(type=prop.BIMProjectProperties)
bpy.app.handlers.load_post.append(decorator.toggle_decorations_on_load)
bpy.types.TOPBAR_MT_file.prepend(ui.file_menu)
bpy.types.TOPBAR_MT_file_context_menu.prepend(ui.file_menu)
wm = bpy.context.window_manager
@@ -88,9 +103,12 @@ def register():
def unregister():
if not bpy.app.background:
bpy.utils.unregister_tool(workspace.ExploreTool)
del bpy.types.Scene.BIMProjectProperties
bpy.app.handlers.load_post.remove(decorator.toggle_decorations_on_load)
bpy.types.TOPBAR_MT_file.remove(ui.file_menu)
bpy.types.TOPBAR_MT_file_context_menu.remove(ui.file_menu)
del bpy.types.Scene.BIMProjectProperties
wm = bpy.context.window_manager
kc = wm.keyconfigs.addon

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