Compare commits

...

133 Commits

Author SHA1 Message Date
Andrej730 c26988b292 root.remove_product to remove IfcRelFlowControlElements 2023-11-10 12:10:22 +05:00
Andrej730 46e70c92c5 fixed issues with ports ui on undo
data.py doesn't update on every undo, so passed blender objects got invalidated on undo resulting in errors. changed blender objects to blender object names
2023-11-10 11:39:49 +05:00
Andrej730 325c3665d9 UI for assigning controls to flow elements #3981
Example - https://imgur.com/a/CZQIoHp
2023-11-10 11:18:52 +05:00
Andrej730 9a80dc14e9 api for assigning/unassigning flow controls #3981 2023-11-09 17:34:26 +05:00
Andrej730 6bda066e1b Fixed typo in control.assign_control, added tests 2023-11-09 15:33:31 +05:00
Andrej730 912b01587b tool.System.add_ports tests 2023-11-08 18:01:30 +05:00
Andrej730 5d4d953320 fixed issue placing ports on a bend if start segment is bending using Z- 2023-11-08 16:54:05 +05:00
Andrej730 11ed8cc787 fixed issue in add_ports mutating object's matrix
inherently modifying ports positions in some cases
2023-11-08 16:49:31 +05:00
Dirk Olbrich d1a7b99eda bbim docs: add user interface subsection, general workspace 2023-11-08 22:12:40 +11:00
Dirk Olbrich 85bdeaeef4 bbimdocs: reorder images folder in users section 2023-11-08 22:12:40 +11:00
Dirk Olbrich a5f0de4d19 bbim docs: reorder images folder in dev section 2023-11-08 22:12:40 +11:00
Dirk Olbrich cb3392ec70 bbim docs: use absolute path for icon images 2023-11-08 22:12:40 +11:00
Dirk Olbrich 1dbe42061d bbim doc: fix list indentation 2023-11-08 22:12:40 +11:00
Dirk Olbrich a024d5c07f bbim docs: fix minor warning in ux-guidelines 2023-11-08 22:12:40 +11:00
Dirk Olbrich ea5e3c30b6 bbim docs: update bbim version, copyright year 2023-11-08 22:12:40 +11:00
Andrej730 ebfc8ef619 remove BIMGroupProperties.is_adding attribute to avoid confusion
The problem with that attribute was that object's and project's group ui section were both using groups loaded to BIMGroupProperties.groups.

So you could run into situation when you try to load object's groups ui and it would show empty list because user never loaded the groups from the project's ui...

So it was either caching groups twice or just using same `is_editing` flag to check whether groups were previously loaded and I went with the single flag.
2023-11-08 15:13:49 +05:00
Andrej730 c0d8467154 define inherited ports attributes in bim.add_constr_type_instance 2023-11-08 14:19:36 +05:00
Andrej730 d0adbab9c2 fixed issue finding finding compatible fitting
it occured sometimes when segment's or fitting's PredefinedType/System were not defined
2023-11-08 12:21:50 +05:00
Andrej730 8e80f456cb fixed bug with system decoration data if no system were selected 2023-11-08 11:50:05 +05:00
Andrej730 8cd9fb9015 test stairs calculated params 2023-11-06 15:56:33 +05:00
Andrej730 d9fdc0908a Nosing Lengths / Tread Gap for parametric stairs #3422
Example - https://imgur.com/d8PxLV4
2023-11-06 15:35:14 +05:00
Ryan Schultz a218ce5160 Select multiple arrays at one time, from various parts. Deselect non-compliant objects. 2023-11-04 11:35:30 -05:00
Igor.Sokolov b3a6b8e07f bf: IfcBSplineCurve is an isntance of abstract entity 2023-11-04 15:41:38 +01:00
Igor.Sokolov 52700452d9 bf: pass-valid-selected-enumeration.ifc 2023-11-04 15:41:38 +01:00
Igor.Sokolov aab011f7bf bf: pass-group-1-rels-IFC2X3.ifc failed 2023-11-04 15:41:38 +01:00
Igor.Sokolov 5f129e2357 bf: pass-attr-count-ok.ifc failed 2023-11-04 15:41:38 +01:00
Dion Moult 4e76f36af1 Black 2023-11-04 16:07:40 +11:00
Dion Moult 6f3ff51d91 Docs now mention historic builds 2023-11-03 22:45:42 +11:00
Dion Moult 0de0422c2e Minor fix 2023-11-03 22:45:42 +11:00
Andrej730 98de86e0ba Customize tread width for the first and last treads #3419
Available on all types of stairs, shouldn't break stairs previously created with BBIM.
Example - https://imgur.com/a/2wSueTM
2023-11-03 16:32:14 +05:00
Andrej730 142e41b3ad minor refactor 2023-11-03 16:32:14 +05:00
Andrej730 1e0dc7749d add force depsgraph update method 2023-11-03 16:32:14 +05:00
Andrej730 0f29de0391 skip black format on type manager template description for readibility 2023-11-03 16:32:14 +05:00
Andrej730 9933cc1b20 stair profile generation code refactor
hopefully it's now a bit more readable
2023-11-03 16:32:14 +05:00
Andrej730 fa6192e9bc Fixed stair calculated params not updating during edit (after 8b23c615c)
.data doesn't update on props change, so need to take data from props explicitly when BIMStairProperties.is_editing
2023-11-03 16:32:14 +05:00
Andrej730 bfd4559da8 move generate_stair_2d_profile to tool, add tests 2023-11-03 16:32:14 +05:00
ceegartner 01f52e069e Update unit.py - convert function, case issue
Hi,
I noticed an issue and suggest a fix:
Unit names from <si_conversions> are all lowercase, whereas project units extracted from the IFC files with <get_project_unit(...).Name> are uppercase. So the convert function didn't seem to work properly.
Hope this helps!
2023-11-03 11:47:44 +01:00
Dion Moult 2fbc9e4424 Forgot rabbit branding. 2023-11-02 15:15:04 +11:00
Dion Moult ba97da20eb New data purging IfcPatch recipe 2023-11-02 14:59:18 +11:00
Dion Moult 44f5be142f Fix #3963. Bind IFC separate to P hotkey and prevent separation on non-IFC meshes. 2023-11-02 13:03:56 +11:00
Dion Moult 2f951d352b Fix #3948. Bug where IfcTester PartOf facet didn't correctly read entity XML. 2023-11-02 12:29:05 +11:00
Andrej730 8b23c615c3 Fix issue with calculated params in stair ui
it was displaying incorrect data when stair was just loaded because it was relying on BIMStairProperties in ui.py
2023-11-01 17:33:38 +05:00
Dion Moult 7c694e5b3e Fix #3951. mats selector now supports IfcMaterialList. 2023-11-01 22:48:31 +11:00
Dion Moult 8eeb212003 See #3951. IfcCSV now supports list concatenation. 2023-11-01 22:47:30 +11:00
Dion Moult 7bf8d58cde Improve data type error reporting 2023-11-01 22:23:55 +11:00
Dion Moult b6e24559e5 Fix #3961. Improve UI so setting null now clears the value. 2023-11-01 18:04:44 +11:00
Andrej730 0d87d5927e bim.select_all_array_objects not to clear previous selection #3960 2023-11-01 10:53:25 +05:00
Dion Moult dfd5cc4728 See #3953. Fix bug where IDSes with a single enumeration value didn't validate. 2023-11-01 16:28:42 +11:00
Dion Moult 20b7ee009d Fix #3957. Fix bug where shared properties were not edited independently. 2023-11-01 15:18:57 +11:00
Dion Moult 19a3f56941 Fix #3958. Bug where reassigning type class would incorrectly assign occurrence classes. 2023-11-01 14:41:25 +11:00
Dion Moult 3b298d44c1 Fix regressions in mapped native swept disk handling 2023-11-01 14:16:12 +11:00
Andrej730 038d3bc41c fix future warning for xml.etree
The warning was: FutureWarning: The behavior of this method will change in future versions. Use specific 'len(elem)' or 'elem is not None' test instead.
2023-10-31 16:24:41 +05:00
Andrej730 235e89ae14 remove blf.size deprecated dpi argument #3876
turn out it wasn't working for awhile now and in Blender 4.0 was completely removed
2023-10-31 16:19:42 +05:00
Andrej730 e2de9efc33 Move new Blender 4.0 Simulation section to Scene/Blender tab #3876 2023-10-31 15:57:54 +05:00
Andrej730 8126085037 Warning message on copying node graph on Blender 4.0+ #3876
As we're still looking for a workaround
2023-10-31 15:38:57 +05:00
Andrej730 bbe14834e4 Remove 3D shaders prefix #3876
In Blender 4.0 3D_* shaders are called without "3D_" prefix, otherwise it throws an error. Tested on Blender 3.6 - they work without "3D_" prefix too.
2023-10-31 15:27:43 +05:00
Andrej730 45f2d301b3 More context override fixes #3876 2023-10-31 15:18:33 +05:00
Andrej730 2ecd11d71f fixing test after 0d0b91e 2023-10-31 15:18:33 +05:00
Gorgious56 b42f072bd5 You can now eyedrop an object in the viewport to copy container when editing container 2023-10-31 11:14:54 +01:00
Ryan Schultz 3b3668eedf More IFC class suggestions 2023-10-30 22:51:55 -05:00
Dion Moult 7f7f353131 Disable batching for removal as it doesn't seem to help mostly from experience. Still testing. 2023-10-31 12:31:05 +11:00
Dion Moult 53594de1ff See #3955. PyPI release now specifies compatible Python versions. 2023-10-31 08:32:51 +11:00
Gorgious56 4fbbfb5191 You can now eyedrop an object in the viewport to copy class and predefined type when reassigning class 2023-10-30 15:04:37 +01:00
Gorgious56 4e1daea838 Clicking on an entry in the search field popup automatically validates and closes the popup
No need to click on "OK" anymore.
This is not a hacky workaround using the mouse position, it uses a signal sent to Blender to close the active popup
See https://blenderartists.org/t/programatically-close-pie-menu-2-79/1488807/2
re-committing since it seems I commited to the wrong branch last week
2023-10-30 14:19:01 +01:00
Andrej730 d8ff83da73 Replacing old context override method #3876 2023-10-30 15:14:34 +05:00
Andrej730 a7712a923f setup_pytest to find site-packages folder near sys.executable
Used to get errors running setup_pytest.py having python 3.10 installed on my pc (`sys_paths[-1]` was the system's python folder, not the blender's python).
2023-10-30 15:14:34 +05:00
Andrej730 01b5f8f237 Fixed #3949 2023-10-30 11:27:55 +05:00
Dion Moult b9ad15c50f Minor fix to Uniclass classification 2023-10-30 09:31:00 +11:00
Thomas Krijnen db06cff997 Update test file for case insensitivity. Thanks Igor. 2023-10-29 14:35:14 +01:00
Dion Moult f0028c0094 get_footprint_area now unions 2D polygons to prevent double counting 2023-10-28 15:54:45 +11:00
Dion Moult 1c954801b0 Fix #3939. Bug where IfcLabel data type was incorrectly used for longer text data. 2023-10-28 14:44:41 +11:00
Ryan Schultz 973e4f0064 Updated default JoinCriteria to accommodate new facet selector for statuses. 2023-10-27 11:24:49 -05:00
Andrej730 be5d291bd9 test creating mep transition 2023-10-26 17:21:42 +05:00
Andrej730 cbec56c2f0 ifc2x3 pset library - fix bunch of unit related vlaidation errors #3929 2023-10-26 15:54:34 +05:00
Dion Moult 880d0ba753 Minor fixes 2023-10-26 13:08:30 +11:00
Dion Moult db85689364 Add support for latest uniclass and add Uniclass July 2023 2023-10-26 13:08:30 +11:00
Sigma Dimensions (Yass) 48a0d8c508 fix displaying undefined or userdefined units in cost schedules 2023-10-25 15:01:52 +01:00
Sigma Dimensions (Yass) df4cd493ee fix displaying cost item totals 2023-10-25 13:54:13 +01:00
Massimo Fabbro 74132dcd49 New operator that creates a ceiling from cursor position in covering tool 2023-10-23 20:58:59 +02:00
Andrej730 5cf1dd4249 fixed core tests after switch_representation changes 2023-10-23 17:25:07 +05:00
Andrej730 29bd30d3bf "Edit Profile" in BIM Tool for mapped representations
basically now "edit profile" will be shown in BIM Tool UI and will work from shift+e hotkey if the active element's representation is mapped (for example, if it has openings)
2023-10-23 16:50:05 +05:00
Andrej730 3f8a0a91b3 switch_representation to automatically switch to unresolved mapped representation
The cool thing about it now that it will automatically create a new mesh for "unresolved" representation if element has openings, otherwise it'll stick to the same mesh representation as the type object.

This fixed a few bugs:
- similar to b9a9abd but during extrusion profile edit
- applying void to the occurence used to remove the type and other occurences. Apparently it occured after 744c08e - previously we seems to rely on `ifcopenshell.geom.create_shape(settings, element)` to unresolve the representations. Though before 744c08e there still was an issue - it would apply the same opening to the all occurences and the type meshes but wasn't removing them.

Probably fixed a few other similar issues.
2023-10-23 16:49:56 +05:00
Andrej730 416a572664 switch_representation not to break with temporary mesh representations
I've found a bug that if you have some object that consists of extrusion and can be edited in profile edit mode, when you finish editing, it would remove the type object (and I guess the other occurences objects).

It happened because during edit mode it's using temporary mesh data and `switch_representation` would replace this temp mesh data with new representation and remove the old mesh data that might still used by the other occurences and the type.

The issue also was leading to a crash during `box.template_icon(icon_value=AuthoringData.data["type_thumbnail"], scale=5)` if you had BIM Tool thumbnail preview opened in the process - removing type object would make thumbnail icon become invalid leading to the crash.

Crash mentioned here: https://community.osarch.org/discussion/1753/bbim-two-door-types-with-connected-properties
2023-10-23 15:24:43 +05:00
Thomas Krijnen 388c72360d Use --start/end-group on linux to fix ld geomserver 2023-10-21 22:26:16 +02:00
Dion Moult b841afc2e4 Update beginners tutorial documentation with images from new UI. 2023-10-21 22:46:15 +11:00
Dion Moult f10d340254 You can now assign and unassign styles to materials 2023-10-21 15:23:55 +11:00
Dion Moult 0d0b91ee9b You can now specify a name when adding materials 2023-10-21 15:23:55 +11:00
Ryan Schultz 8032333a17 Additional clarification on save and save as 2023-10-20 09:53:29 -05:00
Dion Moult 712ec6def3 Reorganise new stakeholders panel, move profiles into geometry tab. General UI polish. 2023-10-20 22:18:32 +11:00
Andrej730 0d43ef0736 option to set active system
Now it's possible to set in UI active system (currently it's done "select" button next to the system).

Also changed the elements that are going to be decorated with system decorator. Previously it was decorating all the mep elements in the projects which was very performance heavy on large projects.
2023-10-20 15:59:24 +05:00
Andrej730 b318509cba Merge systems UI into one
We had two very similar systems panels that were part of the old UI.
2023-10-20 14:00:32 +05:00
Dion Moult f3b541f00a Fix #3095. Very basic implementation of a new zones panel. 2023-10-20 17:51:48 +11:00
Andrej730 4f64dcdbd4 Fixed tests after selector syntax update 2023-10-20 11:06:10 +05:00
Dion Moult 68acf84e26 Fix #3901. Add configurable list concatenation support in IfcFM. 2023-10-20 13:38:17 +11:00
Dion Moult aaceb923ad Fix #3903. Bug where stairs started off with an empty mesh and had a representation added, resulting in an invalid empty point cloud.
Now stairs have should_add_representation=False when creating the IfcStairFlight, and the representation is updated explicitly, similar to how parametric doors and windows etc have their representations updated.
2023-10-20 12:28:18 +11:00
Dion Moult 074de2741a Fix regression from 744c08e where type products cannot have an instance specified in create_shape 2023-10-20 12:24:57 +11:00
Dion Moult 0e4bc320cb Adding / editing shading / rendering styles in the style manager now updates Blender materials 2023-10-20 10:26:35 +11:00
Andrej730 9e9412bec0 Forgot to fix test after 35d66f5 2023-10-19 18:10:31 +05:00
Andrej730 7e92471ac9 Fixed issue unlinking mesh objects
After d67eae5 delete_ifc_object is removing the meshes and it end up invalidating the unlinked object. Now mesh is copied before running delete_ifc_object
2023-10-19 17:05:14 +05:00
Andrej730 078a7eb290 Fix tests after 808ff54 2023-10-19 16:42:20 +05:00
Andrej730 df3876b438 Minor fix after a47c57c 2023-10-19 16:11:57 +05:00
Andrej730 d414de450b Fixed test after c34a9c5
Also fixed bug in the sheeter
2023-10-19 16:11:56 +05:00
Andrej730 9e89bb5044 Fixed test after 418444a 2023-10-19 13:22:06 +05:00
Andrej730 aa0c42e1ec Fixed test after 90b40ff 2023-10-19 13:05:25 +05:00
Andrej730 a4daa0e3d9 Fixed test after 88d96f7 2023-10-19 11:03:27 +05:00
Andrej730 162ac39570 Fixed test after 6e9639446 2023-10-19 11:03:27 +05:00
Andrej730 8c73fa69a1 Fixed #3907 after dd7a6a80e 2023-10-19 10:49:47 +05:00
Dion Moult 159eeeb3a4 You can now edit rendering styles in the style manager 2023-10-19 16:38:42 +11:00
Andrej730 eb0873996f Added IfcZone to systems UI in IFC2X3 #3905
In IFC2X3 IfcZone is a subtype of IfcGroup but not subtype of IfcSystem (it become one after IFC4), therefore IfcZone wasn't presented anywhere in BBIM UI.
It's a bit confusing though to include IfcZone in systems when it's not actually a IfcSystem but having it in IfcGroups would be confusing too.
2023-10-18 17:13:22 +05:00
Andrej730 c85826ef2e Fixed #3901
It was throwing an error because was failing to write a list to an excel cell, now it's explicitly converting the value to string.

Python: Traceback (most recent call last):
  File "\blenderbim\bim\module\fm\operator.py", line 102, in execute
    writer.write_xlsx(filepath)
  File "\blenderbim\libs\site\packages\ifcfm\__init__.py", line 259, in write_xlsx
    cell = worksheet.cell(row=r, column=c, value=col)
  File "\blenderbim\libs\site\packages\openpyxl\worksheet\worksheet.py", line 247, in cell
    cell.value = value
  File "\blenderbim\libs\site\packages\openpyxl\cell\cell.py", line 218, in value
    self._bind_value(value)
  File "\blenderbim\libs\site\packages\openpyxl\cell\cell.py", line 187, in _bind_value
    raise ValueError("Cannot convert {0!r} to Excel".format(value))
ValueError: Cannot convert ['USE TYPE CATALOG'] to Excel
2023-10-18 16:40:07 +05:00
Andrej730 627cf63a4c ifcfm symlink to dev environment setup script 2023-10-18 16:07:27 +05:00
Andrej730 744c08e13e bim.switch_representation to switch to exact representation
previously it was switching to the first representation that matches the context, so it wasn't possible to switch to the second one if you had more than 1 representation in the same context

using always both element and representation in `geometry.import_representation` so it will import the exact representation and not the first one found by `ifcopenshell.geom.create_shape`
2023-10-18 15:48:08 +05:00
Andrej730 dd7a6a80eb activate_drawing to prioritize already active representation
If there are multiple representations in the same subcontext, when it was searching for the representation for some specific subcontext, it would previously always switch to the first representation from the list, now it's going to prioritize the one that's already active.
2023-10-18 15:48:01 +05:00
Andrej730 ab807e3b9b bim.create_all_shapes info message 2023-10-18 12:21:41 +05:00
Andrej730 c35be50c83 Bump build version to fix #3889 2023-10-18 11:10:04 +05:00
Andrej730 7ff98d2918 Some UI to mark booleans as manual #3709
Currently it allows only to either set all object's booleans to be marked as manual or all of them marked as automatic, can't change some specific boolean.

Short demo - https://imgur.com/a/bczv35N
2023-10-17 18:19:45 +05:00
Andrej730 35d66f5d8c move to tools methods for working with manual booleans #3709
Also fixed a bug adding boolean when pset is already added and representation type in add_boolean

```
  File "\addons\blenderbim\tool\ifc.py", line 201, in execute
    IfcStore.execute_ifc_operator(self, context)
  File "\addons\blenderbim\bim\ifc.py", line 336, in execute_ifc_operator
    result = getattr(operator, "_execute")(context)
  File "\addons\blenderbim\bim\module\model\opening.py", line 534, in _execute
    data = json.loads(pset["Data"])
  File "\addons\blenderbim\libs\site\packages\ifcopenshell\entity_instance.py", line 248, in __getitem__
    if key < 0 or key >= len(self):
TypeError: '<' not supported between instances of 'str' and 'int'
```
2023-10-17 18:13:54 +05:00
Andrej730 3f018a4195 localize mathutils dependency in tests #3895
replaced shapebuilder with general ifcopenshell code in `test_add_boolean` (the only place besides `test_shape_builder` where it was used), that way mathutils dependency in tests is localized only to `test_shape_builder.py`.

added test-safe option to makefile, so github workflow would ignore `test_shape_builder` tests for now and it wouldn't interrupt the entire workflow
2023-10-17 15:53:26 +05:00
Andrej730 578e15adb2 refresh_aggregate to show time it took
since this can get very time consuming on large projects
2023-10-17 15:07:43 +05:00
Andrej730 c4474455e9 Fix poll error after 3de9915
The error was
Traceback (most recent call last):
  File "\blenderbim\bim\module\model\covering.py", line 64, in poll
    return element.is_a("IfcCovering")
AttributeError: 'NoneType' object has no attribute 'is_a'
2023-10-17 15:07:43 +05:00
Dion Moult 8f43854cbf Minor fix 2023-10-17 20:47:39 +11:00
Thomas Krijnen 787149da13 Regen express rule code: lowercase str comparison in express rule generated code 2023-10-17 11:04:46 +02:00
Thomas Krijnen 3b31f6932a Compile IFC4X3_ADD2 express rules 2023-10-17 09:50:39 +02:00
Andrej730 6c9e130ca8 small fix f38952dc5 #3898 😬 2023-10-17 10:47:52 +05:00
Andrej730 f38952dc51 small refactor 2023-10-16 16:23:48 +05:00
Andrej730 24eb149221 Support other voiding IfcFeatureElementSubtraction elements #3889
Support other voiding IfcFeatureElementSubtraction besides IfcOpeningElement.
2023-10-16 15:31:53 +05:00
Andrej730 ce8598a0b4 Fixed issue after 19345ff
It wasn't considering that EnumerationReference could be None
2023-10-16 15:31:53 +05:00
Thomas Krijnen fb9cf75412 #3889 Apply handling of openings on IfcFeatureElementSubtraction generically 2023-10-16 11:02:40 +02:00
Andrej730 68f5df73b9 black formatting 2023-10-16 12:21:20 +05:00
Andrej730 e068c523ec Keep placement axes recalculating profiles #3879 2023-10-16 12:19:18 +05:00
Andrej730 aa182bf047 small fix add_profile_representation - use of undefined variables 2023-10-16 11:59:33 +05:00
Ryan Schultz 6e9639446d Add a preference for particular app to open sheet layout 2023-10-15 20:04:53 -05:00
Bruno Postle c48c1c3b32 BlenderBIM minor fix after 7107a91 2023-10-15 21:15:32 +01:00
276 changed files with 38580 additions and 4502 deletions
+1 -2
View File
@@ -88,7 +88,6 @@ jobs:
sudo /usr/bin/python -m pip install networkx
sudo /usr/bin/python -m pip install tabulate
sudo /usr/bin/python -m pip install python-dateutil
sudo /usr/bin/python -m pip install mathutils
- name: Test
run: |
@@ -96,4 +95,4 @@ jobs:
sudo /usr/bin/python tests.py
cd ../src/ifcopenshell-python
mv ifcopenshell ifcopenshell-local # Force testing on installed module
make test
make test-safe
+1 -2
View File
@@ -114,7 +114,6 @@ jobs:
sudo /usr/bin/python -m pip install https://github.com/Andrej730/aud/archive/refs/heads/master-reduced-size.zip
sudo /usr/bin/python -m pip install tabulate
sudo /usr/bin/python -m pip install python-dateutil
sudo /usr/bin/python -m pip install mathutils
- name: Test
run: |
@@ -122,4 +121,4 @@ jobs:
sudo /usr/bin/python tests.py
cd ../src/ifcopenshell-python
mv ifcopenshell ifcopenshell-local # Force testing on installed module
make test
make test-safe
+6 -2
View File
@@ -439,9 +439,13 @@ if(BUILD_IFCGEOM)
if(WASM_BUILD)
set(OPENCASCADE_LIBRARIES ${OPENCASCADE_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT})
else()
# OPENCASCADE_LIBRARIES repeated N times below in order to fix cyclic dependencies - use --start-group ... --end-group instead?
# OPENCASCADE_LIBRARIES repeated N times below in order to fix cyclic dependencies
# tfk: --start-group ... --end-group didn't work on the apple linker when last tested
set(OPENCASCADE_LIBRARIES ${OPENCASCADE_LIBRARIES} ${OPENCASCADE_LIBRARIES} ${OPENCASCADE_LIBRARIES} ${OPENCASCADE_LIBRARIES} ${OPENCASCADE_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT})
if(APPLE)
set(OPENCASCADE_LIBRARIES ${OPENCASCADE_LIBRARIES} ${OPENCASCADE_LIBRARIES} ${OPENCASCADE_LIBRARIES} ${OPENCASCADE_LIBRARIES} ${OPENCASCADE_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT})
else()
set(OPENCASCADE_LIBRARIES -Wl,--start-group ${OPENCASCADE_LIBRARIES} -Wl,--end-group ${CMAKE_THREAD_LIBS_INIT})
endif()
endif()
if (NOT APPLE AND NOT WIN32)
+1 -1
View File
@@ -169,7 +169,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-f0e03c7-$(PLATFORM)64.zip
cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-$(PYNUMBER)-v0.7.0-6c9e130-$(PLATFORM)64.zip
cd dist/working && unzip ifcopenshell-python*
cp -r dist/working/ifcopenshell dist/blenderbim/libs/site/packages/
+9 -5
View File
@@ -125,13 +125,14 @@ classes = [
ui.BIM_UL_generic,
ui.BIM_UL_topics,
ui.BIM_ADDON_preferences,
# Project overview
ui.BIM_PT_project_info,
ui.BIM_PT_project_setup,
ui.BIM_PT_geometry,
ui.BIM_PT_tab_grouping_and_filtering,
# Tabs panel
ui.BIM_PT_tabs,
# Project overview
ui.BIM_PT_tab_project_info,
ui.BIM_PT_tab_project_setup,
ui.BIM_PT_tab_geometry,
ui.BIM_PT_tab_stakeholders,
ui.BIM_PT_tab_grouping_and_filtering,
# Object information
ui.BIM_PT_tab_object_metadata,
ui.BIM_PT_tab_misc,
@@ -140,6 +141,7 @@ classes = [
ui.BIM_PT_tab_representations,
ui.BIM_PT_tab_geometric_relationships,
ui.BIM_PT_tab_parametric_geometry,
ui.BIM_PT_tab_profiles,
ui.BIM_PT_tab_materials,
ui.BIM_PT_tab_styles,
# Drawings and documents
@@ -150,6 +152,7 @@ classes = [
# Services and systems
ui.BIM_PT_tab_services,
ui.BIM_PT_tab_services_object,
ui.BIM_PT_tab_zones,
# Structural analysis
ui.BIM_PT_tab_structural,
# Construction scheduling
@@ -285,6 +288,7 @@ def unregister():
"SCENE_PT_rigid_body_world",
"SCENE_PT_audio",
"SCENE_PT_keying_sets",
"SCENE_PT_simulation",
"SCENE_PT_custom_props",
]:
try:
+12
View File
@@ -319,6 +319,17 @@ if getattr(bpy.types, "SCENE_PT_custom_props"):
return tool.Blender.is_tab(context, "BLENDER")
# available on Blender 4.0+
if getattr(bpy.types, "SCENE_PT_simulation", None):
class Override_SCENE_PT_simulation(bpy.types.SCENE_PT_simulation):
bl_idname = "SCENE_PT_simulation_override"
@classmethod
def poll(cls, context):
return tool.Blender.is_tab(context, "BLENDER")
@persistent
def load_post(scene):
global global_subscription_owner
@@ -361,6 +372,7 @@ def load_post(scene):
"SCENE_PT_rigid_body_world",
"SCENE_PT_audio",
"SCENE_PT_keying_sets",
"SCENE_PT_simulation",
"SCENE_PT_custom_props",
]:
if getattr(bpy.types, panel, None):
+1 -6
View File
@@ -260,12 +260,7 @@ def convert_property_group_from_si(property_group, skip_props=()):
for prop_name in property_group.bl_rna.properties.keys():
if prop_name in skip_props:
continue
prop_bl_rna = property_group.bl_rna.properties[prop_name]
if prop_bl_rna.array_length > 0:
prop_value = prop_bl_rna.default_array
else:
prop_value = prop_bl_rna.default
prop_value = tool.Blender.get_blender_prop_default_value(property_group, prop_name)
if type(prop_value) is float:
prop_value = prop_value * conversion_k
elif type(prop_value) is bpy.types.bpy_prop_array:
+17 -11
View File
@@ -494,7 +494,7 @@ class IfcImporter:
rep = representation
while True:
if rep.Items and rep.Items[0].is_a("IfcMappedItem"):
if len(rep.Items) == 1 and rep.Items[0].is_a("IfcMappedItem"):
rep_matrix = ifcopenshell.util.placement.get_mappeditem_transformation(rep.Items[0])
if not np.allclose(rep_matrix, np.eye(4)):
matrix = rep_matrix @ matrix
@@ -547,7 +547,7 @@ class IfcImporter:
return True
def is_native_swept_disk_solid(self, element, representation):
items = representation.Items or [] # Be forgiving of invalid IFCs because Revit :(
items = [i["item"] for i in ifcopenshell.util.representation.resolve_items(representation)]
if len(items) == 1 and items[0].is_a("IfcSweptDiskSolid"):
if tool.Blender.Modifier.is_railing(element):
return False
@@ -563,6 +563,7 @@ class IfcImporter:
return False
def is_native_faceted_brep(self, representation):
# TODO handle mapped items
for i in representation.Items:
if i.is_a() != "IfcFacetedBrep":
return False
@@ -1316,12 +1317,17 @@ class IfcImporter:
curve.resolution_u = 2
polyline = curve.splines.new("POLY")
for item in native_data["representation"].Items:
for item_data in ifcopenshell.util.representation.resolve_items(native_data["representation"]):
item = item_data["item"]
matrix = item_data["matrix"]
matrix[0][3] *= self.unit_scale
matrix[1][3] *= self.unit_scale
matrix[2][3] *= self.unit_scale
# TODO: support inner radius, start param, and end param
geometry = self.create_generic_shape(item.Directrix)
e = geometry.edges
v = geometry.verts
vertices = [[v[i], v[i + 1], v[i + 2], 1] for i in range(0, len(v), 3)]
vertices = [list(matrix @ [v[i], v[i + 1], v[i + 2], 1]) for i in range(0, len(v), 3)]
edges = [[e[i], e[i + 1]] for i in range(0, len(e), 2)]
v2 = None
for edge in edges:
@@ -1401,7 +1407,8 @@ class IfcImporter:
context_override = {}
context_override["object"] = context_override["active_object"] = target
context_override["selected_objects"] = context_override["selected_editable_objects"] = objs
bpy.ops.object.join(context_override)
with bpy.context.temp_override(**context_override):
bpy.ops.object.join()
target.data.name += "-merge"
for ifc_definition_id in id_set[group_name][1:]:
del self.added_data[ifc_definition_id]
@@ -1458,11 +1465,10 @@ class IfcImporter:
types_collection.hide_viewport = False
bpy.context.view_layer.objects.active = last_obj
context_override = {}
bpy.ops.object.editmode_toggle(context_override)
bpy.ops.mesh.tris_convert_to_quads(context_override)
bpy.ops.mesh.normals_make_consistent(context_override)
bpy.ops.object.editmode_toggle(context_override)
bpy.ops.object.editmode_toggle()
bpy.ops.mesh.tris_convert_to_quads()
bpy.ops.mesh.normals_make_consistent()
bpy.ops.object.editmode_toggle()
types_collection.hide_viewport = True
bpy.context.view_layer.objects.active = last_obj
@@ -1920,7 +1926,7 @@ class IfcImporter:
def link_element(self, element, obj):
self.added_data[element.id()] = obj
IfcStore.link_element(element, obj)
tool.Ifc.link(element, obj)
def set_matrix_world(self, obj, matrix_world):
obj.matrix_world = matrix_world
@@ -62,14 +62,14 @@ class BoundaryDecorator:
gpu.state.point_size_set(6)
gpu.state.blend_set("ALPHA")
self.line_shader = gpu.shader.from_builtin("3D_POLYLINE_UNIFORM_COLOR")
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("3D_UNIFORM_COLOR")
self.shader = gpu.shader.from_builtin("UNIFORM_COLOR")
selected_vertices = []
selected_edges = []
@@ -31,7 +31,7 @@ class BIM_PT_SceneBoundaries(Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_geometry"
bl_parent_id = "BIM_PT_tab_geometry"
@classmethod
def poll(cls, context):
@@ -28,7 +28,7 @@ class BIM_PT_bsdd(Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_project_setup"
bl_parent_id = "BIM_PT_tab_project_setup"
def draw(self, context):
props = context.scene.BIMBSDDProperties
@@ -36,7 +36,7 @@ class BIM_PT_classifications(Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_project_setup"
bl_parent_id = "BIM_PT_tab_project_setup"
@classmethod
def poll(cls, context):
@@ -29,7 +29,7 @@ class BIM_PT_constraints(Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_project_setup"
bl_parent_id = "BIM_PT_tab_project_setup"
@classmethod
def poll(cls, context):
@@ -29,7 +29,7 @@ class BIM_PT_context(bpy.types.Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_geometry"
bl_parent_id = "BIM_PT_tab_geometry"
@classmethod
def poll(cls, context):
@@ -136,13 +136,11 @@ class CostSchedulesData:
data["UnitBasisUnitSymbol"] = "U"
if cost_value.Category == "*":
is_sum = True
cost_quantity = data["TotalCostQuantity"] or 1
if has_unit_basis:
data["TotalCost"] = data["TotalAppliedValue"] / data["UnitBasisValueComponent"]
data["TotalCost"] = data["TotalAppliedValue"] * cost_quantity / data["UnitBasisValueComponent"]
else:
if data["TotalCostQuantity"] is not None:
data["TotalCost"] = data["TotalAppliedValue"] * data["TotalCostQuantity"]
else:
data["TotalCost"] = data["TotalAppliedValue"]
data["TotalCost"] = data["TotalAppliedValue"] * cost_quantity
if is_sum:
data["TotalAppliedValue"] = None
@@ -160,8 +158,10 @@ class CostSchedulesData:
unit = ifcopenshell.util.unit.get_property_unit(quantity, tool.Ifc.get())
if unit:
data["UnitSymbol"] = ifcopenshell.util.unit.get_unit_symbol(unit)
else:
data["UnitSymbol"] = "U"
if quantity.is_a("IfcPhysicalSimpleQuantity"):
measure_class = quantity.wrapped_data.declaration().as_entity().attribute_by_index(3).type_of_attribute().declared_type().name()
if "Count" in measure_class:
data["UnitSymbol"] = "U"
# same_unit_nested_cost_item = set()
# data["DerivedTotalCostQuantity"] = None
@@ -30,12 +30,10 @@ from bpy.props import (
FloatVectorProperty,
CollectionProperty,
)
import blenderbim.tool as tool
import blenderbim.core.geometry
import ifcopenshell
#import blenderbim.tool as tool
#import blenderbim.core.geometry
#import ifcopenshell
class BIMCoveringProperties(PropertyGroup):
pass
# depth: bpy.props.FloatProperty(name="Depth", default=0.1, subtype="DISTANCE", description="Flooring depth")
depth: bpy.props.FloatProperty(name="ceiling_height", default=2.7, subtype="DISTANCE", description="Ceiling height")
ceiling_height: bpy.props.FloatProperty(name="ceiling_height", default=2.7, subtype="DISTANCE", description="Ceiling height")
@@ -92,6 +92,8 @@ class CoveringToolUI:
def draw_default_interface(cls):
row = cls.layout.row(align=True)
row.prop(data=cls.props, property="rl3", text="RL")
row = cls.layout.row(align=True)
row.prop(data=cls.covering_props, property="ceiling_height", text="Ceiling Height")
if AuthoringData.data["ifc_classes"]:
active_obj = bpy.context.active_object
element = tool.Ifc.get_entity(active_obj)
@@ -101,17 +103,26 @@ class CoveringToolUI:
relating_type_id = int(cls.props.relating_type_id)
type_material_usage = ifcopenshell.util.element.get_material(tool.Ifc.get().by_id(relating_type_id)).is_a()
if (AuthoringData.data["predefined_type"] == "FLOORING" and
type_material_usage == "IfcMaterialLayerSet" and
# PLEASE KEEP COMMENTS AS A REMINDER
# 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")
# PLEASE KEEP COMMENTS AS A REMINDER
# elif element and bpy.context.selected_objects and element.is_a("IfcSpace"):
# op. = row.operator("bim.add_istance_flooring_from_spaces"):
if tool.Ifc.get_entity(collection_obj):
op = row.operator("bim.add_instance_flooring_covering_from_cursor")
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
@@ -136,7 +147,7 @@ class CoveringToolUI:
row = cls.layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="", icon="EVENT_G")
op = row.operator("bim.regen_selected_flooring_object")
op = row.operator("bim.regen_selected_covering_object")
@@ -230,13 +241,22 @@ class Hotkey(bpy.types.Operator, Operator):
bpy.ops.bim.add_instance_flooring_covering_from_cursor()
else:
bpy.ops.bim.add_constr_type_instance()
elif AuthoringData.data["predefined_type"] == "CEILING":
if element and bpy.context.selected_objects and element.is_a("IfcWall"):
pass
elif tool.Ifc.get_entity(collection_obj):
bpy.ops.bim.add_instance_ceiling_covering_from_cursor()
else:
bpy.ops.bim.add_constr_type_instance()
else:
bpy.ops.bim.add_constr_type_instance()
def hotkey_S_G(self):
active_obj = bpy.context.active_object
element = tool.Ifc.get_entity(active_obj)
if (element and
bpy.context.selected_objects and
element.is_a("IfcCovering") and
AuthoringData.data["active_material_usage"] == "LAYER3"):
bpy.ops.bim.regen_selected_covering_object()
if AuthoringData.data["predefined_type"] == "FLOORING":
if element and bpy.context.selected_objects and element.is_a("IfcCovering"):
bpy.ops.bim.regen_selected_flooring_object()
@@ -207,6 +207,7 @@ class ExportIfcCsv(bpy.types.Operator):
empty=props.empty_value,
bool_true=props.true_value,
bool_false=props.false_value,
concat=props.concat_value,
sort=sort,
groups=groups,
summaries=summaries,
@@ -72,6 +72,7 @@ class CsvProperties(PropertyGroup):
empty_value: StringProperty(default="-", name="Empty String Value")
true_value: StringProperty(default="YES", name="True Value")
false_value: StringProperty(default="NO", name="False Value")
concat_value: StringProperty(default=", ", name="Concat Value")
csv_delimiter: EnumProperty(
items=[
(";", ";", ""),
@@ -79,6 +79,8 @@ class BIM_PT_ifccsv(Panel):
row.prop(props, "true_value")
row = layout.row()
row.prop(props, "false_value")
row = layout.row()
row.prop(props, "concat_value")
layout.use_property_split = False
blenderbim.bim.helper.draw_filter(self.layout, props, SearchData, "csv")
@@ -218,7 +218,7 @@ class CreateAllShapes(bpy.types.Operator):
len(shape.geometry.edges),
len(shape.geometry.faces),
)
print(f"Failures: {len(failures)}")
self.report({"INFO"}, f"Failed shapes: {len(failures)}, check the system console for details.")
for failure in failures:
print(failure)
return {"FINISHED"}
@@ -29,7 +29,7 @@ class BIM_PT_documents(Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_project_setup"
bl_parent_id = "BIM_PT_tab_project_setup"
@classmethod
def poll(cls, context):
@@ -161,9 +161,9 @@ class BaseDecorator:
os.path.join(bpy.context.scene.BIMProperties.data_dir, "fonts", "OpenGost Type B TT.ttf")
)
# 3D_POLYLINE_UNIFORM_COLOR is good for smoothed lines since `bgl.enable(GL_LINE_SMOOTH)` is deprecated
self.line_shader = gpu.shader.from_builtin("3D_POLYLINE_UNIFORM_COLOR")
self.base_shader = gpu.shader.from_builtin("3D_UNIFORM_COLOR")
# POLYLINE_UNIFORM_COLOR is good for smoothed lines since `bgl.enable(GL_LINE_SMOOTH)` is deprecated
self.line_shader = gpu.shader.from_builtin("POLYLINE_UNIFORM_COLOR")
self.base_shader = gpu.shader.from_builtin("UNIFORM_COLOR")
def get_camera_width_mm(self):
# Horrific prototype code to ensure bgl draws at drawing scales
@@ -390,8 +390,6 @@ class BaseDecorator:
# 0 is the default font, but we're fancier than that
font_id = self.font_id
dpi = context.preferences.system.dpi
color = context.preferences.addons["blenderbim"].preferences.decorations_colour
ang = -Vector((1, 0)).angle_signed(text_dir)
@@ -411,7 +409,7 @@ class BaseDecorator:
font_size_px = int(0.004118616 * mm_to_px) * font_size_mm / 2.5
pos = pos - line_no * font_size_px * rotation_matrix[1]
blf.size(font_id, font_size_px, dpi)
blf.size(font_id, font_size_px)
if box_alignment or center or vcenter:
w, h = blf.dimensions(font_id, text)
@@ -1563,15 +1561,15 @@ class CutDecorator:
gpu.state.point_size_set(1)
gpu.state.blend_set("ALPHA")
# 3D_POLYLINE_UNIFORM_COLOR is good for smoothed lines since `bgl.enable(GL_LINE_SMOOTH)` is deprecated
self.line_shader = gpu.shader.from_builtin("3D_POLYLINE_UNIFORM_COLOR")
# POLYLINE_UNIFORM_COLOR is good for smoothed lines since `bgl.enable(GL_LINE_SMOOTH)` is deprecated
self.line_shader = gpu.shader.from_builtin("POLYLINE_UNIFORM_COLOR")
self.line_shader.bind()
# 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("3D_UNIFORM_COLOR")
self.shader = gpu.shader.from_builtin("UNIFORM_COLOR")
self.shader.bind()
black = (0, 0, 0, 1)
@@ -1931,7 +1929,7 @@ class DecorationsHandler:
if cls.installed:
cls.uninstall()
handler = cls()
# NOTE: we USE POST_PIXEL here so that we can use both 3D_POLYLINE_UNIFORM_COLOR
# NOTE: we USE POST_PIXEL here so that we can use both POLYLINE_UNIFORM_COLOR
# and drawing text in the same handler. BUT this means that we supply coordinates in WINSPACE
cls.installed = SpaceView3D.draw_handler_add(handler, (context,), "WINDOW", "POST_PIXEL")
@@ -259,7 +259,9 @@ class CreateDrawing(bpy.types.Operator):
with profile("Combine SVG layers"):
svg_path = self.combine_svgs(context, underlay_svg, linework_svg, annotation_svg)
tool.Drawing.open_with_user_command(context.preferences.addons["blenderbim"].preferences.svg_command, svg_path)
tool.Drawing.open_with_user_command(
context.preferences.addons["blenderbim"].preferences.svg_command, svg_path
)
if self.print_all:
bpy.ops.bim.activate_drawing(drawing=original_drawing_id, camera_view_point=False)
@@ -534,7 +536,7 @@ class CreateDrawing(bpy.types.Operator):
root = etree.fromstring(results)
group = root.find("{http://www.w3.org/2000/svg}g")
if not group:
if group is None:
with open(svg_path, "wb") as svg:
svg.write(etree.tostring(root))
@@ -918,7 +920,7 @@ class CreateDrawing(bpy.types.Operator):
join_criteria = join_criteria.split(",")
else:
# Drawing convention states that same objects classes with the same material are merged when cut.
join_criteria = ["class", "material.Name", 'r"Pset.*Common"."Status"']
join_criteria = ["class", "material.Name", "/Pset_.*Common/.Status", "EPset_Status.Status"]
group = root.find("{http://www.w3.org/2000/svg}g")
joined_paths = {}
@@ -288,9 +288,9 @@ class BaseShader:
"""
def __init__(self):
# 3D_POLYLINE_UNIFORM_COLOR is good for smoothed lines since `bgl.enable(GL_LINE_SMOOTH)` is deprecated
self.line_shader = gpu.shader.from_builtin("3D_POLYLINE_UNIFORM_COLOR")
self.base_shader = gpu.shader.from_builtin("3D_UNIFORM_COLOR")
# POLYLINE_UNIFORM_COLOR is good for smoothed lines since `bgl.enable(GL_LINE_SMOOTH)` is deprecated
self.line_shader = gpu.shader.from_builtin("POLYLINE_UNIFORM_COLOR")
self.base_shader = gpu.shader.from_builtin("UNIFORM_COLOR")
def get_shader(self):
"""Returns shader for this type"""
@@ -160,7 +160,8 @@ class SheetBuilder:
x = float(image.attrib["x"])
y = float(image.attrib["y"])
if image.attrib["data-type"] == "view-title":
image.attrib["x"] = str(x - readjust.x)
image.attrib["x"] = str(x + readjust.x)
# negate y offset because view-title comes AFTER foreground
image.attrib["y"] = str(y - readjust.y)
else:
image.attrib["x"] = str(x + readjust.x)
@@ -160,7 +160,7 @@ class SvgWriter:
def find_xml_symbol_by_id(self, id):
tree = ET.parse(self.resource_paths["Symbols"])
xml_symbol = tree.find(f'.//*[@id="{id}"]')
return External(xml_symbol) if xml_symbol else None
return External(xml_symbol) if xml_symbol is not None else None
def add_patterns(self):
path = self.resource_paths["Patterns"]
@@ -61,6 +61,7 @@ classes = (
ui.BIM_PT_workarounds,
ui.BIM_MT_object_set_origin,
ui.BIM_MT_separate,
ui.BIM_MT_hotkey_separate,
ui.BIM_UL_representation_items,
)
@@ -91,7 +92,9 @@ def register():
addon_keymaps.append((km, kmi))
kmi = km.keymap_items.new("bim.override_object_duplicate_move_linked_macro", "D", "PRESS", alt=True)
addon_keymaps.append((km, kmi))
kmi = km.keymap_items.new("bim.override_object_duplicate_move_aggregate_macro", "D", "PRESS", ctrl=True, shift=True)
kmi = km.keymap_items.new(
"bim.override_object_duplicate_move_aggregate_macro", "D", "PRESS", ctrl=True, shift=True
)
addon_keymaps.append((km, kmi))
kmi = km.keymap_items.new("bim.override_paste_buffer", "V", "PRESS", ctrl=True)
addon_keymaps.append((km, kmi))
@@ -106,6 +109,9 @@ def register():
km = wm.keyconfigs.addon.keymaps.new(name="Mesh", space_type="EMPTY")
kmi = km.keymap_items.new("bim.override_mode_set_object", "TAB", "PRESS")
addon_keymaps.append((km, kmi))
kmi = km.keymap_items.new("wm.call_menu", "P", "PRESS")
kmi.properties.name = ui.BIM_MT_hotkey_separate.bl_idname
addon_keymaps.append((km, kmi))
km = wm.keyconfigs.addon.keymaps.new(name="Curve", space_type="EMPTY")
kmi = km.keymap_items.new("bim.override_mode_set_object", "TAB", "PRESS")
@@ -34,6 +34,7 @@ import blenderbim.core.drawing
import blenderbim.tool as tool
import blenderbim.bim.handler
from mathutils import Vector, Matrix
from time import time
from blenderbim.bim import import_ifc
from blenderbim.bim.ifc import IfcStore
@@ -66,9 +67,12 @@ class OverrideMeshSeparate(bpy.types.Operator, Operator):
def _execute(self, context):
obj = context.active_object
element = tool.Ifc.get_entity(obj)
if not element:
return
# You cannot separate meshes if the representation is mapped.
relating_type = tool.Root.get_element_type(tool.Ifc.get_entity(obj))
relating_type = tool.Root.get_element_type(element)
if relating_type and tool.Root.does_type_have_representations(relating_type):
# We toggle edit mode to ensure that once representations are
# unmapped, our Blender mesh only has a single user.
@@ -210,18 +214,22 @@ class SwitchRepresentation(bpy.types.Operator, Operator):
should_switch_all_meshes: bpy.props.BoolProperty()
def _execute(self, context):
target = tool.Ifc.get().by_id(self.ifc_definition_id).ContextOfItems
target_representation = tool.Ifc.get().by_id(self.ifc_definition_id)
target = target_representation.ContextOfItems
is_subcontext = target.is_a("IfcGeometricRepresentationSubContext")
for obj in set(context.selected_objects + [context.active_object]):
element = tool.Ifc.get_entity(obj)
if not element:
continue
if is_subcontext:
representation = ifcopenshell.util.representation.get_representation(
element, target.ContextType, target.ContextIdentifier, target.TargetView
)
if obj == context.active_object:
representation = target_representation
else:
representation = ifcopenshell.util.representation.get_representation(element, target.ContextType)
if is_subcontext:
representation = ifcopenshell.util.representation.get_representation(
element, target.ContextType, target.ContextIdentifier, target.TargetView
)
else:
representation = ifcopenshell.util.representation.get_representation(element, target.ContextType)
if not representation:
continue
core.switch_representation(
@@ -790,7 +798,8 @@ class OverrideDuplicateMove(bpy.types.Operator):
new = blenderbim.core.root.copy_class(tool.Ifc, tool.Collector, tool.Geometry, tool.Root, obj=new_obj)
# clean up the orphaned mesh with ifc id of the original object to avoid confusion
if new and temp_data:
# IfcGridAxis keeps the same mesh data (it's pointing to ifc id 0, so it's not a problem)
if new and temp_data and not new.is_a("IfcGridAxis"):
tool.Blender.remove_data_block(temp_data)
if new:
@@ -903,14 +912,14 @@ class OverrideDuplicateMoveAggregate(bpy.types.Operator):
"pset.edit_pset",
tool.Ifc.get(),
pset=tool.Ifc.get().by_id(pset["id"]),
properties={"Data": json.dumps(data)},
properties={"Data": tool.Ifc.get().createIfcText(json.dumps(data))},
)
else:
ifcopenshell.api.run(
"pset.edit_pset",
tool.Ifc.get(),
pset=tool.Ifc.get().by_id(pset["id"]),
properties={"Parent": parent.GlobalId, "Data": json.dumps(data)},
properties={"Parent": parent.GlobalId, "Data": tool.Ifc.get().createIfcText(json.dumps(data))},
)
else:
@@ -922,7 +931,7 @@ class OverrideDuplicateMoveAggregate(bpy.types.Operator):
"pset.edit_pset",
tool.Ifc.get(),
pset=pset,
properties={"Parent": parent.GlobalId, "Data": json.dumps(data)},
properties={"Parent": parent.GlobalId, "Data": tool.Ifc.get().createIfcText(json.dumps(data))},
)
def add_child_to_assembly_data(new_entity):
@@ -936,7 +945,7 @@ class OverrideDuplicateMoveAggregate(bpy.types.Operator):
"pset.edit_pset",
tool.Ifc.get(),
pset=tool.Ifc.get().by_id(parent_pset["id"]),
properties={"Data": json.dumps(data)},
properties={"Data": tool.Ifc.get().createIfcText(json.dumps(data))},
)
def create_data_structure(entity, level=-1):
@@ -1161,6 +1170,7 @@ class RefreshAggregate(bpy.types.Operator):
return {"FINISHED"}
def _execute(self, context):
refresh_start_time = time()
self.new_active_obj = None
old_to_new = {}
@@ -1209,7 +1219,10 @@ class RefreshAggregate(bpy.types.Operator):
"pset.edit_pset",
tool.Ifc.get(),
pset=pset,
properties={"Parent": instance_entity.GlobalId, "Data": json.dumps(data)},
properties={
"Parent": instance_entity.GlobalId,
"Data": tool.Ifc.get().createIfcText(json.dumps(data)),
},
)
part_obj = tool.Ifc.get_object(part)
@@ -1248,7 +1261,10 @@ class RefreshAggregate(bpy.types.Operator):
"pset.edit_pset",
tool.Ifc.get(),
pset=pset,
properties={"Parent": instance_entity.GlobalId, "Data": json.dumps(data)},
properties={
"Parent": instance_entity.GlobalId,
"Data": tool.Ifc.get().createIfcText(json.dumps(data)),
},
)
part_obj = tool.Ifc.get_object(part)
@@ -1364,6 +1380,9 @@ class RefreshAggregate(bpy.types.Operator):
blenderbim.bim.handler.refresh_ui_data()
operator_time = time() - refresh_start_time
if operator_time > 10:
self.report({"INFO"}, "Refresh Aggregate was finished in {:.2f} seconds".format(operator_time))
return {"FINISHED"}
@@ -17,6 +17,7 @@
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
import blenderbim.bim
import blenderbim.tool as tool
from bpy.types import Panel, Menu, UIList
from blenderbim.bim.ifc import IfcStore
@@ -53,6 +54,22 @@ class BIM_MT_separate(Menu):
self.layout.operator("bim.override_mesh_separate", icon="PLUGIN", text="IFC By Loose Parts").type = "LOOSE"
class BIM_MT_hotkey_separate(Menu):
bl_idname = "BIM_MT_hotkey_separate"
bl_label = "Separate"
def draw(self, context):
self.layout.label(text="IFC Separate", icon_value=blenderbim.bim.icons["IFC"].icon_id)
self.layout.operator("bim.override_mesh_separate", text="Selection").type = "SELECTED"
self.layout.operator("bim.override_mesh_separate", text="By Material").type = "MATERIAL"
self.layout.operator("bim.override_mesh_separate", text="By Loose Parts").type = "LOOSE"
self.layout.separator()
self.layout.label(text="Blender Separate", icon="BLENDER")
self.layout.operator("mesh.separate", text="Selection").type = "SELECTED"
self.layout.operator("mesh.separate", text="By Material").type = "MATERIAL"
self.layout.operator("mesh.separate", text="By Loose Parts").type = "LOOSE"
class BIM_MT_object_set_origin(Menu):
bl_idname = "BIM_MT_object_set_origin"
bl_label = "IFC Set Origin"
@@ -29,7 +29,7 @@ class BIM_PT_gis(Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_geometry"
bl_parent_id = "BIM_PT_tab_geometry"
def draw(self, context):
self.layout.use_property_split = True
@@ -29,7 +29,6 @@ classes = (
operator.LoadGroups,
operator.RemoveGroup,
operator.SelectGroupProducts,
operator.ToggleAssigningGroup,
operator.ToggleGroup,
operator.UnassignGroup,
operator.UpdateGroup,
@@ -173,16 +173,6 @@ class DisableEditingGroup(bpy.types.Operator, tool.Ifc.Operator):
return {"FINISHED"}
class ToggleAssigningGroup(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.toggle_assigning_group"
bl_label = "Toggle Assigning Group"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
context.scene.BIMGroupProperties.is_adding = not context.scene.BIMGroupProperties.is_adding
return {"FINISHED"}
class AssignGroup(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.assign_group"
bl_label = "Assign Group"
@@ -49,7 +49,6 @@ class Group(PropertyGroup):
class BIMGroupProperties(PropertyGroup):
group_attributes: CollectionProperty(name="Group Attributes", type=Attribute)
is_editing: BoolProperty(name="Is Editing", default=False)
is_adding: BoolProperty(name="Is Adding", default=False)
groups: CollectionProperty(name="Groups", type=Group)
active_group_index: IntProperty(name="Active Group Index")
active_group_id: IntProperty(name="Active Group Id")
@@ -89,9 +89,9 @@ class BIM_PT_object_groups(Panel):
ObjectGroupsData.load()
self.props = context.scene.BIMGroupProperties
row = self.layout.row(align=True)
if self.props.is_adding:
if self.props.is_editing:
row.label(text="Adding Groups", icon="OUTLINER")
row.operator("bim.toggle_assigning_group", text="", icon="CANCEL")
row.operator("bim.disable_group_editing_ui", text="", icon="CANCEL")
self.layout.template_list(
"BIM_UL_object_groups",
"",
@@ -102,7 +102,7 @@ class BIM_PT_object_groups(Panel):
)
else:
row.label(text=f"{ObjectGroupsData.data['total_groups']} Groups in IFC Project", icon="OUTLINER")
row.operator("bim.toggle_assigning_group", text="", icon="ADD")
row.operator("bim.load_groups", text="", icon="GREASEPENCIL")
for group in ObjectGroupsData.data["groups"]:
row = self.layout.row(align=True)
@@ -11,7 +11,7 @@ class IFCGIT_PT_panel(bpy.types.Panel):
bl_region_type = "WINDOW"
bl_context = "scene"
bl_options = {"DEFAULT_CLOSED"}
bl_parent_id = "BIM_PT_project_info"
bl_parent_id = "BIM_PT_tab_project_info"
def draw(self, context):
@@ -29,7 +29,7 @@ class BIM_PT_libraries(Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_project_setup"
bl_parent_id = "BIM_PT_tab_project_setup"
@classmethod
def poll(cls, context):
@@ -40,10 +40,12 @@ classes = (
operator.EditMaterial,
operator.EditMaterialSetItem,
operator.EditMaterialSetItemProfile,
operator.EditMaterialStyle,
operator.EnableEditingAssignedMaterial,
operator.EnableEditingMaterial,
operator.EnableEditingMaterialSetItem,
operator.EnableEditingMaterialSetItemProfile,
operator.EnableEditingMaterialStyle,
operator.ExpandMaterialCategory,
operator.LoadMaterials,
operator.RemoveConstituent,
@@ -55,6 +57,7 @@ classes = (
operator.ReorderMaterialSetItem,
operator.SelectByMaterial,
operator.UnassignMaterial,
operator.UnassignMaterialStyle,
operator.UnlinkMaterial,
prop.Material,
prop.BIMMaterialProperties,
@@ -38,6 +38,9 @@ class MaterialsData:
"total_materials": cls.total_materials(),
"material_types": cls.material_types(),
"profiles": cls.profiles(),
"styles": cls.styles(),
"contexts": cls.contexts(),
"active_styles": cls.active_styles(),
}
cls.is_loaded = True
@@ -79,6 +82,58 @@ class MaterialsData:
if p.ProfileName
]
@classmethod
def contexts(cls):
results = []
for element in tool.Ifc.get().by_type("IfcGeometricRepresentationContext", include_subtypes=False):
results.append((str(element.id()), element.ContextType or "Unnamed", ""))
for element in tool.Ifc.get().by_type("IfcGeometricRepresentationSubContext", include_subtypes=False):
results.append(
(
str(element.id()),
"{}/{}/{}".format(
element.ContextType or "Unnamed",
element.ContextIdentifier or "Unnamed",
element.TargetView or "Unnamed",
),
"",
)
)
return results
@classmethod
def styles(cls):
return [(str(s.id()), s.Name or "Unnamed", "") for s in tool.Ifc.get().by_type("IfcSurfaceStyle") if s.Name]
@classmethod
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"):
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({
"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
class ObjectMaterialData:
data = {}
@@ -118,10 +118,18 @@ class AddMaterial(bpy.types.Operator, tool.Ifc.Operator):
bl_label = "Add Material"
bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
name: bpy.props.StringProperty(default="Default")
def invoke(self, context, event):
return context.window_manager.invoke_props_dialog(self)
def draw(self, context):
row = self.layout
row.prop(self, "name", text="Name")
def _execute(self, context):
obj = bpy.data.materials.get(self.obj) if self.obj else None
core.add_material(tool.Ifc, tool.Material, tool.Style, obj=obj)
core.add_material(tool.Ifc, tool.Material, tool.Style, obj=obj, name=self.name)
material_prop_purge()
@@ -707,3 +715,50 @@ class ContractMaterialCategory(bpy.types.Operator):
category.is_expanded = False
core.load_materials(tool.Material, props.material_type)
return {"FINISHED"}
class EnableEditingMaterialStyle(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.enable_editing_material_style"
bl_label = "Enable Editing Material Style"
bl_options = {"REGISTER", "UNDO"}
material: bpy.props.IntProperty()
def _execute(self, context):
props = bpy.context.scene.BIMMaterialProperties
props.active_material_id = self.material
props.editing_material_type = "STYLE"
class EditMaterialStyle(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.edit_material_style"
bl_label = "Edit Material Style"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
props = bpy.context.scene.BIMMaterialProperties
material = tool.Ifc.get().by_id(props.active_material_id)
style = tool.Ifc.get().by_id(int(props.styles))
context = tool.Ifc.get().by_id(int(props.contexts))
ifcopenshell.api.run(
"style.assign_material_style", tool.Ifc.get(), material=material, style=style, context=context
)
tool.Material.disable_editing_material()
core.load_materials(tool.Material, props.material_type)
class UnassignMaterialStyle(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.unassign_material_style"
bl_label = "Unassign Material Style"
bl_options = {"REGISTER", "UNDO"}
style: bpy.props.IntProperty()
context: bpy.props.IntProperty()
def _execute(self, context):
props = bpy.context.scene.BIMMaterialProperties
material = tool.Ifc.get().by_id(props.materials[props.active_material_index].ifc_definition_id)
style = tool.Ifc.get().by_id(self.style)
context = tool.Ifc.get().by_id(self.context)
ifcopenshell.api.run(
"style.unassign_material_style", tool.Ifc.get(), material=material, style=style, context=context
)
core.load_materials(tool.Material, props.material_type)
@@ -103,11 +103,24 @@ def get_profiles(self, context):
return MaterialsData.data["profiles"]
def get_styles(self, context):
if not MaterialsData.is_loaded:
MaterialsData.load()
return MaterialsData.data["styles"]
def get_contexts(self, context):
if not MaterialsData.is_loaded:
MaterialsData.load()
return MaterialsData.data["contexts"]
class Material(PropertyGroup):
name: StringProperty(name="Name")
ifc_definition_id: IntProperty(name="IFC Definition ID")
is_category: BoolProperty(name="Is Category", default=False)
is_expanded: BoolProperty(name="Is Expanded", default=True)
has_style: BoolProperty(name="Has Style", default=True)
total_elements: IntProperty(name="Total Elements")
@@ -119,7 +132,9 @@ class BIMMaterialProperties(PropertyGroup):
profiles: EnumProperty(items=get_profiles, name="Profiles")
active_material_id: IntProperty(name="Active Material ID")
material_attributes: CollectionProperty(name="Material Attributes", type=Attribute)
editing_material_type = StringProperty(name="Editing Material Type")
editing_material_type: StringProperty(name="Editing Material Type")
styles: EnumProperty(items=get_styles, name="Styles")
contexts: EnumProperty(items=get_contexts, name="Contexts")
class BIMObjectMaterialProperties(PropertyGroup):
@@ -57,20 +57,19 @@ class BIM_PT_materials(Panel):
row.alignment = "RIGHT"
if self.props.material_type == "IfcMaterial":
if not self.props.active_material_id:
row.operator("bim.add_material", text="", icon="ADD")
row.operator("bim.add_material", text="", icon="ADD")
if self.props.materials and self.props.active_material_index < len(self.props.materials):
material = self.props.materials[self.props.active_material_index]
if material.ifc_definition_id:
if self.props.active_material_id:
row.operator("bim.edit_material", text="", icon="CHECKMARK").material = material.ifc_definition_id
row.operator("bim.disable_editing_material", text="", icon="CANCEL").material = material.ifc_definition_id
self.draw_editable_material_attributes_ui()
else:
op = row.operator("bim.select_by_material", text="", icon="RESTRICT_SELECT_OFF")
op.material = material.ifc_definition_id
row.operator("bim.enable_editing_material", text="", icon="GREASEPENCIL").material = material.ifc_definition_id
row.operator("bim.remove_material", text="", icon="X").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")
op.material = material.ifc_definition_id
op = row.operator("bim.enable_editing_material_style", text="", icon="SHADING_RENDERED")
op.material = material.ifc_definition_id
row.operator("bim.remove_material", text="", icon="X").material = material.ifc_definition_id
self.draw_editing_ui()
else:
row.operator("bim.add_material_set", text="", icon="ADD").set_type = self.props.material_type
if self.props.materials and self.props.active_material_index < len(self.props.materials):
@@ -82,9 +81,34 @@ class BIM_PT_materials(Panel):
self.layout.template_list("BIM_UL_materials", "", self.props, "materials", self.props, "active_material_index")
for style in MaterialsData.data["active_styles"]:
row = self.layout.row(align=True)
row.label(text="", icon="SHADING_RENDERED")
row.label(text=style["context_type"])
row.label(text=style["context_identifier"])
row.label(text=style["target_view"])
row.label(text=style["name"])
op = row.operator("bim.unassign_material_style", text="", icon="X")
op.style = style["id"]
op.context = style["context_id"]
def draw_editing_ui(self):
if not self.props.active_material_id:
return
ifc_definition_id = self.props.active_material_id
if self.props.editing_material_type == "ATTRIBUTES":
blenderbim.bim.helper.draw_attributes(self.props.material_attributes, self.layout)
row = self.layout.row(align=True)
row.operator("bim.edit_material", text="Save Material", icon="CHECKMARK").material = ifc_definition_id
row.operator("bim.disable_editing_material", text="", icon="CANCEL")
elif self.props.editing_material_type == "STYLE":
row = self.layout.row(align=True)
row.prop(self.props, "contexts", text="")
row.prop(self.props, "styles", text="")
row = self.layout.row(align=True)
row.operator("bim.edit_material_style", text="Assign Style", icon="CHECKMARK")
row.operator("bim.disable_editing_material", text="", icon="CANCEL")
def draw_editable_material_attributes_ui(self):
blenderbim.bim.helper.draw_attributes(self.props.material_attributes, self.layout)
class BIM_PT_material(Panel):
bl_label = "Material"
@@ -219,9 +243,7 @@ class BIM_PT_object_material(Panel):
elif self.props.active_material_set_item_id == set_item["id"]:
self.draw_editable_set_item_ui(set_item)
else:
self.draw_read_only_set_item_ui(
set_item, index, is_first=index == 0, is_last=index == total_items - 1
)
self.draw_read_only_set_item_ui(set_item, index, is_first=index == 0, is_last=index == total_items - 1)
def draw_editable_set_item_profile_ui(self, set_item):
box = self.layout.box()
@@ -267,7 +289,10 @@ class BIM_PT_object_material(Panel):
op.old_index = index
op.new_index = index + 1
setattr(op, "material_set", ObjectMaterialData.data["set"]["id"])
if not self.props.active_material_set_item_id and ObjectMaterialData.data["material_class"] != "IfcMaterialList":
if (
not self.props.active_material_set_item_id
and ObjectMaterialData.data["material_class"] != "IfcMaterialList"
):
if "Profile" in ObjectMaterialData.data["material_class"]:
op = row.operator("bim.enable_editing_material_set_item_profile", icon="ITALIC", text="")
op.material_set_item = set_item["id"]
@@ -336,3 +361,6 @@ class BIM_UL_materials(UIList):
row2 = row.row()
row2.alignment = "RIGHT"
row2.label(text=str(item.total_elements))
if item.has_style:
row2.label(text="", icon="SHADING_RENDERED")
@@ -109,7 +109,8 @@ classes = (
space.GenerateSpacesFromWalls,
covering.AddInstanceFlooringCoveringsFromWalls,
covering.AddInstanceFlooringCoveringFromCursor,
covering.RegenSelectedFlooringObject,
covering.AddInstanceCeilingCoveringFromCursor,
covering.RegenSelectedCoveringObject,
space.ToggleSpaceVisibility,
mep.FitFlowSegments,
mep.RegenerateDistributionElement,
@@ -59,7 +59,7 @@ class AddArray(bpy.types.Operator, tool.Ifc.Operator):
"pset.edit_pset",
tool.Ifc.get(),
pset=pset,
properties={"Parent": element.GlobalId, "Data": json.dumps(data)},
properties={"Parent": element.GlobalId, "Data": tool.Ifc.get().createIfcText(json.dumps(data))},
)
return {"FINISHED"}
@@ -132,7 +132,7 @@ class EditArray(bpy.types.Operator, tool.Ifc.Operator):
tool.Model.regenerate_array(parent, data)
pset = tool.Ifc.get().by_id(pset["id"])
data = json.dumps(data)
data = tool.Ifc.get().createIfcText(json.dumps(data))
ifcopenshell.api.run("pset.edit_pset", tool.Ifc.get(), pset=pset, properties={"Data": data})
tool.Blender.Modifier.Array.set_children_lock_state(element, self.item, True)
@@ -196,7 +196,7 @@ class RemoveArray(bpy.types.Operator, tool.Ifc.Operator):
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), pset=pset)
else:
del data[self.item]
data = json.dumps(data)
data = tool.Ifc.get().createIfcText(json.dumps(data))
ifcopenshell.api.run("pset.edit_pset", tool.Ifc.get(), pset=pset, properties={"Data": data})
tool.Blender.Modifier.Array.constrain_children_to_parent(element)
@@ -248,21 +248,30 @@ class SelectAllArrayObjects(bpy.types.Operator):
return True
def execute(self, context):
object = context.active_object
element = tool.Ifc.get_entity(object)
array_pset = ifcopenshell.util.element.get_pset(element, "BBIM_Array")
if not array_pset:
self.report({"ERROR"}, f"Object is not part of an array.")
return {"CANCELLED"}
objects = context.selected_objects
for object in objects:
element = tool.Ifc.get_entity(object)
if not element:
self.report({"ERROR"}, f"Non IFC objects, were deselected.")
object.select_set(False)
try:
parent_element = tool.Ifc.get().by_guid(array_pset["Parent"])
except RuntimeError:
self.report({"ERROR"}, f"Couldn't find array parent by guid '{array_pset['Parent']}'")
return {"CANCELLED"}
if element:
array_pset = ifcopenshell.util.element.get_pset(element, "BBIM_Array")
if not array_pset:
self.report({"ERROR"}, f"Objects not part of an array, were deselected.")
object.select_set(False)
array_objects = tool.Blender.Modifier.Array.get_all_objects(parent_element)
tool.Blender.set_objects_selection(context, active_object=array_objects[0], selected_objects=array_objects)
if array_pset:
try:
parent_element = tool.Ifc.get().by_guid(array_pset["Parent"])
except RuntimeError:
self.report({"ERROR"}, f"Objects that don't have an array parent, were deselected.")
object.select_set(False)
array_objects = tool.Blender.Modifier.Array.get_all_objects(parent_element)
tool.Blender.set_objects_selection(
context, active_object=array_objects[0], selected_objects=array_objects, clear_previous_selection=False
)
return {"FINISHED"}
@@ -51,17 +51,46 @@ class AddInstanceFlooringCoveringFromCursor(bpy.types.Operator, tool.Ifc.Operato
core.add_instance_flooring_covering_from_cursor(tool.Ifc, tool.Spatial, tool.Model, tool.Type, tool.Geometry)
class RegenSelectedFlooringObject(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.regen_selected_flooring_object"
bl_label = "Regen Flooring"
class AddInstanceCeilingCoveringFromCursor(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.add_instance_ceiling_covering_from_cursor"
bl_label = "Add Ceiling From Cursor"
bl_options = {"REGISTER"}
bl_description = "Regen selected flooring object"
bl_description = "Add a typed instance ceiling covering from 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
return tool.Ifc.get_entity(collection_obj)
def _execute(self, context):
def msg(self, context):
self.layout.label(text="NO ACTIVE STOREY")
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")
return
spatial_element = tool.Ifc.get_entity(collection_obj)
if not spatial_element:
bpy.context.window_manager.popup_menu(msg, title="Error", icon="ERROR")
return
core.add_instance_ceiling_covering_from_cursor(tool.Ifc, tool.Spatial, tool.Model, tool.Type, tool.Geometry, tool.Covering)
class RegenSelectedCoveringObject(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.regen_selected_covering_object"
bl_label = "Regen"
bl_options = {"REGISTER"}
bl_description = "Regen selected covering object"
@classmethod
def poll(cls, context):
active_obj = bpy.context.active_object
element = tool.Ifc.get_entity(active_obj)
return element.is_a("IfcCovering")
return element and element.is_a("IfcCovering")
def _execute(self, context):
@@ -74,7 +103,7 @@ class RegenSelectedFlooringObject(bpy.types.Operator, tool.Ifc.Operator):
bpy.context.window_manager.popup_menu(msg, title="Error", icon="ERROR")
return
core.regen_selected_flooring_object(tool.Ifc, tool.Spatial, tool.Model, tool.Type, tool.Geometry)
core.regen_selected_covering_object(tool.Ifc, tool.Spatial, tool.Model, tool.Type, tool.Geometry)
class AddInstanceFlooringCoveringsFromWalls(bpy.types.Operator, tool.Ifc.Operator):
@@ -213,6 +213,7 @@ class AuthoringData:
if bpy.context.active_object:
representation = tool.Geometry.get_active_representation(bpy.context.active_object)
if representation and representation.is_a("IfcShapeRepresentation"):
representation = tool.Geometry.resolve_mapped_representation(representation)
return representation.RepresentationType
@classmethod
@@ -313,6 +314,7 @@ class StairData:
if not cls.data["pset_data"]:
return
cls.data["general_params"] = cls.general_params()
cls.data["calculated_params"] = cls.calculated_params()
@classmethod
def pset_data(cls):
@@ -329,6 +331,10 @@ class StairData:
general_params[prop_readable_name] = prop_value
return general_params
@classmethod
def calculated_params(cls):
return tool.Model.get_active_stair_calculated_params(cls.data["pset_data"]["data_dict"])
class SverchokData:
data = {}
@@ -355,7 +361,7 @@ class SverchokData:
def get_prop_from_data(props, data, prop_name):
prop_value = data[prop_name]
prop_value = data.get(prop_name, tool.Blender.get_blender_prop_default_value(props, prop_name))
prop_value = round(prop_value, 5) if type(prop_value) is float else prop_value
prop_readable_name = props.bl_rna.properties[prop_name].name
return prop_readable_name, prop_value
@@ -191,15 +191,15 @@ class ProfileDecorator:
unselected_edges.append(edge_indices)
### Actually drawing
# 3D_POLYLINE_UNIFORM_COLOR is good for smoothed lines since `bgl.enable(GL_LINE_SMOOTH)` is deprecated
self.line_shader = gpu.shader.from_builtin("3D_POLYLINE_UNIFORM_COLOR")
# POLYLINE_UNIFORM_COLOR is good for smoothed lines since `bgl.enable(GL_LINE_SMOOTH)` is deprecated
self.line_shader = gpu.shader.from_builtin("POLYLINE_UNIFORM_COLOR")
self.line_shader.bind()
# 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("3D_UNIFORM_COLOR")
self.shader = gpu.shader.from_builtin("UNIFORM_COLOR")
self.shader.bind()
# Draw faces
@@ -549,7 +549,7 @@ class AddDoor(bpy.types.Operator, tool.Ifc.Operator):
"pset.edit_pset",
tool.Ifc.get(),
pset=pset,
properties={"Data": json.dumps(door_data, default=list)},
properties={"Data": tool.Ifc.get().createIfcText(json.dumps(door_data, default=list))},
)
update_door_modifier_representation(context, obj)
return {"FINISHED"}
@@ -608,7 +608,7 @@ class FinishEditingDoor(bpy.types.Operator, tool.Ifc.Operator):
update_door_modifier_representation(context, obj)
pset = tool.Pset.get_element_pset(element, "BBIM_Door")
door_data = json.dumps(door_data, default=list)
door_data = tool.Ifc.get().createIfcText(json.dumps(door_data, default=list))
ifcopenshell.api.run("pset.edit_pset", tool.Ifc.get(), pset=pset, properties={"Data": door_data})
return {"FINISHED"}
@@ -18,12 +18,10 @@
import bpy
import ifcopenshell.api
import blenderbim.core.spatial
import blenderbim.tool as tool
from bpy.types import Operator
from bpy.props import FloatProperty, IntProperty
from mathutils import Vector
from blenderbim.bim.ifc import IfcStore
def add_object(self, context):
@@ -66,7 +64,7 @@ def add_object(self, context):
tool.Ifc.get(),
**{"axis_tag": tag, "uvw_axes": "UAxes", "grid": grid},
)
IfcStore.link_element(result, obj)
tool.Ifc.link(result, obj)
ifcopenshell.api.run("grid.create_axis_curve", tool.Ifc.get(), **{"axis_curve": obj, "grid_axis": result})
obj.BIMObjectProperties.ifc_definition_id = result.id()
@@ -91,7 +89,7 @@ def add_object(self, context):
tool.Ifc.get(),
**{"axis_tag": tag, "uvw_axes": "VAxes", "grid": grid},
)
IfcStore.link_element(result, obj)
tool.Ifc.link(result, obj)
ifcopenshell.api.run("grid.create_axis_curve", tool.Ifc.get(), **{"axis_curve": obj, "grid_axis": result})
obj.BIMObjectProperties.ifc_definition_id = result.id()
@@ -451,11 +451,12 @@ class MEPGenerator:
# NOTE: I have a feeling that there are cases where order
# in which we're checking the segments is important
# but I couldn't pin it down to exact cases
for test_segment_data in fitting_data[:]:
for test_segment_data in fitting_data:
for base_segment_data in segments_data:
if not are_segments_compatible(test_segment_data, base_segment_data):
continue
segments_data.remove(test_segment_data)
segments_data.remove(base_segment_data)
break
# all segments were sorted
return len(segments_data) == 0
@@ -856,7 +857,7 @@ class MEPAddTransition(bpy.types.Operator, tool.Ifc.Operator):
"pset.edit_pset",
tool.Ifc.get(),
pset=pset,
properties={"Data": json.dumps(transition_data, default=list)},
properties={"Data": tool.Ifc.get().createIfcText(json.dumps(transition_data, default=list))},
)
tool.System.add_ports(obj, offset_end_port=profile_offset_si)
@@ -1008,6 +1009,8 @@ class MEPAddBend(bpy.types.Operator, tool.Ifc.Operator):
segments_intersection_ws, (end_segment_data["start_point"], end_segment_data["end_point"])
)
# start_/end_segment_sign indicate
# whether segments' z axes are directed towards the bend
start_port = points_ports_map[start_point]
end_port = points_ports_map[end_point]
start_point_on_origin = start_point == start_segment_data["start_point"]
@@ -1076,9 +1079,9 @@ class MEPAddBend(bpy.types.Operator, tool.Ifc.Operator):
radial_offset = V(0, 0, 0)
ref_point_radius = self.radius + profile_dim[lateral_axis]
radial_offset[lateral_axis] = ref_point_radius * (1 - cos(angle)) * lateral_sign
radial_offset.z = ref_point_radius * sin(angle)
second_object_offset = radial_offset + V(0, 0, self.start_length)
second_object_offset += z_axis_end_object_local * (self.end_length * -end_segment_sign)
radial_offset.z = ref_point_radius * sin(angle) * start_segment_sign
end_port_offset = radial_offset + V(0, 0, self.start_length * start_segment_sign)
end_port_offset += z_axis_end_object_local * (self.end_length * -end_segment_sign)
def get_segments_extend():
segments_intersection = segments_intersection_ws - start_point
@@ -1200,9 +1203,9 @@ class MEPAddBend(bpy.types.Operator, tool.Ifc.Operator):
"pset.edit_pset",
tool.Ifc.get(),
pset=pset,
properties={"Data": json.dumps(bend_data, default=list)},
properties={"Data": tool.Ifc.get().createIfcText(json.dumps(bend_data, default=list))},
)
tool.System.add_ports(obj, end_port_pos=second_object_offset)
tool.System.add_ports(obj, end_port_pos=end_port_offset)
# NOTE: at this point we loose current blender objects selection
# create transition element
@@ -528,17 +528,7 @@ class AddBoolean(Operator, tool.Ifc.Operator):
"geometry.add_boolean", tool.Ifc.get(), representation=representation, operator="DIFFERENCE", **mesh_data
)
pset = ifcopenshell.util.element.get_pset(element1, "BBIM_Boolean")
if pset:
pset = tool.Ifc.get().by_id(pset["id"])
data = json.loads(pset["Data"])
data.extend([b.id() for b in booleans])
data = list(set(data))
else:
pset = ifcopenshell.api.run("pset.add_pset", tool.Ifc.get(), product=element1, name="BBIM_Boolean")
data = [b.id() for b in booleans]
ifcopenshell.api.run("pset.edit_pset", tool.Ifc.get(), pset=pset, properties={"Data": json.dumps(data)})
tool.Model.mark_manual_booleans(element1, booleans)
tool.Model.clear_scene_openings()
blenderbim.core.geometry.switch_representation(
@@ -672,17 +662,17 @@ class RemoveBooleans(Operator, tool.Ifc.Operator, AddObjectHelper):
except:
continue
boolean_id = None
boolean = None
for inverse in tool.Ifc.get().get_inverse(item):
if inverse.is_a("IfcBooleanResult"):
boolean_id = inverse.id()
boolean = inverse
break
ifcopenshell.api.run("geometry.remove_boolean", tool.Ifc.get(), item=item)
if obj.data.BIMMeshProperties.obj:
upstream_obj = obj.data.BIMMeshProperties.obj
element = tool.Ifc.get_entity(upstream_obj)
bbim_boolean_updates.setdefault(element, []).append(boolean_id)
bbim_boolean_updates.setdefault(element, []).append(boolean)
body = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
if body:
blenderbim.core.geometry.switch_representation(
@@ -696,18 +686,8 @@ class RemoveBooleans(Operator, tool.Ifc.Operator, AddObjectHelper):
)
bpy.data.objects.remove(obj)
for element, boolean_ids in bbim_boolean_updates.items():
pset = ifcopenshell.util.element.get_pset(element, "BBIM_Boolean")
if not pset:
continue
data = set(json.loads(pset["Data"]))
data -= set(boolean_ids)
data = list(data)
pset = tool.Ifc.get().by_id(pset["id"])
if data:
ifcopenshell.api.run("pset.edit_pset", tool.Ifc.get(), pset=pset, properties={"Data": json.dumps(data)})
else:
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), pset=pset)
for element, booleans in bbim_boolean_updates.items():
tool.Model.unmark_manual_booleans(element, booleans)
tool.Blender.set_active_object(upstream_obj)
return {"FINISHED"}
@@ -801,7 +781,7 @@ class EditOpenings(Operator, tool.Ifc.Operator):
return {"FINISHED"}
def get_all_building_objects_of_similar_openings(self, opening):
if not opening.HasFillings:
if not opening.is_a("IfcOpeningElement") or not opening.HasFillings:
return []
results = set()
for rel in opening.HasFillings:
@@ -893,14 +873,14 @@ class DecorationsHandler:
if not obj:
continue
self.line_shader = gpu.shader.from_builtin("3D_POLYLINE_UNIFORM_COLOR")
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("3D_UNIFORM_COLOR")
self.shader = gpu.shader.from_builtin("UNIFORM_COLOR")
verts = []
selected_edges = []
@@ -249,6 +249,8 @@ class AddConstrTypeInstance(bpy.types.Operator):
mat.translation *= unit_scale
mat = obj.matrix_world @ mat
new_port = tool.Ifc.run("root.create_entity", ifc_class="IfcDistributionPort")
new_port.PredefinedType = port.PredefinedType
new_port.SystemType = port.SystemType
tool.Ifc.run("system.assign_port", element=element, port=new_port)
tool.Ifc.run("geometry.edit_object_placement", product=new_port, matrix=mat, is_si=True)
@@ -559,5 +561,6 @@ def ensure_material_unassigned(usecase_path, ifc_file, settings):
total_removed = 0
for i in to_remove:
obj.active_material_index = i - total_removed
bpy.ops.object.material_slot_remove({"object": obj})
with bpy.context.temp_override(object=obj):
bpy.ops.object.material_slot_remove()
total_removed += 1
@@ -449,6 +449,16 @@ class DumbProfileJoiner:
"geometry.assign_representation", tool.Ifc.get(), product=element, representation=new_axis
)
def get_placement_axes(body_representation):
if not body_representation:
return None, None
extrusion = tool.Model.get_extrusion(body_representation)
if not extrusion:
return None, None
position = extrusion.Position
return (position.Axis.DirectionRatios, position.RefDirection.DirectionRatios)
old_body = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
new_body = ifcopenshell.api.run(
"geometry.add_profile_representation",
tool.Ifc.get(),
@@ -457,9 +467,9 @@ class DumbProfileJoiner:
depth=depth,
cardinal_point=usage.CardinalPoint if usage else None,
clippings=self.clippings,
placement_zx_axes=get_placement_axes(old_body),
)
old_body = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
if old_body:
for inverse in tool.Ifc.get().get_inverse(old_body):
ifcopenshell.util.element.replace_attribute(inverse, old_body, new_body)
@@ -573,15 +583,11 @@ class DumbProfileJoiner:
if connection1 == "ATEND":
if tool.Cad.is_x(abs(xy_angle), (0, 90, 180), tolerance=0.001) and is_orthogonal:
plane = self.get_profile_plane(profile2, furthest_plane)
intersect = mathutils.geometry.intersect_line_plane(
*axis1, plane.translation, plane.col[2].to_3d()
)
intersect = mathutils.geometry.intersect_line_plane(*axis1, plane.translation, plane.col[2].to_3d())
self.body[1] = intersect
else:
plane = self.get_profile_plane(profile2, furthest_plane, z_inwards=False)
intersect = mathutils.geometry.intersect_line_plane(
*axis1, plane.translation, plane.col[2].to_3d()
)
intersect = mathutils.geometry.intersect_line_plane(*axis1, plane.translation, plane.col[2].to_3d())
max_dim = self.get_max_bound_box_dimension(profile1)
self.body[1] = intersect + profile1.matrix_world.to_quaternion() @ Vector((0, 0, max_dim))
@@ -624,15 +630,11 @@ class DumbProfileJoiner:
elif connection1 == "ATSTART":
if tool.Cad.is_x(abs(xy_angle), (0, 90, 180), tolerance=0.001) and is_orthogonal:
plane = self.get_profile_plane(profile2, furthest_plane)
intersect = mathutils.geometry.intersect_line_plane(
*axis1, plane.translation, plane.col[2].to_3d()
)
intersect = mathutils.geometry.intersect_line_plane(*axis1, plane.translation, plane.col[2].to_3d())
self.body[0] = intersect
else:
plane = self.get_profile_plane(profile2, furthest_plane, z_inwards=False)
intersect = mathutils.geometry.intersect_line_plane(
*axis1, plane.translation, plane.col[2].to_3d()
)
intersect = mathutils.geometry.intersect_line_plane(*axis1, plane.translation, plane.col[2].to_3d())
max_dim = self.get_max_bound_box_dimension(profile1)
self.body[0] = intersect - profile1.matrix_world.to_quaternion() @ Vector((0, 0, max_dim))
@@ -676,9 +678,7 @@ class DumbProfileJoiner:
if connection1 == "ATEND":
if tool.Cad.is_x(abs(xy_angle), (0, 90, 180), tolerance=0.001) and is_orthogonal:
plane = self.get_profile_plane(profile2, furthest_plane if is_relating else closest_plane)
intersect = mathutils.geometry.intersect_line_plane(
*axis1, plane.translation, plane.col[2].to_3d()
)
intersect = mathutils.geometry.intersect_line_plane(*axis1, plane.translation, plane.col[2].to_3d())
self.body[1] = intersect
else:
plane = self.get_profile_plane(
@@ -686,9 +686,7 @@ class DumbProfileJoiner:
furthest_plane if is_relating else closest_plane,
z_inwards=False if is_relating else True,
)
intersect = mathutils.geometry.intersect_line_plane(
*axis1, plane.translation, plane.col[2].to_3d()
)
intersect = mathutils.geometry.intersect_line_plane(*axis1, plane.translation, plane.col[2].to_3d())
max_dim = self.get_max_bound_box_dimension(profile1)
self.body[1] = intersect + profile1.matrix_world.to_quaternion() @ Vector((0, 0, max_dim))
self.clippings.append(
@@ -701,9 +699,7 @@ class DumbProfileJoiner:
elif connection1 == "ATSTART":
if tool.Cad.is_x(abs(xy_angle), (0, 90, 180), tolerance=0.001) and is_orthogonal:
plane = self.get_profile_plane(profile2, furthest_plane if is_relating else closest_plane)
intersect = mathutils.geometry.intersect_line_plane(
*axis1, plane.translation, plane.col[2].to_3d()
)
intersect = mathutils.geometry.intersect_line_plane(*axis1, plane.translation, plane.col[2].to_3d())
self.body[0] = intersect
else:
plane = self.get_profile_plane(
@@ -711,9 +707,7 @@ class DumbProfileJoiner:
furthest_plane if is_relating else closest_plane,
z_inwards=False if is_relating else True,
)
intersect = mathutils.geometry.intersect_line_plane(
*axis1, plane.translation, plane.col[2].to_3d()
)
intersect = mathutils.geometry.intersect_line_plane(*axis1, plane.translation, plane.col[2].to_3d())
max_dim = self.get_max_bound_box_dimension(profile1)
self.body[0] = intersect - profile1.matrix_world.to_quaternion() @ Vector((0, 0, max_dim))
self.clippings.append(
@@ -140,6 +140,7 @@ class BIMModelProperties(PropertyGroup):
rl3: bpy.props.FloatProperty(name="RL", default=1, subtype="DISTANCE", description="Z offset for space calculation")
x_angle: bpy.props.FloatProperty(name="X Angle", default=0, subtype="ANGLE", min=-pi / 180 * 89, max=pi / 180 * 89)
type_page: bpy.props.IntProperty(name="Type Page", default=1, update=update_type_page)
# fmt: off
type_template: bpy.props.EnumProperty(
items=(
("MESH", "Custom Mesh", "Use as a representation currently active object mesh or default cube if no object selected"),
@@ -159,6 +160,7 @@ class BIMModelProperties(PropertyGroup):
name="Type Template",
default="MESH",
)
# fmt: on
type_class: bpy.props.EnumProperty(items=get_type_class, name="IFC Class", update=update_type_class)
type_predefined_type: bpy.props.EnumProperty(items=get_type_predefined_type, name="Predefined Type", default=None)
type_name: bpy.props.StringProperty(name="Name", default="TYPEX")
@@ -191,6 +193,10 @@ class BIMArrayProperties(PropertyGroup):
class BIMStairProperties(PropertyGroup):
def validate_nosing_value(self, context):
if self.stair_type != "WOOD/STEEL" and self.nosing_length < 0:
self["nosing_length"] = 0
non_si_units_props = ("is_editing", "number_of_treads", "has_top_nib", "stair_type")
stair_types = (
("CONCRETE", "Concrete", ""),
@@ -201,13 +207,37 @@ class BIMStairProperties(PropertyGroup):
is_editing: bpy.props.BoolProperty(default=False)
width: bpy.props.FloatProperty(name="Width", default=1.2, soft_min=0.01, subtype="DISTANCE")
height: bpy.props.FloatProperty(name="Height", default=1.0, soft_min=0.01, subtype="DISTANCE")
number_of_treads: bpy.props.IntProperty(name="Number of treads", default=6, soft_min=1)
number_of_treads: bpy.props.IntProperty(name="Number of Treads", default=6, soft_min=1)
tread_depth: bpy.props.FloatProperty(name="Tread Depth", default=0.25, soft_min=0.01, subtype="DISTANCE")
tread_run: bpy.props.FloatProperty(name="Tread Run", default=0.3, soft_min=0.01, subtype="DISTANCE")
base_slab_depth: bpy.props.FloatProperty(name="Base slab depth", default=0.25, soft_min=0, subtype="DISTANCE")
top_slab_depth: bpy.props.FloatProperty(name="Top slab depth", default=0.25, soft_min=0, subtype="DISTANCE")
has_top_nib: bpy.props.BoolProperty(name="Has top nib", default=True)
stair_type: bpy.props.EnumProperty(name="Stair type", items=stair_types, default="CONCRETE")
base_slab_depth: bpy.props.FloatProperty(name="Base Slab Depth", default=0.25, soft_min=0, subtype="DISTANCE")
top_slab_depth: bpy.props.FloatProperty(name="Top Slab Depth", default=0.25, soft_min=0, subtype="DISTANCE")
has_top_nib: bpy.props.BoolProperty(name="Has Top Nib", default=True)
stair_type: bpy.props.EnumProperty(
name="Stair Type", items=stair_types, default="CONCRETE", update=validate_nosing_value
)
custom_first_last_tread_run: bpy.props.FloatVectorProperty(
name="Custom First / Last Treads Widths",
description='Specify custom first / last treads widths, different from the general "Tread Run". Leave 0 to disable.',
default=(0, 0),
min=0,
unit="LENGTH",
size=2,
)
# TODO: need to clamp at zero for non WOOD/STEEL
nosing_length: bpy.props.FloatProperty(
name="Nosing Length",
description=(
"Overhang of the tread, not counted as a part of the tread run.\n"
"Can be negative for WOOD/STEEL stair (then it becomes a tread gap)"
),
default=0,
unit="LENGTH",
update=validate_nosing_value,
)
nosing_depth: bpy.props.FloatProperty(
name="Nosing Depth", description="Depth of the tread's nosing", min=0, default=0, unit="LENGTH"
)
def get_props_kwargs(self, convert_to_project_units=False, stair_type=None):
if not stair_type:
@@ -218,10 +248,12 @@ class BIMStairProperties(PropertyGroup):
"height": self.height,
"number_of_treads": self.number_of_treads,
"tread_run": self.tread_run,
"nosing_length": self.nosing_length,
}
if stair_type == "CONCRETE":
concrete_props = {
"nosing_depth": self.nosing_depth,
"base_slab_depth": self.base_slab_depth,
"top_slab_depth": self.top_slab_depth,
"has_top_nib": self.has_top_nib,
@@ -236,7 +268,13 @@ class BIMStairProperties(PropertyGroup):
stair_kwargs.update(wood_steel_props)
elif stair_type == "GENERIC":
pass
generic_props = {
"nosing_depth": self.nosing_depth,
}
stair_kwargs.update(generic_props)
# defined here to appear last in UI
stair_kwargs["custom_first_last_tread_run"] = self.custom_first_last_tread_run
if not convert_to_project_units:
return stair_kwargs
@@ -114,7 +114,7 @@ def update_bbim_railing_pset(element, railing_data):
pset = tool.Pset.get_element_pset(element, "BBIM_Railing")
if not pset:
pset = ifcopenshell.api.run("pset.add_pset", tool.Ifc.get(), product=element, name="BBIM_Railing")
railing_data = json.dumps(railing_data, default=list)
railing_data = tool.Ifc.get().createIfcText(json.dumps(railing_data, default=list))
ifcopenshell.api.run("pset.edit_pset", tool.Ifc.get(), pset=pset, properties={"Data": railing_data})
@@ -412,7 +412,7 @@ def update_bbim_roof_pset(element, roof_data):
pset = tool.Pset.get_element_pset(element, "BBIM_Roof")
if not pset:
pset = ifcopenshell.api.run("pset.add_pset", tool.Ifc.get(), product=element, name="BBIM_Roof")
roof_data = json.dumps(roof_data, default=list)
roof_data = tool.Ifc.get().createIfcText(json.dumps(roof_data, default=list))
ifcopenshell.api.run("pset.edit_pset", tool.Ifc.get(), pset=pset, properties={"Data": roof_data})
@@ -17,22 +17,17 @@
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
import json
import bmesh
import ifcopenshell
import blenderbim
import blenderbim.tool as tool
from mathutils import Vector
from bmesh.types import BMVert
from bpy.types import Operator
from bpy.props import FloatProperty, IntProperty
from bpy_extras.object_utils import AddObjectHelper, object_data_add
import bmesh
from bmesh.types import BMVert
import ifcopenshell
from ifcopenshell.util.shape_builder import V, ShapeBuilder
import blenderbim
import blenderbim.tool as tool
from mathutils import Vector
from pprint import pprint
import json
def add_dumb_stair_object(self, context):
verts = [
@@ -82,138 +77,10 @@ class BIM_OT_add_object(Operator, AddObjectHelper):
return {"FINISHED"}
def generate_stair_2d_profile(
number_of_treads,
height,
width,
tread_run,
stair_type,
# WOOD/STEEL CONCRETE STAIR ARGUMENTS
tread_depth=None,
# CONCRETE STAIR ARGUMENTS
has_top_nib=None,
top_slab_depth=None,
base_slab_depth=None,
):
vertices = []
edges = []
faces = []
number_of_risers = number_of_treads + 1
tread_rise = height / number_of_risers
length = tread_run * number_of_risers
if stair_type == "WOOD/STEEL":
builder = ShapeBuilder(None)
tread_shape = builder.get_rectangle_coords(
size=V(tread_run, 0, tread_depth),
position=V(0, 0, -(tread_depth-tread_rise))
)
tread_offset = V(tread_run, 0, tread_rise)
for i in range(number_of_risers):
cur_trade_shape = [v + tread_offset * i for v in tread_shape]
vertices.extend(cur_trade_shape)
cur_vertex = i * 4
edges.extend([
(cur_vertex, cur_vertex+1),
(cur_vertex+1, cur_vertex+2),
(cur_vertex+2, cur_vertex+3),
(cur_vertex+3, cur_vertex),
])
faces.append(list(range(cur_vertex, cur_vertex + 1)))
return (vertices, edges, faces)
elif stair_type == "GENERIC":
vertices.append(Vector([0, 0, 0]))
tread_verts = [
Vector([0, 0, tread_rise]),
Vector([tread_run, 0, tread_rise])
]
tread_offset = Vector([tread_run, 0, tread_rise])
for i in range(number_of_risers):
current_tread_verts = [v + tread_offset * i for v in tread_verts]
last_vert_i = len(vertices) - 1
edges.extend([
(last_vert_i, last_vert_i + 1),
(last_vert_i + 1, last_vert_i + 2)
])
vertices.extend(current_tread_verts)
last_vert_i = len(vertices)
vertices.append(vertices[-1] * V(1,0,0))
edges.extend([
(last_vert_i - 1, last_vert_i),
(last_vert_i, 0)
])
return (vertices, edges, faces)
elif stair_type == "CONCRETE":
for i in range(number_of_risers):
vertices.extend([
Vector((tread_run*i, 0, tread_rise*i)),
Vector((tread_run*i, 0, tread_rise*(i+1)))
])
cur_vertex = i * 2
if i != 0:
edges.append((cur_vertex - 1, cur_vertex))
edges.append((cur_vertex, cur_vertex + 1))
vertices.append(Vector((tread_run * number_of_risers, 0, tread_rise * number_of_risers)))
edges.append((number_of_risers * 2, number_of_risers * 2 - 1))
td_vector = Vector((vertices[2][2], 0, -vertices[2][0])).normalized() * tread_depth
k = tread_rise / tread_run
s0 = vertices[0] + td_vector
b = s0.z - k * s0.x # comes from y = kx + b
# you could use td_vector as depth_vector
# but then stair won't be perpendicular to the X+
# b is kind of vertical tread_depth (along Z+)
depth_vector = Vector((0, 0, b))
# top nib
if has_top_nib:
vertices.append( vertices[number_of_risers * 2] + Vector((0, 0, -top_slab_depth)) )
vertices.append( vertices[number_of_risers * 2] + Vector(((-top_slab_depth - b) / k, 0, -top_slab_depth)) )
last_vertex_i = len(vertices) - 1
edges.append( (number_of_risers * 2, last_vertex_i - 1) )
edges.append( (last_vertex_i - 1, last_vertex_i) )
else:
vertices.append(vertices[number_of_risers * 2] + depth_vector)
last_vertex_i = len(vertices) - 1
edges.append((number_of_risers * 2, last_vertex_i))
top_nib_end = len(vertices) - 1
# bottom nib
if abs(b) <= base_slab_depth:
vertices.append(vertices[0] + depth_vector)
edges.append((0, len(vertices) - 1))
bottom_nib_end = len(vertices) - 1
else:
vertices.append(vertices[0] + Vector(((-base_slab_depth - b) / k, 0, -base_slab_depth)))
vertices.append(vertices[0] + Vector((0, 0, -base_slab_depth)))
last_vertex_i = len(vertices) - 1
edges.append( (0, last_vertex_i) )
edges.append( (last_vertex_i - 1, last_vertex_i) )
bottom_nib_end = len(vertices) - 2
edges.append( (bottom_nib_end, top_nib_end) )
faces = [list(range(len(vertices)))]
return (vertices, edges, faces)
def update_stair_modifier(context):
def regenerate_stair_mesh(context):
obj = context.active_object
props_kwargs = obj.BIMStairProperties.get_props_kwargs()
vertices, edges, faces = generate_stair_2d_profile(**props_kwargs)
vertices, edges, faces = tool.Model.generate_stair_2d_profile(**props_kwargs)
obj = context.active_object
bm = bmesh.new()
@@ -221,7 +88,7 @@ def update_stair_modifier(context):
bm.edges.index_update()
new_verts = [bm.verts.new(v) for v in vertices]
new_edges = [bm.edges.new( (new_verts[e[0]], new_verts[e[1]]) ) for e in edges]
new_edges = [bm.edges.new((new_verts[e[0]], new_verts[e[1]])) for e in edges]
bm.verts.index_update()
bm.edges.index_update()
@@ -242,6 +109,26 @@ def update_stair_modifier(context):
obj.data.update()
def update_stair_representation(obj):
body = ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model", "Body", "MODEL_VIEW")
representation = ifcopenshell.api.run(
"geometry.add_representation",
tool.Ifc.get(),
context=body,
blender_object=obj,
geometry=obj.data,
coordinate_offset=tool.Geometry.get_cartesian_point_coordinate_offset(obj),
total_items=tool.Geometry.get_total_representation_items(obj),
should_force_faceted_brep=tool.Geometry.should_force_faceted_brep(),
should_force_triangulation=tool.Geometry.should_force_triangulation(),
should_generate_uvs=tool.Geometry.should_generate_uvs(obj),
ifc_representation_class=None,
profile_set_usage=None,
)
tool.Model.replace_object_ifc_representation(body, obj, representation)
tool.Ifc.finish_edit(obj)
def update_ifc_stair_props(obj):
"""should be called after new geometry settled
since it's going to update ifc representation
@@ -259,6 +146,7 @@ def update_ifc_stair_props(obj):
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(ifc_file)
riser_height = props.height / number_of_risers / si_conversion
tread_length = props.tread_depth / si_conversion
nosing_length = props.nosing_length / si_conversion
if element.is_a("IfcStairFlight"):
if tool.Ifc.get_schema() == "IFC2X3":
@@ -284,6 +172,7 @@ def update_ifc_stair_props(obj):
"NumberOfTreads": props.number_of_treads,
"RiserHeight": riser_height,
"TreadLength": tread_length,
"NosingLength": nosing_length,
},
)
tool.Ifc.edit(obj)
@@ -327,7 +216,7 @@ class BIM_OT_add_clever_stair(bpy.types.Operator, tool.Ifc.Operator):
obj.location = spawn_location
collection = context.view_layer.active_layer_collection.collection
collection.objects.link(obj)
body_context = ifcopenshell.util.representation.get_context(ifc_file, "Model", "Body", "MODEL_VIEW")
element = blenderbim.core.root.assign_class(
tool.Ifc,
@@ -335,7 +224,7 @@ class BIM_OT_add_clever_stair(bpy.types.Operator, tool.Ifc.Operator):
tool.Root,
obj=obj,
ifc_class="IfcStairFlight",
should_add_representation=True,
should_add_representation=False,
context=body_context,
)
if tool.Ifc.get_schema() != "IFC2X3":
@@ -370,10 +259,11 @@ class AddStair(bpy.types.Operator, tool.Ifc.Operator):
"pset.edit_pset",
ifc_file,
pset=pset,
properties={"Data": json.dumps(stair_data)},
properties={"Data": tool.Ifc.get().createIfcText(json.dumps(stair_data))},
)
update_stair_modifier(context)
regenerate_stair_mesh(context)
update_ifc_stair_props(obj)
update_stair_representation(obj)
class CancelEditingStair(bpy.types.Operator, tool.Ifc.Operator):
@@ -388,7 +278,7 @@ class CancelEditingStair(bpy.types.Operator, tool.Ifc.Operator):
props = obj.BIMStairProperties
# restore previous settings since editing was canceled
props.set_props_kwargs_from_ifc_data(data)
update_stair_modifier(context)
regenerate_stair_mesh(context)
props.is_editing = False
@@ -407,10 +297,11 @@ class FinishEditingStair(bpy.types.Operator, tool.Ifc.Operator):
data = props.get_props_kwargs(convert_to_project_units=True)
props.is_editing = False
update_stair_modifier(context)
regenerate_stair_mesh(context)
update_stair_representation(obj)
pset = tool.Pset.get_element_pset(element, "BBIM_Stair")
data = json.dumps(data)
data = tool.Ifc.get().createIfcText(json.dumps(data))
ifcopenshell.api.run("pset.edit_pset", tool.Ifc.get(), pset=pset, properties={"Data": data})
# update IfcStairFlight properties
@@ -169,7 +169,7 @@ class UpdateDataFromSverchok(bpy.types.Operator, tool.Ifc.Operator):
"pset.edit_pset",
tool.Ifc.get(),
pset=pset,
properties={"Data": json.dumps(sverchok_data)},
properties={"Data": tool.Ifc.get().createIfcText(json.dumps(sverchok_data))},
)
update_sverchok_modifier(context)
@@ -30,12 +30,13 @@ from blenderbim.bim.module.model.data import (
RoofData,
)
from blenderbim.bim.module.model.prop import get_ifc_class
from blenderbim.bim.module.model.stair import update_stair_modifier
from blenderbim.bim.module.model.stair import regenerate_stair_mesh
from blenderbim.bim.module.model.window import update_window_modifier_bmesh
from blenderbim.bim.module.model.door import update_door_modifier_bmesh
from blenderbim.bim.module.model.railing import update_railing_modifier_bmesh
from blenderbim.bim.module.model.roof import update_roof_modifier_bmesh
from blenderbim.bim.helper import prop_with_search
from collections.abc import Iterable
class LaunchTypeManager(bpy.types.Operator):
@@ -160,7 +161,7 @@ class BIM_PT_Grids(Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_project_setup"
bl_parent_id = "BIM_PT_tab_project_setup"
def draw(self, context):
self.layout.row().operator("mesh.add_grid", icon="ADD", text="Add Grids")
@@ -259,35 +260,42 @@ class BIM_PT_stair(bpy.types.Panel):
row = self.layout.row(align=True)
row.label(text="Stair parameters", icon="IPO_CONSTANT")
stair_data = StairData.data["pset_data"]["data_dict"]
if props.is_editing:
calculated_params = tool.Model.get_active_stair_calculated_params()
row = self.layout.row(align=True)
row.operator("bim.finish_editing_stair", icon="CHECKMARK", text="Finish Editing")
row.operator("bim.cancel_editing_stair", icon="CANCEL", text="")
row = self.layout.row(align=True)
for prop_name in props.get_props_kwargs():
self.layout.prop(props, prop_name)
update_stair_modifier(context)
prop_value = getattr(props, prop_name)
if isinstance(prop_value, Iterable) and not isinstance(prop_value, str):
prop_readable_name = props.bl_rna.properties[prop_name].name
self.layout.label(text=f"{prop_readable_name}:")
self.layout.prop(props, prop_name, text="")
else:
self.layout.prop(props, prop_name)
regenerate_stair_mesh(context)
else:
calculated_params = StairData.data["calculated_params"]
row.operator("bim.enable_editing_stair", icon="GREASEPENCIL", text="")
row.operator("bim.remove_stair", icon="X", text="")
row = self.layout.row(align=True)
for prop_name, prop_value in StairData.data["general_params"].items():
row = self.layout.row(align=True)
row.label(text=prop_name)
row.label(text=str(prop_value))
if isinstance(prop_value, Iterable) and not isinstance(prop_value, str):
row.label(text=f"{prop_name}:")
row = self.layout.row(align=True)
for prop_value_item in prop_value:
row.label(text=str(prop_value_item))
else:
row.label(text=prop_name)
row.label(text=str(prop_value))
# calculated properties
number_of_rises = props.number_of_treads + 1
row = self.layout.row(align=True)
row.label(text="Number of risers")
row.label(text=str(number_of_rises))
row = self.layout.row(align=True)
row.label(text="Tread rise")
row.label(text=str(round(props.height / number_of_rises, 5)))
row = self.layout.row(align=True)
row.label(text="Length")
row.label(text=str(round(props.tread_run * number_of_rises, 5)))
for prop_name, prop_value in calculated_params.items():
row = self.layout.row(align=True)
row.label(text=prop_name)
row.label(text=str(prop_value))
else:
row = self.layout.row()
row.label(text="No Stair Found")
@@ -496,7 +496,7 @@ class AddWindow(bpy.types.Operator, tool.Ifc.Operator):
"pset.edit_pset",
tool.Ifc.get(),
pset=pset,
properties={"Data": json.dumps(window_data, default=list)},
properties={"Data": tool.Ifc.get().createIfcText(json.dumps(window_data, default=list))},
)
update_window_modifier_representation(context, obj)
return {"FINISHED"}
@@ -553,7 +553,7 @@ class FinishEditingWindow(bpy.types.Operator, tool.Ifc.Operator):
update_window_modifier_representation(context, obj)
pset = tool.Pset.get_element_pset(element, "BBIM_Window")
window_data = json.dumps(window_data, default=list)
window_data = tool.Ifc.get().createIfcText(json.dumps(window_data, default=list))
ifcopenshell.api.run("pset.edit_pset", tool.Ifc.get(), pset=pset, properties={"Data": window_data})
return {"FINISHED"}
@@ -616,6 +616,7 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator):
usage = tool.Model.get_usage_type(element)
if not usage:
representation = tool.Geometry.get_active_representation(obj)
representation = tool.Geometry.resolve_mapped_representation(representation)
if representation and representation.RepresentationType == "SweptSolid":
usage = "SWEPTSOLID"
else:
@@ -83,7 +83,7 @@ class BIM_PT_people(bpy.types.Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_project_setup"
bl_parent_id = "BIM_PT_tab_stakeholders"
@classmethod
def poll(cls, context):
@@ -142,7 +142,7 @@ class BIM_PT_organisations(bpy.types.Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_project_setup"
bl_parent_id = "BIM_PT_tab_stakeholders"
@classmethod
def poll(cls, context):
@@ -189,7 +189,7 @@ class BIM_PT_owner(bpy.types.Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_project_setup"
bl_parent_id = "BIM_PT_tab_stakeholders"
@classmethod
def poll(cls, context):
@@ -240,7 +240,7 @@ class BIM_PT_actor(bpy.types.Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_project_setup"
bl_parent_id = "BIM_PT_tab_stakeholders"
@classmethod
def poll(cls, context):
@@ -27,11 +27,11 @@ from blenderbim.bim.module.profile.prop import generate_thumbnail_for_active_pro
class BIM_PT_profiles(Panel):
bl_label = "Profiles"
bl_idname = "BIM_PT_profiles"
bl_options = {"DEFAULT_CLOSED"}
bl_options = {"HIDE_HEADER"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_geometry"
bl_parent_id = "BIM_PT_tab_profiles"
@classmethod
def poll(cls, context):
@@ -58,11 +58,11 @@ class BIM_PT_profiles(Panel):
box.template_icon(icon_value=preview_image.icon_id, scale=5)
row = self.layout.row(align=True)
row.label(text=f"{ProfileData.data['total_profiles']} Named Profiles Found", icon="SNAP_GRID")
row.label(text=f"{ProfileData.data['total_profiles']} Named Profiles", icon="ITALIC")
if self.props.is_editing:
row.operator("bim.disable_profile_editing_ui", text="", icon="CANCEL")
else:
row.operator("bim.load_profiles", text="", icon="GREASEPENCIL")
row.operator("bim.load_profiles", text="", icon="IMPORT")
if not self.props.is_editing:
return
@@ -1115,8 +1115,8 @@ class ExportIFC(bpy.types.Operator):
@classmethod
def description(cls, context, properties):
if properties.should_save_as:
return "Export the IFC project to a selected file"
return "Export the IFC project to this file"
return "Save the IFC file under a new name, or relocate file"
return "Save the IFC file. Will save both .IFC/.BLEND files if synced together"
class ImportIFC(bpy.types.Operator):
@@ -84,7 +84,7 @@ class BIM_PT_project(Panel):
bl_region_type = "WINDOW"
bl_context = "scene"
bl_options = {"HIDE_HEADER"}
bl_parent_id = "BIM_PT_project_info"
bl_parent_id = "BIM_PT_tab_project_info"
def draw(self, context):
if not ProjectData.is_loaded:
@@ -244,7 +244,7 @@ class BIM_PT_project_library(Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_project_setup"
bl_parent_id = "BIM_PT_tab_project_setup"
def draw(self, context):
self.layout.use_property_decorate = False
@@ -287,7 +287,7 @@ class BIM_PT_links(Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_project_setup"
bl_parent_id = "BIM_PT_tab_project_setup"
def draw(self, context):
self.props = context.scene.BIMProjectProperties
@@ -29,7 +29,7 @@ class BIM_PT_pset_template(Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_project_setup"
bl_parent_id = "BIM_PT_tab_project_setup"
def draw(self, context):
if not PsetTemplatesData.is_loaded:
@@ -79,7 +79,8 @@ def calculate_formwork_area(objs, context):
modifier = copied_obj.modifiers.new(type="BOOLEAN", name="Boolean")
modifier.operation = "UNION"
modifier.object = obj
bpy.ops.object.modifier_apply({"object": copied_obj}, modifier="Boolean")
with context.temp_override(object=copied_obj):
bpy.ops.object.modifier_apply(modifier="Boolean")
copied_obj.name = "Formwork"
copied_obj.BIMObjectProperties.ifc_definition_id = 0
@@ -66,6 +66,7 @@ class EnableReassignClass(bpy.types.Operator):
context.scene.BIMRootProperties.ifc_product = ifc_product
element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
context.scene.BIMRootProperties.ifc_class = element.is_a()
context.scene.BIMRootProperties.relating_class_object = None
if hasattr(element, "PredefinedType") and element.PredefinedType:
context.scene.BIMRootProperties.ifc_predefined_type = element.PredefinedType
return {"FINISHED"}
@@ -91,7 +92,10 @@ class ReassignClass(bpy.types.Operator):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
objects = [bpy.data.objects.get(self.obj)] if self.obj else context.selected_objects
if self.obj:
objects = [bpy.data.objects.get(self.obj)]
else:
objects = set(context.selected_objects + [context.active_object])
self.file = IfcStore.get_file()
predefined_type = context.scene.BIMRootProperties.ifc_predefined_type
if predefined_type == "USERDEFINED":
@@ -100,14 +104,12 @@ class ReassignClass(bpy.types.Operator):
product = ifcopenshell.api.run(
"root.reassign_class",
self.file,
**{
"product": self.file.by_id(obj.BIMObjectProperties.ifc_definition_id),
"ifc_class": context.scene.BIMRootProperties.ifc_class,
"predefined_type": predefined_type,
},
product=self.file.by_id(obj.BIMObjectProperties.ifc_definition_id),
ifc_class=context.scene.BIMRootProperties.ifc_class,
predefined_type=predefined_type,
)
obj.name = "{}/{}".format(product.is_a(), getattr(product, "Name", "None"))
IfcStore.link_element(product, obj)
tool.Ifc.link(product, obj)
obj.BIMObjectProperties.is_reassigning_class = False
return {"FINISHED"}
@@ -174,15 +176,21 @@ class UnlinkObject(bpy.types.Operator):
if element:
if self.should_delete:
obj_copy = obj.copy()
# copy object data, so it won't be removed by `delete_ifc_object`
if obj.data:
obj_copy.data = obj.data.copy()
if obj.type == "MESH":
obj_copy.data.BIMMeshProperties.ifc_definition_id = 0
for collection in obj.users_collection:
collection.objects.link(obj_copy)
tool.Geometry.delete_ifc_object(obj)
obj = obj_copy
if obj in IfcStore.edited_objs:
IfcStore.edited_objs.remove(obj)
IfcStore.unlink_element(obj=obj)
if obj.data:
obj.data = obj.data.copy()
tool.Ifc.unlink(obj=obj)
for material_slot in obj.material_slots:
if material_slot.material:
material_slot.material = material_slot.material.copy()
@@ -19,6 +19,7 @@
import bpy
import ifcopenshell
import ifcopenshell.util.schema
import blenderbim.tool as tool
from blenderbim.bim.module.root.data import IfcClassData
from bpy.types import PropertyGroup
from bpy.props import (
@@ -80,12 +81,46 @@ def get_contexts(self, context):
return IfcClassData.data["contexts"]
def update_relating_class_from_object(self, context):
if self.relating_class_object is None:
return
element = tool.Ifc.get_entity(self.relating_class_object)
if not element:
return
self.ifc_class = element.is_a()
predefined_type = ifcopenshell.util.element.get_predefined_type(element)
if predefined_type:
if element.PredefinedType == "USERDEFINED":
self.ifc_predefined_type = "USERDEFINED"
self.ifc_userdefined_type = predefined_type
else:
self.ifc_predefined_type = predefined_type
bpy.ops.bim.reassign_class()
def is_object_class_applicable(self, obj):
element = tool.Ifc.get_entity(obj)
if not element:
return False
active_element = tool.Ifc.get_entity(bpy.context.active_object)
if not active_element:
return False
return element.is_a("IfcTypeObject") == active_element.is_a("IfcTypeObject")
class BIMRootProperties(PropertyGroup):
contexts: EnumProperty(items=get_contexts, name="Contexts")
ifc_product: EnumProperty(items=get_ifc_products, name="Products", update=refresh_classes)
ifc_class: EnumProperty(items=get_ifc_classes, name="Class", update=refresh_predefined_types)
ifc_predefined_type: EnumProperty(items=get_ifc_predefined_types, name="Predefined Type", default=None)
ifc_userdefined_type: StringProperty(name="Userdefined Type")
relating_class_object: PointerProperty(
type=bpy.types.Object,
name="Copy Class",
update=update_relating_class_from_object,
poll=is_object_class_applicable,
description="Copy the selected object's class and predefined type to the active object",
)
getter_enum_suggestions = {
"ifc_class": get_ifc_classes_suggestions,
@@ -58,6 +58,7 @@ class BIM_PT_class(Panel):
root_prop.get_ifc_predefined_types(context.scene.BIMRootProperties, context),
is_reassigning_class=True,
)
self.layout.prop(context.scene.BIMRootProperties, "relating_class_object", icon="COPYDOWN")
else:
row = self.layout.row(align=True)
row.label(text=IfcClassData.data["name"])
@@ -57,6 +57,25 @@ def updateContainerName(self, context):
props.container_name = self.name
def update_relating_container_from_object(self, context):
if self.relating_container_object is None or context.active_object is None:
return
element = tool.Ifc.get_entity(self.relating_container_object)
if not element:
return
container = ifcopenshell.util.element.get_container(element)
if container:
bpy.ops.bim.assign_container(structure=container.id())
else:
bpy.ops.bim.disable_editing_container()
bpy.ops.bim.remove_container()
def is_object_applicable(self, obj):
element = tool.Ifc.get_entity(obj)
return bool(element)
class SpatialElement(PropertyGroup):
name: StringProperty(name="Name")
long_name: StringProperty(name="Long Name")
@@ -73,6 +92,13 @@ class BIMSpatialProperties(PropertyGroup):
class BIMObjectSpatialProperties(PropertyGroup):
is_editing: BoolProperty(name="Is Editing")
relating_container_object: PointerProperty(
type=bpy.types.Object,
name="Copy Container",
update=update_relating_container_from_object,
poll=is_object_applicable,
description="Copy the target object's container to the active object",
)
class BIMContainer(PropertyGroup):
@@ -62,6 +62,7 @@ class BIM_PT_spatial(Panel):
row.operator("bim.disable_editing_container", icon="CANCEL", text="")
self.layout.template_list("BIM_UL_containers", "", props, "containers", props, "active_container_index")
self.layout.prop(osprops, "relating_container_object")
else:
row = self.layout.row(align=True)
if SpatialData.data["label"]:
@@ -103,7 +104,7 @@ class BIM_PT_SpatialManager(Panel):
bl_region_type = "WINDOW"
bl_context = "scene"
bl_options = {"DEFAULT_CLOSED"}
bl_parent_id = "BIM_PT_project_setup"
bl_parent_id = "BIM_PT_tab_project_setup"
@classmethod
def poll(cls, context):
@@ -25,14 +25,14 @@ classes = (
operator.AddStyle,
operator.BrowseExternalStyle,
operator.ClearTextureMapPath,
operator.DisableAddPresentationStyle,
operator.DisableAddingPresentationStyle,
operator.DisableEditingExternalStyle,
operator.DisableEditingStyle,
operator.DisableEditingStyles,
operator.EditExternalStyle,
operator.EditStyle,
operator.EditSurfaceStyle,
operator.EnableAddPresentationStyle,
operator.EnableAddingPresentationStyle,
operator.EnableEditingExternalStyle,
operator.EnableEditingStyle,
operator.EnableEditingSurfaceStyle,
@@ -18,8 +18,8 @@
import bpy
import ifcopenshell
from ifcopenshell.util.doc import get_entity_doc
import blenderbim.tool as tool
from ifcopenshell.util.doc import get_entity_doc
def refresh():
@@ -33,9 +33,18 @@ class StylesData:
@classmethod
def load(cls):
cls.data = {"style_types": cls.style_types(), "total_styles": cls.total_styles()}
cls.data = {
"style_types": cls.style_types(),
"total_styles": cls.total_styles(),
"reflectance_methods": cls.reflectance_methods(),
}
cls.is_loaded = True
@classmethod
def reflectance_methods(cls):
declaration = tool.Ifc.schema().declaration_by_name("IfcReflectanceMethodEnum")
return [(i, i, "") for i in declaration.enumeration_items()]
@classmethod
def style_types(cls):
declaration = tool.Ifc.schema().declaration_by_name("IfcPresentationStyle")
@@ -431,8 +431,8 @@ class ClearTextureMapPath(bpy.types.Operator):
return {"FINISHED"}
class EnableAddPresentationStyle(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.enable_add_presentation_style"
class EnableAddingPresentationStyle(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.enable_adding_presentation_style"
bl_label = "Enable Add Presentation Style"
bl_options = {"REGISTER", "UNDO"}
@@ -441,8 +441,8 @@ class EnableAddPresentationStyle(bpy.types.Operator, tool.Ifc.Operator):
props.is_adding = True
class DisableAddPresentationStyle(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.disable_add_presentation_style"
class DisableAddingPresentationStyle(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.disable_adding_presentation_style"
bl_label = "Disable Add Presentation Style"
bl_options = {"REGISTER", "UNDO"}
@@ -477,6 +477,13 @@ class AddPresentationStyle(bpy.types.Operator, tool.Ifc.Operator):
)
if props.surface_style_class == "IfcSurfaceStyleRendering":
surface_style.ReflectanceMethod = "NOTDEFINED"
material = bpy.data.materials.new(style.Name)
tool.Ifc.link(style, material)
material.use_fake_user = True
if surface_style.is_a("IfcSurfaceStyleShading"):
tool.Loader.create_surface_style_shading(material, surface_style)
elif surface_style.is_a("IfcSurfaceStyleRendering"):
tool.Loader.create_surface_style_rendering(material, surface_style)
props.is_adding = False
core.load_styles(tool.Style, style_type=props.style_type)
@@ -492,20 +499,60 @@ class EnableEditingSurfaceStyle(bpy.types.Operator, tool.Ifc.Operator):
props = bpy.context.scene.BIMStylesProperties
style = tool.Ifc.get().by_id(self.style)
shading = None
surface_style = None
for style2 in style.Styles:
if style2.is_a() == self.ifc_class:
surface_style = style2
break
if style2.is_a() == "IfcSurfaceStyleShading":
shading = style2
color_to_tuple = lambda x: (x.Red, x.Green, x.Blue)
if surface_style:
color_to_tuple = lambda x: (x.Red, x.Green, x.Blue)
if self.ifc_class == "IfcSurfaceStyleShading":
props.surface_colour = color_to_tuple(surface_style.SurfaceColour)
props.transparency = surface_style.Transparency or 0.
props.transparency = surface_style.Transparency or 0.0
elif self.ifc_class == "IfcSurfaceStyleRendering":
props.surface_colour = color_to_tuple(surface_style.SurfaceColour)
props.transparency = surface_style.Transparency or 0.
props.transparency = surface_style.Transparency or 0.0
if surface_style.DiffuseColour:
props.is_diffuse_colour_null = False
if surface_style.DiffuseColour.is_a("IfcColourRgb"):
props.diffuse_colour_class = "IfcColourRgb"
props.diffuse_colour = color_to_tuple(surface_style.DiffuseColour)
else:
props.diffuse_colour_class = "IfcNormalisedRatioMeasure"
props.diffuse_colour_ratio = surface_style.DiffuseColour.wrappedValue
else:
props.is_diffuse_colour_null = False
if surface_style.SpecularColour:
props.is_specular_colour_null = False
if surface_style.SpecularColour.is_a("IfcColourRgb"):
props.specular_colour_class = "IfcColourRgb"
props.specular_colour = color_to_tuple(surface_style.SpecularColour)
else:
props.specular_colour_class = "IfcNormalisedRatioMeasure"
props.specular_colour_ratio = surface_style.SpecularColour.wrappedValue
else:
props.is_specular_colour_null = False
if surface_style.SpecularHighlight:
props.is_specular_highlight_null = False
if surface_style.SpecularHighlight.is_a("IfcSpecularRoughness"):
props.specular_highlight = surface_style.SpecularHighlight.wrappedValue
else:
props.is_specular_highlight_null = False # Exponent is meaningless
else:
props.is_specular_highlight_null = True
props.reflectance_method = surface_style.ReflectanceMethod
elif shading:
props.surface_colour = color_to_tuple(shading.SurfaceColour)
props.transparency = shading.Transparency or 0.0
props.is_editing_style = self.style
props.is_editing_class = self.ifc_class
@@ -518,12 +565,12 @@ class EditSurfaceStyle(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
self.props = bpy.context.scene.BIMStylesProperties
style = tool.Ifc.get().by_id(self.props.is_editing_style)
self.style = tool.Ifc.get().by_id(self.props.is_editing_style)
self.surface_style = None
self.shading_style = None
self.rendering_style = None
for style2 in style.Styles:
for style2 in self.style.Styles:
if style2.is_a() == self.props.is_editing_class:
self.surface_style = style2
if style2.is_a() == "IfcSurfaceStyleShading":
@@ -540,11 +587,82 @@ class EditSurfaceStyle(bpy.types.Operator, tool.Ifc.Operator):
core.load_styles(tool.Style, style_type=self.props.style_type)
def edit_existing_style(self):
material = tool.Ifc.get_object(self.style)
if self.surface_style.is_a() == "IfcSurfaceStyleShading":
self.surface_style.SurfaceColour.Red = self.props.surface_colour[0]
self.surface_style.SurfaceColour.Green = self.props.surface_colour[1]
self.surface_style.SurfaceColour.Blue = self.props.surface_colour[2]
self.surface_style.Transparency = self.props.transparency or None
ifcopenshell.api.run(
"style.edit_surface_style",
tool.Ifc.get(),
style=self.surface_style,
attributes={
"SurfaceColour": self.color_to_dict(self.props.surface_colour),
"Transparency": self.props.transparency or None,
},
)
tool.Loader.create_surface_style_shading(material, self.surface_style)
elif self.surface_style.is_a() == "IfcSurfaceStyleRendering":
ifcopenshell.api.run(
"style.edit_surface_style",
tool.Ifc.get(),
style=self.surface_style,
attributes=self.get_rendering_attributes(),
)
tool.Loader.create_surface_style_rendering(material, self.surface_style)
def add_new_style(self):
pass
material = tool.Ifc.get_object(self.style)
if self.props.is_editing_class == "IfcSurfaceStyleShading":
surface_style = ifcopenshell.api.run(
"style.add_surface_style",
tool.Ifc.get(),
style=self.style,
ifc_class="IfcSurfaceStyleShading",
attributes=self.get_shading_attributes(),
)
tool.Loader.create_surface_style_shading(material, surface_style)
elif self.props.is_editing_class == "IfcSurfaceStyleRendering":
surface_style = ifcopenshell.api.run(
"style.add_surface_style",
tool.Ifc.get(),
style=self.style,
ifc_class="IfcSurfaceStyleRendering",
attributes=self.get_rendering_attributes(),
)
tool.Loader.create_surface_style_rendering(material, surface_style)
def get_shading_attributes(self):
return {
"SurfaceColour": self.color_to_dict(self.props.surface_colour),
"Transparency": self.props.transparency or None,
}
def get_rendering_attributes(self):
if self.props.is_diffuse_colour_null:
diffuse_colour = None
elif self.props.diffuse_colour_class == "IfcColourRgb":
diffuse_colour = self.color_to_dict(self.props.diffuse_colour)
elif self.props.diffuse_colour_class == "IfcNormalisedRatioMeasure":
diffuse_colour = self.props.diffuse_colour_ratio
if self.props.is_specular_colour_null:
specular_colour = None
elif self.props.specular_colour_class == "IfcColourRgb":
specular_colour = self.color_to_dict(self.props.specular_colour)
elif self.props.specular_colour_class == "IfcNormalisedRatioMeasure":
specular_colour = self.props.specular_colour_ratio
if self.props.is_specular_highlight_null:
specular_highlight = None
else:
specular_highlight = {"SpecularRoughness": self.props.specular_highlight}
return {
"SurfaceColour": self.color_to_dict(self.props.surface_colour),
"Transparency": self.props.transparency or None,
"ReflectanceMethod": self.props.reflectance_method,
"DiffuseColour": diffuse_colour,
"SpecularColour": specular_colour,
"SpecularHighlight": specular_highlight,
}
def color_to_dict(self, x):
return {"Red": x[0], "Green": x[1], "Blue": x[2]}
@@ -40,6 +40,12 @@ def get_style_types(self, context):
return StylesData.data["style_types"]
def get_reflectance_methods(self, context):
if not StylesData.is_loaded:
StylesData.load()
return StylesData.data["reflectance_methods"]
class Style(PropertyGroup):
name: StringProperty(name="Name")
ifc_definition_id: IntProperty(name="IFC Definition ID")
@@ -49,6 +55,10 @@ class Style(PropertyGroup):
surface_colour: bpy.props.FloatVectorProperty(
name="Surface Colour", subtype="COLOR", default=(1, 1, 1), min=0.0, max=1.0, size=3
)
has_diffuse_colour: BoolProperty(name="Has Diffuse Colour", default=False)
diffuse_colour: bpy.props.FloatVectorProperty(
name="Diffuse Colour", subtype="COLOR", default=(1, 1, 1), min=0.0, max=1.0, size=3
)
STYLE_TYPES = [
@@ -91,6 +101,27 @@ class BIMStylesProperties(PropertyGroup):
name="Surface Colour", subtype="COLOR", default=(1, 1, 1), min=0.0, max=1.0, size=3
)
transparency: bpy.props.FloatProperty(name="Transparency", default=0.0, min=0.0, max=1.0)
is_diffuse_colour_null: BoolProperty(name="Is Null")
diffuse_colour_class: EnumProperty(
items=[(x, x, "") for x in ("IfcColourRgb", "IfcNormalisedRatioMeasure")],
name="Diffuse Colour Class",
)
diffuse_colour: bpy.props.FloatVectorProperty(
name="Diffuse Colour", subtype="COLOR", default=(1, 1, 1), min=0.0, max=1.0, size=3
)
diffuse_colour_ratio: bpy.props.FloatProperty(name="Diffuse Ratio", default=0.0, min=0.0, max=1.0)
is_specular_colour_null: BoolProperty(name="Is Null")
specular_colour_class: EnumProperty(
items=[(x, x, "") for x in ("IfcColourRgb", "IfcNormalisedRatioMeasure")],
name="Specular Colour Class",
)
specular_colour: bpy.props.FloatVectorProperty(
name="Specular Colour", subtype="COLOR", default=(1, 1, 1), min=0.0, max=1.0, size=3
)
specular_colour_ratio: bpy.props.FloatProperty(name="Specular Ratio", default=0.0, min=0.0, max=1.0)
is_specular_highlight_null: BoolProperty(name="Is Null")
specular_highlight: bpy.props.FloatProperty(name="Specular Highlight", default=0.0, min=0.0, max=1.0)
reflectance_method: EnumProperty(name="Reflectance Method", items=get_reflectance_methods)
styles: CollectionProperty(name="Styles", type=Style)
active_style_index: IntProperty(name="Active Style Index")
active_style_type: EnumProperty(
@@ -48,7 +48,7 @@ class BIM_PT_styles(Panel):
row = self.layout.row(align=True)
row.label(text="{} {}s".format(len(self.props.styles), self.props.style_type), icon="SHADING_RENDERED")
if not self.props.is_adding:
row.operator("bim.enable_add_presentation_style", text="", icon="ADD")
row.operator("bim.enable_adding_presentation_style", text="", icon="ADD")
row.operator("bim.disable_editing_styles", text="", icon="CANCEL")
else:
row = self.layout.row(align=True)
@@ -69,7 +69,7 @@ class BIM_PT_styles(Panel):
row.prop(self.props, "surface_colour", text="Colour")
row = box.row(align=True)
row.operator("bim.add_presentation_style", text="Save New Style", icon="CHECKMARK")
row.operator("bim.disable_add_presentation_style", text="", icon="CANCEL")
row.operator("bim.disable_adding_presentation_style", text="", icon="CANCEL")
if self.props.styles and self.props.active_style_index < len(self.props.styles):
row = self.layout.row(align=True)
@@ -131,6 +131,55 @@ class BIM_PT_styles(Panel):
row.prop(self.props, "surface_colour")
row = self.layout.row()
row.prop(self.props, "transparency")
row = self.layout.row()
row.prop(self.props, "reflectance_method")
row = self.layout.row(align=True)
row.label(text="Diffuse")
row.prop(self.props, "diffuse_colour_class", text="")
if self.props.diffuse_colour_class == "IfcColourRgb":
row.prop(self.props, "diffuse_colour", text="")
else:
row.prop(self.props, "diffuse_colour_ratio", text="")
row.prop(
self.props,
"is_diffuse_colour_null",
text="",
icon="RADIOBUT_OFF" if self.props.is_diffuse_colour_null else "RADIOBUT_ON",
)
row = self.layout.row(align=True)
if self.props.reflectance_method in ("PHYSICAL", "NOTDEFINED"):
row.label(text="Metallic")
else:
row.label(text="Specular")
row.prop(self.props, "specular_colour_class", text="")
if self.props.specular_colour_class == "IfcColourRgb":
row.prop(self.props, "specular_colour", text="")
else:
row.prop(self.props, "specular_colour_ratio", text="")
row.prop(
self.props,
"is_specular_colour_null",
text="",
icon="RADIOBUT_OFF" if self.props.is_specular_colour_null else "RADIOBUT_ON",
)
row = self.layout.row(align=True)
if self.props.reflectance_method in ("PHYSICAL", "NOTDEFINED"):
row.label(text="Roughness")
elif self.props.reflectance_method in ("PHONG"):
row.label(text="Shininess")
else:
row.label(text="Highlight")
row.prop(self.props, "specular_highlight", text="")
row.prop(
self.props,
"is_specular_highlight_null",
text="",
icon="RADIOBUT_OFF" if self.props.is_specular_highlight_null else "RADIOBUT_ON",
)
row = self.layout.row(align=True)
row.operator("bim.edit_surface_style", text="Save Rendering Style", icon="CHECKMARK")
row.operator("bim.disable_editing_style", text="", icon="CANCEL")
@@ -285,9 +334,11 @@ class BIM_UL_styles(UIList):
row = layout.row(align=True)
row.label(text=item.name)
if item.has_surface_colour:
row = row.row()
row = row.row(align=True)
row.enabled = False
row.prop(item, "surface_colour", text="")
if item.has_diffuse_colour:
row.prop(item, "diffuse_colour", text="")
row2 = row.row()
row2.alignment = "RIGHT"
row2.label(text=str(item.total_elements))
@@ -22,38 +22,52 @@ from . import ui, prop, operator, decorator
classes = (
operator.AddPort,
operator.AddSystem,
operator.AddZone,
operator.AssignSystem,
operator.AssignUnassignFlowControl,
operator.ConnectPort,
operator.DisableEditingSystem,
operator.DisableEditingZone,
operator.DisableSystemEditingUI,
operator.DisconnectPort,
operator.EditSystem,
operator.EditZone,
operator.EnableEditingSystem,
operator.EnableEditingZone,
operator.HidePorts,
operator.LoadSystems,
operator.LoadZones,
operator.MEPConnectElements,
operator.RemovePort,
operator.RemoveSystem,
operator.RemoveZone,
operator.SelectSystemProducts,
operator.MEPConnectElements,
operator.SetFlowDirection,
operator.ShowPorts,
operator.UnassignSystem,
operator.UnloadZones,
prop.System,
prop.Zone,
prop.BIMSystemProperties,
prop.BIMZoneProperties,
ui.BIM_PT_systems,
ui.BIM_PT_object_systems,
ui.BIM_PT_zones,
ui.BIM_PT_active_object_zones,
ui.BIM_PT_ports,
ui.BIM_PT_port,
ui.BIM_PT_flow_controls,
ui.BIM_UL_systems,
ui.BIM_UL_object_systems,
ui.BIM_UL_zones,
)
def register():
bpy.types.Scene.BIMSystemProperties = bpy.props.PointerProperty(type=prop.BIMSystemProperties)
bpy.types.Scene.BIMZoneProperties = bpy.props.PointerProperty(type=prop.BIMZoneProperties)
bpy.app.handlers.load_post.append(decorator.toggle_decorations_on_load)
def unregister():
del bpy.types.Scene.BIMSystemProperties
del bpy.types.Scene.BIMZoneProperties
bpy.app.handlers.load_post.remove(decorator.toggle_decorations_on_load)
@@ -25,6 +25,8 @@ import blenderbim.tool as tool
def refresh():
SystemData.is_loaded = False
ZonesData.is_loaded = False
ActiveObjectZonesData.is_loaded = False
ObjectSystemData.is_loaded = False
PortData.is_loaded = False
SystemDecorationData.is_loaded = False
@@ -47,6 +49,7 @@ class SystemData:
declaration = tool.Ifc.schema().declaration_by_name("IfcSystem")
declarations = ifcopenshell.util.schema.get_subtypes(declaration)
version = tool.Ifc.get_schema()
# We're only interested in systems for services. Not sure why IFC groups these together.
return [
(c, c, get_entity_doc(version, c).get("description", ""))
@@ -56,7 +59,7 @@ class SystemData:
@classmethod
def total_systems(cls):
return len(tool.Ifc.get().by_type("IfcSystem"))
return len(tool.System.get_systems())
class ObjectSystemData:
@@ -67,9 +70,9 @@ class ObjectSystemData:
def load(cls):
cls.data = {
"systems": cls.systems(),
"total_systems": cls.total_systems(),
# AFTER SYSTEMS
"connected_elements": cls.connected_elements(),
"flow_controls_data": cls.flow_controls_data(),
}
cls.is_loaded = True
@@ -83,16 +86,32 @@ class ObjectSystemData:
results.append({"id": system.id(), "name": system.Name or "Unnamed", "ifc_class": system.is_a()})
return results
@classmethod
def total_systems(cls):
return len(tool.Ifc.get().by_type("IfcSystem"))
@classmethod
def connected_elements(cls):
if not cls.element:
return set()
return tool.System.get_connected_elements(cls.element)
@classmethod
def flow_controls_data(cls):
flow_controls_data = {}
if not cls.element or not (
cls.element.is_a("IfcDistributionControlElement") or cls.element.is_a("IfcDistributionFlowElement")
):
return flow_controls_data
if cls.element.is_a("IfcDistributionControlElement"):
flow_controls_data["type"] = "IfcDistributionControlElement"
flow_element = tool.System.get_flow_control_flow_element(cls.element)
flow_element_obj = tool.Ifc.get_object(flow_element).name if flow_element else None
flow_controls_data["flow_element"] = flow_element, flow_element_obj
else:
flow_controls_data["type"] = "IfcDistributionFlowElement"
controls = [(c, tool.Ifc.get_object(c).name) for c in tool.System.get_flow_element_controls(cls.element)]
flow_controls_data["controls"] = controls
return flow_controls_data
class PortData:
data = {}
@@ -107,8 +126,8 @@ class PortData:
"total_ports": cls.total_ports(),
"located_ports_data": cls.located_ports_data(),
"is_port": is_port,
"port_connected_object": cls.port_connected_object() if is_port else None,
"port_relating_object": cls.port_relating_object() if is_port else None,
"port_connected_object_name": cls.port_connected_object_name() if is_port else None,
"port_relating_object_name": cls.port_relating_object_name() if is_port else None,
}
# AFTER located_ports_data
cls.data["selected_objects_flow_direction"] = cls.selected_objects_flow_direction() if not is_port else None
@@ -123,16 +142,16 @@ class PortData:
return cls.element and cls.element.is_a("IfcDistributionPort")
@classmethod
def port_relating_object(cls):
return tool.Ifc.get_object(tool.System.get_port_relating_element(cls.element))
def port_relating_object_name(cls):
return tool.Ifc.get_object(tool.System.get_port_relating_element(cls.element)).name
@classmethod
def port_connected_object(cls):
def port_connected_object_name(cls):
connected_port = tool.System.get_connected_port(cls.element)
if not connected_port:
return
connected_element = tool.System.get_port_relating_element(connected_port)
return tool.Ifc.get_object(connected_element)
return tool.Ifc.get_object(connected_element).name
@classmethod
def located_ports_data(cls):
@@ -140,19 +159,23 @@ class PortData:
data = []
for port in ports:
port_obj = tool.Ifc.get_object(port)
# port may be not present as a scene object
port_obj_name = getattr(tool.Ifc.get_object(port), "name", None)
connected_port = tool.System.get_connected_port(port)
if connected_port:
connected_obj = tool.Ifc.get_object(tool.System.get_port_relating_element(connected_port))
connected_obj_name = tool.Ifc.get_object(tool.System.get_port_relating_element(connected_port)).name
else:
connected_obj = None
connected_obj_name = None
data.append((port, port_obj, connected_obj))
data.append((port, port_obj_name, connected_obj_name))
return data
@classmethod
def selected_objects_flow_direction(cls):
for port, port_obj, connected_obj in cls.data["located_ports_data"]:
for port, _, connected_obj_name in cls.data["located_ports_data"]:
if connected_obj_name is None:
continue
connected_obj = bpy.data.objects[connected_obj_name]
if connected_obj in bpy.context.selected_objects:
return port.FlowDirection
@@ -164,7 +187,9 @@ class SystemDecorationData:
@classmethod
def load(cls):
cls.data = {}
cls.data = {
"decorated_elements": cls.decorated_elements(),
}
cls.is_loaded = True
cls.elements_ports_positions = {}
@@ -190,3 +215,55 @@ class SystemDecorationData:
ports_data.append(port_data)
cls.elements_ports_positions[element] = ports_data
return cls.elements_ports_positions[element]
@classmethod
def decorated_elements(cls):
if not ObjectSystemData.is_loaded:
ObjectSystemData.load()
# Priority:
# 1. currently selected systems
# 2. active system
# 3. if previous steps didn't worked - decorate connected elements
decorated_elements = set()
if ObjectSystemData.data["systems"]:
for system in ObjectSystemData.data["systems"]:
system = tool.Ifc.get().by_id(system["id"])
decorated_elements.update(ifcopenshell.util.system.get_system_elements(system))
elif active_system := tool.System.get_active_system():
decorated_elements = set(ifcopenshell.util.system.get_system_elements(active_system))
if not decorated_elements:
decorated_elements.update(ObjectSystemData.data["connected_elements"])
return decorated_elements
class ZonesData:
data = {}
is_loaded = False
@classmethod
def load(cls):
cls.data = {"total_zones": cls.total_zones()}
cls.is_loaded = True
@classmethod
def total_zones(cls):
return len(tool.Ifc.get().by_type("IfcZone"))
class ActiveObjectZonesData:
data = {}
is_loaded = False
@classmethod
def load(cls):
cls.data = {"zones": cls.zones()}
cls.is_loaded = True
@classmethod
def zones(cls):
systems = ifcopenshell.util.system.get_element_systems(tool.Ifc.get_entity(bpy.context.active_object))
return [s.Name or "Unnamed" for s in systems if s.is_a("IfcZone")]
@@ -122,15 +122,15 @@ class SystemDecorator:
selected_vertices = decoration_data["selected_vertices"]
### Actually drawing
# 3D_POLYLINE_UNIFORM_COLOR is good for smoothed lines since `bgl.enable(GL_LINE_SMOOTH)` is deprecated
self.line_shader = gpu.shader.from_builtin("3D_POLYLINE_UNIFORM_COLOR")
# POLYLINE_UNIFORM_COLOR is good for smoothed lines since `bgl.enable(GL_LINE_SMOOTH)` is deprecated
self.line_shader = gpu.shader.from_builtin("POLYLINE_UNIFORM_COLOR")
self.line_shader.bind()
# 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("3D_UNIFORM_COLOR")
self.shader = gpu.shader.from_builtin("UNIFORM_COLOR")
self.shader.bind()
self.draw_batch("LINES", all_vertices, transparent_color(unselected_elements_color), unselected_edges)
@@ -21,6 +21,7 @@ import ifcopenshell.api
import blenderbim.tool as tool
import blenderbim.core.system as core
import blenderbim.bim.handler
import blenderbim.bim.helper
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.module.system.data import PortData
from mathutils import Matrix
@@ -67,7 +68,7 @@ class EditSystem(bpy.types.Operator, Operator):
def _execute(self, context):
core.edit_system(
tool.Ifc, tool.System, system=tool.Ifc.get().by_id(context.scene.BIMSystemProperties.active_system_id)
tool.Ifc, tool.System, system=tool.Ifc.get().by_id(context.scene.BIMSystemProperties.edited_system_id)
)
@@ -326,3 +327,161 @@ class SetFlowDirection(bpy.types.Operator, Operator):
self.report({"ERROR"}, "Selected elements are not connected to set the flow direction")
return {"CANCELLED"}
class LoadZones(bpy.types.Operator, Operator):
bl_idname = "bim.load_zones"
bl_label = "Load Zones"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
props = bpy.context.scene.BIMZoneProperties
props.zones.clear()
for zone in tool.Ifc.get().by_type("IfcZone"):
new = props.zones.add()
new.ifc_definition_id = zone.id()
new.name = zone.Name or "Unnamed"
props.is_loaded = True
props.is_editing = 0
class UnloadZones(bpy.types.Operator, Operator):
bl_idname = "bim.unload_zones"
bl_label = "Unload Zones"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
props = bpy.context.scene.BIMZoneProperties
props.is_loaded = False
class AddZone(bpy.types.Operator, Operator):
bl_idname = "bim.add_zone"
bl_label = "Add Zone"
bl_options = {"REGISTER", "UNDO"}
name: bpy.props.StringProperty()
def invoke(self, context, event):
return context.window_manager.invoke_props_dialog(self)
def draw(self, context):
row = self.layout
row.prop(self, "name", text="Name")
def _execute(self, context):
element = ifcopenshell.api.run("system.add_system", tool.Ifc.get(), ifc_class="IfcZone")
if self.name:
element.Name = self.name
bpy.ops.bim.load_zones()
class EnableEditingZone(bpy.types.Operator, Operator):
bl_idname = "bim.enable_editing_zone"
bl_label = "Enable Editing Zone"
bl_options = {"REGISTER", "UNDO"}
zone: bpy.props.IntProperty()
def _execute(self, context):
props = bpy.context.scene.BIMZoneProperties
props.attributes.clear()
blenderbim.bim.helper.import_attributes2(tool.Ifc.get().by_id(self.zone), props.attributes)
props.is_editing = self.zone
class DisableEditingZone(bpy.types.Operator, Operator):
bl_idname = "bim.disable_editing_zone"
bl_label = "Disable Editing Zone"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
props = bpy.context.scene.BIMZoneProperties
props.is_editing = 0
class EditZone(bpy.types.Operator, Operator):
bl_idname = "bim.edit_zone"
bl_label = "Edit Zone"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
props = bpy.context.scene.BIMZoneProperties
zone = tool.Ifc.get().by_id(props.is_editing)
attributes = blenderbim.bim.helper.export_attributes(props.attributes)
ifcopenshell.api.run("system.edit_system", tool.Ifc.get(), system=zone, attributes=attributes)
props.is_editing = 0
bpy.ops.bim.load_zones()
class RemoveZone(bpy.types.Operator, Operator):
bl_idname = "bim.remove_zone"
bl_label = "Remove Zone"
bl_options = {"REGISTER", "UNDO"}
zone: bpy.props.IntProperty()
def _execute(self, context):
ifcopenshell.api.run("system.remove_system", tool.Ifc.get(), system=tool.Ifc.get().by_id(self.zone))
bpy.ops.bim.load_zones()
class AssignUnassignFlowControl(bpy.types.Operator, Operator):
bl_idname = "bim.assign_unassign_flow_control"
bl_label = "Assign/Unassign Flow Control"
bl_options = {"REGISTER", "UNDO"}
flow_element: bpy.props.IntProperty(options={"SKIP_SAVE"})
flow_control: bpy.props.IntProperty(options={"SKIP_SAVE"})
assign: bpy.props.BoolProperty(name="Assign/Unassign", default=True, options={"SKIP_SAVE"})
def _execute(self, context):
ifc_file = tool.Ifc.get()
flow_element = None
flow_controls = []
from_selected_objects = False
if self.flow_element != 0:
flow_element = ifc_file.by_id(self.flow_element)
if self.flow_control != 0:
flow_controls = [ifc_file.by_id(self.flow_control)]
# if not provided as arguments tried to get them from
# the selected objects
if not flow_element or flow_controls:
from_selected_objects = True
for obj in context.selected_objects:
element = tool.Ifc.get_entity(obj)
if not element:
continue
if element.is_a("IfcDistributionControlElement") and self.flow_control == 0:
flow_controls.append(element)
elif element.is_a("IfcDistributionFlowElement") and self.flow_element == 0:
if flow_element:
self.report(
{"ERROR"},
"More than one flow element selected. Control can be assigned to only 1 flow element.",
)
return {"CANCELLED"}
flow_element = element
if not flow_element:
self.report({"ERROR"}, "No flow element selected.")
return {"CANCELLED"}
if not flow_controls:
self.report({"ERROR"}, "No flow controls selected.")
return {"CANCELLED"}
for control in flow_controls:
if self.assign:
tool.Ifc.run(
"system.assign_flow_control", relating_flow_element=flow_element, related_flow_control=control
)
else:
tool.Ifc.run(
"system.unassign_flow_control", relating_flow_element=flow_element, related_flow_control=control
)
if from_selected_objects:
self.report(
{"INFO"}, f"{len(flow_controls)} flow controls were {'assigned' if self.assign else 'unassigned'}."
)
return {"FINISHED"}
@@ -45,6 +45,11 @@ class System(PropertyGroup):
ifc_definition_id: IntProperty(name="IFC Definition ID")
class Zone(PropertyGroup):
name: StringProperty(name="Name")
ifc_definition_id: IntProperty(name="IFC Definition ID")
def toggle_decorations(self, context):
toggle = self.should_draw_decorations
if toggle:
@@ -60,7 +65,16 @@ class BIMSystemProperties(PropertyGroup):
systems: CollectionProperty(name="Systems", type=System)
active_system_index: IntProperty(name="Active System Index")
active_system_id: IntProperty(name="Active System Id")
edited_system_id: IntProperty(name="Edited System Id")
system_class: EnumProperty(items=get_system_class, name="Class")
should_draw_decorations: BoolProperty(
name="Should Draw Decorations", description="Toggle system decorations", update=toggle_decorations
)
class BIMZoneProperties(PropertyGroup):
attributes: CollectionProperty(name="Attributes", type=Attribute)
is_loaded: BoolProperty(name="Is Loaded", default=False)
is_editing: IntProperty(name="Is Editing", default=0)
zones: CollectionProperty(name="Zones", type=Zone)
active_zone_index: IntProperty(name="Active Zone Index")
+199 -100
View File
@@ -16,11 +16,12 @@
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
from blenderbim.bim.helper import prop_with_search
import bpy
import blenderbim.bim.helper
import blenderbim.tool as tool
from blenderbim.bim.helper import prop_with_search
from bpy.types import Panel, UIList
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.module.system.data import SystemData, ObjectSystemData, PortData
from blenderbim.bim.module.system.data import SystemData, ZonesData, ActiveObjectZonesData, ObjectSystemData, PortData
FLOW_DIRECTION_TO_ICON = {
@@ -30,6 +31,15 @@ FLOW_DIRECTION_TO_ICON = {
"NOTDEFINED": "CHECKBOX_DEHLT",
}
SYSTEM_ICONS = {
"IfcSystem": "EXTERNAL_DRIVE",
"IfcDistributionSystem": "NETWORK_DRIVE",
"IfcDistributionCircuit": "DRIVER",
"IfcBuildingSystem": "MOD_BUILD",
"IfcBuiltSystem": "MOD_BUILD",
"IfcZone": "CUBE",
}
class BIM_PT_systems(Panel):
bl_label = "Systems"
@@ -42,15 +52,45 @@ class BIM_PT_systems(Panel):
@classmethod
def poll(cls, context):
return IfcStore.get_file()
return tool.Ifc.get()
def draw(self, context):
if not SystemData.is_loaded:
SystemData.load()
if not ObjectSystemData.is_loaded:
ObjectSystemData.load()
def draw_system_ui(row, system_id, system_name, system_class):
row = self.layout.row(align=True)
row.label(text=system_name, icon=SYSTEM_ICONS[system_class])
op = row.operator("bim.select_system_products", text="", icon="RESTRICT_SELECT_OFF")
op.system = system_id
op = row.operator("bim.unassign_system", text="", icon="X")
op.system = system_id
self.props = context.scene.BIMSystemProperties
row = self.layout.row(align=True)
row.prop(self.props, "should_draw_decorations")
row = self.layout.row()
if active_system := tool.System.get_active_system():
row.label(text=f"Active system:")
row = self.layout.row(align=True)
draw_system_ui(row, active_system.id(), active_system.Name, active_system.is_a())
else:
row.label(text="No active system is selected")
if ObjectSystemData.data["systems"]:
row = self.layout.row()
row.label(text="Active object systems:")
for system in ObjectSystemData.data["systems"]:
row = self.layout.row(align=True)
draw_system_ui(row, system["id"], system["name"], system["ifc_class"])
else:
self.layout.label(text="No System associated with active object")
row = self.layout.row(align=True)
row.label(text="{} Systems Found".format(SystemData.data["total_systems"]), icon="OUTLINER")
row.label(text="{} Systems Found in Project".format(SystemData.data["total_systems"]), icon="OUTLINER")
if self.props.is_editing:
row.operator("bim.disable_system_editing_ui", text="", icon="CANCEL")
@@ -58,7 +98,7 @@ class BIM_PT_systems(Panel):
prop_with_search(row, self.props, "system_class", text="")
row.operator("bim.add_system", text="", icon="ADD")
else:
row.operator("bim.load_systems", text="", icon="GREASEPENCIL")
row.operator("bim.load_systems", text="", icon="IMPORT")
if self.props.is_editing:
self.layout.template_list(
@@ -70,7 +110,7 @@ class BIM_PT_systems(Panel):
"active_system_index",
)
if self.props.active_system_id:
if self.props.edited_system_id:
self.draw_editable_ui(context)
def draw_editable_ui(self, context):
@@ -81,67 +121,6 @@ class BIM_PT_systems(Panel):
row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
class BIM_PT_object_systems(Panel):
bl_label = "Systems"
bl_idname = "BIM_PT_object_systems"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
bl_order = 1
bl_parent_id = "BIM_PT_tab_services_object"
@classmethod
def poll(cls, context):
if not context.active_object:
return False
return IfcStore.get_file() and context.active_object.BIMObjectProperties.ifc_definition_id
def draw(self, context):
if not ObjectSystemData.is_loaded:
ObjectSystemData.load()
self.props = context.scene.BIMSystemProperties
row = self.layout.row(align=True)
row.prop(self.props, "should_draw_decorations")
if self.props.is_editing:
row = self.layout.row()
row.alignment = "RIGHT"
row.operator("bim.disable_system_editing_ui", text="", icon="CANCEL")
self.layout.template_list(
"BIM_UL_object_systems",
"",
self.props,
"systems",
self.props,
"active_system_index",
)
else:
row = self.layout.row(align=True)
row.label(text=f"{ObjectSystemData.data['total_systems']} Systems in IFC Project", icon="OUTLINER")
row.operator("bim.load_systems", text="", icon="GREASEPENCIL")
system_icons = {
"IfcSystem": "EXTERNAL_DRIVE",
"IfcDistributionSystem": "NETWORK_DRIVE",
"IfcDistributionCircuit": "DRIVER",
"IfcBuildingSystem": "MOD_BUILD",
"IfcBuiltSystem": "MOD_BUILD",
"IfcZone": "CUBE",
}
for system in ObjectSystemData.data["systems"]:
row = self.layout.row(align=True)
row.label(text=system["name"], icon=system_icons[system["ifc_class"]])
op = row.operator("bim.select_system_products", text="", icon="RESTRICT_SELECT_OFF")
op.system = system["id"]
op = row.operator("bim.unassign_system", text="", icon="X")
op.system = system["id"]
if not ObjectSystemData.data["systems"]:
self.layout.label(text="No System associated with Active Object")
class BIM_PT_ports(Panel):
bl_label = "Ports"
bl_idname = "BIM_PT_ports"
@@ -198,23 +177,24 @@ class BIM_PT_ports(Panel):
cols = [row.column(align=True) for i in range(6)]
for i, port_data in enumerate(PortData.data["located_ports_data"]):
port, port_obj, connected_obj = port_data
port, port_obj_name, connected_obj_name = port_data
flow_direction_icon = FLOW_DIRECTION_TO_ICON[port.FlowDirection or "NOTDEFINED"]
if port_obj:
if port_obj_name:
cols[0].label(text="", icon=flow_direction_icon)
cols[1].operator("bim.select_entity", text="", icon="RESTRICT_SELECT_OFF").ifc_id = port.id()
cols[2].label(text=port_obj.name)
cols[2].label(text=port_obj_name)
else:
cols[0].label(text="", icon=flow_direction_icon)
cols[1].label(text="", icon="HIDE_ON")
cols[2].label(text="Port is hidden")
if connected_obj:
if connected_obj_name:
connected_obj = bpy.data.objects[connected_obj_name]
cols[3].operator("bim.disconnect_port", text="", icon="UNLINKED").element_id = port.id()
cols[4].operator(
"bim.select_entity", text="", icon="RESTRICT_SELECT_OFF"
).ifc_id = connected_obj.BIMObjectProperties.ifc_definition_id
cols[5].label(text=f"{connected_obj.name}")
cols[5].label(text=connected_obj_name)
else:
cols[3].label(text="", icon="UNLINKED")
cols[4].label(text="", icon="BLANK1")
@@ -264,19 +244,21 @@ class BIM_PT_port(Panel):
# port located on
row = layout.row(align=True)
relating_object = PortData.data["port_relating_object"]
relating_object_name = PortData.data["port_relating_object_name"]
relating_object = bpy.data.objects[relating_object_name]
row.label(text="Port located on:")
row.label(text=relating_object.name)
row.label(text=relating_object_name)
row.operator(
"bim.select_entity", text="", icon="RESTRICT_SELECT_OFF"
).ifc_id = relating_object.BIMObjectProperties.ifc_definition_id
# object connected to the port
row = layout.row(align=True)
connected_object = PortData.data["port_connected_object"]
if connected_object:
connected_object_name = PortData.data["port_connected_object_name"]
if connected_object_name:
connected_object = bpy.data.objects[connected_object_name]
row.label(text="Port connected to:")
row.label(text=connected_object.name)
row.label(text=connected_object_name)
row.operator(
"bim.select_entity", text="", icon="RESTRICT_SELECT_OFF"
).ifc_id = connected_object.BIMObjectProperties.ifc_definition_id
@@ -294,26 +276,152 @@ class BIM_PT_port(Panel):
).direction = flow_direction
class BIM_PT_flow_controls(Panel):
bl_label = "Flow Controls"
bl_idname = "BIM_PT_flow_controls"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
bl_order = 1
bl_parent_id = "BIM_PT_tab_services_object"
@classmethod
def poll(cls, context):
if not context.active_object:
return False
element = tool.Ifc.get_entity(context.active_object)
if not element or not (
element.is_a("IfcDistributionControlElement") or element.is_a("IfcDistributionFlowElement")
):
return False
return True
def draw(self, context):
if not ObjectSystemData.is_loaded:
ObjectSystemData.load()
def display_element(control_id, flow_element_id, displayed_object_name):
displayed_object = bpy.data.objects[displayed_object_name]
row = self.layout.row(align=True)
op = row.operator("bim.assign_unassign_flow_control", text="", icon="X")
op.flow_control = control_id
op.flow_element = flow_element_id
op.assign = False
row.operator(
"bim.select_entity", text="", icon="RESTRICT_SELECT_OFF"
).ifc_id = displayed_object.BIMObjectProperties.ifc_definition_id
row.label(text=f"{displayed_object_name}")
element = tool.Ifc.get_entity(context.active_object)
flow_controls_data = ObjectSystemData.data["flow_controls_data"]
if flow_controls_data["type"] == "IfcDistributionFlowElement":
controls = flow_controls_data["controls"]
row = self.layout.row(align=True)
if controls:
row.label(text="Controls assigned to the element:")
else:
row.label(text="No controls assigned to the flow element")
row.operator("bim.assign_unassign_flow_control", text="", icon="ADD").assign = True
if controls:
for control_data in controls:
control, control_obj_name = control_data
display_element(control.id(), element.id(), control_obj_name)
else:
flow_element, flow_element_obj_name = flow_controls_data["flow_element"]
row = self.layout.row(align=True)
if flow_element:
row.label(text="Flow element controlled by the flow control:")
display_element(element.id(), flow_element.id(), flow_element_obj_name)
else:
row.label(text="No flow element controlled by the flow control")
row.operator("bim.assign_unassign_flow_control", text="", icon="ADD").assign = True
class BIM_PT_zones(Panel):
bl_label = "Zones"
bl_idname = "BIM_PT_zones"
bl_options = {"HIDE_HEADER"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_tab_zones"
@classmethod
def poll(cls, context):
return tool.Ifc.get()
def draw(self, context):
if not ZonesData.is_loaded:
ZonesData.load()
self.props = context.scene.BIMZoneProperties
row = self.layout.row(align=True)
row.label(text="{} Zones Found".format(ZonesData.data["total_zones"]), icon="SEQ_STRIP_META")
if not self.props.is_loaded:
row.operator("bim.load_zones", text="", icon="IMPORT")
return
row.operator("bim.add_zone", text="", icon="ADD")
row.operator("bim.unload_zones", text="", icon="CANCEL")
if self.props.zones and self.props.active_zone_index < len(self.props.zones):
row = self.layout.row(align=True)
ifc_definition_id = self.props.zones[self.props.active_zone_index].ifc_definition_id
row.operator("bim.enable_editing_zone", text="Edit Zone", icon="GREASEPENCIL").zone = ifc_definition_id
row.operator("bim.select_system_products", text="", icon="RESTRICT_SELECT_OFF").system = ifc_definition_id
row.operator("bim.assign_system", text="", icon="KEYFRAME_HLT").system = ifc_definition_id
row.operator("bim.unassign_system", text="", icon="KEYFRAME").system = ifc_definition_id
row.operator("bim.remove_zone", text="", icon="X").zone = ifc_definition_id
self.layout.template_list("BIM_UL_zones", "", self.props, "zones", self.props, "active_zone_index")
if self.props.is_editing:
blenderbim.bim.helper.draw_attributes(self.props.attributes, self.layout)
row = self.layout.row(align=True)
row.operator("bim.edit_zone", icon="CHECKMARK")
row.operator("bim.disable_editing_zone", icon="CANCEL", text="")
class BIM_PT_active_object_zones(Panel):
bl_label = "Active Object Zones"
bl_idname = "BIM_PT_active_object_zones"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_tab_zones"
@classmethod
def poll(cls, context):
return tool.Ifc.get() and context.active_object and tool.Ifc.get_entity(context.active_object)
def draw(self, context):
if not ActiveObjectZonesData.is_loaded:
ActiveObjectZonesData.load()
self.props = context.scene.BIMZoneProperties
for zone in ActiveObjectZonesData.data["zones"]:
row = self.layout.row()
row.label(text=zone, icon="SEQ_STRIP_META")
if not ActiveObjectZonesData.data["zones"]:
row = self.layout.row()
row.label(text="Active Object Has No Zones")
class BIM_UL_systems(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
system_icons = {
"IfcSystem": "EXTERNAL_DRIVE",
"IfcDistributionSystem": "NETWORK_DRIVE",
"IfcDistributionCircuit": "DRIVER",
"IfcBuildingSystem": "MOD_BUILD",
"IfcBuiltSystem": "MOD_BUILD",
"IfcZone": "CUBE",
}
if item:
row = layout.row(align=True)
row.label(text=item.name, icon=system_icons[item.ifc_class])
row.label(text=item.name, icon=SYSTEM_ICONS[item.ifc_class])
system_id = item.ifc_definition_id
if context.scene.BIMSystemProperties.active_system_id == system_id:
row.operator("bim.assign_system", text="", icon="ADD").system = item.ifc_definition_id
if context.scene.BIMSystemProperties.edited_system_id == system_id:
op = row.operator("bim.select_system_products", text="", icon="RESTRICT_SELECT_OFF")
op.system = system_id
row.operator("bim.edit_system", text="", icon="CHECKMARK")
row.operator("bim.disable_editing_system", text="", icon="CANCEL")
elif context.scene.BIMSystemProperties.active_system_id:
elif context.scene.BIMSystemProperties.edited_system_id:
op = row.operator("bim.select_system_products", text="", icon="RESTRICT_SELECT_OFF")
op.system = system_id
op = row.operator("bim.remove_system", text="", icon="X")
@@ -327,17 +435,8 @@ class BIM_UL_systems(UIList):
op.system = system_id
class BIM_UL_object_systems(UIList):
class BIM_UL_zones(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
system_icons = {
"IfcSystem": "EXTERNAL_DRIVE",
"IfcDistributionSystem": "NETWORK_DRIVE",
"IfcDistributionCircuit": "DRIVER",
"IfcBuildingSystem": "MOD_BUILD",
"IfcBuiltSystem": "MOD_BUILD",
"IfcZone": "CUBE",
}
if item:
row = layout.row(align=True)
row.label(text=item.name, icon=system_icons[item.ifc_class])
row.operator("bim.assign_system", text="", icon="ADD").system = item.ifc_definition_id
row.label(text=item.name)
@@ -410,7 +410,7 @@ class AddType(bpy.types.Operator, tool.Ifc.Operator):
obj=obj,
predefined_type=predefined_type,
ifc_class="IfcStairFlightType",
should_add_representation=True,
should_add_representation=False,
context=body,
)
tool.Blender.select_and_activate_single_object(context, obj)
@@ -30,7 +30,7 @@ class BIM_PT_units(Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_project_setup"
bl_parent_id = "BIM_PT_tab_geometry"
@classmethod
def poll(cls, context):
@@ -25,6 +25,7 @@ classes = (
operator.RemoveFilling,
operator.RemoveOpening,
operator.SelectDecomposition,
operator.BooleansMarkAsManual,
prop.VoidProperties,
ui.BIM_PT_voids,
ui.BIM_PT_booleans,
@@ -67,32 +67,39 @@ class VoidsData:
return results
class BooleansData:
data = {}
is_loaded = False
@classmethod
def load(cls):
cls.data = {"total_booleans": cls.total_booleans()}
cls.data = {}
cls.data["total_booleans"] = cls.booleans()
cls.data["manual_booleans"] = cls.manual_booleans()
cls.is_loaded = True
@classmethod
def total_booleans(cls):
def booleans(cls):
obj = bpy.context.active_object
if (
not obj.data
or not hasattr(obj.data, "BIMMeshProperties")
or not obj.data.BIMMeshProperties.ifc_definition_id
):
return 0
return []
representation = tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id)
total_booleans = 0
for item in representation.Items:
while True:
if item.is_a("IfcBooleanResult"):
total_booleans += 1
item = item.FirstOperand
else:
break
return total_booleans
return tool.Model.get_booleans(representation=representation)
@classmethod
def manual_booleans(cls):
obj = bpy.context.active_object
if (
not obj.data
or not hasattr(obj.data, "BIMMeshProperties")
or not obj.data.BIMMeshProperties.ifc_definition_id
):
return []
representation = tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id)
return tool.Model.get_manual_booleans(tool.Ifc.get_entity(obj), representation=representation)
@@ -137,7 +137,7 @@ class RemoveOpening(bpy.types.Operator, tool.Ifc.Operator):
if opening_obj:
opening_obj.name = "/".join(opening_obj.name.split("/")[1:])
IfcStore.unlink_element(obj=opening_obj)
tool.Ifc.unlink(obj=opening_obj)
ifcopenshell.api.run("void.remove_opening", tool.Ifc.get(), opening=opening)
@@ -218,3 +218,42 @@ class SelectDecomposition(bpy.types.Operator):
if subobj:
subobj.select_set(True)
return {"FINISHED"}
class BooleansMarkAsManual(bpy.types.Operator):
bl_idname = "bim.booleans_mark_as_manual"
bl_label = "Mark Booleans as Manual"
bl_options = {"REGISTER", "UNDO"}
bl_description = (
"\nMark all booleans in object's current representations as manual.\n"
"Manual booleans will be preserved until they are removed explicitly"
)
mark_as_manual: bpy.props.BoolProperty(name="Mark as Manual", default=True)
@classmethod
def poll(cls, context):
obj = context.active_object
if (
obj
and tool.Ifc.get_entity(obj)
and hasattr(obj.data, "BIMMeshProperties")
and obj.data.BIMMeshProperties.ifc_definition_id
):
return True
cls.poll_message_set("Need to select IFC element with representation")
return False
def execute(self, context):
obj = context.active_object
element = tool.Ifc.get_entity(obj)
representation = tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id)
booleans = tool.Model.get_booleans(representation=representation)
if self.mark_as_manual:
tool.Model.mark_manual_booleans(element, booleans)
else:
tool.Model.unmark_manual_booleans(element, booleans)
self.report({"INFO"}, f"{len(booleans)} were marked as {'manual' if self.mark_as_manual else 'automatic'}")
blenderbim.bim.handler.refresh_ui_data()
return {"FINISHED"}
@@ -111,13 +111,21 @@ class BIM_PT_booleans(Panel):
if context.active_object.data.BIMMeshProperties.ifc_definition_id:
row = layout.row(align=True)
row.label(text=f"{BooleansData.data['total_booleans']} Booleans Found")
row.operator("bim.add_boolean", text="Apply Boolean", icon="ADD")
total_booleans = BooleansData.data["total_booleans"]
manual_booleans = BooleansData.data["manual_booleans"]
row.label(text=f"{len(total_booleans)} Booleans Found ({len(manual_booleans)} Manual)")
row.operator("bim.add_boolean", text="", icon="ADD")
show_boolean_button = row.row(align=True)
show_boolean_button.operator("bim.show_booleans", text="", icon="HIDE_OFF")
show_boolean_button.enabled = BooleansData.data["total_booleans"] > 0
show_boolean_button.enabled = len(total_booleans) > 0
row.operator("bim.hide_booleans", text="", icon="HIDE_ON")
booleans_are_manual = len(manual_booleans) == len(total_booleans)
op = row.operator(
"bim.booleans_mark_as_manual", text="", icon="PINNED" if booleans_are_manual else "UNPINNED"
)
op.mark_as_manual = not booleans_are_manual
elif context.active_object.data.BIMMeshProperties.ifc_boolean_id:
upsteam_obj = context.active_object.data.BIMMeshProperties.obj
upstream_obj_ifc_id = upsteam_obj.BIMObjectProperties.ifc_definition_id
@@ -816,6 +816,10 @@ def update_enum_property_search_prop(self, context):
if prop.name == self.dummy_name:
setattr(context.data, self.prop_name, self.collection_identifiers[i].name)
predefined_type = self.collection_predefined_types[i].name
if self.first_launch:
self.first_launch = False
else:
context.window.screen = context.window.screen
if predefined_type:
try:
setattr(context.data, "ifc_predefined_type", predefined_type)
@@ -828,6 +832,7 @@ class BIM_OT_enum_property_search(bpy.types.Operator):
bl_idname = "bim.enum_property_search"
bl_label = "Search For Property"
bl_options = {"REGISTER", "UNDO"}
first_launch: bpy.props.BoolProperty(default=True, options={"SKIP_SAVE"})
dummy_name: bpy.props.StringProperty(name="Property", update=update_enum_property_search_prop)
collection_names: bpy.props.CollectionProperty(type=StrProperty)
collection_identifiers: bpy.props.CollectionProperty(type=StrProperty)
+13 -1
View File
@@ -174,6 +174,18 @@ def update_attribute_value(self, context):
self.is_null = False
def update_is_null(self, context):
if not self.is_null:
return
self.string_value = ""
self.int_value = 0
self.float_value = 0
self.length_value = 0
self.bool_value = False
if self.is_null is not True:
self.is_null = True
def set_int_value(self, new_value):
set_numerical_value(self, "int_value", new_value)
@@ -240,7 +252,7 @@ class Attribute(PropertyGroup):
enum_items: StringProperty(name="Value")
enum_descriptions: CollectionProperty(type=StrProperty)
enum_value: EnumProperty(items=get_attribute_enum_values, name="Value", update=update_attribute_value)
is_null: BoolProperty(name="Is Null")
is_null: BoolProperty(name="Is Null", update=update_is_null)
is_optional: BoolProperty(name="Is Optional")
is_uri: BoolProperty(name="Is Uri", default=False)
is_selected: BoolProperty(name="Is Selected", default=False)
File diff suppressed because it is too large Load Diff
+50 -4
View File
@@ -132,6 +132,7 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
description='E.g. [["inkscape", "svg", "-o", "eps"], ["pstoedit", "-dt", "-f", "dxf:-polyaslines -mm", "eps", "dxf", "-psarg", "-dNOSAFER"]]',
)
svg_command: StringProperty(name="SVG Command", description='E.g. [["firefox", "path"]]')
layout_svg_command: StringProperty(name="Layout SVG Command", description='E.g. [["firefox", "path"]]')
pdf_command: StringProperty(name="PDF Command", description='E.g. [["firefox", "path"]]')
spreadsheet_command: StringProperty(name="Spreadsheet Command", description='E.g. [["libreoffice", "path"]]')
openlca_port: IntProperty(name="OpenLCA IPC Port", default=8080)
@@ -205,6 +206,8 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
row = layout.row()
row.prop(self, "svg_command")
row = layout.row()
row.prop(self, "layout_svg_command")
row = layout.row()
row.prop(self, "pdf_command")
row = layout.row()
row.prop(self, "spreadsheet_command")
@@ -336,7 +339,7 @@ class BIM_PT_tabs(Panel):
tab_entry.enabled = enabled
class BIM_PT_project_info(Panel):
class BIM_PT_tab_project_info(Panel):
bl_label = "Project Info"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
@@ -350,16 +353,30 @@ class BIM_PT_project_info(Panel):
pass
class BIM_PT_project_setup(Panel):
class BIM_PT_tab_project_setup(Panel):
bl_label = "Project Setup"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
@classmethod
def poll(cls, context):
return tool.Blender.is_tab(context, "PROJECT")
def draw(self, context):
pass
class BIM_PT_tab_stakeholders(Panel):
bl_label = "Stakeholders"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_options = {"DEFAULT_CLOSED"}
@classmethod
def poll(cls, context):
return tool.Blender.is_tab(context, "PROJECT")
return tool.Blender.is_tab(context, "PROJECT") and tool.Ifc.get()
def draw(self, context):
pass
@@ -394,7 +411,7 @@ class BIM_PT_tab_grouping_and_filtering(Panel):
pass
class BIM_PT_geometry(Panel):
class BIM_PT_tab_geometry(Panel):
bl_label = "Geometry"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
@@ -492,6 +509,20 @@ class BIM_PT_tab_services(Panel):
pass
class BIM_PT_tab_zones(Panel):
bl_label = "Zones"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
@classmethod
def poll(cls, context):
return tool.Blender.is_tab(context, "SERVICES") and tool.Ifc.get()
def draw(self, context):
pass
class BIM_PT_tab_quality_control(Panel):
bl_label = "Quality Control"
bl_space_type = "PROPERTIES"
@@ -644,6 +675,21 @@ class BIM_PT_tab_styles(Panel):
pass
class BIM_PT_tab_profiles(Panel):
bl_label = "Profiles"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_order = 1
@classmethod
def poll(cls, context):
return tool.Blender.is_tab(context, "GEOMETRY") and tool.Ifc.get()
def draw(self, context):
pass
class BIM_PT_tab_sheets(Panel):
bl_label = "Sheets"
bl_space_type = "PROPERTIES"
+43 -8
View File
@@ -44,13 +44,6 @@ def add_instance_flooring_covering_from_cursor(ifc, spatial, model, Type, geomet
name = "Covering"
mesh = spatial.get_named_mesh_from_bmesh(name = name, bmesh = bm)
# spatial.edit_active_space_obj_from_mesh(mesh)
# else:
# if relating_type:
# name = model.generate_occurrence_name(relating_type, "IfcCovering")
# else:
obj = spatial.get_named_obj_from_mesh(name, mesh)
spatial.set_obj_origin_to_cursor_position(obj)
@@ -63,7 +56,49 @@ def add_instance_flooring_covering_from_cursor(ifc, spatial, model, Type, geomet
body = spatial.get_body_representation(obj)
spatial.regen_obj_representation(ifc, geometry, obj, body)
def regen_selected_flooring_object(ifc, spatial, model, Type, geometry):
def add_instance_ceiling_covering_from_cursor(ifc, spatial, model, Type, geometry, covering):
active_obj = spatial.get_active_obj()
selected_objects = spatial.get_selected_objects()
element = None
relating_type_id = spatial.get_relating_type_id()
relating_type = None
if relating_type_id:
relating_type = ifc.get().by_id(int(relating_type_id))
if not relating_type.is_a("IfcCoveringType"):
relating_type = None
if selected_objects and active_obj:
x, y, z, h, mat = spatial.get_x_y_z_h_mat_from_active_obj(active_obj)
element = ifc.get_entity(active_obj)
else:
x, y, z, h, mat = spatial.get_x_y_z_h_mat_from_cursor()
z = covering.get_z_from_ceiling_height()
space_polygon = spatial.get_space_polygon_from_context_visible_objects(x, y)
if not space_polygon:
return
bm = spatial.get_bmesh_from_polygon(space_polygon, h=0)
name = "Covering"
mesh = spatial.get_named_mesh_from_bmesh(name = name, bmesh = bm)
obj = spatial.get_named_obj_from_mesh(name, mesh)
spatial.set_obj_origin_to_cursor_position(obj)
spatial.traslate_obj_to_z_location(obj, z)
spatial.link_obj_to_active_collection(obj)
points = spatial.get_2d_vertices_from_obj(obj)
spatial.assign_type_to_obj(obj)
spatial.assign_swept_area_outer_curve_from_2d_vertices(obj, vertices = points)
body = spatial.get_body_representation(obj)
spatial.regen_obj_representation(ifc, geometry, obj, body)
def regen_selected_covering_object(ifc, spatial, model, Type, geometry):
active_obj = spatial.get_active_obj()
selected_objects = spatial.get_selected_objects()
+1 -1
View File
@@ -90,7 +90,7 @@ def add_sheet(ifc, drawing, titleblock=None):
def open_sheet(drawing, sheet=None):
drawing.open_svg(drawing.get_document_uri(sheet, "LAYOUT"))
drawing.open_layout_svg(drawing.get_document_uri(sheet, "LAYOUT"))
def remove_sheet(ifc, drawing, sheet=None):
+11 -3
View File
@@ -104,11 +104,15 @@ def switch_representation(
return
entity = ifc.get_entity(obj)
current_obj_data = geometry.get_object_data(obj)
# doesn't resolve mapped representations in case if it's going to have openings
# otherwise we would also add openings to the type and other occurences mesh data
has_openings = apply_openings and getattr(entity, "HasOpenings", None)
if not has_openings:
if has_openings:
# if it has openings make sure to switch to element's mapped representation
representation = geometry.unresolve_type_representation(representation, entity)
else:
# doesn't resolve mapped representations in case if it's going to have openings
# otherwise we would also add openings to the type and other occurences mesh data
representation = geometry.resolve_mapped_representation(representation)
old_repr_data = geometry.get_representation_data(representation)
@@ -125,6 +129,10 @@ def switch_representation(
# we assume that all the occurences and the type have the same representation context active
# so geometry.delete_data cannot remove the data that's still used by some other object
if should_reload and old_repr_data:
# if current object was using some temporary mesh (like during profile edit mode) instead of `old_repr_data`
# then `change_object_data` won't switch the mesh for all the occurences and we need to do it explicitly
if current_obj_data != old_repr_data and geometry.has_data_users(old_repr_data):
geometry.replace_object_data_globally(old_repr_data, new_repr_data)
geometry.delete_data(old_repr_data)
geometry.clear_modifiers(obj)
+2 -2
View File
@@ -21,9 +21,9 @@ def unlink_material(ifc, obj=None):
ifc.unlink(obj=obj)
def add_material(ifc, material, style, obj=None):
def add_material(ifc, material, style, obj=None, name=None):
if not obj:
obj = material.add_default_material_object()
obj = material.add_default_material_object(name)
ifc_material = ifc.run("material.add_material", name=material.get_name(obj))
ifc.link(ifc_material, obj)
ifc_style = style.get_style(obj)
+2 -1
View File
@@ -47,7 +47,7 @@ def remove_system(ifc, system_tool, system=None):
def enable_editing_system(system_tool, system=None):
system_tool.import_system_attributes(system)
system_tool.set_active_system(system)
system_tool.set_active_edited_system(system)
def disable_editing_system(system):
@@ -64,6 +64,7 @@ def unassign_system(ifc, system=None, product=None):
def select_system_products(system_tool, system=None):
system_tool.select_system_products(system)
system_tool.set_active_system(system)
def show_ports(ifc, system, spatial, element=None):
+8 -1
View File
@@ -316,6 +316,7 @@ class Drawing:
def is_drawing_active(cls): pass
def is_editing_sheets(cls): pass
def move_file(cls, src, dest): pass
def open_layout_svg(cls, uri): pass
def open_spreadsheet(cls, uri): pass
def open_svg(cls, filepath): pass
def remove_literal_from_annotation(cls, obj, literal): pass
@@ -369,6 +370,7 @@ class Geometry:
def remove_connection(cls, connection): pass
def rename_object(cls, obj, name): pass
def replace_object_with_empty(cls, obj): pass
def replace_object_data_globally(cls, old_data, new_data): pass
def resolve_mapped_representation(cls, representation): pass
def run_geometry_update_representation(cls, obj=None): pass
def run_style_add_style(cls, obj=None): pass
@@ -377,6 +379,7 @@ class Geometry:
def should_force_triangulation(cls): pass
def should_generate_uvs(cls, obj): pass
def should_use_presentation_style_assignment(cls): pass
def unresolve_type_representation(cls, representation, element): pass
@interface
@@ -445,7 +448,7 @@ class Loader:
@interface
class Material:
def add_default_material_object(cls): pass
def add_default_material_object(cls, name): pass
def add_material_to_set(cls, material_set, material): pass
def delete_object(cls, obj): pass
def disable_editing_material(cls): pass
@@ -848,6 +851,9 @@ class Spatial:
def regen_obj_representation(cls, ifc, geometry, obj, body): pass
def toggle_spaces_visibility_wired_and_textured(cls, spaces): pass
@interface
class Covering:
def get_z_from_ceiling_height(cls): pass
@interface
class Structural:
@@ -915,6 +921,7 @@ class System:
def run_geometry_edit_object_placement(cls, obj=None): pass
def run_root_assign_class(cls, obj=None, ifc_class=None, predefined_type=None, should_add_representation=True, context=None, ifc_representation_class=None): pass
def select_system_products(cls, system): pass
def set_active_edited_system(cls, system): pass
def set_active_system(cls, system): pass
@@ -47,6 +47,7 @@ from blenderbim.tool.resource import Resource
from blenderbim.tool.root import Root
from blenderbim.tool.sequence import Sequence
from blenderbim.tool.spatial import Spatial
from blenderbim.tool.covering import Covering
from blenderbim.tool.structural import Structural
from blenderbim.tool.style import Style
from blenderbim.tool.surveyor import Surveyor

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