Compare commits

...

671 Commits

Author SHA1 Message Date
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
Dion Moult 14224ac538 Fix #3870. Bug where element filtering wasn't initialised properly when loading a camera for the first time.
Raster_x/y would only be set after the handler triggers. This is no longer necessary with the new explicit width / height properties.
2023-10-15 22:23:10 +11:00
Dion Moult 58be92d3fc New verbose option in util get_pset(s) which lets you get ID and class for complex processing. 2023-10-15 08:57:37 +11:00
Bruno Postle 7107a91f6f BlenderBIM keep camera position on git switch
New 'zoom extents' behaviour on IFC load is unwanted when switching
between git revisions
2023-10-14 21:54:16 +01:00
Thomas Krijnen 438ed22f8f Merge pull request #3888 from isma3lMB/v0.7.0
Adding support for non ascii characters when reporting
2023-10-14 16:00:18 +02:00
isma3lMB a9dfc9a9fd Adding support for non ascii characters when reporting 2023-10-14 15:31:33 +02:00
Thomas Krijnen f8b09b3525 Update build-deps.cmd: proj compilation 2023-10-13 22:34:35 +02:00
Massimo Fabbro f1ab02b311 Fix bug in covering/space tool with matrix global and local 2023-10-13 22:03:43 +02:00
Dion Moult 25df2f6414 See #3884. Rewrite native geometry handling with a much more robust approach fixing a number of invalid coordinates and simplifying things. 2023-10-13 23:40:23 +11:00
Dion Moult 42719d3fe3 Fix bug where native meshes had normal smoothing enabled by default 2023-10-13 23:40:23 +11:00
Dion Moult 1f6d23c19c Minor fixes to placement utilities for getting mapped item matrixes 2023-10-13 23:40:23 +11:00
Thomas Krijnen 94db9ec522 Add IFC4X3_ADD2 schema 2023-10-13 11:51:05 +02:00
Dion Moult a47c57c22c First draft of style manager panel 2023-10-13 17:38:55 +11:00
Dion Moult 8f4bc0ea00 Material manager panel now moved to geometry tab 2023-10-13 17:37:24 +11:00
Dion Moult dd70b09fb1 Representation items panel now shows styles and layers 2023-10-12 22:56:36 +11:00
Dion Moult c930050da6 Accommodate bug in Revit where mapped representations were missing... well, a representation map. 2023-10-12 22:56:36 +11:00
Dion Moult 55a7a72e61 Minor bug fixes with error message when selecting objects in layers without objects 2023-10-12 22:56:36 +11:00
Dion Moult ca2ae36d41 Modernize zone and type table mappings for COBie 2.4 with Emma Hooper 2023-10-12 22:56:36 +11:00
Thomas Krijnen 81b4e42ca2 Use schema_identifier for schema and express rule validation. See #3126 2023-10-12 10:37:44 +02:00
Massimo Fabbro 55e61b55d8 better UI for covering tool 2023-10-12 05:58:52 +02:00
Massimo Fabbro a99529cb1e fix generate space and covering where objects weren't created in the right location 2023-10-12 05:58:52 +02:00
Dion Moult bf43efe509 Begin drafting out a new representation items panel for managing styles and layers properly 2023-10-11 23:26:48 +11:00
Vukas Pajic da7f5b015b add missing attributes in ids.parse 2023-10-11 10:06:54 +02:00
Massimo Fabbro 3de9915389 Add generate flooring from cursor and regen (#3874) 2023-10-11 09:15:36 +11:00
Dion Moult 418444a196 Fix #3873. Regression due to new importing logic. 2023-10-11 09:12:00 +11:00
Dion Moult 171251b27d See #1619. Fix issue where copying non-templated properties didn't work. 2023-10-11 08:53:53 +11:00
Ryan Schultz 9fef920a6f Further clarify the bim.link_ifc command 2023-10-10 13:43:56 -05:00
Dion Moult 5bff837a61 Minor fix 2023-10-10 22:21:32 +11:00
Dion Moult 05f6f4efab Manual booleans are now tracked via a Pset instead of arbitrary class conventions which should fix a bunch of issues. 2023-10-10 17:33:00 +11:00
Dion Moult e4f5b0e3d8 Write documentation for placement utility 2023-10-10 10:56:28 +11:00
Dion Moult 49b733f660 See #3817. WARNING: fundamental change. Standardise prioritisation of all context permutations when loading occurrences and types. 2023-10-09 23:21:09 +11:00
Dion Moult 36ed618adf You can now federate FM spreadsheets using the Blender UI 2023-10-09 17:26:45 +11:00
Dion Moult 48042011e2 Merge pull request #3860 from IfcOpenShell/Representation_Utilities_more_functions
Adding "Manually Save Representation" and bim.get_representation_ifc_…
2023-10-09 09:58:58 +11:00
Andrej730 0e1939a871 Minor fix #3862 2023-10-08 21:32:34 +05:00
Ryan Schultz bea1e61739 Adding "Manually Save Representation" and bim.get_representation_ifc_parameters back to Representation Utilities panel
from: https://github.com/IfcOpenShell/IfcOpenShell/commit/3a812a9ec156818061c01bd5b2ac2c2bee09faf1#r123820129
2023-10-08 09:24:18 -05:00
Ryan Schultz 72cd82f450 fixed error when creating new walltype in IFC2x3 2023-10-07 10:25:22 -05:00
Massimo Fabbro befa059591 Merge pull request #3838 from maxfb87/covering_tool
Covering tool (see #3754)
2023-10-07 14:20:27 +02:00
Bruno Postle 7229f7271c BlenderBIM make fca90e9 optional
setup_viewport_camera() does a 'zoom extents' on IFC import.
Create a should_setup_viewport_camera option (default True)
2023-10-06 22:34:43 +01:00
Andrej730 9c5970e757 fixed issue with placing end port for a bend 2023-10-06 17:30:01 +05:00
Andrej730 2fe3f0a9df system decorations - indicate both directions flow 2023-10-06 17:30:01 +05:00
Dion Moult 3929f02e94 Fix #3843. Regression where IfcMaterialList UI didn't show list of materials. 2023-10-06 22:43:56 +11:00
Andrej730 1abf80c310 Minor fix after b9a9abd30 2023-10-06 16:03:40 +05:00
Andrej730 2c18c89807 revert flow directions icons to arrows 2023-10-06 16:01:46 +05:00
Andrej730 1b81fa5aa1 Minor fix after 78c748df5
RuntimeError: Error: Registering panel class: parent 'BIM_PT_tab_placement' for 'BIM_PT_placement' not found
2023-10-06 15:31:41 +05:00
Hilko baa08b3221 Update build-deps.cmd 2023-10-06 12:16:51 +02:00
Andrej730 b9a9abd30d Fixed adding opening to meshes of all occurences for non material based representations
Mentioned in #3817
2023-10-06 15:11:14 +05:00
Andrej730 33536957d8 Moving some code to tool.Geometry 2023-10-06 15:11:14 +05:00
Dion Moult d092fd45ba You can now select coloured objects in a colourscheme. 2023-10-06 16:51:50 +11:00
Dion Moult 78c748df5d New placement panel to consolidate placement data in geometry tab 2023-10-06 15:25:51 +11:00
Dion Moult 3d76de3041 New geometry tree documentation picture and minor documentation improvements including mention of COBie 2.4 legacy mode in IfcFM. 2023-10-06 13:05:38 +11:00
Dion Moult ed3e9f3347 New documentation for shape utility 2023-10-06 13:04:54 +11:00
Dion Moult eb836436b9 Implement projected surface area calculation function in util.shape 2023-10-06 13:04:34 +11:00
Dion Moult c34a9c52d0 New drawing width / height / depth / raster DPI controls 2023-10-06 10:45:54 +11:00
Dion Moult 88d96f7d57 You can now easily create orthographic and perspective model drawings from a preset 2023-10-06 10:44:51 +11:00
Dion Moult 69e447be1c Active drawing panel moved from camera properties to drawing tab. 2023-10-06 08:17:03 +11:00
Massimo Fabbro df09acfa31 Merge branch 'v0.7.0' into covering_tool 2023-10-05 21:23:11 +02:00
Massimo Fabbro 6343f77c0d Minor spatial refactor 2023-10-05 21:17:25 +02:00
Thomas Krijnen 285fc2269e #3852 Update pyproject.toml 2023-10-05 15:52:59 +02:00
Andrej730 68a1da9190 test for model.get_manual_booleans() #3709
added test that model.get_manual_booleans will ignore IfcBooleanClippings until we find some other solution to distinguish automatic and manual clippings. And some refactor along the way.
2023-10-05 18:38:46 +05:00
Andrej730 ceae7d4fdb DumbWallJoiner to work with IfcBooleanResult correctly #3831
It wasn't detecting extrusion as part of IfcBooleanResult and was using default wall data as fallback.

Occured after 0044ef5
Solves #3831
Related to #3709
2023-10-05 17:31:24 +05:00
Dion Moult 371daa6c80 Revise COBie 2.4 mappings for facility, floor, and some of space worksheet in collaboration with Emma Hooper 2023-10-05 22:49:52 +11:00
Dion Moult 948993e066 Fix #3845. Select similar for predefined type now takes into account inheritance. 2023-10-05 22:47:12 +11:00
Andrej730 ec4f0e5ad6 Fixed issue applying array with Sync Children #3822 2023-10-05 15:32:45 +05:00
taylor ef56c6ce7e Fixed model reference 2023-10-05 10:17:03 +02:00
Dion Moult 3c02da416e Georeference module now supports IFC2X3 georeferencing / conversion utilities. 2023-10-05 16:15:28 +11:00
Dion Moult 6660bf7618 Document geolocation util and add support for ePSet IFC2X3 fallbacks. 2023-10-05 16:15:28 +11:00
Dion Moult 000e5ec29a Merge pull request #3835 from c4rlosdias/v0.7.0
Fix problem in facets.py (IfcTester)
2023-10-05 11:39:47 +11:00
Dion Moult ef8f4df4ab Fix #3841. Bump IOS. 2023-10-05 09:57:03 +11:00
Andrej730 dff70f2608 bim.flip_object #3803
Added new operator bim.flip_object that flips the object by flipping two of its axes and keeps the original placement.

Currently it can be found either from F3 search or from Shift-F hotkey for IfcBeam and IfcColumn in BIM tools.
2023-10-04 18:20:17 +05:00
Andrej730 0d7967ff62 .reg file for #3778
Forgot to commit the .reg file...
2023-10-04 15:13:57 +05:00
Andrej730 4fe459dab6 Fixed typo 2023-10-04 09:47:38 +05:00
Thomas Krijnen 0e5e1f7873 Option to write Earth-Centered Earth-Fixed glTF 2023-10-03 21:26:11 +02:00
Dion Moult 82d4829c83 Clean up IfcFM's Basic preset to be more true to IFC rather than follow naming schemes from stuff like COBie 2023-10-03 21:21:28 +11:00
Dion Moult a24fab6d92 You can now select and bulk process multiple models using IfcFM in Blender. 2023-10-03 21:21:28 +11:00
Dion Moult 748055dcf1 New IfcPatch recipe to fix Revit bug with occurrence based classifications 2023-10-03 21:21:28 +11:00
Dion Moult 3904162360 New IfcPatch recipe to fix Revit bug with hardcoded Uniformat classification 2023-10-03 21:21:28 +11:00
Andrej730 7ca24ede4c IfcSverchok docs update urls 2023-10-03 10:50:56 +05:00
Ryan Schultz 1e59b10479 Merge pull request #3820 from brunoperdigao/fix-duplicate-agg-pset-for-new-objs
Fixed a bug where the refresh aggregate operator was not working afte…
2023-10-02 15:14:05 -05:00
Massimo Fabbro cda61bff5b Refactor create_space operator and better UI for covering tool 2023-10-02 22:13:03 +02:00
Massimo Fabbro 09a9753ca7 refactor create spaces from walls 2023-10-02 22:13:03 +02:00
Massimo Fabbro 1621449143 Add predefined type to AuthoringData 2023-10-02 22:13:02 +02:00
Ryan Schultz d0ef7f69b1 fix #3792 2023-10-02 14:56:17 -05:00
c4rlosdias 3df95b8970 Merge branch 'v0.7.0' of https://github.com/c4rlosdias/IfcOpenShell into v0.7.0 2023-10-02 13:49:32 -03:00
c4rlosdias ddd4b0e262 Fix problem in getpsets(facet.py) and added descriptions to specifications (IfcTester) 2023-10-02 13:49:27 -03:00
Carlos Dias 6b05b6e7fb Merge branch 'IfcOpenShell:v0.7.0' into v0.7.0 2023-10-02 12:58:10 -03:00
Andrej730 9dd728ce7e Fixed error creating a door type / window types after 8f6be60c6 2023-10-02 17:32:56 +05:00
Andrej730 90b40ff0dc Getting drawing group more precisely
Previously it was conflicting with other possible groups assigned to the object. It's now even more important since we're actively using groups to save search queries.

Example error caused by this issue - during drawing activation if you had 2 IfcGroups on the drawing element:
draiwng's IfcGroup RelatedObjects attribute was updated, then inverse attrtibute `drawing.HasAssignments` order was updated too (DRAWING IfcGroup now would be positioned second after IfcGroup for search query) and it was leading to errors during filtering current drawing annotations
2023-10-02 16:55:38 +05:00
Andrej730 3c27c52c9f Small fix for 05d4dc566 2023-10-02 15:38:26 +05:00
Andrej730 05d4dc5666 Rever IFC Project
https://i.imgur.com/ajQiK8D.png
2023-10-02 15:30:32 +05:00
Andrej730 edaabf5bd2 Fixed #3829 occured after ae45e6b
similar_openings wasn't reset on each iteration causing moving unrelated openings
2023-10-02 11:44:31 +05:00
Andrej730 ad79c63a37 Fixed #3832 2023-10-02 10:57:24 +05:00
Sigma Dimensions (Yass) 9461bf2129 fix f2097c0 2023-09-29 17:30:33 +01:00
Sigma Dimensions (Yass) f2097c0c5f importing csv cost schedules now allows using selector queries for effortless product assignments to cost items, and add new ods and csv format templates (in french) 2023-09-29 16:10:16 +01:00
Dion Moult d838e1808d IfcFM can now be used to federate multiple spreadsheets together 2023-09-29 23:03:58 +10:00
Dion Moult 5ed54d9e7c Revise COBie 2.4 contact table mapping in collaboration with Emma Hooper 2023-09-29 23:03:58 +10:00
Andrej730 78aba1c7e4 Workaround for #3778 2023-09-29 15:28:11 +05:00
Andrej730 3faaee80ec copy_deep to always pass exclude_callback to the recursion
It wasn't passing the exclude_callback if attribute of the copied element wasn't a tuple, resulting in duplicated named profiles in case if you'd copy a IfcBooleanClippingResult #3810

For example, how copy_deep of IfcProductDefinitionShape with boolean clipping previously would work:
V IfcShapeRepresentation - copied with exlude_callback (part of .Representations[])
V IfcBooleanClippingResult - copied with exlude_callback (part of .Items[])
X IfcExtrudedAreaSolid - copied without exlude_callback becuase it's part of .FirstOperand (not an array) and we have IfcIShapeProfileDef duplicated

Case without booleanclippings:
V IfcShapeRepresentation - copied with exlude_callback (part of .Representations[])
V IfcExtrudedAreaSolid - copied with exlude_callback, since it's part of .Items[], IfcIShapeProfileDef not duplicated
2023-09-29 09:29:25 +05:00
Andrej730 a847b8f4e4 bim.remove_opening not to force Model/Body/Model_view
Now it won't be forcing Model/Body/Model_view representation on the objects if some other context is active for the object.

Indirectly solves #3817
2023-09-28 18:51:27 +05:00
Dion Moult 24170a8a16 Ship with fallback IFC2X3 georeferencing pset 2023-09-28 23:07:43 +10:00
Massimo Fabbro 5438331fe1 fix generate spaces from walls bug 2023-09-27 18:52:49 +02:00
Carlos Dias 3c957e0cbe Merge branch 'IfcOpenShell:v0.7.0' into v0.7.0 2023-09-27 10:55:52 -03:00
Andrej730 10672f3e1a Print unused elements stats / purge unused elements
Added to Purge Data section in Quality Control some tools - https://i.imgur.com/Nz3YoiH.png

1) Print unused elements - useful to investigate how much unused data you have in IFC file.
https://i.imgur.com/9Fkn0JD.png

2) purge unused elements by ifc class, it's doing it in pretty naive way and can be dangerous, so maybe it should be moved to debug section.
2023-09-27 15:42:31 +05:00
Andrej730 fa90e420eb info message for amount of purged elements 2023-09-27 13:55:56 +05:00
Dion Moult 347bb2df4b Introductory docs written for IfcFM 2023-09-27 18:22:02 +10:00
Dion Moult 986ffe66f8 New introduction section in IOS docs 2023-09-27 18:22:02 +10:00
Andrej730 c1bedec354 Minor fix after 22edbdd
Error was:

TypeError: EnumProperty(..., default='cobie'): not found in enum members
Exception in module register(): \3.6\scripts\addons\blenderbim\__init__.py
Traceback (most recent call last):
  File "\Blender\3.6\scripts\modules\addon_utils.py", line 369, in enable
    mod.register()
  File "\3.6\scripts\addons\blenderbim\__init__.py", line 42, in register
    blenderbim.bim.register()
  File "\3.6\scripts\addons\blenderbim\bim\__init__.py", line 190, in register
    bpy.utils.register_class(cls)
ValueError: bpy_struct "BIMFMProperties" registration error: 'engine' EnumProperty could not register (see previous error)
2023-09-27 12:09:44 +05:00
Dion Moult 182b6c7b07 IfcFM now supports writing to Pandas Dataframes 2023-09-27 14:18:48 +10:00
Dion Moult 22edbddd84 You can now interact with IfcFM via CLI 2023-09-27 14:17:46 +10:00
Dion Moult 50f6f44d26 You can now specify empty formatting for IfcCSV via CLI 2023-09-27 14:16:43 +10:00
Dion Moult e93a697179 Fix #3788. Fix crash when opening drawing SVGs using webbrowser module which is not recommended and the file is on a Dropbox synced external drive. Now replaced with generic os.startfile / xdg-open / open commands. 2023-09-27 11:50:52 +10:00
Sigma Dimensions (Yass) f1a5805b1c fix loading animation color scheme 2023-09-26 18:33:52 +01:00
Sigma Dimensions (Yass) 126f530af1 run black and forgotten calender utils 2023-09-26 16:44:35 +01:00
Sigma Dimensions (Yass) 5dfbbb8d96 maintain tasks bar visual property state when tasks properties are reloaded 2023-09-26 16:42:35 +01:00
Sigma Dimensions (Yass) 6962e505f9 hide spaces when creating an animation 2023-09-26 16:38:33 +01:00
Sigma Dimensions (Yass) 5c2523e752 fix warning statement when there are no saved animation color schemes 2023-09-26 16:36:48 +01:00
Sigma Dimensions (Yass) 635dff73b4 fix typo 2023-09-26 16:35:31 +01:00
Sigma Dimensions (Yass) c85e4e7ceb fix missing task types for animations color scheme 2023-09-26 16:35:00 +01:00
Sigma Dimensions (Yass) 7ed4301c6c fix tasks not displaying derived calendar 2023-09-26 16:29:53 +01:00
Andrej730 1193e7025a system decorations - live time update
Decoration data is now updating in live time, caching some parts in Data for performance. Real time update is needed because Data doesn't allow update on objects positions change and later will need real time update for viewport orientation data.
2023-09-26 17:08:39 +05:00
Andrej730 cc1ee19115 Add ports to MEP bends/transitions types instead of occurences 2023-09-26 15:19:49 +05:00
Andrej730 e975eac4a7 system decorations to show flow direction 2023-09-26 11:47:10 +05:00
Andrej730 779fc35228 settings flow direction without jumping to ports
https://imgur.com/a/F00tG7g
2023-09-26 11:47:10 +05:00
Dion Moult 8e59282fe0 Possible optimisation by caching in IfcTester property checks 2023-09-26 16:03:10 +10:00
Bruno Perdigão 11c8d47909 fixed a bug where the refresh aggregate operator was not working after adding new objects to the parent aggregate 2023-09-25 22:55:56 -03:00
Claudio Benghi 2661999f3e Fixed most issues with IDS auditing 2023-09-26 11:06:58 +10:00
Ryan Schultz e4bfb05875 proposed fix for #3792 2023-09-25 10:32:06 -05:00
Massimo Fabbro fe14988d50 Merge pull request #3790 from maxfb87/new_covering_tools
New covering tools (see #3754)
2023-09-25 16:03:56 +02:00
Massimo Fabbro d45bef5e70 Merge branch 'v0.7.0' into new_covering_tools 2023-09-25 15:01:03 +02:00
Andrej730 82d03b98c6 Descriptions for add_representation conversion methods 2023-09-25 16:32:48 +05:00
Andrej730 9c1ecf3f2c draw calculate_all_quantities hotkey in bim tool ui
hotkey was already there, it just wasn't added to UI

https://i.imgur.com/nar5WYI.png
2023-09-25 16:32:48 +05:00
Andrej730 8f6be60c6e recalculate fills on applying changes to ifc windows / ifc doors
previously if you'd move the window and then change it's parametric data and try to apply it, opening's position wouldn't be updated
2023-09-25 16:32:48 +05:00
Andrej730 81a1bd9206 Moved common duplication operators code to DuplicateMove
Now OverrideDuplicateMoveLinked allways will be in sync with OverrideDuplicateMove.

Also fixed the issue when DuplicateMoveLinked for non ifc elements worked the same was DuplicateMove (it was copying mesh data when it's not suppose to).
2023-09-25 16:32:48 +05:00
Andrej730 9f7dffb29d replace Drawing.remove_object_data with more general method 2023-09-25 16:32:47 +05:00
Dion Moult eee799bfc4 Purge IfcCOBie deps from build 2023-09-25 19:24:01 +10:00
Dion Moult 9665adb61d Fix #3793. Bug where flipping a wall with generic connection relationships would fail. 2023-09-25 08:34:48 +10:00
Sigma Dimensions (Yass) e40019f3cb resources code cleanup 2023-09-24 19:02:35 +01:00
Sigma Dimensions (Yass) b2f95bfa60 change tests due to new class assignment workflow ref commit 808ff54 2023-09-24 18:45:12 +01:00
Bruno Perdigão be2c7a62fd remove connections with old objects when duplicating aggregates 2023-09-24 22:23:55 +10:00
Dion Moult 38f5468f19 We now support separating IFC meshes. 2023-09-24 15:45:22 +10:00
Dion Moult 31dcbc0159 Splitting by edge now resolved mapped representations, voided, and non-mesh geometries 2023-09-24 15:45:22 +10:00
Sigma Dimensions (Yass) bd81320c08 fix parametric duration/ resource update 2023-09-24 04:39:30 +01:00
Dion Moult 0707f37247 Minor fix during loading arrays 2023-09-24 13:18:32 +10:00
Dion Moult 0993736cea Fix #3791. Add isodate as an optional dependency to ifcopenshell-python. 2023-09-24 13:14:56 +10:00
Sigma Dimensions (Yass) cc3e2204e2 improved template for resource csv imports 2023-09-24 03:17:39 +01:00
Sigma Dimensions (Yass) 8ac5f0318b fix location search filter 2023-09-23 23:35:17 +01:00
Sigma Dimensions (Yass) 270a61e73a bring back filter operators into new Filter subpanel and change filter by storey to filter by container 2023-09-23 21:26:58 +01:00
Massimo Fabbro 1c057b1ce1 Add new covering tool. Mock up and initial functions 2023-09-23 18:09:59 +02:00
Sigma Dimensions (Yass) e74e824417 Proposal to use resource's parent cost, reducing manual insertions 2023-09-23 16:04:01 +01:00
Dion Moult 73d1de5dcf New debug feature to override the display type. 2023-09-23 14:51:27 +10:00
Dion Moult 4549be0d05 Autodetect dense meshes to make it faster when loading so users don't need to go into advanced mode. 2023-09-23 14:51:27 +10:00
Andrej730 e3f2f2175d Moving common code for duplication operators 2023-09-22 16:37:50 +05:00
Andrej730 a604cf2b2a Duplicate the array if all it's elements are selected #3763
Demo - https://imgur.com/a/0wYZOPl
2023-09-22 16:37:49 +05:00
Andrej730 d6b4c1f454 Setup arrays constraints on loading .ifc #3753 2023-09-22 13:56:55 +05:00
Andrej730 d3258693b2 Clear constraint and transform lock on duplicating array child object
Previously it was locked even though it's not part of an array anymore.
2023-09-22 13:56:55 +05:00
A. R. S 9eba3b6eaa Try to get unit from EnumerationReference for IfcPropertyEnumeratedValue 2023-09-22 17:41:05 +10:00
Dion Moult 7b75364feb Refactor IfcFM to make it easier to exclude things 2023-09-22 17:17:15 +10:00
Dion Moult 4a4a0f09ef Finally purge IfcCOBie to be superseded by IfcFM 2023-09-22 17:04:22 +10:00
Andrej730 6d14c2d448 removing arrays if all array elements are selected #3781
I think there is still some space to improve but it should cover the main problem with need going to array section to remove it
Demo - https://imgur.com/a/9Z3t5Q8
2023-09-22 11:25:56 +05:00
Andrej730 57edd47ed7 Fixed error editing pset with prop with empty prop.NominalValue
Traceback:
```
Error: Python: Traceback (most recent call last):
  File "\blenderbim\bim\module\pset\operator.py", line 103, in execute
    self.load_from_pset_data(pset)
  File "\blenderbim\bim\module\pset\operator.py", line 226, in load_from_pset_data
    value = prop.NominalValue.wrappedValue
AttributeError: 'NoneType' object has no attribute 'wrappedValue'
```
2023-09-22 10:25:51 +05:00
Massimo Fabbro aefcedf754 Add model test about manual booleans 2023-09-21 23:19:47 +02:00
Massimo Fabbro 2c685c846f Added calculating quantities function for Qto_DuctSegmentBaseQuantities and Qto_DuctFittingBaseQuantities 2023-09-21 23:19:24 +02:00
Ryan Schultz 6536f7b56b small fix for 9e67565 2023-09-21 12:58:16 -05:00
Vukas Pajic 9d3881b00e typo 2023-09-21 16:14:37 +02:00
Vukas Pajic baffbd6a32 Update ids validation
see https://github.com/IfcOpenShell/IfcOpenShell/issues/2933
2023-09-21 13:27:38 +02:00
Sigma Dimensions (Yass) 9e4702c102 small fix when calculating resource work 2023-09-21 06:04:24 +01:00
Dion Moult b63f3f335c Autogenerate API docs for more modules. 2023-09-21 13:33:26 +10:00
Dion Moult 29b0bcb6b9 Forgot to commit BCF docs 2023-09-21 13:23:43 +10:00
Dion Moult 62fa0bb218 Migrate more documentation to the official Sphinx docs and mention IfcOpenShell-WASM as an installation option 2023-09-21 13:21:43 +10:00
Ryan Schultz 9e67565c71 Refresh aggregate from selecting a part 2023-09-20 08:54:04 -05:00
Andrej730 dee4cf591e mep system decorators - basic implementation
All it does currently it draws all MEP elements connections in viewport. It also highlights with green color all elements connected to the active object

Demo - https://imgur.com/a/haAQeWt
2023-09-20 15:28:06 +05:00
Andrej730 bc5d6ee113 fixed error in api.system.disconnect_port
Error was:
TypeError: can only concatenate tuple (not "list") to tuple
2023-09-20 15:28:05 +05:00
Andrej730 5af3b537da get_viewport_context to also return space 2023-09-20 15:28:05 +05:00
Andrej730 4b159d400c bim.draw_system_arrows updated for ifc4 2023-09-20 15:28:05 +05:00
Sigma Dimensions (Yass) f3c012660a improve pset editing workflow and use refactored core and tool functions 2023-09-20 01:22:18 +01:00
Sigma Dimensions (Yass) b5b8b5df26 cascade schedule when adding a sequence relationship 2023-09-20 01:15:06 +01:00
Sigma Dimensions (Yass) 6d38d9b538 fix count_working_days when the start and finish are the same same hours 2023-09-20 01:11:42 +01:00
Sigma Dimensions (Yass) ef11ae6540 fix calculating critical path to consider:
- parent tasks with sequence assignments #3772
- tasks that are constrained by a schedule start
2023-09-20 01:10:34 +01:00
Sigma Dimensions (Yass) a3802c25c0 fix cascading schedule when parent tasks have sequence relationships #3772 2023-09-20 01:05:19 +01:00
Andrej 377bcfe6da Merge pull request #3732 from brunoperdigao/fix-refresh-aggregate-rotation 2023-09-19 23:57:52 +05:00
Dion Moult 63a5c0986f Datatype checks in IfcTester are now case insensitive 2023-09-19 23:56:04 +10:00
Andrej730 8320623210 Exposed some MEP operators in BIM Tool UI
https://i.imgur.com/U61QqlQ.png
2023-09-19 17:00:14 +05:00
Andrej730 90c4a2fd41 bim.mep_connect_elements to connect elements by the closest ports
Previously it was looking for exact match. Now operator also adjusts connected ports
2023-09-19 17:00:14 +05:00
Andrej730 4ee66caaf9 override_outliner_delete poll error fix 2023-09-19 16:59:15 +05:00
Andrej730 23251de6f1 info message for refresh_aggregate 2023-09-19 16:59:15 +05:00
Dion Moult 37287bac7e You can now add bSDD properties when assigning bSDD classifications 2023-09-19 20:30:56 +10:00
Vukas Pajic 43a7340f5d Fix case when ids_dict has key but value is None 2023-09-19 12:28:09 +02:00
Dion Moult 2363b5e167 Minor fix 2023-09-19 19:13:21 +10:00
Dion Moult 62bf8c2287 Fix crash where IFCTester read directly from IFC in the UI 2023-09-19 18:17:24 +10:00
Dion Moult a5a958d6e2 Basic integration of bSDD with BlenderBIM Add-on: you can now assign classification references from the bSDD and set the active bSDD domain. 2023-09-19 18:17:24 +10:00
Dion Moult 812d8508f4 Implement classification searching in bSDD library 2023-09-19 18:17:24 +10:00
Andrej730 c40cc3f740 select array parent / all array objects to work without parent guid argument
now it's getting parent guid from the currently active object, not from provided guid argument - so now it's possible to add those operators to quick favorites
2023-09-19 11:48:06 +05:00
Ryan Schultz 56040951f1 Have bim.select_aggregate make the aggregate empty the active object, as well. 2023-09-18 20:21:19 -05:00
Dion Moult 39b1a65dc5 IfcTester now includes percentages per requirement 2023-09-19 09:03:53 +10:00
Sigma Dimensions (Yass) 438f35fe88 purge cost schedules 2023-09-18 20:20:23 +01:00
Dion Moult 9240518066 Accommodate potential manual deletion of EPset drawing. 2023-09-18 23:41:13 +10:00
Dion Moult 27b71a9d07 Fix #3752. Accommodate situations where layouts don't match IFC. 2023-09-18 23:15:05 +10:00
Andrej730 0f1d675bef Associate IFC files with BlenderBIM on Windows
Buttons located in addon preferences - https://i.imgur.com/Unp4P9L.png

Just waiting for this to break somehow...

It's not completely automatic now and it doesn't create assign special icon for IFC files yet.
2023-09-18 18:01:20 +05:00
Andrej730 19345ff538 Remove orphaned IfcPropertyEnumeration
Noticed a couple hundreds of orphaned IfcPropertyEnumeration in my .ifc like below 😁

IFCPROPERTYENUMERATION('Status',(IFCLABEL('NEW'),IFCLABEL('EXISTING'),IFCLABEL('DEMOLISH'),IFCLABEL('TEMPORARY'),IFCLABEL('OTHER'),IFCLABEL('NOTKNOWN'),IFCLABEL('UNSET')),$);
2023-09-18 12:08:16 +05:00
Andrej730 7b28bc9c68 fixed bug creating transition shapes
it was accidentally assigning extrusion vector instead of an offset
2023-09-18 11:30:36 +05:00
Andrej730 d67eae55df Clean up orphaned meshes
- delete_ifc_project
- bim.add_constr_type_instance
- adding transitions and bends
- add profile / slab / wall
2023-09-18 10:58:26 +05:00
Andrej730 8e375dcf73 Save last active context editing doors/windows, solves #3735
previously - https://imgur.com/a/gri6zMk
now - https://imgur.com/a/GIwZP6c
2023-09-18 10:04:45 +05:00
Sigma Dimensions (Yass) a34d0cabfc MSP calendar exceptions are now also imported #3766 2023-09-17 03:10:31 +01:00
Sigma Dimensions (Yass) a668743d10 fix msp .xml import: exception days for a standard working week #3766 2023-09-16 19:29:48 +01:00
Massimo Fabbro b13cbbb244 Partial spatial tool refactor 2023-09-15 22:47:35 +02:00
Andrej730 b6ae5fd344 For parametric doors and windows remove "Edit Profile" button from BIM Tool UI to avoid confusion 2023-09-15 15:55:15 +05:00
Andrej730 1c85034e19 Add starting/saving IFC project to Blender F4 file context menu
https://i.imgur.com/FDvZZwc.png
2023-09-15 15:36:33 +05:00
Andrej730 b0d1823968 MEP bends - fixed bunch of bugs related to adjusting already existing bends types 2023-09-15 15:36:33 +05:00
Dion Moult f0e03c79df Project advanced mode filtering now uses new filter syntax 2023-09-15 11:25:08 +10:00
Andrej730 6109cdf41b Ooops, forgot to remove debug shape creation 2023-09-14 18:19:03 +05:00
Andrej730 ec0873d527 transitions - fixed bug finding compatible fitting
For example, there was a fitting from segment of type A to the segment of type B with start/end transition lengths equal a/b respectively.
And then if you'd try to create a transition from B to A and start/end transition lengths being equal to b/a, previously BBIM wouldn't be able to find that already existing fitting is still compatible.
2023-09-14 18:19:03 +05:00
Andrej730 63223172b4 mep operators - clamp properties below zero 2023-09-14 18:19:03 +05:00
Andrej730 9778253d21 bends - support for zero radius bends 2023-09-14 18:19:03 +05:00
Dion Moult 0140b4ded1 IfcDiff can now visualise across linked IFCs 2023-09-14 22:50:57 +10:00
Dion Moult dca60b83a9 Make IfcDiff more forgiving when invalid geometry occurs. See #3755. 2023-09-14 22:28:03 +10:00
Andrej730 42e683375c Fixed bug using tab editing arbitrary profile
It was assuming we were editing extrusion profile and therefore was giving errors.
2023-09-14 11:06:42 +05:00
Dion Moult 66e07c2f69 Include buildingSMART psets in IfcFM Basic preset 2023-09-14 15:17:52 +10:00
Dion Moult 2b067b9dec Minor fix 2023-09-14 14:27:07 +10:00
Dion Moult c22d7d2f41 More minor fixes to IfcFM basic preset 2023-09-14 14:24:06 +10:00
Dion Moult ea3d489c24 Minor fixes to IfcFM 2023-09-14 12:10:36 +10:00
Andrej730 97683a14ef Revert "Replace currently selected objects with occurences of type"
This reverts commit 58199ef7c9.
2023-09-13 18:44:00 +05:00
Andrej730 d79b912e0e bends - consider profile dimensions searching for compatible fitting
So basically now it won't reuse a bend fitting created for the same profile but with different dimension.
2023-09-13 16:30:20 +05:00
Andrej730 7ec8f8b03e bends - adjusting compatible fitting rotation to match the segments
Demo - https://imgur.com/a/cYdWy9X (note that no bend types created in the process - it's just adjusting the type created for the first two segments).
2023-09-13 16:30:20 +05:00
Andrej730 58199ef7c9 Replace currently selected objects with occurences of type
Added operator to replace currently selected objects with occurences of the chosen type. I kept doing this from the code and thought it could be generally useful.

It's working pretty naive now and just replacing objects with occurences preserving the original transform (location/rotation/scale), nothing else is preserved.

Demo - https://imgur.com/a/qm8a6Ns

PS Hopefully I'm not reinventing the wheel and not missed some other similar operator
2023-09-13 16:30:20 +05:00
Andrej730 d5c46514d3 bends - considering parametric data searching for compatible fitting 2023-09-13 16:30:20 +05:00
Thomas Krijnen f400973557 Correct xsi:schemaLocation 2023-09-13 12:47:33 +02:00
Thomas Krijnen 148d6e7b1b Update IDS.xsd 2023-09-13 10:11:01 +02:00
Dion Moult 74dec650a7 New FM module in BlenderBIM Add-on for interacting with IfcFM. 2023-09-13 17:08:33 +10:00
Dion Moult 5f6e003e4e Implement COBie documents and attributes 2023-09-13 17:08:33 +10:00
Andrej730 d3df22b44b bends - use IfcArcIndex to define bends curves
instead of manually generating polylines of segments
2023-09-12 16:29:33 +05:00
Andrej730 257208d55b bends - support for start and end segments 2023-09-12 16:00:02 +05:00
Andrej730 d8b9b09d14 shapebuilder - replace y extrusion args with shortcut for readibility 2023-09-12 16:00:02 +05:00
Andrej730 6f9e14ceb0 Bends - replace faceset with simple extrusion 2023-09-12 16:00:02 +05:00
Andrej730 d854984dda MEP bends - support for circular profiles
Demo - https://imgur.com/a/XQBOvGz
2023-09-12 16:00:01 +05:00
Dion Moult 2951721886 Don't strip selector query keys to allow for explicit "mistakes" in key names. 2023-09-12 20:45:24 +10:00
Dion Moult 02801b32a6 Minor fix in IfcCSV where strings didn't get auto quoted. 2023-09-12 17:48:23 +10:00
Dion Moult b243e04fd2 New util.system.get_port_element function. 2023-09-12 17:48:23 +10:00
Dion Moult 02902d9a41 Allow optional second arguments to substr formatting. 2023-09-12 17:48:23 +10:00
Dion Moult 5c33034455 Implement COBie assemblies, connections, spares, resources, and jobs. 2023-09-12 17:48:23 +10:00
Thomas Krijnen 7541293a3c Case sensitivity on entity str comparison from cmd line https://sourceforge.net/p/ifcopenshell/discussion/1782717/thread/2bf3324a7f 2023-09-11 21:27:54 +02:00
Dion Moult da7d4dac7a Implement COBie systems 2023-09-11 23:24:34 +10:00
Andrej730 507ab8ec11 Fixed top and center alignment in the schedule
Solves #3599

Before - https://i.imgur.com/9s7MxH5.png
After - https://i.imgur.com/ojDj09G.png
Inkscape - https://i.imgur.com/pWF4Ouy.png
2023-09-11 17:30:59 +05:00
Andrej730 e93ff7a6b1 MEP bends - more robust start/end points detection
also support for bends with angles > 90 degrees and some other bugs fixed
2023-09-11 16:28:14 +05:00
Dion Moult 098ae2b69f Implement COBie type and component tables 2023-09-11 18:31:37 +10:00
Dion Moult a58a10e93d Deprecate purge_module_data in favour of newer refresh_ui_data function. 2023-09-10 18:27:05 +10:00
Dion Moult 9be7d951c3 Improve usability of pset template UI by removing save button and using new explicit applicable entity defaults to mitigate possible invalid pset templates. 2023-09-10 17:38:07 +10:00
Dion Moult 6cf482dc63 Minor UI bugfix where pset template files sometimes displayed with truncated filenames 2023-09-10 17:36:07 +10:00
Dion Moult aaa4683462 Fix bug where you couldn't add psets that were inherited. 2023-09-10 17:35:33 +10:00
Dion Moult 0b5535b6d4 Implement COBie zones 2023-09-10 09:04:19 +10:00
Andrej730 45feaeaec4 Spatial Manager - try to preserve the selection on loading the manager
Also noticed in Sigma Dimensions video that every time user deletes a storey or applies new elevation the selection was jumping to the first storey. Now it's going to be more natural.

Before - https://imgur.com/a/gbn7ryQ
After - https://imgur.com/a/QX2RulF
2023-09-09 18:04:35 +05:00
Andrej730 90f92cd139 Units formatting for Spatial Manager
Noticed in Sigma Dimensions video that we have some floating point garbage displayed in the Spatial Manager UI and I've added some simple formatting.

Now - https://i.imgur.com/eM1rDDu.png
Before - https://i.imgur.com/ySln5uU.png
2023-09-09 17:55:58 +05:00
Dion Moult 0285d97e3f Reimplement IfcCOBie contacts, facilities, floors, and spaces in IfcFM. 2023-09-09 21:27:10 +10:00
Andrej730 bf80e4b5e1 Considering parametric data for fitting #3695
Search for compatible fitting wasn't taking into account that maybe fitting used for the same segments but fitting's parameters are not the same (such as start_length, end_length, angle and offset between profiles).

I also forgot to promote transition angle to operator's property😬
2023-09-08 19:40:18 +05:00
Andrej730 d4e4717857 Fixed issue creating slabs in IFC2X3 #3696
Error traceback is below - it was trying to set IfcExtrudedAreaSolid.Position to None when in IFC2x3 Position wasn't optional, so now in IFC2X3 it's going to have some default position

RuntimeError: Error: Python: Traceback (most recent call last):
  File "\bim\module\model\product.py", line 123, in execute
    return IfcStore.execute_ifc_operator(self, context)
  File "\bim\ifc.py", line 336, in execute_ifc_operator
    result = getattr(operator, "_execute")(context)
  File "\bim\module\model\product.py", line 148, in _execute
    if self.generate_layered_element(ifc_class, relating_type):
  File "\bim\module\model\product.py", line 274, in generate_layered_element
    obj = slab.DumbSlabGenerator(relating_type).generate()
  File "\bim\module\model\slab.py", line 143, in generate
    return self.derive_from_cursor()
  File "\bim\module\model\slab.py", line 147, in derive_from_cursor
    return self.create_slab()
  File "\bim\module\model\slab.py", line 179, in create_slab
    representation = ifcopenshell.api.run(
  File "\libs\site\packages\ifcopenshell\api\__init__.py", line 66, in run
    result = usecase_class(ifc_file, **settings).execute()
  File "\libs\site\packages\ifcopenshell\api\geometry\add_slab_representation.py", line 42, in execute
    [self.create_item()],
  File "\libs\site\packages\ifcopenshell\api\geometry\add_slab_representation.py", line 58, in create_item
    extrusion = self.file.createIfcExtrudedAreaSolid(
  File "\libs\site\packages\ifcopenshell\file.py", line 337, in create_entity
    e[idx] = arg
  File "\libs\site\packages\ifcopenshell\entity_instance.py", line 260, in __setitem__
    self.wrapped_data.setArgumentAsNull(idx)
  File "\libs\site\packages\ifcopenshell\ifcopenshell_wrapper.py", line 4929, in setArgumentAsNull
    return _ifcopenshell_wrapper.entity_instance_setArgumentAsNull(self, i)
RuntimeError: Attribute not set
2023-09-08 17:46:58 +05:00
Andrej730 fca90e9b0f automatically adjust viewport camera on loading ifc
It will make sure you'll be able to see the project's elements in viewport when it's loaded (same way as if you'd select all objects and hit "Numpad .").

Couldn't find any way to make it work without enablging / disabling selection - https://projects.blender.org/blender/blender/issues/112141
2023-09-08 17:10:52 +05:00
Andrej730 ffbb91216f Fixed issue using mitre with small angles between walls (#3721)
Normalization is unrelated, just added to be safe.
2023-09-08 15:47:20 +05:00
Andrej730 09620e060f some DumbWallJoiner.join() refactor 2023-09-08 15:47:20 +05:00
Dion Moult 0ea38384cc Minor fix after 6887210 2023-09-08 17:44:33 +10:00
Dion Moult aae659c5fd Fix bug when casting to tessellation for invalid IFCs which don't use material sets properly. 2023-09-08 15:52:44 +10:00
Dion Moult b7a481ec79 IfcFM now uses Openpyxl and supports xlsx. Reorder columns to be more logical and group similar data types together (at least for me). 2023-09-08 13:16:17 +10:00
Dion Moult 763eaf83da Fix #3733. Fix round function precision error. 2023-09-08 12:20:52 +10:00
Dion Moult 2843d1e13e Enable Sphinx auto section labels. Minor doc link fix. 2023-09-08 12:06:59 +10:00
Dion Moult e897797955 For convenience, rounding to an integer value now also casts to integer. 2023-09-08 11:17:28 +10:00
Bruno Perdigão cc8f103465 fix rotation from refresh aggregate operator 2023-09-07 21:06:08 -03:00
Ryan Schultz f1c3a90bc5 Update installation.rst 2023-09-07 14:23:37 -05:00
Dion Moult c90c438324 ResetAbsoluteCoordinates IfcPatch recipe can now choose whether to reset placements, geometry, or both points. 2023-09-07 22:15:44 +10:00
Dion Moult ae1ff3f6f7 Fix bug where unedited mesh objects were still tagged as edited which led to unnecessary processing prior to drawing generation or saving. 2023-09-07 22:09:26 +10:00
Dion Moult e2224386bf You can now customise the display of empty strings in IfcCSV. 2023-09-07 18:15:12 +10:00
Dion Moult 6ad51ebd18 Update documentation with new selector keys. 2023-09-07 18:11:55 +10:00
Dion Moult 0fb9f584cb Big clean up to IfcFM. It's almost usable! 2023-09-07 17:38:33 +10:00
Dion Moult 9a34b94568 Fix #3682. You can now tag setout points with dynamic xyz / enh values. 2023-09-07 13:47:54 +10:00
Dion Moult 7bca3bcac3 Drawing text annotation now use the new format functions instead of executing arbitrary code. 2023-09-07 13:05:03 +10:00
Dion Moult de27554043 New substr formatting function. 2023-09-07 13:02:45 +10:00
Dion Moult 306ee3dfa4 You can now choose both input and output units for imperial formatting 2023-09-07 12:18:43 +10:00
Ryan Schultz 3593b6b5c2 Update ifc_classes_suggestions.json 2023-09-06 20:13:55 -05:00
Dion Moult e06acab39b Revert "Spreadsheet Import/Export: Fix feet and inch formatting"
This reverts commit 921d412fc7.
2023-09-07 10:13:00 +10:00
Dion Moult 6dd4e0f53a Revert "Small fix for feet and inch formatting on Spreadsheet Import/Export"
This reverts commit 7935bd22c2.
2023-09-07 10:12:50 +10:00
Dion Moult 2c2a37cacb Fix #3722. Fix ifcopenshell pypi distribution to use modern manylinux tag for glibc 2.31. 2023-09-07 09:57:58 +10:00
Ryan Schultz 7935bd22c2 Small fix for feet and inch formatting on Spreadsheet Import/Export 2023-09-06 13:49:32 -05:00
Andrej730 6887210a1a remove the orphaned data created on object duplicate
orphaned mesh still had ifc_definition_id of the original object and could have caused issues in theory
2023-09-06 17:28:37 +05:00
Andrej730 33dc3a4b06 Update status visibility automatically #3721 2023-09-06 15:57:39 +05:00
Dion Moult ba4610155f Fix #3705. Fix ambiguity in using commas in unquoted strings in facet selector. 2023-09-06 16:34:39 +10:00
Dion Moult 28eeba49ba Fix #3712. Fix bug where grid axes couldn't be duplicated. 2023-09-06 15:11:07 +10:00
Dion Moult 0a00e529c3 Improve hello world documentation to be more beginner friendly 2023-09-05 23:42:31 +10:00
Dirk Olbrich c5024cf6be cmake: ifcconvert is build in minimal 2023-09-05 13:00:39 +02:00
Dirk Olbrich 0f3b416c3f cmake: reorder build options 2023-09-05 13:00:39 +02:00
Dirk Olbrich addacd9ce5 cmake: move check fo 3ds max sdk to external file 2023-09-04 15:01:12 +02:00
Dirk Olbrich 5582bb2d4c ifcmax: format and unify cmake 2023-09-04 15:01:12 +02:00
Andrej730 8b27c0e755 hotfix for ca4efa6e5 2023-09-04 16:33:02 +05:00
Andrej730 ca4efa6e50 Remove operator's name from set_tab tooltip #3704 2023-09-04 16:14:56 +05:00
Andrej730 cfac835607 bim.mep_add_bend
Added operator to create bend fittings between objects, it's still work in progress and now it only supports rectangular profiles.

Small demo - https://imgur.com/a/vM7hnJa
2023-09-04 15:42:45 +05:00
Andrej730 3aa72ae74a notify about unsupported transition case 2023-09-04 15:42:45 +05:00
Dion Moult 07579ee9a1 See #3700. Fix issue in drawing include filter where "addition" selections like GlobalIds and classes didn't have any impact. 2023-09-04 15:59:13 +10:00
Dirk Olbrich fe4823681f ifcgeom: fix compiler warning -Wunused-but-set-variable 2023-09-03 20:02:20 +02:00
Dirk Olbrich 6816ad5c0f ifcparse: fix compiler warning -Wignored-qualifiers 2023-09-03 20:02:20 +02:00
Dirk Olbrich aace987149 ifcgeom: fix compiler warning -Wreorder-ctor 2023-09-03 20:02:20 +02:00
Carlos Dias 339ab9eeab Merge branch 'IfcOpenShell:v0.7.0' into v0.7.0 2023-09-03 12:33:20 -03:00
Thomas Krijnen eee844e204 Express rules: Workaround str case in IfcBlobTexture 2023-09-02 15:58:05 +02:00
Dion Moult 46cfe70181 You can now load 2D point clouds. 2023-09-02 16:31:08 +10:00
Dion Moult 294935ceed See #3682. You can now add symbols and multisymbols (i.e. one vertex per symbol) annotations. New default symbols for setout points in demo library. 2023-09-02 16:31:08 +10:00
Dirk Olbrich f2097b9036 cmake: fix PATHS syntax error closes #2632 2023-09-01 17:35:02 +02:00
Andrej730 e48a3a06c0 Fixed bug with adding obstruction fittings 2023-09-01 18:24:02 +05:00
Andrej730 c07371cb76 couple descriptions for setting north 2023-09-01 18:05:22 +05:00
Dion Moult 8d450aa500 Fix #3470. Fix bug where hppfcl for IfcClash was incorrectly bundled for Windows. 2023-09-01 22:14:09 +10:00
Dirk Olbrich 726c792fbe cmake: remove unused CONVERT_PRECISION closes #3672 2023-09-01 11:56:11 +02:00
Dirk Olbrich 83dda3e73f cmake: fix QT version number from merge conflict 2023-09-01 11:56:11 +02:00
Dirk Olbrich 5c78e29849 cmake: fix target versions closes #3671 2023-09-01 11:56:11 +02:00
Dirk Olbrich e6d48cd37f cmake: fix reset to ROOT_PATH back in 2023-09-01 11:56:11 +02:00
Dirk Olbrich a3ecde3ad9 cmake: reset wrong fix on merge conflict 2023-09-01 11:56:11 +02:00
Dirk Olbrich 2bb64a6cae cmake: fix order to set boost components 2023-09-01 11:56:11 +02:00
Dirk Olbrich 764c818d10 cmake: fix inline variable error 2023-09-01 11:56:11 +02:00
Dirk Olbrich 2d18185928 cmake: fix lowercase and duplicates from rebase 2023-09-01 11:56:11 +02:00
Dirk Olbrich 43468166d2 fix format of license block 2023-09-01 11:56:11 +02:00
Dirk Olbrich fbfd6bc485 refactor order of unify envvars 2023-09-01 11:56:11 +02:00
Dirk Olbrich 1578efe366 refactor conditional for 3ds Max SDK 2023-09-01 11:56:11 +02:00
Dirk Olbrich 76948c5156 add uninstall target 2023-09-01 11:56:11 +02:00
Dirk Olbrich 562ed5908d fix IFC_MAX check 2023-09-01 11:56:11 +02:00
Dirk Olbrich 9caac14014 make 3ds SDK search optional, order cmake options
# Conflicts:
#	cmake/CMakeLists.txt
2023-09-01 11:56:11 +02:00
Dirk Olbrich 695f75f3a7 fix missing capitalization 2023-09-01 11:56:11 +02:00
Dirk Olbrich 7d7c04ddc0 cmake format and unify file 2023-09-01 11:56:11 +02:00
Dion Moult 863bf8ebbe Fix bug where models that might have invalid shapes or 2D far shapes might break georeferencing and model loading. 2023-09-01 17:39:53 +10:00
Dion Moult 0fcc28f50c Minor fix 2023-09-01 17:33:33 +10:00
Dion Moult 04e9a08f3d IfcTester BCFs now include viewpoints for occurrences 2023-09-01 17:29:06 +10:00
Dion Moult 837c401116 Add filepath popup when saving IDS reports to BCF 2023-09-01 17:28:19 +10:00
Dion Moult b087d5125f Bump IOS 2023-09-01 16:55:18 +10:00
Dion Moult a6bff09c78 Update BBIM tester module UI to be compatible with new IfcTester upgrades 2023-09-01 16:47:55 +10:00
Dion Moult 4716c0b7b7 IfcTester now supports much more granular statistic reports and more detailed / beautiful HTML reports. 2023-09-01 16:46:49 +10:00
Dion Moult 1d040a170e Fix #3649. Increase profile threshold. 2023-08-31 23:04:55 +10:00
Dion Moult 951026a95c Fix #3667. Allow removing drawings if the sheet hasn't yet been generated or manually removed. 2023-08-31 22:36:18 +10:00
Dion Moult c6b82bfa56 Fix #3647. Fix bug where Windows .exe suffix needed to be explicit in commands. No more eval, and commands must now be provided in json form, and variables should be given as simple strings. 2023-08-31 22:28:33 +10:00
Sigma Dimensions (Yass) 01b6d90432 fix failed context for hotkey Shitf+C #3676 2023-08-31 11:14:53 +01:00
dushyant basson 504a116ebf Update src/qtviewer/ParseIfcFile.cpp
Co-authored-by: Thomas Krijnen <t.krijnen@gmail.com>
2023-08-31 10:17:30 +02:00
dushyant basson be785a878c Update src/qtviewer/ParseIfcFile.cpp
Co-authored-by: Thomas Krijnen <t.krijnen@gmail.com>
2023-08-31 10:17:30 +02:00
dushyant basson 5c2b7404d2 Revert "Updated included header files"
This reverts commit 65f86d929e.
2023-08-31 10:17:30 +02:00
dushyant basson 84e9e2396a Updated included header files 2023-08-31 10:17:30 +02:00
Dushyant Basson 7687d5e26e Added output panel (for debug info, etc.) 2023-08-31 10:17:30 +02:00
Dushyant Basson 25ce3b039e added IFCOS libraries; Qt include dir 2023-08-31 10:17:30 +02:00
Dushyant Basson 9c4c42e27e comment edit: Qt5 to Qt6 2023-08-31 10:17:30 +02:00
dushyant basson 26dd850ac1 added .cache to .gitignore 2023-08-31 10:17:30 +02:00
dushyant basson 0380c286a5 Rename files - fixing case change 2023-08-31 10:17:30 +02:00
dushyant basson 10aca77de4 Updated to use Qt6; Created initial structure of the viewer with OpenGL widget 2023-08-31 10:17:30 +02:00
Dion Moult b6f2fdef77 Fix #3531. See #3660. Exclude 2D elements from generating shapely surface polygons in drawings. 2023-08-31 18:14:06 +10:00
Thomas Krijnen 9cc1f5f0e3 #3660 Oops... propagate setting 2023-08-31 08:27:45 +02:00
Dion Moult adcf22f281 Fix #3002. You can now have metadata classes on projection lines. 2023-08-31 12:40:39 +10:00
Dion Moult ad0c49ac37 Bump IOS to take advantage of setSegmentProjection 2023-08-31 12:40:39 +10:00
Thomas Krijnen 73d77b8382 Update rule_executor.py - catch some runtime errors 2023-08-30 14:56:35 +02:00
Andrej730 3e45f39ea2 Fixed #3666 after 101972f 2023-08-30 17:43:11 +05:00
Thomas Krijnen a2bb015c79 Update validate.py - catch some runtime errors 2023-08-30 13:48:31 +02:00
Thomas Krijnen b231f43fc7 Update ci.yml 2023-08-30 11:38:28 +02:00
Thomas Krijnen 383038fdb4 Update ci-py-only.yml 2023-08-30 11:38:20 +02:00
Thomas Krijnen fbd8ea1edb #3660 setSegmentProjection() / --svg-segment-projection options 2023-08-30 10:57:33 +02:00
Dirk Olbrich 26e292dd34 docs: fix backslash in cmake command 2023-08-30 10:31:41 +02:00
Dion Moult c520e16031 Fix #3644. Annotation matrixes properly calculated now for RCPs. 2023-08-30 18:16:08 +10:00
Bruno Postle 101972fbad BlenderBIM keep view when switching git revision 2023-08-29 22:28:02 +01:00
Sigma Dimensions (Yass) f23ebc4031 fix issue where add materials operator would only show when a material is selected 2023-08-29 17:00:24 +01:00
Sigma Dimensions (Yass) 44431db092 fix toggle link selectability and visibility when linked IFCs use a relative path 2023-08-29 16:32:18 +01:00
Sigma Dimensions (Yass) d1b6c1ffe4 fix loading and unloading linked IFCs with a relative path 2023-08-29 16:30:29 +01:00
Sigma Dimensions (Yass) 4d98151b62 fix various issues by preventing data from linked IFCs to be saved in current project 2023-08-29 16:28:40 +01:00
Sigma Dimensions (Yass) 5e8e18e3b5 Maintain active status filters override when activating 3D model during drawing worfklows 2023-08-29 10:29:03 +01:00
Andrej730 808ff548ec IfcElementType now appears first in Object Metadata
Because it's seems more general workflow to create types than just assign classes.
Example - https://i.imgur.com/DD8twX3.png
2023-08-29 13:58:14 +05:00
Andrej730 bc9b482373 Rename IFC AU Library to IFC Furniture library
Just thought watching BIM Voice last video that some people may overlook it like something Australia specific with "IFC AU Library" name.
2023-08-29 13:58:14 +05:00
Sigma Dimensions (Yass) 4506eb0df9 fix removing drawings from sheets when deleting drawings #3645 2023-08-29 00:45:01 +01:00
Ryan Schultz 921d412fc7 Spreadsheet Import/Export: Fix feet and inch formatting 2023-08-28 10:11:24 -05:00
Andrej730 6089d3af78 shape_builder docs test 2023-08-28 16:14:05 +05:00
Christoph Mellueh b8168ca606 add build to Makefile and use it in bcf-pypi Workflow 2023-08-28 11:24:19 +02:00
Andrej730 61a7325bc0 fill-bg for text instead of the entire symbol by default #3594
Before - https://i.imgur.com/OsQycgW.png
After - https://i.imgur.com/rZvXIoT.png
2023-08-28 11:58:17 +05:00
Chetan c483116b32 Create constants ELEMENT_ARRAY_BUFFER and ARRAY_BUFFER to allow for more descriptive bufferview construction. Change the bufferViewId variable from a static variable to a member variable because python instances of IfcOpenShell can have multiple serializers open at any given time. This commit also includes some minor code reformatting and code comments explaining the changes done. Finally integer constants GLB_FILE_HEAER, GLB_JSON_HEADER, GLB_BINARY_CHUNK_HEADER were added to make the glb file header creation more descriptive 2023-08-28 08:46:11 +02:00
Chetan 55a623dc97 Give each accessor its own buffer view to improve the memory and rendering efficiency of the glb file 2023-08-28 08:46:11 +02:00
Sigma Dimensions (Yass) ad420108b0 run black and fix silly code #3652 2023-08-27 18:43:33 +01:00
Sigma Dimensions (Yass) 9944c7d2de you can now reload linked IFCs #3652 2023-08-27 18:21:19 +01:00
Sigma Dimensions (Yass) 1a8f2be4fd Move grids panel under Project Setup 2023-08-27 18:19:33 +01:00
Dion Moult 13f64fdfe2 Write documentation describing facet selector and element query syntax. 2023-08-27 22:32:41 +10:00
Dion Moult 38a9e29102 Bump IOS and enable unify inputs. 2023-08-26 22:11:54 +10:00
Dion Moult 3c23a57138 Minor fix to use new regex syntax in select similar dialog. 2023-08-26 18:26:03 +10:00
Dion Moult 7baed872c6 Minor fixes, and IfcCSV now uses pandas to import ODS/XLSX for more robust importing. 2023-08-26 18:24:48 +10:00
Dion Moult 9a7a284561 Selector element key queries now allow for regex matching, and single regex matches return that only item, not a list with one item in it. 2023-08-26 16:42:18 +10:00
Dion Moult e9c24a2656 Fix #3587. IfcCSV now supports custom formatting functions. 2023-08-26 15:15:34 +10:00
Gorgious56 5c73ef0a6d Fix #3641 : Error when creating roof type 2023-08-25 15:13:07 +02:00
Carlos Dias 6c29c53a72 Merge branch 'IfcOpenShell:v0.7.0' into v0.7.0 2023-08-25 08:25:25 -03:00
Thomas Krijnen dadcbe61ea #3158 Fix unify inputs 2023-08-25 12:48:45 +02:00
Thomas Krijnen 93502a05ea Update draw.py --with-unify-inputs [True] 2023-08-25 12:43:32 +02:00
Andrej730 5ebde4c79a small info message on bim.get_representation_ifc_parameters
just to notify that it worked but haven't found any parameters
2023-08-25 12:16:40 +05:00
Dion Moult cf21ae22f1 Fix incorrect implementation of regex matching 2023-08-25 17:15:21 +10:00
Dion Moult cc500ae833 Fix #3635. Minor fix. 2023-08-25 16:59:08 +10:00
Dion Moult 256c0baa14 Accommodate invalid Revit models where they have invalid material relationships. 2023-08-25 16:52:00 +10:00
Dion Moult 66decc604e Fix failing tests and optimise brick tests. 2023-08-25 16:52:00 +10:00
Andrej730 356bc18078 fixed errors after 13309cb8c #3624
for example it was throwing errors when using WindowTool you selected a wall and it would try to assign current type to wall
2023-08-25 11:26:37 +05:00
rileywong311 1344cbc192 Update core/test_brick.py 2023-08-24 16:38:54 -07:00
rileywong311 e00ffb07ad Update tool/test_brick.py 2023-08-24 16:38:54 -07:00
rileywong311 dba43590a5 Fix bug where split screen selection from previous page is out of range on the next 2023-08-24 16:38:54 -07:00
rileywong311 a0613cfee6 Undo indentation from commit <e08283350> (did not account for no name existing) 2023-08-24 16:38:54 -07:00
rileywong311 c3de72b71a Fix Brick core
- Refresh viewer now reloads both screens by default
- Rename "add_namespace" to "add_brick_namespace"
- Fix error where "view_brick_class" was run instead of "brick.run_view_brick_class"
2023-08-24 16:38:54 -07:00
rileywong311 39fc60a482 Update core\tool.py for Brick 2023-08-24 16:38:54 -07:00
rileywong311 0d7e71a269 Fix Brick tool
- Data is now stored regularly and parsed in props
- "get_convertable_brick_spaces" returns a set
- "export_brick_attributes" always returns a tuple of strings
2023-08-24 16:38:54 -07:00
rileywong311 78a2f65ec3 Fix brick.feature
For one particular, I removed "And I set 'scene.BIMRootProperties.new_brick_relation_type' to 'hasPart'". Although it should really be "https://brickschema.org/schema/Brick#hasPart" to begin with, it was still failing the test. So removing it as a work around.
2023-08-24 16:38:54 -07:00
rileywong311 802bd74c52 Refactor UI get_brick_relations 2023-08-24 16:38:54 -07:00
rileywong311 33cf40debb Fix condition for hiding "view_brick_item" 2023-08-24 16:38:54 -07:00
rileywong311 ff63fd9f6f Rename 'add_brick_failed" to "add_brick_relation_failed" 2023-08-24 16:38:54 -07:00
rileywong311 5b207e6f54 Fix indentation for assign Brick reference 2023-08-24 16:38:54 -07:00
Gorgious 695dc6ec5a Fix #3636 : Check if representation class exists before testing if it's a circle extrusion representation 2023-08-24 20:42:42 +02:00
Sigma Dimensions (Yass) d3bcb8c6b9 updating a duration will not auto update schedule usage 2023-08-24 15:51:33 +01:00
Andrej730 6226aea3c3 Fixed bug adding ports to the transitions between profiles with an offset
end port ended up located without the offset
2023-08-24 18:22:03 +05:00
Andrej730 a30ad9b886 Fixed bug in shape_builder.mep_transition_calculate
it wasn't taking account that `h` can also end up negative
2023-08-24 18:22:03 +05:00
Andrej730 2b1e17681f Fixed bug with segments incorrectly adjusted after adding transition
If transition was really long comparing to segments length then DumbProfileJoiner.join_E would start change their their length in unexpected way - ATSTART when you'd still expect ATEND and vice versa.

Also fixed the bug when segments length was incorrectly calculated - it wasn't calculating it world space when it makes more sense here to calculate it by local segments Z axis.
2023-08-24 18:22:03 +05:00
Dion Moult 8f25a31f19 Fix more failing tests. 2023-08-24 23:03:43 +10:00
Sigma Dimensions (Yass) 1c7e6cf1b9 fix bug where nested resources in task ICOM panel didn't show 2023-08-24 13:58:18 +01:00
Sigma Dimensions (Yass) fc7459c07f Resource Features :
- Adding/removing and edit productivity data is now easier (+ cleaner UI)
- Pressing calculate schedule work on a parent resource will default to calculating its nested resources schedule work.
- Show derived Schedule Work for a parent resource
- Improve apperance of Resource Tree Structure
2023-08-24 13:43:25 +01:00
Gorgious56 8f6932de64 Prevent user from editing the mesh of a circle based extrusion 2023-08-24 14:16:59 +02:00
Dion Moult b708100c03 See #3581. Fix failing IOS tests. 2023-08-24 22:12:44 +10:00
Christoph Mellueh d4e2d3d2dd rename File and unify with ci-ifctester-pypi 2023-08-24 21:05:54 +10:00
Andrej730 13309cb8c4 Fixed #3624 - tools props were updating only for BIM Tool
It wasn't taking into account WallTool, BeamTool and all other tools we have.
2023-08-24 15:42:23 +05:00
Andrej730 5a4bc0909b Update currently selected ifc class and type on selecting object
Had this idea for awhile now and heard it multiple times from other people. Please give feedback if this feature gets in the way instead of helping.

Example - https://imgur.com/a/K4hz2FB
2023-08-24 15:20:36 +05:00
Andrej730 39540e5794 A button to jump back from boolean to the original object
Example - https://imgur.com/a/A3dVbFM
2023-08-24 14:29:42 +05:00
Dion Moult 0a0f977b5c Fix #3632. Make search and CSV UIs share the same Blender props for DRY. 2023-08-24 17:01:25 +10:00
Dion Moult 13a75e7ad7 You can now reorder columns in IfcCSV 2023-08-24 13:56:43 +10:00
Dion Moult 99574d6255 Loading IFCs now prioritise 2D bodies over 3D non-bodies. This helps prevent loading and seeing a bunch of Box / Clearance representations. 2023-08-24 13:24:08 +10:00
Dion Moult 968cceb725 Fix #3627. Bug where exclude didn't take into account entities are "OR" not "AND". Whoops. 2023-08-24 11:50:48 +10:00
Sigma Dimensions (Yass) 002985a446 run black 2023-08-23 21:21:19 +01:00
Sigma Dimensions (Yass) 5adc29b0b3 Feature to calculate required resources 2023-08-23 21:21:03 +01:00
Sigma Dimensions (Yass) 52ea4ff44d Calculates the number of resources based on required person-hours. 2023-08-23 21:19:16 +01:00
Sigma Dimensions (Yass) 4e31a87c20 clean up Resource UI using column layout 2023-08-23 21:15:59 +01:00
Jesusbill 5d85390715 Fix duplicate id in EP_set_Drawing template 2023-08-23 17:01:25 +02:00
Andrej730 3e13eb8942 bump ifcsverchok release in readme 2023-08-23 18:43:27 +05:00
Andrej730 262c6d1fcb added UI to display connections for realizing elements
Example - https://imgur.com/a/1ZZlTwp
2023-08-23 18:42:07 +05:00
Andrej730 12b5a88861 Added small UI to show realizing elements for the connections
Example - https://i.imgur.com/JLDcnbK.png
2023-08-23 18:42:06 +05:00
Andrej730 2ccc9d886e test to make sure nothing happened to ifcsverchok 😅 2023-08-23 18:42:06 +05:00
Andrej730 a78769c8ff a bit faster renaming ifc elements when renaming blender objects
Previously when you renamed blender object it was requiring the "Ifc.../" prefix so it would also change the name for ifc element. So when you wanted to rename something you'd only select and change the name part without prefix.

Now you can just type the name right in after using F2 which seems a bit quicker by a few less clicks.

Before - https://imgur.com/a/Rn7a17W
After - https://imgur.com/a/a3lmxte
2023-08-23 18:42:06 +05:00
Andrej730 1a142ad5eb fixed bug in mep transitions
it wasn't taking into account that offset can be too big to sustain the angle
2023-08-23 18:42:06 +05:00
Andrej730 2bc22e150c added tests for shape builder transition length calculator 2023-08-23 18:42:06 +05:00
Sigma Dimensions (Yass) aad5d280fd you can now also update resource numbers (usage) from the task ICOM resource list 2023-08-23 14:13:16 +01:00
Sigma Dimensions (Yass) 7ab07ed149 refactor highligh_task code 2023-08-23 14:04:39 +01:00
Dion Moult 3690998dbc Fix #3620. Bug where manually inserted windows should preseve the existing Z value. 2023-08-23 23:03:13 +10:00
Sigma Dimensions (Yass) 1b83fcc1c7 when editing sequence relationships, enabling selecting successor and predecessor task 2023-08-23 14:02:53 +01:00
Sigma Dimensions (Yass) 5cf25c45f9 feature to select a resource in resource tree from task ICOM list 2023-08-23 13:59:31 +01:00
Dion Moult efa734c06e Fix #3628. Minor issue where regex queries didn't load properly. 2023-08-23 22:42:21 +10:00
Dion Moult e242244ec0 The copy property tool now works on enumerated properties. Hooray for copying over statuses! 2023-08-23 17:47:20 +10:00
Andrej730 6f9da163fb Small fix for 422f772c4 2023-08-23 12:32:37 +05:00
Dion Moult a3fd169bb6 IfcPatch now uses the new facet selector syntax. 2023-08-23 17:10:17 +10:00
Dion Moult 899056f929 Due to popular demand, custom EPset_Status to be used for "userdefined" status values as well as for things which don't have a status. 2023-08-23 17:10:17 +10:00
Dion Moult 132fef1b76 New Status panel for managing statuses in the costing / scheduling tab. 2023-08-23 17:10:17 +10:00
Andrej730 422f772c40 Fixed bug with remembering materials assigned to faces
The problem was that bim.update_representation was assigning representation styles without taking into account that some styles may not be actually used in the mesh, so it was always assigning first style (blender material) to the first IFCPOLYGONALFACESET even though it might be using for example the 3rd style.

More - https://community.osarch.org/discussion/1636/materials-not-remembering-their-assigment
2023-08-23 11:07:57 +05:00
Sigma Dimensions (Yass) 66f5081ada implement calculating task duration based on resource usage change for fixed person-hours and prevent constrained attributes from updating 2023-08-23 02:19:44 +01:00
Dion Moult 839e35f1da Fix #3627. Error where using filters incorrectly applied includes / excludes. 2023-08-23 11:15:38 +10:00
Sigma Dimensions (Yass) 40d9713326 consolidate constraint utilities into ifcopenshell.util.constraint and refactor 2023-08-23 02:11:18 +01:00
rileywong311 17b04ad697 Update Brick feature tests 2023-08-22 13:15:01 -07:00
rileywong311 2f99dd8e98 Catch Assign_Brick_Reference with no selections 2023-08-22 13:15:01 -07:00
rileywong311 49f825ba82 Refactor+polish Brick UI 2023-08-22 13:15:01 -07:00
rileywong311 3f18ad0bc5 Use "split" in Brick relations list to better align layout 2023-08-22 13:15:01 -07:00
rileywong311 991c42e816 Update Brick References Panel 2023-08-22 13:15:01 -07:00
rileywong311 20d6eae50a Export/assign Brick library reference uses "label" if there is one 2023-08-22 13:15:01 -07:00
rileywong311 9740582a84 Brick UI relations list uses "label" if there is one 2023-08-22 13:15:01 -07:00
rileywong311 a8046a6d69 Restore Brick select ifcGlobalID functionality 2023-08-22 13:15:01 -07:00
rileywong311 d6655a5c14 Move Brick "set last saved" to BrickStore 2023-08-22 13:15:01 -07:00
rileywong311 0a3a2db4ae Simplify buttons for add Brick relations manually 2023-08-22 13:15:01 -07:00
rileywong311 499eb5ea14 Rework Brick UI into toggle panels 2023-08-22 13:15:01 -07:00
rileywong311 b8ca287883 Fix up convert_ifc_to_brick
Remove line separation between code, but add comments
Add full URI to Brick relations
Fix typo where "system_uris" was written as "space_uris"
Remove unnecessary get of "distribution_elements = brick.get_convertable_brick_elements()" again
2023-08-22 13:15:01 -07:00
rileywong311 52f3392ece Remove Brick data debugging printouts 2023-08-22 13:15:01 -07:00
rileywong311 d53641ce5e Check Brick library location exists 2023-08-22 13:15:01 -07:00
Sigma Dimensions (Yass) 7a3ad6c3a1 forgotten bits and bobs + run black 2023-08-22 20:03:09 +01:00
Sigma Dimensions (Yass) 0b2007011e Feature to constrain a resource work or number of used resources 2023-08-22 20:01:19 +01:00
Sigma Dimensions (Yass) eae6983ca5 Fix calculating resource work based from parent's productivity and improve UI to show this 2023-08-22 19:54:37 +01:00
Sigma Dimensions (Yass) 0ec337748f improve purge of remove_metric usecase 2023-08-22 19:36:01 +01:00
Sigma Dimensions (Yass) 2699278ba3 api usecase to add a reference path to a metric objective 2023-08-22 19:34:47 +01:00
Sigma Dimensions (Yass) 0406166d8a fix updating an objective's metric 2023-08-22 19:21:26 +01:00
Andrej730 2128eda87b Save blenderbim last commit hash to the ifc header
Now it's possible to identify blenderbim version from .ifc file which is useful for debugging

Example:
FILE_NAME('test.ifc','2023-08-22T18:07:07+05:00',(),(),'IfcOpenShell v0.7.0-fc50bdd3a','BlenderBIM 0.0.999999-64bc7af','Nobody');
2023-08-22 19:59:46 +05:00
Carlos Dias 59e11aa804 Merge branch 'IfcOpenShell:v0.7.0' into v0.7.0 2023-08-22 11:14:04 -03:00
Dion Moult 21f4fb169f New util function to format a length value in preparation for custom formatting rules in drawings and schedules. 2023-08-22 23:12:37 +10:00
Thomas Krijnen 30fc52ebd9 #3616 Only consider distance orthogonal to projection vector for 2d boolean preprocessing 2023-08-22 15:00:25 +02:00
Andrej730 64bc7af4ef mep transitions - correct angle when connected segments share the same dimensions
Basically there are 3 methods to define the transition angle (https://i.imgur.com/AtCIJty.png). Before we supported only the second method, now we support 1 and 3 too.
2023-08-22 16:06:24 +05:00
Dion Moult b3f1fc667a IfcCSV can now create summary rows (e.g. total, average) at the end of the table (may or may not be in addition to group totals). 2023-08-22 21:04:59 +10:00
Dion Moult fcbb28f9d8 When you force an object to be a tessellation, automatically remove all parametric constraints for you. 2023-08-22 17:32:29 +10:00
Dion Moult 240a31db3a Fix bug where split elements by plane didn't work on multiple elements due to forgetting to toggle normals of the cutting plane. 2023-08-22 17:31:54 +10:00
Dion Moult c1bde09585 IfcCSV now supports grouping and group operations like concat, sum, min, max, etc. 2023-08-22 15:43:31 +10:00
Dion Moult 5dd2b5e555 New support for sorting IfcCSV outputs 2023-08-22 14:32:26 +10:00
Dion Moult 37dcb92220 Drawing include/exclude filters now use the new facet selector syntax 2023-08-22 11:31:18 +10:00
Dion Moult 6c43c60d04 Drawings now can include non-drawing specific annotations too. 2023-08-22 11:30:48 +10:00
Dion Moult b8d191ac1c Now support custom queries for advanced users in the facet selector (similar to old selector). Also support types and count keys. (e.g. filter by objects with zero types, or zero materials, etc). 2023-08-21 23:44:20 +10:00
Dion Moult 57173c7740 You can now get specific container types via util.element or util.selector. E.g. if you wanted to know what building an object is in or what storey an object is on. 2023-08-21 23:07:02 +10:00
Andrej730 2d3295ec54 more work on transitions for MEP
- support transitions from and to circle profiles
- reworked transition length algorithm now it should be more accurate
- added support for creating transitions between profiles that are parallel but not collinear
2023-08-21 17:56:17 +05:00
Andrej730 941ede117c show error if transition length is more the segments length 2023-08-21 17:56:17 +05:00
Andrej730 4210b8333f fix the errors occured trying to find compatible fitting type
1) It would stop if the first occurence would be connected to a segment without type
2) It would raise an error if one of the fitting's ports was not connected.
2023-08-21 17:56:17 +05:00
Sigma Dimensions (Yass) 1723d1e5a9 Update tests for editing sequence relationships 2023-08-21 13:43:43 +01:00
Sigma Dimensions (Yass) 3ee06ee7a0 remove unused function 2023-08-21 13:41:42 +01:00
Sigma Dimensions (Yass) d3e8da58dc stop committing print statements 2023-08-21 13:30:20 +01:00
Sigma Dimensions (Yass) 29e000b939 clean up updating selected task ICOM and UI re-ordering for work schedule subpanels 2023-08-21 13:28:08 +01:00
Sigma Dimensions (Yass) 6686a72d1f editing sequence relationships now requires less clicks 2023-08-21 13:24:36 +01:00
Sigma Dimensions (Yass) 7dd20f69d7 ligten up add cost schedule UI 2023-08-21 01:13:17 +01:00
Dion Moult 0044ef5588 Close #3615. Follow (terrible, unofficial) convention that boolean result is intentional and boolean clipping result is automated. 2023-08-21 08:53:14 +10:00
Dion Moult ffa5686b22 Fix #3614. Remove_pset API now supports removing predefined property sets (E.g. door lining properties). 2023-08-21 08:44:24 +10:00
Sigma Dimensions (Yass) ab8d6bea99 update sequence tool functions 2023-08-20 23:02:31 +01:00
Sigma Dimensions (Yass) e7bb89f582 You can now save and load 4D animation color schemes 2023-08-20 21:39:00 +01:00
Dion Moult c80b0464e5 Fix #3551. Project specific pset templates now show up in the pset template editor. 2023-08-20 23:51:38 +10:00
Dion Moult 7e3547b309 Fix #3610. Glitch where editing existing float cells in IfcCSV ODS files created new cells or didn't update. 2023-08-20 23:42:11 +10:00
Dion Moult 45c7432ff7 Minor fix 2023-08-20 22:53:32 +10:00
Dion Moult c98c20d403 Fix #3583. BaseQuantity QTOs in IFC4 can now be set on types. 2023-08-20 20:50:46 +10:00
Dion Moult d26a2580ea Fix #3605. Bug where invalid representation type was created when booleans are added / removed. 2023-08-20 17:12:01 +10:00
Dion Moult f84c4ab2e5 IfcCSV now supports importing from ODS, XLSX, and Pandas dataframes in addition to CSV 2023-08-20 16:55:01 +10:00
Dion Moult de6676a516 Fix #3609. IfcCSV can now optionally attempt to "preserve" formatting for XLSX and ODS. 2023-08-20 16:34:42 +10:00
Dion Moult bfbb7bec94 Fix #2973. Bundle pandas, openpyxl, and odfpy in preprataion for upgrades in IfcCSV and IfcFM. 2023-08-20 15:28:09 +10:00
Dion Moult ac00aaae4d Fix #3607. IfcCSV now supports omitting the GlobalId column. This is great for presentation schedules, but the tradeoff is that you can't use it for imports. 2023-08-20 14:41:34 +10:00
Dion Moult 422222a7cb Bump IOS 2023-08-19 23:13:25 +10:00
Dion Moult e1c7b37d62 See #3587. IfcCSV now supports custom header titles. 2023-08-19 23:09:40 +10:00
Ryan Schultz dec978743b small fix on 'Live development environment' instructions 2023-08-18 13:15:35 -05:00
Sigma Dimensions (Yass) 10fae033c8 fix saving and loading coulour schemes 2023-08-18 17:09:00 +01:00
Sigma Dimensions (Yass) b23180d287 fix enumartion of saved searches 2023-08-18 17:08:01 +01:00
Gorgious56 99ccd81b3f You can now purge unused representations from the purge data panel 2023-08-18 16:20:41 +02:00
Gorgious56 e2d3f3a351 You can now purge types from the Purge data panel
And refactor a bit the purge profiles code to make use of the profile tool module
2023-08-18 16:04:08 +02:00
Dion Moult 5e6a444aa2 Advanced users can now directly edit search filter queries. 2023-08-18 23:39:13 +10:00
Dion Moult 72cf5cfc59 Minor fix to allow edit_work_time to work seamlessly for IFC4X3. 2023-08-18 23:01:24 +10:00
Andrej730 75cb68d2f1 Refactored svg angle decorator code, solves #3602, more constistency between viewport and svg
Now it should be more consistent and more stable since svg and viewport are now using much more similar approach.
2023-08-18 17:59:40 +05:00
Dion Moult 7f40a9d723 See #3603. Base64 encode gantt chart JSON data to prevent JSON parsing problems. 2023-08-18 22:19:27 +10:00
Andrej730 dcc9d0e9cd set_objects_selection to set selection only if active_object is not None
It was creating errors if you'd try to add angle annotation without having any objects active.
2023-08-18 15:37:04 +05:00
Sigma Dimensions (Yass) 7228329f5e Start improving UI for clash detection and management 2023-08-18 11:04:21 +01:00
Gorgious56 d8786717b6 Active profil users count is now dynamically fetched when the profile is clicked in the UI list 2023-08-18 09:43:46 +02:00
Andrej730 b73bd3506f Fixed clearing scales (after 3345d7e)
It was producing a bug with text rotation in elevation view
Added fix to make matrix normalization work for the cases when matrix is rotated and I've just found that mathutils.Matrix.normalize(d) is very conveniently ignoring 4th column (translation) (same for mathutils.Vector)
2023-08-18 12:05:53 +05:00
Andrej730 bdb2791b35 Fill-bg for template tags #3594
Also simplified create_text_tag a bit - now it adds fill_bg by just by copying text tags and adding fill_bg filter to them instead of going through the entire logic again.
2023-08-18 12:04:42 +05:00
Dion Moult 549ea11945 Support arbitrary element selection for manual groups in search / IfcCSV, with support for huge element selections. 2023-08-18 16:58:07 +10:00
Dion Moult aed01b5703 IfcCSV can now use the graphical filter query builder too. 2023-08-18 13:43:42 +10:00
Dion Moult 13eb8562e0 Add support for converting IFC spaces, systems, and feed / location / part relationships into Brick. Brick utilities can now get downstream equipment, and system util can now take into account port flow direction. 2023-08-18 10:25:06 +10:00
rileywong311 c022a12835 Support displaying Brick Bnode relations
- Important for Ifc External References and other data.
- Set predicate_uri of predicates of the Bnode to None, which is used to remove the ability to delete the relationship in the UI.
- Also added try/excepts to difficult edge cases such as the object_uri not having a "#" character in it (such as "<http://qudt.org/vocab/unit/>") and the object_uri not being a brick class, which breaks "view_brick_item".
- Removing a Bnode type from the entity's relation removes all relations of the Bnode also
2023-08-17 16:19:49 -07:00
rileywong311 04eb47ff65 HIde Brick "Create Relation:" when not toggled 2023-08-17 16:19:49 -07:00
rileywong311 10ecd16f0c Refresh Brick split screen on ConvertIfcToBrick 2023-08-17 16:19:49 -07:00
rileywong311 75852bfa75 Rework "view brick item" conditional access
Change from never show the operator if the relation is "type" to not showing it only if the "type" is the current class being viewed.

This may come in handy if other list views other than hierarchy are implemented.

It also implies why we don't want to show "view brick item" here: because we already viewing it.
2023-08-17 16:19:49 -07:00
rileywong311 750c720c9b Support Add Brick with IfcEntity selected (part 2 aka references)
Can now assign references again.
See https://github.com/BrickSchema/Brick/tree/master/examples/ifc for examples.
2023-08-17 16:19:49 -07:00
rileywong311 4697681fcb Support Add Brick with IfcEntity selected (part 1)
Next is to add the reference
2023-08-17 16:19:49 -07:00
Dion Moult 9398363cbf Fix #3589. Users can now configure how the null values are represented in IfcCSV. 2023-08-18 08:50:48 +10:00
Massimo Fabbro ae45e6b960 Clone opening initial draft 2023-08-18 08:30:36 +10:00
Gorgious 88c002703e Move profile data and inverse check in data class 2023-08-17 21:00:03 +02:00
Gorgious56 51c78a89bd simplify ifc class suggestions json file and operator 2023-08-17 17:31:00 +02:00
Gorgious56 e36a958c0d Scene properties : Highlight buttons to make them stand out more (#3598) 2023-08-17 16:56:26 +02:00
Gorgious56 a8126a6f90 You can now purge unused profiles 2023-08-17 16:26:21 +02:00
Dion Moult 9b857cdefa Fix #3596. Fix bug where objects marked as edited were not unmarked after saving when toggling mode. Also fix bug where updating representation didn't check whether or not the meshlike representation has openings applied (which would bake the openings, instead of leaving them dynamic). 2023-08-18 00:04:02 +10:00
Gorgious56 90e6400115 Active Profile's inverse relationships count is now displayed in the profiles editor 2023-08-17 15:44:37 +02:00
Dion Moult 2dcfcf9708 Fix #3591. Fix bug where IfcCSV import was not symmetric capability wise. Now you can set all sorts of complex values with the exact same keys you are using to query. 2023-08-17 23:33:40 +10:00
Thomas Krijnen f4971da42e #3429 Fix b operand non-planar face check 2023-08-17 11:28:02 +02:00
Andrej730 008f953b6c Fix tests errors after 8689164
Error was AttributeError: module 'blenderbim.bim.handler' has no attribute 'setDefaultProperties'
2023-08-17 13:18:27 +05:00
Andrej730 5481237480 geometry.edit_object_placement was producing orphaned placements
It was producing orphaned IfcLocalPlacement if placement was parent to some other one. During edit_object_placement we were replacing all parent references with the new placement but wasn't removing the old one.
2023-08-17 13:18:27 +05:00
Andrej730 4f7564e5ae misspelling 😅 2023-08-17 13:18:27 +05:00
Dion Moult d214cbaece Fix #3590. Update dev instructions to symlink other utility libraries in BBIM. 2023-08-17 17:37:37 +10:00
Dion Moult d0c5f1e69f Fix #2626. New IFC overrides for setting object origins. 2023-08-17 17:25:40 +10:00
Massimo Fabbro 4ba3240fb4 fix #3213 Problem related with sheet created on windows and opened on Linux 2023-08-17 16:22:16 +10:00
rileywong311 6d8e5e4c83 Merge Brick classification
https://github.com/rileywong311/BrickIfcClassification
2023-08-17 16:21:29 +10:00
rileywong311 cd1402570b Merge IfcClassification
https://github.com/Moult/IfcClassification/tree/master
2023-08-17 16:21:29 +10:00
Sigma Dimensions (Yass) 6c0e528dd2 enable an object to take part in an object type decomposition #3592 2023-08-16 16:08:27 +01:00
Dion Moult 297a513d21 Don't strictly require guids / class filters that exist in new facet selector. 2023-08-16 14:05:26 +10:00
Andrej730 9f0a4ba064 Calculate/guess quantity descriptions to avoid confusion 2023-08-15 14:42:31 +05:00
Dion Moult 8813ed66e0 See #3582. Fix bug where switching representations didn't handle curves in a body / model_view.
This means that if you created a curve body, you couldn't switch to it, or if you deleted everything, you ended up with an empty object. If you then tried to switch to that, it would "sync" the empty object and leave you with an empty cartesian point list. E.g:

 1. Create cube and assign IFC element.
 2. Remove all faces only (leaving edges and verts). Save changes.
 3. Switch to representation (bug!)
 4. Delete everything (no verts, edges, or faces). Attempt to save.
 5. "Revert" switch will fail, leading to empty mesh.
 6. Manually try to switch. Empty mesh will sync, creating an empty point list.
2023-08-15 18:06:16 +10:00
Andrej730 fafcd5dfca root.copy_class to remove connections from previously connected ports 2023-08-15 10:35:36 +05:00
Carlos Dias 155a8b79da Merge branch 'IfcOpenShell:v0.7.0' into v0.7.0 2023-08-14 22:10:54 -03:00
Dion Moult f523870b57 Fix #3584. Support matching enumerated properties in new facet selector. 2023-08-15 09:59:50 +10:00
Sigma Dimensions (Yass) 2ed24434c8 experimental import plot coordinates 2023-08-14 18:22:11 +01:00
Andrej730 6878b2c072 Handle ods schedules without column styles #3573
Turned out there are ODS files (and turned out we're producing them ourselves from ifccsv) where column styles are not defined and therefore no width/style assigned to each column which was resulting in errors on building schedule.
2023-08-14 17:46:10 +05:00
Dion Moult 0ed8dc970d New simplified IfcFM parser for basic object data. 2023-08-14 16:14:57 +10:00
Andrej730 bc85f09109 Small info message on using validate 2023-08-14 11:09:26 +05:00
Andrej730 793b28e43b Remove empty IfcDocumentInformationRelationship #3581 2023-08-14 11:09:26 +05:00
Dion Moult ed4abacd1e Fix #3578. Support querying surface styles in tools like IfcCSV. 2023-08-14 11:55:15 +10:00
Dion Moult 8bf55ab46c See #3561. Misc annotation decorator now only highlights non-internal edges. 2023-08-13 23:34:06 +10:00
Dion Moult 7edabce657 Fix #3562. Non-manifold geometry is no longer considered in join criteria as there is no such thing as a closed polygon. 2023-08-13 23:04:32 +10:00
Sigma Dimensions (Yass) 56187e4da3 Improve Cost Schedule Layout 2023-08-13 13:23:31 +01:00
Sigma Dimensions (Yass) 31a12faef1 woopsies 2023-08-13 12:18:48 +01:00
Sigma Dimensions (Yass) eb94c16465 fix the bug fix #07b5c49 - where counting objects controlled by a cost item stopped working 2023-08-13 12:16:29 +01:00
Dion Moult e8437a9c54 Fix #3253. Bug where duplicating elements with fillings incorrectly recreated fillings at the cursor (i.e. the cursor was used as a target destination).
Now, instead of trying to be clever and regenerating filling locations and opening types, we simply copy the existing scenario. This also means it'll be more stable for weird fillings coming from proprietary apps.
2023-08-13 20:07:33 +10:00
Dion Moult 840039ef1e Fix #3472. You can now toggle selectability of linked IFCs. 2023-08-13 15:49:23 +10:00
Dion Moult 84a749605e See #3579. Support Google Colab as an installation option. 2023-08-13 14:48:49 +10:00
Bruno Postle c1283d2ab0 fix SyntaxError: '(' was never closed 2023-08-12 21:36:40 +01:00
Sigma Dimensions (Yass) 07b5c4918b fix api logic where only quantities of the same type can be assignmed to a cost control 2023-08-12 15:17:32 +01:00
Sigma Dimensions (Yass) 703f0a6b70 fix orphaned data when unassigning element quantities from cost item control 2023-08-12 14:57:01 +01:00
Dion Moult 5e98dee46f Fix #3554. Support editing IfcIndexedPolyCurve profiles where IfcLineIndex has >2 indices. 2023-08-12 22:25:34 +10:00
Sigma Dimensions (Yass) b2eddf55d2 Fix contracting/expanding cost schedule tree and add tests to avoid proof changes 2023-08-12 12:58:16 +01:00
Massimo Fabbro db7be640d4 Fix #3569 Variable name override caused the bug (#3575) 2023-08-12 19:03:58 +10:00
Dion Moult 04fa597844 Fix #3564. Bug where IfcBuiltSystems didn't show up due to missing icon in IFC4X3. 2023-08-12 17:09:48 +10:00
Dion Moult 694dbeb96c Fix #3576. Explicit drawing annotations are now immune to "include / exclude" filters. 2023-08-12 17:06:22 +10:00
Dion Moult 7afd358c3c Fix #3556. Bug where quantities could not be imported using IfcCSV. Also make IfcCSV support any qset name, not just "Qto" prefixed names. 2023-08-12 16:41:57 +10:00
Dion Moult b0a5a570a9 IfcCSV now uses the new filter query system. 2023-08-12 16:40:59 +10:00
Dion Moult c45b84c850 Fix #3577. Don't force uppercase in schedules. 2023-08-12 16:16:51 +10:00
rileywong311 d575a43aee Make refresh Brick viewer refresh both screens 2023-08-11 16:14:41 -07:00
rileywong311 0b151ea559 Second level query for Brick equipment+point roots 2023-08-11 16:14:41 -07:00
rileywong311 926970b256 Polish Brick UI and add "last saved" label 2023-08-11 16:14:41 -07:00
rileywong311 d58af0bfa5 Filter Brick class if deprecated 2023-08-11 16:14:41 -07:00
rileywong311 429da7c6f3 Filter more (all) Brick ontology namespaces
I'm not sure if this method of filtering out the entire domain will cause problems. This could use investigation.
2023-08-11 16:14:41 -07:00
rileywong311 60a7433573 Clear Brick breadcrumbs on close project 2023-08-11 16:14:41 -07:00
rileywong311 08a5d444d4 Remove another needless BrickStore.graph.triples check
As before, this returns a generator type which always evaluates true, even if it is empty.
2023-08-11 16:14:41 -07:00
rileywong311 1982069b69 Add description to Brick operators 2023-08-11 16:14:41 -07:00
rileywong311 7c06c65df3 Move Brick "set root" button and change roots on update instead of operator 2023-08-11 16:14:41 -07:00
Sigma Dimensions (Yass) 6263766202 fix Cost schedule of rates UI bug where linked types dont't refresh correctly (and black formatting) 2023-08-11 14:57:43 +01:00
Dion Moult 66fbbeff54 See #3561. Fix bug where schedules couldn't be removed. 2023-08-11 22:47:42 +10:00
Dion Moult 3c5ad1328f Auto create relevant context when attempting to add an annotation to make it friendlier for new users. 2023-08-11 22:41:47 +10:00
Dion Moult 3345d7e088 Fix #2894. Implement reflected ceiling plans. Note that this requires new bot build. 2023-08-11 22:41:47 +10:00
Dion Moult 2bb25cb874 See #3501. Minor matrix math fix that caused annotations to not appear in global coordinates. 2023-08-11 22:41:47 +10:00
Dion Moult 444a728e7a Improve default search filter to include types and spatial elements. 2023-08-11 22:41:47 +10:00
Sigma Dimensions (Yass) 70bea0c865 Feature to add a camera to extents of the scene objects 2023-08-11 13:39:58 +01:00
Sigma Dimensions (Yass) 6eea1e9035 feature to clear previous 4D Animation (object keyframes & colors) 2023-08-11 13:36:14 +01:00
Kristoffer Andersen 4deb1836ad Update bld.bat
reduce number of schema variations on windows as well
2023-08-11 11:42:08 +02:00
Kristoffer Andersen 4c8007dcfe Update meta.yaml
pin compatible boost, occt and cgal-cpp
2023-08-11 08:44:25 +02:00
Kristoffer Andersen 000116bb3d Update build.sh
limit number of schema variants
2023-08-11 08:22:52 +02:00
Dion Moult a2b6f21c7a See #2894. Implement SVG mirroring options (typically used for RCPs or useful when converting to other things like DXF). 2023-08-11 16:01:01 +10:00
Sigma Dimensions (Yass) a374ee39d3 simplify setup_default_task_columns 2023-08-11 02:06:49 +01:00
Sigma Dimensions (Yass) 0f5c82dffd animation settings now use human readable durations to make it less confusing 2023-08-11 02:05:37 +01:00
Sigma Dimensions (Yass) 268fed761e simplify updateTaskDuration 2023-08-11 02:03:07 +01:00
Sigma Dimensions (Yass) fe01d252db WIP - Scheduling UI improvements 2023-08-10 22:07:34 +01:00
Sigma Dimensions (Yass) dc36d74337 Hide Header for Costing and Schedule Tab 2023-08-10 16:36:35 +01:00
Sigma Dimensions (Yass) 7a0da7bd8e Revert "Rename and move sub panels of Costing and Schedule Tab"
This reverts commit 9d4715c0a9.
2023-08-10 16:20:13 +01:00
Andrej730 530d5a4e63 plug add_transition to fit_flow_segments 2023-08-10 17:37:52 +05:00
Andrej730 2d847d1704 Adjusting connected elements on bim.regenerate_distribution_element
Basically commented out automatic adjustment after 45f81b478 - now it's hapenning only when you decide to regenerate it.

It's adjusting connected segments extrusion and location, for all other elements besides segments it's adjusting only location. The idea is it will try to change as less as possible.

Demo - https://imgur.com/oZ1I2Bl
2023-08-10 16:31:08 +05:00
Andrej730 631d8fcfb1 quickly connect mep elements if they have matching ports
Demo - https://imgur.com/a/o9u8M1u
2023-08-10 15:06:16 +05:00
Andrej730 a8888efb88 fixed bug getting port relating element in ifc2x3 2023-08-10 14:12:20 +05:00
Andrej730 a51ecea9b1 More descriptive error message on attempt to delete drawings category 2023-08-10 13:55:46 +05:00
Andrej730 4d49517111 small refactor 2023-08-10 13:49:35 +05:00
Thomas Krijnen d5d1b3567b Update draw.py --include-curves 2023-08-10 15:44:21 +08:00
Kristoffer Andersen 7827ce3448 Update build.sh
fix env var declaration
2023-08-10 08:22:07 +02:00
Kristoffer Andersen a05059db01 Update build.sh
Reduce number of schema versions
2023-08-10 08:06:38 +02:00
Thomas Krijnen b78490ca56 #3510 Sample points along diagonal as well for quick and dirty occlusion detection 2023-08-10 10:52:06 +08:00
Carlos Dias b46ccc9b78 Merge branch 'IfcOpenShell:v0.7.0' into v0.7.0 2023-08-06 11:21:39 -03:00
c4rlosdias 4f1e21dfb0 Add element guId in BCF report 2023-08-05 10:50:27 -03:00
568 changed files with 1132812 additions and 9989 deletions
+57
View File
@@ -0,0 +1,57 @@
name: ci-bcf-pypi
on:
schedule:
# ┌───────────── minute (0 - 59)
# │ ┌───────────── hour (0 - 23)
# │ │ ┌───────────── day of the month (1 - 31)
# │ │ │ ┌───────────── month (1 - 12 or JAN-DEC)
# │ │ │ │ ┌───────────── day of the week (0 - 6 or SUN-SAT)
# * * * * *
- cron: "0 0 18 * *"
push:
paths:
- '.github/workflows/ci-bcf-pypi.yml'
workflow_dispatch:
env:
major: 0
minor: 0
name: ifcopenshell
jobs:
activate:
runs-on: ubuntu-latest
if: |
github.repository == 'IfcOpenShell/IfcOpenShell'
steps:
- name: Set env
run: echo ok go
build:
needs: activate
name: ${{ matrix.config.name }}-${{ matrix.pyver }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
steps:
- uses: actions/checkout@v2 # https://github.com/actions/checkout
- uses: actions/setup-python@v2 # https://github.com/actions/setup-python
with:
python-version: '3.10' # Version range or exact version of a Python version to use, using SemVer's version range syntax
architecture: 'x64' # optional x64 or x86. Defaults to x64 if not specified
- run: echo ${{ env.DATE }}
- name: Get current date
id: date
run: echo "::set-output name=date::$(date +'%y%m%d')"
- name: Compile
run: |
pip install build
cd src/bcf &&
make dist
- name: Publish a Python distribution to PyPI
uses: ortega2247/pypi-upload-action@master
with:
user: __token__
password: ${{ secrets.PYPI_API_TOKEN }}
packages_dir: src/bcf/dist
-37
View File
@@ -1,37 +0,0 @@
name: ci-bcf
on:
push:
jobs:
activate:
runs-on: ubuntu-latest
if: |
github.repository == 'IfcOpenShell/IfcOpenShell' &&
contains(github.event.head_commit.message, '[bcf release]')
steps:
- run: echo ok go
upload:
needs: activate
name: Upload BCF package to Pypi
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
with:
python-version: '3.10'
- name: Build package
run: |
cd src/bcf
pip install build
python -m build
- name: Test
run: |
cd src/bcf
make test
- name: Publish package
uses: pypa/gh-action-pypi-publish@release/v1
with:
user: __token__
password: ${{ secrets.PYPI_TOKEN }}
packages_dir: src/bcf/dist
+1 -1
View File
@@ -95,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 -1
View File
@@ -121,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
+4 -1
View File
@@ -96,5 +96,8 @@ src/ifcopenshell-python/ifcopenshell/ifcopenshell_wrapper.py
# apple
.DS_Store
# clangd cache
.cache
# Brickschema
src/blenderbim/blenderbim/bim/schema/Brick.ttl
src/blenderbim/blenderbim/bim/schema/Brick.ttl
-1
View File
@@ -55,7 +55,6 @@ Those marked with an asterisk are part of IfcOpenShell.
| ifcblender | Historic Blender IFC import add-on | LGPL-3.0-or-later\* |
| ifccityjson | Convert CityJSON to IFC | LGPL-3.0-or-later |
| ifcclash | Clash detection library and CLI app | LGPL-3.0-or-later |
| ifccobie | Extract IFC data for COBie handover requirements | LGPL-3.0-or-later |
| ifcconvert | CLI app to convert IFC to many other formats | LGPL-3.0-or-later\* |
| ifccsv | Library and CLI app to export and import schedules from IFC | LGPL-3.0-or-later |
| ifcdiff | Compare changes between IFC models | LGPL-3.0-or-later |
+645 -585
View File
File diff suppressed because it is too large Load Diff
+43
View File
@@ -0,0 +1,43 @@
################################################################################
# #
# This file is part of IfcOpenShell. #
# #
# IfcOpenShell is free software: you can redistribute it and/or modify #
# it under the terms of the Lesser GNU General Public License as published by #
# the Free Software Foundation, either version 3.0 of the License, or #
# (at your option) any later version. #
# #
# IfcOpenShell is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# Lesser GNU General Public License for more details. #
# #
# You should have received a copy of the Lesser GNU General Public License #
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
# #
################################################################################
if(NOT EXISTS "@CMAKE_BINARY_DIR@/install_manifest.txt")
message(FATAL_ERROR "Cannot find install manifest: @CMAKE_BINARY_DIR@/install_manifest.txt")
endif()
file(READ "@CMAKE_BINARY_DIR@/install_manifest.txt" files)
string(REGEX REPLACE "\n" ";" files "${files}")
foreach(file ${files})
message(STATUS "Uninstalling $ENV{DESTDIR}${file}")
if(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}")
exec_program(
"@CMAKE_COMMAND@" ARGS "-E remove \"$ENV{DESTDIR}${file}\""
OUTPUT_VARIABLE rm_out
RETURN_VALUE rm_retval
)
if(NOT "${rm_retval}" STREQUAL 0)
message(FATAL_ERROR "Problem when removing $ENV{DESTDIR}${file}")
endif()
else(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}")
message(STATUS "File $ENV{DESTDIR}${file} does not exist.")
endif()
endforeach()
+1
View File
@@ -4,6 +4,7 @@ set MY_PY_VER=%PY_VER:.=%
set LIBXML2="%LIBRARY_PREFIX%/lib/libxml2.lib"
cmake -G "Ninja" ^
-D SCHEMA_VERSIONS="2x3;4;4x1;4x3;4x3_add1" ^
-D CMAKE_BUILD_TYPE:STRING=Release ^
-D CMAKE_INSTALL_PREFIX:FILEPATH="%LIBRARY_PREFIX%" ^
-D CMAKE_PREFIX_PATH:FILEPATH="%LIBRARY_PREFIX%" ^
+1
View File
@@ -13,6 +13,7 @@ if [ `uname` == Darwin ]; then
fi
cmake -G Ninja \
-DSCHEMA_VERSIONS="2x3;4;4x1;4x3;4x3_add1" \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX=$PREFIX \
${CMAKE_PLATFORM_FLAGS[@]} \
+3 -3
View File
@@ -36,10 +36,10 @@ requirements:
run:
- python
- boost-cpp
- occt ==7.7.0
- {{ pin_compatible('occt', max_pin='x.x.x') }}
- {{ pin_compatible('cgal-cpp', max_pin='x.x.x') }}
- {{ pin_compatible('boost-cpp', max_pin='x.x.x') }}
- libxml2
- cgal-cpp
- hdf5
- mpfr
- gmp # [unix]
+14 -1
View File
@@ -1,3 +1,9 @@
VERSION:=`date '+%y%m%d'`
SED:=sed -i
ifeq ($(UNAME_S),Darwin)
SED:=sed -i '' -e
endif
.PHONY: ci
ci:
tox
@@ -12,6 +18,13 @@ models:
cd src && xsdata generate -p bcf.v2.model --unnest-classes --kw-only --slots -ds Google bcf/v2/xsd
cd src && xsdata generate -p bcf.v3.model --unnest-classes --kw-only --slots -ds Google bcf/v3/xsd
.PHONY: dist
dist:
rm -rf dist
$(SED) "s/999999/$(VERSION)/" pyproject.toml
python -m build
$(SED) "s/$(VERSION)/999999/" pyproject.toml
# .PHONY
# api:
# openapi-python-client generate --url https://api.swaggerhub.com/apis/buildingSMART/BCF/3.0
# openapi-python-client generate --url https://api.swaggerhub.com/apis/buildingSMART/BCF/3.0
+3 -82
View File
@@ -1,84 +1,5 @@
# bcf
A simple Python implementation of BCF.
Manipulation of BCF-XML is available via `bcfxml.py` and manipulation of BCF-API
is available via `bcfapi.py`.
It tries to support BCF-XML version 2.1 and 3.0, and BCF-API 3.0.
## bcfxml
The `bcfxml.load` function lets you read a BCF-XML file.
It takes care of using the right version based on the "bcf.version" file contained in the BCF package.
The BCF files are extracted and parsed on-demand, and edits are stored in memory until you call the `save` method.
```python
from bcf.bcfxml import load
# Load a project
with load("/path/to/file.bcf") as bcfxml:
project = bcfxml.project
print(project.name)
# To edit a project, just modify the object directly
bcfxml.project.name = "New name"
# Get a dictionary of topics
topics = bcfxml.topics
for topic_guid, topic_handler in bcfxml.topics.items():
topic = topic_handler.topic
print("Topic guid is", topic.guid)
print("Topic title is", topic.title)
# Fetch extra data about a topic
header = topic_handler.header
comments = topic_handler.comments
viewpoints = topic_handler.viewpoints
for comment in comments:
print(comment.guid)
print(comment.comment)
print(comment.author)
# Get a particular topic
topic = bcfxml.get_topic(guid)
# Modify a topic
topic.title = "New title"
bcfxml.save()
```
## bcfapi
The `bcfapi` module lets you interact with the BCF-API standard.
```python
from bcf.v3.bcfapi import FoundationClient, BcfClient
foundation_client = FoundationClient("YOUR_CLIENT_ID", "YOUR_CLIENT_SECRET", "OPENCDE_BASEURL")
auth_methods = foundation_client.get_auth_methods()
# Our library currently only implements the authorization_code flow
if "authorization_code" in auth_methods:
foundation_client.login()
bcf_client = BcfClient(foundation_client)
versions = foundation_client.get_versions()
for version in versions:
if "3.0" in versions:
if version["api_id"] == "bcf" and version["version_id"] == "3.0":
bcf_client.set_version(version)
data = bcf_client.get_projects()
print(data)
project_id = data[0]["project_id"]
print(project_id)
data = bcf_client.get_project(project_id)
print(data)
data = bcf_client.get_extensions(project_id)
print(data)
```
A simple Python implementation of the BCF standard. Manipulation of BCF-XML is
available via `bcfxml.py` and manipulation of BCF-API is available via
`bcfapi.py`.
+3 -3
View File
@@ -17,7 +17,7 @@ dependencies = [
"numpy",
"ifcopenshell",
]
version = "0.0.1"
version = "0.0.999999"
classifiers = [
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
"Operating System :: OS Independent",
@@ -76,7 +76,7 @@ deps =
black
isort
pylint
commands =
commands =
black {posargs:.}
isort {posargs:.}
pylint {posargs:.} --output-format=colorized
@@ -138,4 +138,4 @@ max-attributes = 10
[tool.pylint.format]
expected-line-ending-format = "LF"
max-line-length = 120
max-line-length = 120
+26 -36
View File
@@ -38,22 +38,25 @@ endif
VERSION:=`date '+%y%m%d'`
LAST_COMMIT_HASH:=$(shell git rev-parse HEAD)
PYVERSION:=py310
PYPI_IMP:=cp
ifeq ($(PYVERSION), py39)
PYLIBDIR:=python3.9
PYNUMBER:=39
PYPI_VERSION:=3.9
endif
ifeq ($(PYVERSION), py310)
PYLIBDIR:=python3.10
PYNUMBER:=310
PYPI_VERSION:=3.10
endif
ifeq ($(PLATFORM), linux)
PYPI_PLATFORM:=--platform manylinux_2_17_x86_64
ifeq ($(PYVERSION), py39)
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/2.3.4/download/linux-64/hpp-fcl-2.3.4-py39h40a70d0_0.conda
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/3.1.0/download/linux-64/eigenpy-3.1.0-py39hdfdd6bb_0.conda
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/linux-64/boost-1.78.0-py39h7c9e3ff_4.tar.bz2
QHULL_URL:=https://anaconda.org/conda-forge/qhull/2020.2/download/linux-64/qhull-2020.2-h4bd325d_2.tar.bz2
LXML_URL:=https://files.pythonhosted.org/packages/19/d9/a69c6aff5673554df48120565a14a50eaa41d29ae03b02faa0b023666318/lxml-4.6.3-cp39-cp39-manylinux2014_x86_64.whl
SHAPELY_URL:=https://files.pythonhosted.org/packages/2d/f2/8ec281d357e8bb7d08dc8d727f0e4c8ef3dae7d3fa75c69c8e452bb82d50/shapely-2.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
PILLOW_URL:=https://files.pythonhosted.org/packages/01/61/3ff85fb4bb596ce3d223c8fcf93c8df5c12bc8899dfb4fb3cb1c5b20dd5f/Pillow-9.2.0-cp39-cp39-manylinux_2_28_x86_64.whl
@@ -62,23 +65,23 @@ ifeq ($(PYVERSION), py310)
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/2.3.4/download/linux-64/hpp-fcl-2.3.4-py310h995690b_0.conda
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/3.1.0/download/linux-64/eigenpy-3.1.0-py310hf02b7e0_0.conda
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/linux-64/boost-1.78.0-py310hc4a4660_4.tar.bz2
QHULL_URL:=https://anaconda.org/conda-forge/qhull/2020.2/download/linux-64/qhull-2020.2-h4bd325d_2.tar.bz2
LXML_URL:=https://files.pythonhosted.org/packages/25/1e/19b46d8e8881fe0df2e20945d51919eeb1817836d62a90efa8506530e45c/lxml-4.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl
SHAPELY_URL:=https://files.pythonhosted.org/packages/a8/a5/403728b5614b28083f6424dfdefec5fcf58068495fb03bb08532671c642f/shapely-2.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
PILLOW_URL:=https://files.pythonhosted.org/packages/f6/51/320986ebd6d46a0e95c2240468ced73153b691ce07617078bcdf30c609ec/Pillow-9.2.0-cp310-cp310-manylinux_2_28_x86_64.whl
endif
ASSIMP_URL:=https://anaconda.org/conda-forge/assimp/5.0.1/download/linux-64/assimp-5.0.1-hedfc422_6.tar.bz2
QHULL_URL:=https://anaconda.org/conda-forge/qhull/2020.2/download/linux-64/qhull-2020.2-h4bd325d_2.tar.bz2
OCTOMAP_URL:=https://anaconda.org/conda-forge/octomap/1.9.8/download/linux-64/octomap-1.9.8-h924138e_0.tar.bz2
BOOSTCPP_URL:=https://anaconda.org/conda-forge/boost-cpp/1.78.0/download/linux-64/boost-cpp-1.78.0-h6582d0a_3.conda
ZLIB_URL:=https://anaconda.org/conda-forge/zlib/1.2.11/download/linux-64/zlib-1.2.11-h516909a_1010.tar.bz2
endif
ifeq ($(PLATFORM), macos)
PYPI_PLATFORM:=--platform macosx_10_9_x86_64
ifeq ($(PYVERSION), py39)
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/2.3.4/download/osx-64/hpp-fcl-2.3.4-py39hd85b194_0.conda
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/3.1.0/download/osx-64/eigenpy-3.1.0-py39hc4d6e28_0.conda
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/osx-64/boost-1.78.0-py39h953a6b8_4.tar.bz2
QHULL_URL:=https://anaconda.org/conda-forge/qhull/2020.2/download/osx-64/qhull-2020.2-h940c156_2.tar.bz2
LXML_URL:=https://files.pythonhosted.org/packages/b8/74/a71f7ad72e8db54ce899efab84507b801660750cbbfa6a39e6717557d36a/lxml-4.6.3-cp39-cp39-macosx_10_9_x86_64.whl
SHAPELY_URL:=https://files.pythonhosted.org/packages/36/a4/7e542a209f862f967d7cb8e939eff155f4294a27d17e16441fb8bdd51a2c/shapely-2.0.1-cp39-cp39-macosx_10_9_x86_64.whl
PILLOW_URL:=https://files.pythonhosted.org/packages/88/7a/ddfe28b485b623361457d4783007c1f9ba83a87f93e7fec32f64793efb6c/Pillow-9.2.0-cp39-cp39-macosx_10_10_x86_64.whl
@@ -87,23 +90,23 @@ ifeq ($(PYVERSION), py310)
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/2.3.4/download/osx-64/hpp-fcl-2.3.4-py310h1db6f5f_0.conda
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/3.1.0/download/osx-64/eigenpy-3.1.0-py310h43da829_0.conda
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/osx-64/boost-1.78.0-py310h3e792ce_4.tar.bz2
QHULL_URL:=https://anaconda.org/conda-forge/qhull/2020.2/download/osx-64/qhull-2020.2-h940c156_2.tar.bz2
LXML_URL:=https://files.pythonhosted.org/packages/a1/44/17b7dac7a18807d30e2fe10c3328c152808f5464565e230bfd0e77f178c6/lxml-4.8.0-cp310-cp310-macosx_10_15_x86_64.whl
SHAPELY_URL:=https://files.pythonhosted.org/packages/1f/2a/dc3353c2431cf53e8d04bb8fba27e584410ca3435c9c85f76d71bf0c0e80/shapely-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl
PILLOW_URL:=https://files.pythonhosted.org/packages/d8/60/b13c00d403f34110e96c1b5c0afa73ce461efe3fe960c3a7e3e7fe190d82/Pillow-9.2.0-cp310-cp310-macosx_10_10_x86_64.whl
endif
ASSIMP_URL:=https://anaconda.org/conda-forge/assimp/5.0.1/download/osx-64/assimp-5.0.1-h1224e73_6.tar.bz2
QHULL_URL:=https://anaconda.org/conda-forge/qhull/2020.2/download/osx-64/qhull-2020.2-h940c156_2.tar.bz2
OCTOMAP_URL:=https://anaconda.org/conda-forge/octomap/1.9.8/download/osx-64/octomap-1.9.8-hb8565cd_0.tar.bz2
BOOSTCPP_URL:=https://anaconda.org/conda-forge/boost-cpp/1.78.0/download/osx-64/boost-cpp-1.78.0-hf5ba120_3.conda
ZLIB_URL:=https://anaconda.org/conda-forge/zlib/1.2.11/download/osx-64/zlib-1.2.11-h7795811_1010.tar.bz2
endif
ifeq ($(PLATFORM), macosm1)
PYPI_PLATFORM:=--platform macosx_11_0_arm64
ifeq ($(PYVERSION), py39)
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/2.3.4/download/osx-arm64/hpp-fcl-2.3.4-py39hc34188a_0.conda
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/3.1.0/download/osx-arm64/eigenpy-3.1.0-py39h13cfc01_0.conda
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/osx-arm64/boost-1.78.0-py39h99de9ae_4.tar.bz2
QHULL_URL:=https://anaconda.org/conda-forge/qhull/2020.2/download/osx-arm64/qhull-2020.2-hc021e02_2.tar.bz2
LXML_URL:=https://anaconda.org/conda-forge/lxml/4.9.1/download/osx-arm64/lxml-4.9.1-py39h9eb174b_0.tar.bz2
SHAPELY_URL:=https://files.pythonhosted.org/packages/ea/aa/45fbd031edf3149cb767d8b9f9db45d5faf0324d743c6b8fb0298cc022d0/shapely-2.0.1-cp39-cp39-macosx_11_0_arm64.whl
PILLOW_URL:=https://files.pythonhosted.org/packages/aa/bc/21097cd891dd2fa02f2b3d767e02e883e026482e59d29975d1bc30024aa3/Pillow-9.2.0-cp39-cp39-macosx_11_0_arm64.whl
@@ -112,18 +115,19 @@ ifeq ($(PYVERSION), py310)
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/2.3.4/download/osx-arm64/hpp-fcl-2.3.4-py310h46fc4cd_0.conda
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/3.1.0/download/osx-arm64/eigenpy-3.1.0-py310ha2643af_0.conda
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/osx-arm64/boost-1.78.0-py310h629746b_4.tar.bz2
QHULL_URL:=https://anaconda.org/conda-forge/qhull/2020.2/download/osx-arm64/qhull-2020.2-hc021e02_2.tar.bz2
LXML_URL:=https://anaconda.org/conda-forge/lxml/4.9.1/download/osx-arm64/lxml-4.9.1-py310h02f21da_0.tar.bz2
SHAPELY_URL:=https://files.pythonhosted.org/packages/ec/41/d59208743e737184e1b403e95a937aebb022b8459e99efbcd5208fc8be46/shapely-2.0.1-cp310-cp310-macosx_11_0_arm64.whl
PILLOW_URL:=https://files.pythonhosted.org/packages/0c/5f/117b653cad585f3aedfe0de996c292e67d4b020ed77f652e5a6c8c24f908/Pillow-9.2.0-cp310-cp310-macosx_11_0_arm64.whl
endif
ASSIMP_URL:=https://anaconda.org/conda-forge/assimp/5.0.1/download/osx-arm64/assimp-5.0.1-h0f81e16_7.tar.bz2
QHULL_URL:=https://anaconda.org/conda-forge/qhull/2020.2/download/osx-arm64/qhull-2020.2-hc021e02_2.tar.bz2
OCTOMAP_URL:=https://anaconda.org/conda-forge/octomap/1.9.8/download/osx-arm64/octomap-1.9.8-hffc8910_0.tar.bz2
BOOSTCPP_URL:=https://anaconda.org/conda-forge/boost-cpp/1.78.0/download/osx-arm64/boost-cpp-1.78.0-h9ed8d21_3.conda
ZLIB_URL:=https://anaconda.org/conda-forge/zlib/1.2.11/download/osx-arm64/zlib-1.2.11-h90dfc92_1014.tar.bz2
endif
ifeq ($(PLATFORM), win)
PYPI_PLATFORM:=--platform win_amd64
ifeq ($(PYVERSION), py39)
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/2.3.4/download/win-64/hpp-fcl-2.3.4-py39h39b25cb_0.conda
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/3.1.0/download/win-64/eigenpy-3.1.0-py39hb6c915b_0.conda
@@ -140,7 +144,8 @@ LXML_URL:=https://files.pythonhosted.org/packages/f6/71/65c80a4caa1617a4c6e8fe15
SHAPELY_URL:=https://files.pythonhosted.org/packages/81/8a/7ac076a86b2632f1872284c5e60ed5f2fc26094875a85b35d9fa17b52504/shapely-2.0.1-cp310-cp310-win_amd64.whl
PILLOW_URL:=https://files.pythonhosted.org/packages/02/55/67a3c17b9e7d972ed8c246f104da99ca4f3ea42fba566697e479011b84b6/Pillow-9.2.0-cp310-cp310-win_amd64.whl
endif
ASSIMP_URL:=https://anaconda.org/conda-forge/assimp/5.0.1/download/win-64/assimp-5.0.1-hc2aa0de_6.tar.bz2
ASSIMP_URL:=https://anaconda.org/conda-forge/assimp/5.2.5/download/win-64/assimp-5.2.5-h4dcb625_0.tar.bz2
QHULL_URL:=https://anaconda.org/conda-forge/qhull/2020.2/download/win-64/qhull-2020.2-h70d2c02_2.tar.bz2
OCTOMAP_URL:=https://anaconda.org/conda-forge/octomap/1.9.8/download/win-64/octomap-1.9.8-h91493d7_0.tar.bz2
BOOSTCPP_URL:=https://anaconda.org/conda-forge/boost-cpp/1.78.0/download/win-64/boost-cpp-1.78.0-h9f4b32c_3.conda
ZLIB_URL:=https://anaconda.org/conda-forge/zlib/1.2.11/download/win-64/zlib-1.2.11-h62dcd97_1010.tar.bz2
@@ -164,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-fc50bdd-$(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/
@@ -194,8 +199,10 @@ endif
cp -r dist/working/IfcOpenShell-0.7.0/src/ifcbimtester/bimtester dist/blenderbim/libs/site/packages/
# Provides IFCTester functionality
cp -r dist/working/IfcOpenShell-0.7.0/src/ifctester/ifctester dist/blenderbim/libs/site/packages/
# Provides IFCCOBie functionality
cp -r dist/working/IfcOpenShell-0.7.0/src/ifccobie/* dist/blenderbim/libs/site/packages/
# Provides IFCFM functionality
cp -r dist/working/IfcOpenShell-0.7.0/src/ifcfm/ifcfm dist/blenderbim/libs/site/packages/
# Provides bSDD functionality
cp -r dist/working/IfcOpenShell-0.7.0/src/bsdd/* dist/blenderbim/libs/site/packages/
# Provides IFCDiff functionality
cp -r dist/working/IfcOpenShell-0.7.0/src/ifcdiff/* dist/blenderbim/libs/site/packages/
# Provides IFCCSV functionality
@@ -376,6 +383,10 @@ endif
mkdir dist/working
cd dist/working && $(PYTHON) -m venv env
cd dist/working && mkdir site-packages
# Required by IfcCSV
cd dist/working && . env/bin/activate && $(PIP) install pandas $(PYPI_PLATFORM) --python-version $(PYPI_VERSION) --implementation $(PYPI_IMP) --only-binary=:all: --target=./site-packages
cd dist/working && . env/bin/activate && $(PIP) install openpyxl --target=./site-packages
cd dist/working && . env/bin/activate && $(PIP) install odfpy --target=./site-packages
# Required by IFCCityJSON
cd dist/working && . env/bin/activate && $(PIP) install cjio --target=./site-packages
# Provides express rule validation for ifcopenshell.validate
@@ -426,8 +437,7 @@ ifeq ($(PLATFORM), win)
endif
rm -rf dist/working
# Required by hpp-fcl except on Windows
ifneq ($(PLATFORM), win)
# Required by hpp-fcl
mkdir dist/working
cd dist/working && wget $(QHULL_URL)
cd dist/working && tar -xf qhull*
@@ -440,8 +450,10 @@ endif
ifeq ($(PLATFORM), macosm1)
cp -r dist/working/lib/*.dylib dist/blenderbim/libs/
endif
rm -rf dist/working
ifeq ($(PLATFORM), win)
cp -r dist/working/Library/bin/*.dll dist/blenderbim/libs/site/packages/hppfcl/
endif
rm -rf dist/working
# Required by hpp-fcl
mkdir dist/working
@@ -476,7 +488,7 @@ ifeq ($(PLATFORM), macosm1)
cp -r dist/working/lib/*.dylib dist/blenderbim/libs/
endif
ifeq ($(PLATFORM), win)
# Uh, do nothing, apparently? No DLLs are shipped.
cp -r dist/working/Library/bin/*.dll dist/blenderbim/libs/site/packages/hppfcl/
endif
rm -rf dist/working
@@ -582,28 +594,6 @@ endif
cd dist/working/parse_type-0.5.2/ && cp -r parse_type ../../blenderbim/libs/site/packages/
rm -rf dist/working
# Required by IFCCOBie for XLSX support
# TODO: see if we can replace this with openpyxl which does both read/write
mkdir dist/working
cd dist/working && wget https://files.pythonhosted.org/packages/0c/bc/82d6783f83f65f56d8b77d052773c4a2f952fa86385f0cd54e1e006658d7/XlsxWriter-1.2.9.tar.gz
cd dist/working && tar -xzvf XlsxWriter*
cd dist/working/XlsxWriter-1.2.9/ && cp -r xlsxwriter ../../blenderbim/libs/site/packages/
rm -rf dist/working
# Required by IFCCOBie for ODS support
mkdir dist/working
cd dist/working && wget https://files.pythonhosted.org/packages/97/73/8ade73f6749177003f7ce3304f524774adda96e6aaab30ea79fd8fda7934/odfpy-1.4.1.tar.gz
cd dist/working && tar -xzvf odfpy*
cd dist/working/odfpy-1.4.1/ && cp -r odf ../../blenderbim/libs/site/packages/
rm -rf dist/working
# Required by odfpy
mkdir dist/working
cd dist/working && wget https://files.pythonhosted.org/packages/a4/5f/f8aa58ca0cf01cbcee728abc9d88bfeb74e95e6cb4334cfd5bed5673ea77/defusedxml-0.6.0.tar.gz
cd dist/working && tar -xzvf defusedxml*
cd dist/working/defusedxml-0.6.0/ && cp -r defusedxml ../../blenderbim/libs/site/packages/
rm -rf dist/working
# Required by augin
mkdir dist/working
cd dist/working && wget https://files.pythonhosted.org/packages/76/b4/b7baffbda025efd5dc8fcd8d2e953e3aa939c236a484084fa8f4c3588ee9/boto3-1.17.17.tar.gz
+22 -6
View File
@@ -30,6 +30,7 @@ modules = {
"project": None,
"search": None,
"bcf": None,
"bsdd": None,
"root": None,
"unit": None,
"model": None,
@@ -44,7 +45,7 @@ modules = {
"void": None,
"aggregate": None,
"geometry": None,
"cobie": None,
"fm": None,
"resource": None,
"cost": None,
"sequence": None,
@@ -77,6 +78,7 @@ modules = {
"augin": None,
"debug": None,
"ifcgit": None,
"covering": None,
# Uncomment this line to enable loading of the demo module. Happy hacking!
# The name "demo" must correlate to a folder name in `bim/module/`.
# "demo": None,
@@ -123,33 +125,47 @@ 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,
# Geometry and materials
ui.BIM_PT_tab_placement,
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
ui.BIM_PT_tab_sheets,
ui.BIM_PT_tab_drawings,
ui.BIM_PT_tab_schedules,
ui.BIM_PT_tab_references,
# 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
ui.BIM_PT_tab_status,
ui.BIM_PT_tab_resources,
ui.BIM_PT_tab_cost,
ui.BIM_PT_tab_sequence,
# Facility management
ui.BIM_PT_tab_handover,
ui.BIM_PT_tab_operations,
# Quality and coordination
ui.BIM_PT_tab_quality_control,
ui.BIM_PT_tab_clash_detection,
ui.BIM_PT_tab_collaboration,
ui.BIM_PT_tab_sandbox,
# TODO: move this somewhere else and clean it up
@@ -44,17 +44,29 @@
<circle r="5" fill="white" stroke="black" style="stroke-width: 0.25;" />
<line x1="-5" y1="0" x2="5" y2="0" style="stroke: black; stroke-width: 0.25;" />
</g>
<g id="SurveyArea">
<g id="dot">
<circle r="1" fill="black" stroke="black" style="stroke-width: 0.25;" />
</g>
<g id="SurveyArea-CONTROLPOINT">
<path style="fill: white; stroke: black; stroke-width: 0.25;" d="M 2.286165,1.4798666 H -1.0000316e-7 -2.2861651 L -1.1430826,-0.5000101 -1.0000316e-7,-2.479888 1.1430827,-0.5000101 Z" />
<g id="setout-tag">
<circle fill="white" stroke="black" r="2.5" style="stroke-width: 0.5"/>
<path d="M 0 0 L -2.5 0 A 2.5 2.5 0 0 1 0 -2.5 Z" fill="black"/>
<path d="M 0 0 L 2.5 0 A 2.5 2.5 0 0 1 0 2.5 Z" fill="black"/>
<text x="3" y="-8" class="regular" text-anchor="start" dominant-baseline="auto" data-type="text-template"></text>
<text x="3" y="-4" class="regular" text-anchor="start" dominant-baseline="auto" data-type="text-template"></text>
</g>
<g id="setout-point">
<circle fill="white" stroke="black" r="2.5" style="stroke-width: 0.5"/>
<path d="M 0 0 L -2.5 0 A 2.5 2.5 0 0 1 0 -2.5 Z" fill="black"/>
<path d="M 0 0 L 2.5 0 A 2.5 2.5 0 0 1 0 2.5 Z" fill="black"/>
</g>
<g id="control-point">
<path style="fill: white; stroke: black; stroke-width: 0.5;" d="M 2.286165,1.4798666 H -1.0000316e-7 -2.2861651 L -1.1430826,-0.5000101 -1.0000316e-7,-2.479888 1.1430827,-0.5000101 Z" />
<circle r="0.5" fill="black" stroke="black" style="stroke-width: 0.25;" />
</g>
<g id="SurveyArea-TRAVERSEPOINT">
<g id="traverse-point">
<path style="fill: white; stroke: black; stroke-width: 0.5;" d="M -2,-2 2,2 M -2,2 2,-2" />
</g>
<g id="SurveyArea-SPOTELEVATION">
<g id="spot-elevation">
<path style="fill: white; stroke: black; stroke-width: 0.5;" d="M 0,-2 0,2 M -2,0 2,0" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

@@ -51,5 +51,5 @@ var json_data = `{{{json_data}}}`;
<script>
var data = `{{{data}}}`;
setupPage(data);
create_gantt_chart(json_data)
</script>
create_gantt_chart(atob(json_data))
</script>
@@ -1,17 +1,17 @@
ISO-10303-21;
HEADER;
FILE_DESCRIPTION(('ViewDefinition[DesignTransferView]'),'2;1');
FILE_NAME('/dev/null','2023-07-30T21:07:33+10:00',(),(),'IfcOpenShell v0.7.0-8f41ae0c1','IfcOpenShell v0.7.0-8f41ae0c1','Nobody');
FILE_NAME('/dev/null','2023-09-07T13:40:06+10:00',(),(),'IfcOpenShell v0.7.0-fbd8ea1ed','IfcOpenShell v0.7.0-fbd8ea1ed','Nobody');
FILE_SCHEMA(('IFC4'));
ENDSEC;
DATA;
#1=IFCPROJECT('0TszJmTqLCyxcP10DtK_MP',$,'BlenderBIM Demo',$,$,$,$,(#12,#16),#7);
#2=IFCPROJECTLIBRARY('0Y_CJXSsbCgBlSGFw0mbLa',$,'BlenderBIM Demo Library',$,$,$,$,$,$);
#3=IFCRELDECLARES('3IxQaOPH5Fx90CGmGm1gtQ',$,$,$,#1,(#2));
#1=IFCPROJECT('1arVsMni92iPUaaryGTUz2',$,'BlenderBIM Demo',$,$,$,$,(#12,#16),#7);
#2=IFCPROJECTLIBRARY('1FfDG5b21DuBDOO4eUE9rF',$,'BlenderBIM Demo Library',$,$,$,$,$,$);
#3=IFCRELDECLARES('3tfDIC3Tf9wP8EUOoFeycK',$,$,$,#1,(#2));
#4=IFCSIUNIT(*,.LENGTHUNIT.,$,.METRE.);
#5=IFCSIUNIT(*,.AREAUNIT.,$,.SQUARE_METRE.);
#6=IFCSIUNIT(*,.VOLUMEUNIT.,$,.CUBIC_METRE.);
#7=IFCUNITASSIGNMENT((#6,#4,#5));
#7=IFCUNITASSIGNMENT((#5,#6,#4));
#8=IFCCARTESIANPOINT((0.,0.,0.));
#9=IFCDIRECTION((0.,0.,1.));
#10=IFCDIRECTION((1.,0.,0.));
@@ -25,82 +25,82 @@ DATA;
#18=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Body','Plan',*,*,*,*,#16,$,.PLAN_VIEW.,$);
#19=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Annotation','Plan',*,*,*,*,#16,$,.PLAN_VIEW.,$);
#20=IFCMATERIAL('Unknown',$,$);
#21=IFCWALLTYPE('3U0gAUglf9WBlcfpB15EK0',$,'WAL50',$,$,$,$,$,$,.NOTDEFINED.);
#21=IFCWALLTYPE('2o8NqQQbHA5BeErrgwfr7d',$,'WAL50',$,$,$,$,$,$,.NOTDEFINED.);
#22=IFCMATERIALLAYERSET((#24),$,$);
#23=IFCRELASSOCIATESMATERIAL('3J36H22Gz1ovVzdiKgApaM',$,$,$,(#21),#22);
#23=IFCRELASSOCIATESMATERIAL('1Q$5DZKr98n82Eitz2dWey',$,$,$,(#21),#22);
#24=IFCMATERIALLAYER(#20,0.05,$,$,$,$,$);
#25=IFCRELDECLARES('28ApgMJzH10h$Ng$fmCpYf',$,$,$,#2,(#63,#21,#54,#87,#153,#34,#67,#210,#38,#82,#77,#42,#26,#48,#72,#59,#92,#30,#96));
#26=IFCWALLTYPE('1wGfdUdC10TuASJo4jnO99',$,'WAL100',$,$,$,$,$,$,.NOTDEFINED.);
#25=IFCRELDECLARES('1KXKUEn8z0g8lvyEsCTWD3',$,$,$,#2,(#38,#82,#77,#42,#26,#48,#72,#59,#92,#30,#96,#63,#21,#54,#87,#153,#34,#67,#210));
#26=IFCWALLTYPE('3KlIOv_P9A79tkt8D_jq_m',$,'WAL100',$,$,$,$,$,$,.NOTDEFINED.);
#27=IFCMATERIALLAYERSET((#29),$,$);
#28=IFCRELASSOCIATESMATERIAL('0NRoxSQPzCgQHfsu5ru91z',$,$,$,(#26),#27);
#28=IFCRELASSOCIATESMATERIAL('32B9Wx8Z1FxhIN7m77MiHG',$,$,$,(#26),#27);
#29=IFCMATERIALLAYER(#20,0.1,$,$,$,$,$);
#30=IFCWALLTYPE('1lt5MuGLLDfwmZd0tNruF7',$,'WAL200',$,$,$,$,$,$,.NOTDEFINED.);
#30=IFCWALLTYPE('2DqrSeel1AGw6h3b$ujafZ',$,'WAL200',$,$,$,$,$,$,.NOTDEFINED.);
#31=IFCMATERIALLAYERSET((#33),$,$);
#32=IFCRELASSOCIATESMATERIAL('21rGVxPODC$xmWYFaDy1T6',$,$,$,(#30),#31);
#32=IFCRELASSOCIATESMATERIAL('2FeYUpt3D4tAvwgGmwXWZn',$,$,$,(#30),#31);
#33=IFCMATERIALLAYER(#20,0.2,$,$,$,$,$);
#34=IFCWALLTYPE('0NZm1D8h12xAxyW7fL5nc1',$,'WAL300',$,$,$,$,$,$,.NOTDEFINED.);
#34=IFCWALLTYPE('2jNdEOFIP1eQb3WZePlFGY',$,'WAL300',$,$,$,$,$,$,.NOTDEFINED.);
#35=IFCMATERIALLAYERSET((#37),$,$);
#36=IFCRELASSOCIATESMATERIAL('2yvmN42crCJevtirp5cMeV',$,$,$,(#34),#35);
#36=IFCRELASSOCIATESMATERIAL('2bFeM6xevAwucuwMlauX77',$,$,$,(#34),#35);
#37=IFCMATERIALLAYER(#20,0.3,$,$,$,$,$);
#38=IFCCOVERINGTYPE('1eb4wZ_5n6xRGrs3rlKMiM',$,'COV10',$,$,$,$,$,$,.NOTDEFINED.);
#38=IFCCOVERINGTYPE('3310gCgH59w9tu$DfmiCEN',$,'COV10',$,$,$,$,$,$,.NOTDEFINED.);
#39=IFCMATERIALLAYERSET((#41),$,$);
#40=IFCRELASSOCIATESMATERIAL('3HKIKf06T6xRqM6Lp69$OM',$,$,$,(#38),#39);
#40=IFCRELASSOCIATESMATERIAL('3pTNmWd_jBoOWyHc8Yf4qI',$,$,$,(#38),#39);
#41=IFCMATERIALLAYER(#20,0.01,$,$,$,$,$);
#42=IFCCOVERINGTYPE('2E89aYmtrEfxhX_jsNqviN',$,'COV20',$,$,(#46),$,$,$,.NOTDEFINED.);
#42=IFCCOVERINGTYPE('2sksWFuPzCgeNk1ofXS6PZ',$,'COV20',$,$,(#46),$,$,$,.NOTDEFINED.);
#43=IFCMATERIALLAYERSET((#45),$,$);
#44=IFCRELASSOCIATESMATERIAL('2115lEhhP4Z9G1Yhd_DqSC',$,$,$,(#42),#43);
#44=IFCRELASSOCIATESMATERIAL('36rl5NddT1ux6ejkQAkT2z',$,$,$,(#42),#43);
#45=IFCMATERIALLAYER(#20,0.02,$,$,$,$,$);
#46=IFCPROPERTYSET('0vtU63YmjAgg8DeHlTgNx7',$,'EPset_Parametric',$,(#47));
#46=IFCPROPERTYSET('2VZJ9xfdn1NeOY6uEmnnYW',$,'EPset_Parametric',$,(#47));
#47=IFCPROPERTYSINGLEVALUE('LayerSetDirection',$,IFCLABEL('AXIS2'),$);
#48=IFCCOVERINGTYPE('0XiSBI57L9Uf8f2NaQ_ksi',$,'COV30',$,$,(#52),$,$,$,.NOTDEFINED.);
#48=IFCCOVERINGTYPE('1uU6rxt95AC91lZ0jTRG6G',$,'COV30',$,$,(#52),$,$,$,.NOTDEFINED.);
#49=IFCMATERIALLAYERSET((#51),$,$);
#50=IFCRELASSOCIATESMATERIAL('3U3JdGAmbABA4mB$Fgpq1l',$,$,$,(#48),#49);
#50=IFCRELASSOCIATESMATERIAL('2lFv4xQYr5bwtiSsMTVCgz',$,$,$,(#48),#49);
#51=IFCMATERIALLAYER(#20,0.03,$,$,$,$,$);
#52=IFCPROPERTYSET('17nhQ5I4b9ZvhDu4gRJLKO',$,'EPset_Parametric',$,(#53));
#52=IFCPROPERTYSET('0HnrRhcd16Yfbgtci4sqCG',$,'EPset_Parametric',$,(#53));
#53=IFCPROPERTYSINGLEVALUE('LayerSetDirection',$,IFCLABEL('AXIS3'),$);
#54=IFCRAMPTYPE('1BTHjVDP19tgb3MDCSkWCJ',$,'RAM200',$,$,$,$,$,$,.NOTDEFINED.);
#54=IFCRAMPTYPE('3nonXCVn58jhVV08N8c97B',$,'RAM200',$,$,$,$,$,$,.NOTDEFINED.);
#55=IFCMATERIALLAYERSET((#57),$,$);
#56=IFCRELASSOCIATESMATERIAL('3HN21bHtL16vvHA2GnnxH1',$,$,$,(#54),#55);
#56=IFCRELASSOCIATESMATERIAL('2NdIrmbUP6bh4x$p_RY31C',$,$,$,(#54),#55);
#57=IFCMATERIALLAYER(#20,0.2,$,$,$,$,$);
#58=IFCCIRCLEPROFILEDEF(.AREA.,$,$,0.3);
#59=IFCPILETYPE('04_2WZSqH8TgT7ET6DCCpd',$,'P1',$,$,$,$,$,$,.NOTDEFINED.);
#59=IFCPILETYPE('2uuf5kq5TDDfO9jGN7XIHh',$,'P1',$,$,$,$,$,$,.NOTDEFINED.);
#60=IFCMATERIALPROFILESET($,$,(#62),$);
#61=IFCRELASSOCIATESMATERIAL('1qTZZRXsPF9hmYwT$tgFgp',$,$,$,(#59),#60);
#61=IFCRELASSOCIATESMATERIAL('3hi821Ffj8YR9yFjp226LU',$,$,$,(#59),#60);
#62=IFCMATERIALPROFILE($,$,#20,#58,$,$);
#63=IFCSLABTYPE('3_OV9$Sd52KRhIW2TWIxbW',$,'FLR150',$,$,$,$,$,$,.NOTDEFINED.);
#63=IFCSLABTYPE('2janmz3nH4P9IHaOVvGY_B',$,'FLR150',$,$,$,$,$,$,.NOTDEFINED.);
#64=IFCMATERIALLAYERSET((#66),$,$);
#65=IFCRELASSOCIATESMATERIAL('1agyf45$X1NhR127pUL0Iw',$,$,$,(#63),#64);
#65=IFCRELASSOCIATESMATERIAL('0og5Zg4ib67g6XWgOgqBWy',$,$,$,(#63),#64);
#66=IFCMATERIALLAYER(#20,0.2,$,$,$,$,$);
#67=IFCSLABTYPE('0LvmpIGf9AVPcG$Kt06TEk',$,'FLR250',$,$,$,$,$,$,.NOTDEFINED.);
#67=IFCSLABTYPE('26UKpEaTb9fh4qrqi3Ymzj',$,'FLR250',$,$,$,$,$,$,.NOTDEFINED.);
#68=IFCMATERIALLAYERSET((#70),$,$);
#69=IFCRELASSOCIATESMATERIAL('0EKQPSL656secN86SgeutH',$,$,$,(#67),#68);
#69=IFCRELASSOCIATESMATERIAL('1BeBXPqen9TQEE42SxzhjF',$,$,$,(#67),#68);
#70=IFCMATERIALLAYER(#20,0.3,$,$,$,$,$);
#71=IFCRECTANGLEPROFILEDEF(.AREA.,'500x600',$,0.5,0.6);
#72=IFCCOLUMNTYPE('1SI8m9m813tAXfSnM1fCbm',$,'C1',$,$,$,$,$,$,.NOTDEFINED.);
#72=IFCCOLUMNTYPE('1KnWzBBoLCNfJ3_J79LT7d',$,'C1',$,$,$,$,$,$,.NOTDEFINED.);
#73=IFCMATERIALPROFILESET($,$,(#75),$);
#74=IFCRELASSOCIATESMATERIAL('1EhwSTRtPBQgBt2x6dhuT$',$,$,$,(#72),#73);
#74=IFCRELASSOCIATESMATERIAL('1W8OWQjEL05gH5hEpve6Lh',$,$,$,(#72),#73);
#75=IFCMATERIALPROFILE($,$,#20,#71,$,$);
#76=IFCCIRCLEHOLLOWPROFILEDEF(.AREA.,'500.0x5.0 CHS',$,0.25,0.005);
#77=IFCCOLUMNTYPE('25GZwy2zLFcgi57r5S8iY3',$,'C2',$,$,$,$,$,$,.NOTDEFINED.);
#77=IFCCOLUMNTYPE('33jPFERfL9bfTeWfTL5kZ7',$,'C2',$,$,$,$,$,$,.NOTDEFINED.);
#78=IFCMATERIALPROFILESET($,$,(#80),$);
#79=IFCRELASSOCIATESMATERIAL('2VURAOhz93ZQqT9YJUA1DO',$,$,$,(#77),#78);
#79=IFCRELASSOCIATESMATERIAL('3czOrQw2v9BfS0PeQCrzWq',$,$,$,(#77),#78);
#80=IFCMATERIALPROFILE($,$,#20,#76,$,$);
#81=IFCRECTANGLEHOLLOWPROFILEDEF(.AREA.,'150x75x2.0 RHS',$,0.075,0.15,0.002,0.005,0.005);
#82=IFCCOLUMNTYPE('1Wg$I84s9BTh$iDuZnmSkH',$,'C3',$,$,$,$,$,$,.NOTDEFINED.);
#82=IFCCOLUMNTYPE('21YcNdKNj2$95U55yNx1Nw',$,'C3',$,$,$,$,$,$,.NOTDEFINED.);
#83=IFCMATERIALPROFILESET($,$,(#85),$);
#84=IFCRELASSOCIATESMATERIAL('3fIIwEe7DAle7x_zISvMUD',$,$,$,(#82),#83);
#84=IFCRELASSOCIATESMATERIAL('3n6Cru3xr6jvDptU9u7QEF',$,$,$,(#82),#83);
#85=IFCMATERIALPROFILE($,$,#20,#81,$,$);
#86=IFCISHAPEPROFILEDEF(.AREA.,'DEMO-I',$,0.1,0.2,0.005,0.01,0.005,$,$);
#87=IFCBEAMTYPE('2yg2Irs7L26gFsD_AvvFUu',$,'B1',$,$,$,$,$,$,.NOTDEFINED.);
#87=IFCBEAMTYPE('2CsGpD$6nDCxWv9jXTtvq9',$,'B1',$,$,$,$,$,$,.NOTDEFINED.);
#88=IFCMATERIALPROFILESET($,$,(#90),$);
#89=IFCRELASSOCIATESMATERIAL('1PU_gbkifF9hYpi9DvXTAU',$,$,$,(#87),#88);
#89=IFCRELASSOCIATESMATERIAL('2LJExc74j9rgBSBx51HDPK',$,$,$,(#87),#88);
#90=IFCMATERIALPROFILE($,$,#20,#86,$,$);
#91=IFCCSHAPEPROFILEDEF(.AREA.,'DEMO-C',$,0.2,0.1,0.0015,0.03,0.005);
#92=IFCBEAMTYPE('2Nf2L5qpz8K8KhUNFBg_fT',$,'B2',$,$,$,$,$,$,.NOTDEFINED.);
#92=IFCBEAMTYPE('32pHN2b0P0awGw$U8PpneO',$,'B2',$,$,$,$,$,$,.NOTDEFINED.);
#93=IFCMATERIALPROFILESET($,$,(#95),$);
#94=IFCRELASSOCIATESMATERIAL('1SgarGNCzEQQpfs29NMq0y',$,$,$,(#92),#93);
#94=IFCRELASSOCIATESMATERIAL('2wg8R1Drb2xhQ2Y_pRSh3c',$,$,$,(#92),#93);
#95=IFCMATERIALPROFILE($,$,#20,#91,$,$);
#96=IFCWINDOWTYPE('2zRUFP8Ef81xDOR2iJx1zK',$,'WT01',$,$,$,(#135,#152),$,$,.NOTDEFINED.,.NOTDEFINED.,$,$);
#96=IFCWINDOWTYPE('1Gg9HfZEr69u8SdWfDs2J3',$,'WT01',$,$,$,(#135,#152),$,$,.NOTDEFINED.,.NOTDEFINED.,$,$);
#97=IFCINDEXEDPOLYGONALFACE((13,17,18,14));
#98=IFCINDEXEDPOLYGONALFACE((5,6,3,4));
#99=IFCINDEXEDPOLYGONALFACE((7,8,2,1));
@@ -157,7 +157,7 @@ DATA;
#150=IFCDIRECTION((0.,0.,1.));
#151=IFCAXIS2PLACEMENT3D(#148,#150,#149);
#152=IFCREPRESENTATIONMAP(#151,#147);
#153=IFCDOORTYPE('0oekOIA$z8lxWiQs9_tFWU',$,'DT01',$,$,$,(#196,#209),$,$,.NOTDEFINED.,.NOTDEFINED.,$,$);
#153=IFCDOORTYPE('0NBUmPKyT9WecsIeYJrEqg',$,'DT01',$,$,$,(#196,#209),$,$,.NOTDEFINED.,.NOTDEFINED.,$,$);
#154=IFCINDEXEDPOLYGONALFACE((17,16,15,14));
#155=IFCINDEXEDPOLYGONALFACE((2,3,29,28));
#156=IFCINDEXEDPOLYGONALFACE((27,28,29,30,32,31));
@@ -214,7 +214,7 @@ DATA;
#207=IFCDIRECTION((0.,0.,1.));
#208=IFCAXIS2PLACEMENT3D(#205,#207,#206);
#209=IFCREPRESENTATIONMAP(#208,#204);
#210=IFCFURNITURETYPE('0I2mc$m3T60hLXZYg9Y1wu',$,'BUN01',$,$,$,(#931,#952),$,$,.NOTDEFINED.,.NOTDEFINED.);
#210=IFCFURNITURETYPE('3Kyc6IyarAUw3_8fkNtXIg',$,'BUN01',$,$,$,(#931,#952),$,$,.NOTDEFINED.,.NOTDEFINED.);
#211=IFCINDEXEDPOLYGONALFACE((187,278,44));
#212=IFCINDEXEDPOLYGONALFACE((21,52,60));
#213=IFCINDEXEDPOLYGONALFACE((91,100,31));
@@ -957,131 +957,161 @@ DATA;
#950=IFCDIRECTION((0.,0.,1.));
#951=IFCAXIS2PLACEMENT3D(#948,#950,#949);
#952=IFCREPRESENTATIONMAP(#951,#947);
#953=IFCTYPEPRODUCT('2be705eyD4DPBr6j4K7ZEx',$,'DASHED',$,'IfcAnnotation/LINEWORK',(#954),$,$);
#954=IFCPROPERTYSET('0uEfutIxDDIxMIBUJOZpSM',$,'EPset_Annotation',$,(#955));
#955=IFCPROPERTYSINGLEVALUE('Classes',$,IFCLABEL('dashed'),$);
#956=IFCTYPEPRODUCT('1M7mm9wUHFoxdM0MdAMyqM',$,'FINE',$,'IfcAnnotation/LINEWORK',(#957),$,$);
#957=IFCPROPERTYSET('3AZ8m1Bdn1gfbvagzKr2c3',$,'EPset_Annotation',$,(#958));
#958=IFCPROPERTYSINGLEVALUE('Classes',$,IFCLABEL('fine'),$);
#959=IFCTYPEPRODUCT('3LdrNIFOvAkBzrNnqWAZfA',$,'THIN',$,'IfcAnnotation/LINEWORK',(#960),$,$);
#960=IFCPROPERTYSET('0$9iwgSID7xBul4Tb9r2du',$,'EPset_Annotation',$,(#961));
#961=IFCPROPERTYSINGLEVALUE('Classes',$,IFCLABEL('thin'),$);
#962=IFCTYPEPRODUCT('3fFNO0t1TB$wYRaIkKwUCH',$,'MEDIUM',$,'IfcAnnotation/LINEWORK',(#963),$,$);
#963=IFCPROPERTYSET('33Fm$9zgb1cgsCOy859RlR',$,'EPset_Annotation',$,(#964));
#964=IFCPROPERTYSINGLEVALUE('Classes',$,IFCLABEL('medium'),$);
#965=IFCTYPEPRODUCT('1CpMBhemj5VQC43bU9J7zG',$,'THICK',$,'IfcAnnotation/LINEWORK',(#966),$,$);
#966=IFCPROPERTYSET('1OLDsFSDz2TPz5ifkQvWo8',$,'EPset_Annotation',$,(#967));
#967=IFCPROPERTYSINGLEVALUE('Classes',$,IFCLABEL('thick'),$);
#968=IFCTYPEPRODUCT('0NITtya3b16hoA3sh17SL4',$,'STRONG',$,'IfcAnnotation/LINEWORK',(#969),$,$);
#969=IFCPROPERTYSET('1_kfqWVDn0puI29Ah6eitX',$,'EPset_Annotation',$,(#970));
#970=IFCPROPERTYSINGLEVALUE('Classes',$,IFCLABEL('strong'),$);
#971=IFCTYPEPRODUCT('0rRN9EsvL5AQ3PorxrZSKM',$,'DOOR-TAG',$,'IfcAnnotation/TEXT',(#972),(#991),$);
#972=IFCPROPERTYSET('3lRZ$d2VH8cxZYh$mm45iq',$,'EPset_Annotation',$,(#973));
#973=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('door-tag'),$);
#974=IFCCARTESIANPOINT((0.,0.,0.));
#975=IFCDIRECTION((0.,0.,1.));
#976=IFCDIRECTION((1.,0.,0.));
#977=IFCAXIS2PLACEMENT3D(#974,#975,#976);
#978=IFCPLANAREXTENT(1000.,1000.);
#979=IFCTEXTLITERALWITHEXTENT('{{type.Name}}',#977,.RIGHT.,#978,'center');
#980=IFCCARTESIANPOINT((0.,0.,0.));
#981=IFCDIRECTION((0.,0.,1.));
#982=IFCDIRECTION((1.,0.,0.));
#983=IFCAXIS2PLACEMENT3D(#980,#981,#982);
#984=IFCPLANAREXTENT(1000.,1000.);
#985=IFCTEXTLITERALWITHEXTENT('{{Name}}',#983,.RIGHT.,#984,'center');
#986=IFCSHAPEREPRESENTATION(#19,'Annotation','Annotation2D',(#979,#985));
#987=IFCCARTESIANPOINT((0.,0.,0.));
#988=IFCDIRECTION((1.,0.,0.));
#989=IFCDIRECTION((0.,0.,1.));
#990=IFCAXIS2PLACEMENT3D(#987,#989,#988);
#991=IFCREPRESENTATIONMAP(#990,#986);
#992=IFCTYPEPRODUCT('0bFZUWHjbA2e03hRHNinTt',$,'WINDOW-TAG',$,'IfcAnnotation/TEXT',(#993),(#1006),$);
#993=IFCPROPERTYSET('3TJOWv5NDBXBNCshYFjsFV',$,'EPset_Annotation',$,(#994));
#994=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('window-tag'),$);
#995=IFCCARTESIANPOINT((0.,0.,0.));
#996=IFCDIRECTION((0.,0.,1.));
#953=IFCTYPEPRODUCT('0TBMBnD_b66QLUWKD9HLsc',$,'SETOUT-POINT',$,'IfcAnnotation/SYMBOL',(#954),$,$);
#954=IFCPROPERTYSET('2dQzX_K4r1Xet6dz9zBlgs',$,'EPset_Annotation',$,(#955));
#955=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('setout-point'),$);
#956=IFCTYPEPRODUCT('0vo_7PU3H9ygTnrIipqPRI',$,'CONTROL-POINT',$,'IfcAnnotation/SYMBOL',(#957),$,$);
#957=IFCPROPERTYSET('3APQOw$FP1ivxp08UxHsl6',$,'EPset_Annotation',$,(#958));
#958=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('control-point'),$);
#959=IFCTYPEPRODUCT('32V27G3$T1OO3TbXg6948F',$,'TRAVERSE-POINT',$,'IfcAnnotation/SYMBOL',(#960),$,$);
#960=IFCPROPERTYSET('0cTSYXMc9B$PTXVDrhBYYW',$,'EPset_Annotation',$,(#961));
#961=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('traverse-point'),$);
#962=IFCTYPEPRODUCT('2PXIC7Bg914gJhRh2XeGnN',$,'DASHED',$,'IfcAnnotation/LINEWORK',(#963),$,$);
#963=IFCPROPERTYSET('0RiYsOxp529gXnCkw44wF8',$,'EPset_Annotation',$,(#964));
#964=IFCPROPERTYSINGLEVALUE('Classes',$,IFCLABEL('dashed'),$);
#965=IFCTYPEPRODUCT('1Ou1kA3Vb4HhuNBvM0uGe0',$,'FINE',$,'IfcAnnotation/LINEWORK',(#966),$,$);
#966=IFCPROPERTYSET('2gzly9D0L1qPK2MExeXgRO',$,'EPset_Annotation',$,(#967));
#967=IFCPROPERTYSINGLEVALUE('Classes',$,IFCLABEL('fine'),$);
#968=IFCTYPEPRODUCT('3lx$KQPRbEZwbQ7Xdfm5gw',$,'THIN',$,'IfcAnnotation/LINEWORK',(#969),$,$);
#969=IFCPROPERTYSET('0vsVpqs6zArvA2bfBtvQew',$,'EPset_Annotation',$,(#970));
#970=IFCPROPERTYSINGLEVALUE('Classes',$,IFCLABEL('thin'),$);
#971=IFCTYPEPRODUCT('00j$y97p903w2HOb35lAQ2',$,'MEDIUM',$,'IfcAnnotation/LINEWORK',(#972),$,$);
#972=IFCPROPERTYSET('240rEBDOn8PAvfnm_43s55',$,'EPset_Annotation',$,(#973));
#973=IFCPROPERTYSINGLEVALUE('Classes',$,IFCLABEL('medium'),$);
#974=IFCTYPEPRODUCT('2tVdFGorj6dfrL4uA1kyW8',$,'THICK',$,'IfcAnnotation/LINEWORK',(#975),$,$);
#975=IFCPROPERTYSET('2IEGHncr1D$R8fKA9zCEJP',$,'EPset_Annotation',$,(#976));
#976=IFCPROPERTYSINGLEVALUE('Classes',$,IFCLABEL('thick'),$);
#977=IFCTYPEPRODUCT('1T5C$$ONTBB8A9a7vv0Gn6',$,'STRONG',$,'IfcAnnotation/LINEWORK',(#978),$,$);
#978=IFCPROPERTYSET('1gRomAS1L2WguA51ZI0E40',$,'EPset_Annotation',$,(#979));
#979=IFCPROPERTYSINGLEVALUE('Classes',$,IFCLABEL('strong'),$);
#980=IFCTYPEPRODUCT('2LmKYrAEr2K8EwJyCAsyc4',$,'SETOUT-TAG',$,'IfcAnnotation/TEXT',(#981),(#1000),$);
#981=IFCPROPERTYSET('0nF9du8qDAF9aldqjCUsbX',$,'EPset_Annotation',$,(#982));
#982=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('setout-tag'),$);
#983=IFCCARTESIANPOINT((0.,0.,0.));
#984=IFCDIRECTION((0.,0.,1.));
#985=IFCDIRECTION((1.,0.,0.));
#986=IFCAXIS2PLACEMENT3D(#983,#984,#985);
#987=IFCPLANAREXTENT(1000.,1000.);
#988=IFCTEXTLITERALWITHEXTENT('E ``round({{easting}}, 0.001)``',#986,.RIGHT.,#987,'center');
#989=IFCCARTESIANPOINT((0.,0.,0.));
#990=IFCDIRECTION((0.,0.,1.));
#991=IFCDIRECTION((1.,0.,0.));
#992=IFCAXIS2PLACEMENT3D(#989,#990,#991);
#993=IFCPLANAREXTENT(1000.,1000.);
#994=IFCTEXTLITERALWITHEXTENT('N ``round({{northing}}, 0.001)``',#992,.RIGHT.,#993,'center');
#995=IFCSHAPEREPRESENTATION(#19,'Annotation','Annotation2D',(#988,#994));
#996=IFCCARTESIANPOINT((0.,0.,0.));
#997=IFCDIRECTION((1.,0.,0.));
#998=IFCAXIS2PLACEMENT3D(#995,#996,#997);
#999=IFCPLANAREXTENT(1000.,1000.);
#1000=IFCTEXTLITERALWITHEXTENT('{{Name}}',#998,.RIGHT.,#999,'center');
#1001=IFCSHAPEREPRESENTATION(#19,'Annotation','Annotation2D',(#1000));
#1002=IFCCARTESIANPOINT((0.,0.,0.));
#1003=IFCDIRECTION((1.,0.,0.));
#1004=IFCDIRECTION((0.,0.,1.));
#1005=IFCAXIS2PLACEMENT3D(#1002,#1004,#1003);
#1006=IFCREPRESENTATIONMAP(#1005,#1001);
#1007=IFCTYPEPRODUCT('2DlmDdasX05un_uMP4O23U',$,'SPACE-TAG',$,'IfcAnnotation/TEXT',(#1008),(#1033),$);
#1008=IFCPROPERTYSET('1$OAHURHr3GON7ZzAJnv72',$,'EPset_Annotation',$,(#1009));
#1009=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('space-tag'),$);
#998=IFCDIRECTION((0.,0.,1.));
#999=IFCAXIS2PLACEMENT3D(#996,#998,#997);
#1000=IFCREPRESENTATIONMAP(#999,#995);
#1001=IFCTYPEPRODUCT('1cdhVmqEPF6e13_TFOdlQw',$,'DOOR-TAG',$,'IfcAnnotation/TEXT',(#1002),(#1021),$);
#1002=IFCPROPERTYSET('1MUPGQolnDEgp0NxcMN56E',$,'EPset_Annotation',$,(#1003));
#1003=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('door-tag'),$);
#1004=IFCCARTESIANPOINT((0.,0.,0.));
#1005=IFCDIRECTION((0.,0.,1.));
#1006=IFCDIRECTION((1.,0.,0.));
#1007=IFCAXIS2PLACEMENT3D(#1004,#1005,#1006);
#1008=IFCPLANAREXTENT(1000.,1000.);
#1009=IFCTEXTLITERALWITHEXTENT('{{type.Name}}',#1007,.RIGHT.,#1008,'center');
#1010=IFCCARTESIANPOINT((0.,0.,0.));
#1011=IFCDIRECTION((0.,0.,1.));
#1012=IFCDIRECTION((1.,0.,0.));
#1013=IFCAXIS2PLACEMENT3D(#1010,#1011,#1012);
#1014=IFCPLANAREXTENT(1000.,1000.);
#1015=IFCTEXTLITERALWITHEXTENT('{{Name}}',#1013,.RIGHT.,#1014,'center');
#1016=IFCCARTESIANPOINT((0.,0.,0.));
#1017=IFCDIRECTION((0.,0.,1.));
#1016=IFCSHAPEREPRESENTATION(#19,'Annotation','Annotation2D',(#1009,#1015));
#1017=IFCCARTESIANPOINT((0.,0.,0.));
#1018=IFCDIRECTION((1.,0.,0.));
#1019=IFCAXIS2PLACEMENT3D(#1016,#1017,#1018);
#1020=IFCPLANAREXTENT(1000.,1000.);
#1021=IFCTEXTLITERALWITHEXTENT('{{Description}}',#1019,.RIGHT.,#1020,'center');
#1022=IFCCARTESIANPOINT((0.,0.,0.));
#1023=IFCDIRECTION((0.,0.,1.));
#1024=IFCDIRECTION((1.,0.,0.));
#1025=IFCAXIS2PLACEMENT3D(#1022,#1023,#1024);
#1026=IFCPLANAREXTENT(1000.,1000.);
#1027=IFCTEXTLITERALWITHEXTENT('``round({{Qto_SpaceBaseQuantities.NetFloorArea}} or 0., 2)``',#1025,.RIGHT.,#1026,'center');
#1028=IFCSHAPEREPRESENTATION(#19,'Annotation','Annotation2D',(#1015,#1021,#1027));
#1029=IFCCARTESIANPOINT((0.,0.,0.));
#1030=IFCDIRECTION((1.,0.,0.));
#1031=IFCDIRECTION((0.,0.,1.));
#1032=IFCAXIS2PLACEMENT3D(#1029,#1031,#1030);
#1033=IFCREPRESENTATIONMAP(#1032,#1028);
#1034=IFCTYPEPRODUCT('2vNx3CIlrFs8bLFlmGvJF3',$,'MATERIAL-TAG',$,'IfcAnnotation/TEXT',(#1035),(#1048),$);
#1035=IFCPROPERTYSET('1l$T4lUqnBb93zcgfbSPvx',$,'EPset_Annotation',$,(#1036));
#1036=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('rectangle-tag'),$);
#1037=IFCCARTESIANPOINT((0.,0.,0.));
#1038=IFCDIRECTION((0.,0.,1.));
#1039=IFCDIRECTION((1.,0.,0.));
#1040=IFCAXIS2PLACEMENT3D(#1037,#1038,#1039);
#1041=IFCPLANAREXTENT(1000.,1000.);
#1042=IFCTEXTLITERALWITHEXTENT('{{material.Name}}',#1040,.RIGHT.,#1041,'center');
#1043=IFCSHAPEREPRESENTATION(#19,'Annotation','Annotation2D',(#1042));
#1044=IFCCARTESIANPOINT((0.,0.,0.));
#1045=IFCDIRECTION((1.,0.,0.));
#1046=IFCDIRECTION((0.,0.,1.));
#1047=IFCAXIS2PLACEMENT3D(#1044,#1046,#1045);
#1048=IFCREPRESENTATIONMAP(#1047,#1043);
#1049=IFCTYPEPRODUCT('3hqzFtbSb56RjhAZNaB4Vh',$,'TYPE-TAG',$,'IfcAnnotation/TEXT',(#1050),(#1063),$);
#1050=IFCPROPERTYSET('3U7kaKt_nBUvD$U2ryif_H',$,'EPset_Annotation',$,(#1051));
#1051=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('capsule-tag'),$);
#1019=IFCDIRECTION((0.,0.,1.));
#1020=IFCAXIS2PLACEMENT3D(#1017,#1019,#1018);
#1021=IFCREPRESENTATIONMAP(#1020,#1016);
#1022=IFCTYPEPRODUCT('0OuDi3gRH2CxW9mrtE0vXw',$,'WINDOW-TAG',$,'IfcAnnotation/TEXT',(#1023),(#1036),$);
#1023=IFCPROPERTYSET('2X7dAo_1P5ruRnlKA4kIl6',$,'EPset_Annotation',$,(#1024));
#1024=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('window-tag'),$);
#1025=IFCCARTESIANPOINT((0.,0.,0.));
#1026=IFCDIRECTION((0.,0.,1.));
#1027=IFCDIRECTION((1.,0.,0.));
#1028=IFCAXIS2PLACEMENT3D(#1025,#1026,#1027);
#1029=IFCPLANAREXTENT(1000.,1000.);
#1030=IFCTEXTLITERALWITHEXTENT('{{Name}}',#1028,.RIGHT.,#1029,'center');
#1031=IFCSHAPEREPRESENTATION(#19,'Annotation','Annotation2D',(#1030));
#1032=IFCCARTESIANPOINT((0.,0.,0.));
#1033=IFCDIRECTION((1.,0.,0.));
#1034=IFCDIRECTION((0.,0.,1.));
#1035=IFCAXIS2PLACEMENT3D(#1032,#1034,#1033);
#1036=IFCREPRESENTATIONMAP(#1035,#1031);
#1037=IFCTYPEPRODUCT('3WEV_9wQn6AQTESgjW36PH',$,'SPACE-TAG',$,'IfcAnnotation/TEXT',(#1038),(#1063),$);
#1038=IFCPROPERTYSET('02FAX8dIzAlunWD5ak$6sU',$,'EPset_Annotation',$,(#1039));
#1039=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('space-tag'),$);
#1040=IFCCARTESIANPOINT((0.,0.,0.));
#1041=IFCDIRECTION((0.,0.,1.));
#1042=IFCDIRECTION((1.,0.,0.));
#1043=IFCAXIS2PLACEMENT3D(#1040,#1041,#1042);
#1044=IFCPLANAREXTENT(1000.,1000.);
#1045=IFCTEXTLITERALWITHEXTENT('{{Name}}',#1043,.RIGHT.,#1044,'center');
#1046=IFCCARTESIANPOINT((0.,0.,0.));
#1047=IFCDIRECTION((0.,0.,1.));
#1048=IFCDIRECTION((1.,0.,0.));
#1049=IFCAXIS2PLACEMENT3D(#1046,#1047,#1048);
#1050=IFCPLANAREXTENT(1000.,1000.);
#1051=IFCTEXTLITERALWITHEXTENT('{{Description}}',#1049,.RIGHT.,#1050,'center');
#1052=IFCCARTESIANPOINT((0.,0.,0.));
#1053=IFCDIRECTION((0.,0.,1.));
#1054=IFCDIRECTION((1.,0.,0.));
#1055=IFCAXIS2PLACEMENT3D(#1052,#1053,#1054);
#1056=IFCPLANAREXTENT(1000.,1000.);
#1057=IFCTEXTLITERALWITHEXTENT('{{type.Name}}',#1055,.RIGHT.,#1056,'center');
#1058=IFCSHAPEREPRESENTATION(#19,'Annotation','Annotation2D',(#1057));
#1057=IFCTEXTLITERALWITHEXTENT('``round({{Qto_SpaceBaseQuantities.NetFloorArea}}, 0.01)``',#1055,.RIGHT.,#1056,'center');
#1058=IFCSHAPEREPRESENTATION(#19,'Annotation','Annotation2D',(#1045,#1051,#1057));
#1059=IFCCARTESIANPOINT((0.,0.,0.));
#1060=IFCDIRECTION((1.,0.,0.));
#1061=IFCDIRECTION((0.,0.,1.));
#1062=IFCAXIS2PLACEMENT3D(#1059,#1061,#1060);
#1063=IFCREPRESENTATIONMAP(#1062,#1058);
#1064=IFCTYPEPRODUCT('2SnSxJPiD9sh$NDx92OCSS',$,'NAME-TAG',$,'IfcAnnotation/TEXT',(#1065),(#1078),$);
#1065=IFCPROPERTYSET('3Lu86QVuv5nfjkuPLvdQSH',$,'EPset_Annotation',$,(#1066));
#1066=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('capsule-tag'),$);
#1064=IFCTYPEPRODUCT('1evqvQLLL1zxdsIWqEn0lK',$,'MATERIAL-TAG',$,'IfcAnnotation/TEXT',(#1065),(#1078),$);
#1065=IFCPROPERTYSET('2KPJlGyer0UBmphfR7952k',$,'EPset_Annotation',$,(#1066));
#1066=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('rectangle-tag'),$);
#1067=IFCCARTESIANPOINT((0.,0.,0.));
#1068=IFCDIRECTION((0.,0.,1.));
#1069=IFCDIRECTION((1.,0.,0.));
#1070=IFCAXIS2PLACEMENT3D(#1067,#1068,#1069);
#1071=IFCPLANAREXTENT(1000.,1000.);
#1072=IFCTEXTLITERALWITHEXTENT('{{Name}}',#1070,.RIGHT.,#1071,'center');
#1072=IFCTEXTLITERALWITHEXTENT('{{material.Name}}',#1070,.RIGHT.,#1071,'center');
#1073=IFCSHAPEREPRESENTATION(#19,'Annotation','Annotation2D',(#1072));
#1074=IFCCARTESIANPOINT((0.,0.,0.));
#1075=IFCDIRECTION((1.,0.,0.));
#1076=IFCDIRECTION((0.,0.,1.));
#1077=IFCAXIS2PLACEMENT3D(#1074,#1076,#1075);
#1078=IFCREPRESENTATIONMAP(#1077,#1073);
#1079=IFCTYPEPRODUCT('3Nem6d4xX87O3deyWDi3AW',$,'TYPE-TAG',$,'IfcAnnotation/TEXT',(#1080),(#1093),$);
#1080=IFCPROPERTYSET('15bspgnA9CEuFox6xFjoTL',$,'EPset_Annotation',$,(#1081));
#1081=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('capsule-tag'),$);
#1082=IFCCARTESIANPOINT((0.,0.,0.));
#1083=IFCDIRECTION((0.,0.,1.));
#1084=IFCDIRECTION((1.,0.,0.));
#1085=IFCAXIS2PLACEMENT3D(#1082,#1083,#1084);
#1086=IFCPLANAREXTENT(1000.,1000.);
#1087=IFCTEXTLITERALWITHEXTENT('{{type.Name}}',#1085,.RIGHT.,#1086,'center');
#1088=IFCSHAPEREPRESENTATION(#19,'Annotation','Annotation2D',(#1087));
#1089=IFCCARTESIANPOINT((0.,0.,0.));
#1090=IFCDIRECTION((1.,0.,0.));
#1091=IFCDIRECTION((0.,0.,1.));
#1092=IFCAXIS2PLACEMENT3D(#1089,#1091,#1090);
#1093=IFCREPRESENTATIONMAP(#1092,#1088);
#1094=IFCTYPEPRODUCT('0klFX9AjnEnPBkdIURv8XD',$,'NAME-TAG',$,'IfcAnnotation/TEXT',(#1095),(#1108),$);
#1095=IFCPROPERTYSET('1d53tifbv2rwcoFFJosiHf',$,'EPset_Annotation',$,(#1096));
#1096=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('capsule-tag'),$);
#1097=IFCCARTESIANPOINT((0.,0.,0.));
#1098=IFCDIRECTION((0.,0.,1.));
#1099=IFCDIRECTION((1.,0.,0.));
#1100=IFCAXIS2PLACEMENT3D(#1097,#1098,#1099);
#1101=IFCPLANAREXTENT(1000.,1000.);
#1102=IFCTEXTLITERALWITHEXTENT('{{Name}}',#1100,.RIGHT.,#1101,'center');
#1103=IFCSHAPEREPRESENTATION(#19,'Annotation','Annotation2D',(#1102));
#1104=IFCCARTESIANPOINT((0.,0.,0.));
#1105=IFCDIRECTION((1.,0.,0.));
#1106=IFCDIRECTION((0.,0.,1.));
#1107=IFCAXIS2PLACEMENT3D(#1104,#1106,#1105);
#1108=IFCREPRESENTATIONMAP(#1107,#1103);
ENDSEC;
END-ISO-10303-21;
@@ -5,27 +5,27 @@ FILE_NAME('EPset_Drawing.ifc','2020-01-01T00:00:00',(),(),'EPset_Drawing','EPset
FILE_SCHEMA(('IFC4'));
ENDSEC;
DATA;
#1=IFCPROPERTYSETTEMPLATE('2JhNIvqZrFnAgxfhK0XVQX',$,'EPset_Drawing','',.PSET_OCCURRENCEDRIVEN.,'IfcAnnotation',(#2,#3,#4,#5,#6,#7,#8,#9,#10,#11,#12,#13,#14,#15,#16,#17,#18,#19,#20,#21));
#1=IFCPROPERTYSETTEMPLATE('2JhNIvqZrFnAgxfhK0XVQX',$,'EPset_Drawing','',.PSET_OCCURRENCEDRIVEN.,'IfcAnnotation',(#2,#3,#4,#5,#6,#7,#8,#9,#10,#11,#12,#13,#14,#15,#16,#17,#18,#19,#20,#21,#22));
#2=IFCSIMPLEPROPERTYTEMPLATE('23JavTMk98ZxXhrUEnjAcf',$,'TargetView','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
#3=IFCSIMPLEPROPERTYTEMPLATE('1yVWUt5H9DAOuu0OaMMLpe',$,'Scale','The scale of this drawing represented as a numerator and denominator, such as 1/100',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
#4=IFCSIMPLEPROPERTYTEMPLATE('3gsuPBtU93b8f0gg1pjkq6',$,'HumanScale','The scale of this drawing in human readable format, such as 1:100',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
#4=IFCSIMPLEPROPERTYTEMPLATE('2T$a4OFsv2LeD5JeBKEV4f',$,'IsNTS','Whether or not the scale is intended to be significant',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
#5=IFCSIMPLEPROPERTYTEMPLATE('0AK5C2UpL4$eaac2LszAx$',$,'HasUnderlay','',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
#6=IFCSIMPLEPROPERTYTEMPLATE('2j2ZEZR8X5tONm7kli5hM6',$,'HasLinework','',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
#7=IFCSIMPLEPROPERTYTEMPLATE('1ttChRysH9UuEX2FeMj5Hu',$,'HasAnnotation','',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
#8=IFCSIMPLEPROPERTYTEMPLATE('2NPPxuABv1huDTVh32TFgw',$,'GlobalReferencing','',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
#9=IFCSIMPLEPROPERTYTEMPLATE('10hT_1zrzEbRRKMXYAWvtD',$,'Metadata','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
#10=IFCSIMPLEPROPERTYTEMPLATE('3Z0BXPSG5CWgtI33ioV7aj',$,'Include','Selector expression to include ifc elements in the drawing',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
#11=IFCSIMPLEPROPERTYTEMPLATE('1RVts_g3PAw98PJA2yL3bO',$,'Exclude','Selector expression to exclude ifc elements in the drawing',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
#12=IFCSIMPLEPROPERTYTEMPLATE('0c1$8NpYDEaBiJrj16jHIo',$,'Stylesheet','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
#13=IFCSIMPLEPROPERTYTEMPLATE('3mRF52q81FQB$h4oTh7M45',$,'Markers','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
#14=IFCSIMPLEPROPERTYTEMPLATE('1rhr_0N3LDtuORcEJP0KXM',$,'Symbols','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
#15=IFCSIMPLEPROPERTYTEMPLATE('2sHDBuW7P4TROy$hL2w7ct',$,'Patterns','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
#16=IFCSIMPLEPROPERTYTEMPLATE('1$xfo9EVb26QLqmPll2_RK',$,'MetricPrecision','',.P_SINGLEVALUE.,'IfcReal',$,$,$,$,$,.READWRITE.);
#17=IFCSIMPLEPROPERTYTEMPLATE('38uAtrp9nD_901NO42zd$7',$,'ImperialPrecision','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
#18=IFCSIMPLEPROPERTYTEMPLATE('1MX0uffTL6TOvtvEJpxFmk',$,'DecimalPlaces','',.P_SINGLEVALUE.,'IfcInteger',$,$,$,$,$,.READWRITE.);
#19=IFCSIMPLEPROPERTYTEMPLATE('0joEq0Rd10cxweEh0NeHT6',$,'JoinCriteria','Comma separated selection keys which determine what cut objects are to be joined.',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
#20=IFCSIMPLEPROPERTYTEMPLATE('0nYMT3OSj5gArVniCWZRtv',$,'ShadingStyles','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
#21=IFCSIMPLEPROPERTYTEMPLATE('3VWG22eZXBdQwdKlzMeVQH',$,'CurrentShadingStyle','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
#5=IFCSIMPLEPROPERTYTEMPLATE('2T$a4OFsv2LeD5JeBKEV4f',$,'IsNTS','Whether or not the scale is intended to be significant',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
#6=IFCSIMPLEPROPERTYTEMPLATE('0AK5C2UpL4$eaac2LszAx$',$,'HasUnderlay','',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
#7=IFCSIMPLEPROPERTYTEMPLATE('2j2ZEZR8X5tONm7kli5hM6',$,'HasLinework','',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
#8=IFCSIMPLEPROPERTYTEMPLATE('1ttChRysH9UuEX2FeMj5Hu',$,'HasAnnotation','',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
#9=IFCSIMPLEPROPERTYTEMPLATE('2NPPxuABv1huDTVh32TFgw',$,'GlobalReferencing','',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
#10=IFCSIMPLEPROPERTYTEMPLATE('10hT_1zrzEbRRKMXYAWvtD',$,'Metadata','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
#11=IFCSIMPLEPROPERTYTEMPLATE('3Z0BXPSG5CWgtI33ioV7aj',$,'Include','Selector expression to include ifc elements in the drawing',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
#12=IFCSIMPLEPROPERTYTEMPLATE('1RVts_g3PAw98PJA2yL3bO',$,'Exclude','Selector expression to exclude ifc elements in the drawing',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
#13=IFCSIMPLEPROPERTYTEMPLATE('0c1$8NpYDEaBiJrj16jHIo',$,'Stylesheet','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
#14=IFCSIMPLEPROPERTYTEMPLATE('3mRF52q81FQB$h4oTh7M45',$,'Markers','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
#15=IFCSIMPLEPROPERTYTEMPLATE('1rhr_0N3LDtuORcEJP0KXM',$,'Symbols','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
#16=IFCSIMPLEPROPERTYTEMPLATE('2sHDBuW7P4TROy$hL2w7ct',$,'Patterns','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
#17=IFCSIMPLEPROPERTYTEMPLATE('1$xfo9EVb26QLqmPll2_RK',$,'MetricPrecision','',.P_SINGLEVALUE.,'IfcReal',$,$,$,$,$,.READWRITE.);
#18=IFCSIMPLEPROPERTYTEMPLATE('38uAtrp9nD_901NO42zd$7',$,'ImperialPrecision','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
#19=IFCSIMPLEPROPERTYTEMPLATE('1MX0uffTL6TOvtvEJpxFmk',$,'DecimalPlaces','',.P_SINGLEVALUE.,'IfcInteger',$,$,$,$,$,.READWRITE.);
#20=IFCSIMPLEPROPERTYTEMPLATE('0joEq0Rd10cxweEh0NeHT6',$,'JoinCriteria','Comma separated selection keys which determine what cut objects are to be joined.',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
#21=IFCSIMPLEPROPERTYTEMPLATE('0nYMT3OSj5gArVniCWZRtv',$,'ShadingStyles','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
#22=IFCSIMPLEPROPERTYTEMPLATE('3VWG22eZXBdQwdKlzMeVQH',$,'CurrentShadingStyle','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
ENDSEC;
END-ISO-10303-21;
@@ -0,0 +1,13 @@
ISO-10303-21;
HEADER;
FILE_DESCRIPTION((),'2;1');
FILE_NAME('EPset_Status.ifc','2020-01-01T00:00:00',(),(),'EPset_Status','EPset_Status',$);
FILE_SCHEMA(('IFC4'));
ENDSEC;
DATA;
#1=IFCPROPERTYSETTEMPLATE('3JoyTt$cz4fhYuQvJeTMhs',$,'EPset_Status','',.PSET_TYPEDRIVENOVERRIDE.,'IfcProduct',(#2,#4));
#2= IFCSIMPLEPROPERTYTEMPLATE('2HBOdOfz5E3hZ7IBLCq$$D',$,'Status','Status of the element, predominately used in renovation or retrofitting projects. The status can be assigned to as "New" - element designed as new addition, "Existing" - element exists and remains, "Demolish" - element existed but is to be demolished, "Temporary" - element will exists only temporary (like a temporary support structure).',.P_ENUMERATEDVALUE.,'IfcLabel',$,#3,$,$,$,.READWRITE.);
#3= IFCPROPERTYENUMERATION('EPEnum_ElementStatus',(IFCLABEL('NEW'),IFCLABEL('EXISTING'),IFCLABEL('DEMOLISH'),IFCLABEL('TEMPORARY'),IFCLABEL('OTHER'),IFCLABEL('NOTKNOWN'),IFCLABEL('UNSET')),$);
#4=IFCSIMPLEPROPERTYTEMPLATE('3ttXxqmlz62BlxjzEPpqvH',$,'UserDefinedStatus','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
ENDSEC;
END-ISO-10303-21;
@@ -0,0 +1,23 @@
ISO-10303-21;
HEADER;
FILE_DESCRIPTION(('ViewDefinition [CoordinationView]'),'2;1');
FILE_NAME('','2023-09-28T23:00:50',(),(),'IfcOpenShell v0.7.0-fbd8ea1ed','IfcOpenShell v0.7.0-fbd8ea1ed','');
FILE_SCHEMA(('IFC4'));
ENDSEC;
DATA;
#1=IFCPROPERTYSETTEMPLATE('1TLQmgUKf0I8Zi07bm9Jqn',$,'ePSet_ProjectedCRS','',.PSET_OCCURRENCEDRIVEN.,'IfcProject',(#2,#3,#4,#5,#6,#7));
#2=IFCSIMPLEPROPERTYTEMPLATE('2cH53PtVT3v88uf_uGFblG',$,'Name','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
#3=IFCSIMPLEPROPERTYTEMPLATE('3JsReA6vXDqR7aNC$haRYW',$,'Description','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
#4=IFCSIMPLEPROPERTYTEMPLATE('0u0uEsT5HFtuX5GvozO9ba',$,'GeodeticDatum','',.P_SINGLEVALUE.,'IfcIdentifier',$,$,$,$,$,.READWRITE.);
#5=IFCSIMPLEPROPERTYTEMPLATE('1JgEX9Fsf0$ggxYltdkqiY',$,'VerticalDatum','',.P_SINGLEVALUE.,'IfcIdentifier',$,$,$,$,$,.READWRITE.);
#6=IFCSIMPLEPROPERTYTEMPLATE('3ahTZjYrD0MAOo5IH6QuGH',$,'MapProjection','',.P_SINGLEVALUE.,'IfcIdentifier',$,$,$,$,$,.READWRITE.);
#7=IFCSIMPLEPROPERTYTEMPLATE('3hqJR2ghD0GAti7q4sXKj7',$,'MapZone','',.P_SINGLEVALUE.,'IfcIdentifier',$,$,$,$,$,.READWRITE.);
#8=IFCPROPERTYSETTEMPLATE('1jGHTycOj0vA8xHfRl4TNs',$,'ePSet_MapConversion','',.PSET_OCCURRENCEDRIVEN.,'IfcProject',(#9,#10,#11,#12,#13,#15));
#9=IFCSIMPLEPROPERTYTEMPLATE('0nxsULXo17JRrAvNteYtNR',$,'Eastings','',.P_SINGLEVALUE.,'IfcLengthMeasure',$,$,$,$,$,.READWRITE.);
#10=IFCSIMPLEPROPERTYTEMPLATE('26RfaGJs5CqemN0Xin12XL',$,'Northings','',.P_SINGLEVALUE.,'IfcLengthMeasure',$,$,$,$,$,.READWRITE.);
#11=IFCSIMPLEPROPERTYTEMPLATE('0ziCn3nZv3m850VEUwWH42',$,'OrthogonalHeight','',.P_SINGLEVALUE.,'IfcLengthMeasure',$,$,$,$,$,.READWRITE.);
#12=IFCSIMPLEPROPERTYTEMPLATE('3d4BOt4In8WR119ms1SaAe',$,'XAxisAbscissa','',.P_SINGLEVALUE.,'IfcReal',$,$,$,$,$,.READWRITE.);
#13=IFCSIMPLEPROPERTYTEMPLATE('1Q4Voqj2j84RQRI8l67m28',$,'XAxisOrdinate','',.P_SINGLEVALUE.,'IfcReal',$,$,$,$,$,.READWRITE.);
#15=IFCSIMPLEPROPERTYTEMPLATE('0O1Hc0$XnDNOG_jPBmbW5r',$,'Scale','',.P_SINGLEVALUE.,'IfcReal',$,$,$,$,$,.READWRITE.);
ENDSEC;
END-ISO-10303-21;
+7 -2
View File
@@ -82,7 +82,7 @@ class IfcExporter:
)
self.file.wrapped_data.header.file_name.preprocessor_version = "IfcOpenShell {}".format(ifcopenshell.version)
self.file.wrapped_data.header.file_name.originating_system = "{} {}".format(
self.get_application_name(), self.get_application_version()
self.get_application_name(), tool.Blender.get_blenderbim_version()
)
def sync_all_objects(self):
@@ -93,6 +93,8 @@ class IfcExporter:
try:
if isinstance(obj, bpy.types.Material):
continue
if obj.library:
continue
tool.Collector.sync(obj)
result = self.sync_object_placement(obj)
if result:
@@ -162,7 +164,7 @@ class IfcExporter:
return "BlenderBIM"
def get_application_version(self):
return ".".join(
version = ".".join(
[
str(x)
for x in [
@@ -172,6 +174,9 @@ class IfcExporter:
][0]
]
)
if blenderbim.bim.last_commit_hash != "8888888":
version += f"-{blenderbim.bim.last_commit_hash[:7]}"
return version
class IfcExportSettings:
+33 -41
View File
@@ -27,6 +27,7 @@ from bpy.app.handlers import persistent
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.module.owner.prop import get_user_person, get_user_organisation
from blenderbim.bim.module.model.data import AuthoringData
from blenderbim.bim.module.model.workspace import LIST_OF_TOOLS, TOOLS_TO_CLASSES_MAP
from mathutils import Vector
from math import cos, degrees
@@ -35,24 +36,6 @@ cwd = os.path.dirname(os.path.realpath(__file__))
global_subscription_owner = object()
def mode_callback(obj, data):
objects = bpy.context.selected_objects
if bpy.context.active_object:
objects += [bpy.context.active_object]
for obj in objects:
if (
obj.mode != "EDIT"
or not obj.data
or not isinstance(obj.data, (bpy.types.Mesh, bpy.types.Curve, bpy.types.TextCurve))
or not obj.BIMObjectProperties.ifc_definition_id
):
continue
if obj.data.BIMMeshProperties.ifc_definition_id:
tool.Ifc.edit(obj)
elif IfcStore.get_file().by_id(obj.BIMObjectProperties.ifc_definition_id).is_a("IfcGridAxis"):
tool.Ifc.edit(obj)
def name_callback(obj, data):
try:
obj.name
@@ -73,17 +56,27 @@ def name_callback(obj, data):
refresh_ui_data()
return
if not obj.BIMObjectProperties.ifc_definition_id or "/" not in obj.name:
if not obj.BIMObjectProperties.ifc_definition_id:
return
element = IfcStore.get_file().by_id(obj.BIMObjectProperties.ifc_definition_id)
if "/" in obj.name:
object_name = obj.name
element_name = obj.name.split("/", 1)[1]
else:
element_name = obj.name
object_name = element.is_a() + f"/{element_name}"
obj.name = object_name # NOTE: doesn't trigger infinite recursion
if element.is_a("IfcGridAxis"):
element.AxisTag = obj.name.split("/")[1]
element.AxisTag = object_name.split("/")[1]
refresh_ui_data()
if not element.is_a("IfcRoot"):
return
element.Name = element_name
if obj.BIMObjectProperties.collection:
obj.BIMObjectProperties.collection.name = obj.name
element.Name = "/".join(obj.name.split("/")[1:])
obj.BIMObjectProperties.collection.name = object_name
refresh_ui_data()
@@ -106,7 +99,7 @@ def update_bim_tool_props():
return
mode = bpy.context.mode
current_tool = bpy.context.workspace.tools.from_space_view3d_mode(mode)
if not current_tool or current_tool.idname != "bim.bim_tool":
if not current_tool or current_tool.idname not in LIST_OF_TOOLS:
return
element = tool.Ifc.get_entity(obj)
if not element:
@@ -114,6 +107,16 @@ def update_bim_tool_props():
representation = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
if not representation:
return
props = bpy.context.scene.BIMModelProperties
if element.is_a("IfcElementType") or element.is_a("IfcElement"):
element_type = ifcopenshell.util.element.get_type(element)
if element_type:
is_bim_tool = current_tool.idname == "bim.bim_tool"
if is_bim_tool:
props.ifc_class = element_type.is_a()
if is_bim_tool or TOOLS_TO_CLASSES_MAP.get(current_tool.idname) == element_type.is_a():
props.relating_type_id = str(element_type.id())
extrusion = tool.Model.get_extrusion(representation)
if not extrusion:
return
@@ -124,7 +127,6 @@ def update_bim_tool_props():
return x_angle
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
props = bpy.context.scene.BIMModelProperties
if not AuthoringData.is_loaded:
AuthoringData.load()
@@ -175,30 +177,20 @@ def refresh_ui_data():
except AttributeError:
pass
if isinstance(tool.Ifc.get(), ifcopenshell.sqlite):
tool.Ifc.get().clear_cache()
def purge_module_data():
from blenderbim.bim import modules
refresh_ui_data()
for name, value in modules.items():
try:
getattr(getattr(getattr(ifcopenshell.api, name), "data"), "Data").purge()
except AttributeError:
pass
# TODO: deprecate prop purge functions and refactor into data classes.
try:
getattr(value, "prop").purge()
except AttributeError:
pass
if isinstance(tool.Ifc.get(), ifcopenshell.sqlite):
tool.Ifc.get().clear_cache()
@persistent
def loadIfcStore(scene):
IfcStore.purge()
purge_module_data()
refresh_ui_data()
if not IfcStore.get_file():
return
IfcStore.get_schema()
@@ -210,7 +202,7 @@ def undo_post(scene):
if IfcStore.last_transaction != bpy.context.scene.BIMProperties.last_transaction:
IfcStore.last_transaction = bpy.context.scene.BIMProperties.last_transaction
IfcStore.undo(until_key=bpy.context.scene.BIMProperties.last_transaction)
purge_module_data()
refresh_ui_data()
tool.Ifc.rebuild_element_maps()
@@ -219,7 +211,7 @@ def redo_post(scene):
if IfcStore.last_transaction != bpy.context.scene.BIMProperties.last_transaction:
IfcStore.last_transaction = bpy.context.scene.BIMProperties.last_transaction
IfcStore.redo(until_key=bpy.context.scene.BIMProperties.last_transaction)
purge_module_data()
refresh_ui_data()
tool.Ifc.rebuild_element_maps()
+75
View File
@@ -273,6 +273,81 @@ def convert_property_group_from_si(property_group, skip_props=()):
setattr(property_group, prop_name, prop_value)
def draw_filter(layout, props, data, module):
if not data.is_loaded:
data.load()
sprops = bpy.context.scene.BIMSearchProperties
if tool.Ifc.get():
row = layout.row(align=True)
row.label(text=f"{len(data.data['saved_searches'])} Saved Searches")
if data.data["saved_searches"]:
row.operator("bim.load_search", text="", icon="IMPORT").module = module
row.operator("bim.save_search", text="", icon="EXPORT").module = module
row = layout.row(align=True)
row.operator("bim.add_filter_group", text="Add Search Group", icon="ADD").module = module
row.operator("bim.edit_filter_query", text="", icon="FILTER").module = module
for i, filter_group in enumerate(props.filter_groups):
box = layout.box()
row = box.row(align=True)
row.prop(sprops, "facet", text="")
op = row.operator("bim.add_filter", text="Add Filter", icon="ADD")
op.type = sprops.facet
op.index = i
op.module = module
for j, ifc_filter in enumerate(filter_group.filters):
if ifc_filter.type == "entity":
row = box.row(align=True)
row.prop(ifc_filter, "value", text="", icon="FILE_3D")
elif ifc_filter.type == "attribute":
row = box.row(align=True)
row.prop(ifc_filter, "name", text="", icon="COPY_ID")
row.prop(ifc_filter, "value", text="")
elif ifc_filter.type == "type":
row = box.row(align=True)
row.prop(ifc_filter, "value", text="", icon="FILE_VOLUME")
elif ifc_filter.type == "material":
row = box.row(align=True)
row.prop(ifc_filter, "value", text="", icon="MATERIAL")
elif ifc_filter.type == "property":
row = box.row(align=True)
row.prop(ifc_filter, "pset", text="", icon="PROPERTIES")
row.prop(ifc_filter, "name", text="")
row.prop(ifc_filter, "value", text="")
elif ifc_filter.type == "classification":
row = box.row(align=True)
row.prop(ifc_filter, "value", text="", icon="OUTLINER")
elif ifc_filter.type == "location":
row = box.row(align=True)
row.prop(ifc_filter, "value", text="", icon="PACKAGE")
elif ifc_filter.type == "query":
row = box.row(align=True)
row.prop(ifc_filter, "name", text="", icon="POINTCLOUD_DATA")
row.prop(ifc_filter, "value", text="")
elif ifc_filter.type == "instance":
row = box.row(align=True)
row.prop(ifc_filter, "value", text="", icon="GRIP")
op = row.operator("bim.select_filter_elements", text="", icon="EYEDROPPER")
op.group_index = i
op.index = j
op.module = module
op = row.operator("bim.remove_filter", text="", icon="X")
op.group_index = i
op.index = j
op.module = module
row = box.row()
op = row.operator("bim.remove_filter_group", icon="X")
op.index = i
op.module = module
# TODO this should move into ifcopenshell.util
+8 -6
View File
@@ -220,8 +220,9 @@ class IfcStore:
if isinstance(obj, bpy.types.Material):
blenderbim.bim.handler.subscribe_to(obj, "diffuse_color", blenderbim.bim.handler.color_callback)
elif isinstance(obj, bpy.types.Object):
blenderbim.bim.handler.subscribe_to(obj, "mode", blenderbim.bim.handler.mode_callback)
blenderbim.bim.handler.subscribe_to(obj, "active_material_index", blenderbim.bim.handler.active_material_index_callback)
blenderbim.bim.handler.subscribe_to(
obj, "active_material_index", blenderbim.bim.handler.active_material_index_callback
)
if IfcStore.history:
data = {"id": element.id(), "guid": getattr(element, "GlobalId", None), "obj": obj.name}
@@ -247,8 +248,9 @@ class IfcStore:
if isinstance(obj, bpy.types.Material):
blenderbim.bim.handler.subscribe_to(obj, "diffuse_color", blenderbim.bim.handler.color_callback)
elif isinstance(obj, bpy.types.Object):
blenderbim.bim.handler.subscribe_to(obj, "mode", blenderbim.bim.handler.mode_callback)
blenderbim.bim.handler.subscribe_to(obj, "active_material_index", blenderbim.bim.handler.active_material_index_callback)
blenderbim.bim.handler.subscribe_to(
obj, "active_material_index", blenderbim.bim.handler.active_material_index_callback
)
# TODO Listeners are not re-registered. Does this cause nasty problems to debug later on?
# TODO We're handling id_map and guid_map, but what about edited_objs? This might cause big problems.
@@ -321,7 +323,7 @@ class IfcStore:
IfcStore.begin_transaction(operator)
if tool.Ifc.get():
tool.Ifc.get().begin_transaction()
if BrickStore.graph:
if BrickStore.graph is not None: # `if BrickStore.graph` by itself takes ages.
BrickStore.begin_transaction()
# This empty transaction ensures that each operator has at least one transaction
IfcStore.add_transaction_operation(operator, rollback=lambda data: True, commit=lambda data: True)
@@ -339,7 +341,7 @@ class IfcStore:
IfcStore.add_transaction_operation(
operator, rollback=lambda d: tool.Ifc.get().undo(), commit=lambda d: tool.Ifc.get().redo()
)
if BrickStore.graph:
if BrickStore.graph is not None: # `if BrickStore.graph` by itself takes ages.
BrickStore.end_transaction()
IfcStore.end_transaction(operator)
blenderbim.bim.handler.refresh_ui_data()
+296 -255
View File
@@ -20,10 +20,9 @@ import os
import re
import bpy
import time
import json
import bmesh
import shutil
import logging
import threading
import mathutils
import numpy as np
import multiprocessing
@@ -31,7 +30,6 @@ import ifcopenshell
import ifcopenshell.geom
import ifcopenshell.util.unit
import ifcopenshell.util.element
import ifcopenshell.util.selector
import ifcopenshell.util.geolocation
import blenderbim.tool as tool
from itertools import chain, accumulate
@@ -39,16 +37,6 @@ from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.module.drawing.prop import ANNOTATION_TYPES_DATA
class FileCopy(threading.Thread):
def __init__(self, file_path, destination):
threading.Thread.__init__(self)
self.file_path = file_path
self.destination = destination
def run(self):
shutil.copy(self.file_path, self.destination)
class MaterialCreator:
def __init__(self, ifc_import_settings, ifc_importer):
self.mesh = None
@@ -108,7 +96,9 @@ class MaterialCreator:
has_parsed = True
elif hasattr(element, "RepresentationMaps"):
for representation_map in element.RepresentationMaps:
if self.parse_representation(representation_map.MappedRepresentation):
if not representation_map.MappedRepresentation:
has_parsed = True # Accommodate invalid IFC data from Revit
elif self.parse_representation(representation_map.MappedRepresentation):
has_parsed = True
return has_parsed
@@ -158,12 +148,15 @@ class MaterialCreator:
faces_remap = None
texture_map = None
if coordinates.is_a("IfcIndexedPolygonalTextureMap"):
faces_remap = [[coordinates_remap[i-1] for i in tex_coord_index.TexCoordsOf.CoordIndex]
for tex_coord_index in coordinates.TexCoordIndices]
faces_remap = [
[coordinates_remap[i - 1] for i in tex_coord_index.TexCoordsOf.CoordIndex]
for tex_coord_index in coordinates.TexCoordIndices
]
texture_map = [tex_coord_index.TexCoordIndex for tex_coord_index in coordinates.TexCoordIndices]
elif coordinates.is_a("IfcIndexedTriangleTextureMap"):
faces_remap = [[coordinates_remap[i-1] for i in triangle_face]
for triangle_face in coordinates.MappedTo.CoordIndex]
faces_remap = [
[coordinates_remap[i - 1] for i in triangle_face] for triangle_face in coordinates.MappedTo.CoordIndex
]
texture_map = coordinates.TexCoordIndex
# apply uv to each face
@@ -178,7 +171,7 @@ class MaterialCreator:
)
# apply uv to each loop
for loop, i in zip(bface.loops, texCoordIndex):
loop[uv_layer].uv = coordinates.TexCoords.TexCoordsList[i-1]
loop[uv_layer].uv = coordinates.TexCoords.TexCoordsList[i - 1]
# Finish up, write the bmesh back to the mesh
bm.to_mesh(self.mesh)
@@ -223,20 +216,8 @@ class IfcImporter:
self.ifc_import_settings = ifc_import_settings
self.diff = None
self.file = None
self.settings = ifcopenshell.geom.settings()
self.settings.set_deflection_tolerance(self.ifc_import_settings.deflection_tolerance)
self.settings.set_angular_tolerance(self.ifc_import_settings.angular_tolerance)
self.settings.set(self.settings.STRICT_TOLERANCE, True)
self.settings_curve = ifcopenshell.geom.settings()
self.settings_curve.set_deflection_tolerance(self.ifc_import_settings.deflection_tolerance)
self.settings_curve.set_angular_tolerance(self.ifc_import_settings.angular_tolerance)
self.settings_curve.set(self.settings_curve.STRICT_TOLERANCE, True)
self.settings_curve.set(self.settings_curve.INCLUDE_CURVES, True)
self.settings_native = ifcopenshell.geom.settings()
self.settings_native.set(self.settings_native.INCLUDE_CURVES, True)
self.settings_2d = ifcopenshell.geom.settings()
self.settings_2d.set(self.settings_2d.INCLUDE_CURVES, True)
self.settings_2d.set(self.settings_2d.STRICT_TOLERANCE, True)
self.context_settings = []
self.contexts = []
self.project = None
self.has_existing_project = False
self.collections = {}
@@ -275,6 +256,8 @@ class IfcImporter:
self.profile_code("Calculate unit scale")
self.calculate_model_offset()
self.profile_code("Calculate model offset")
self.predict_dense_mesh()
self.profile_code("Predict dense mesh")
self.set_units()
self.profile_code("Set units")
self.create_project()
@@ -318,6 +301,9 @@ class IfcImporter:
self.profile_code("Merging by colour")
self.set_default_context()
self.profile_code("Setting default context")
if self.ifc_import_settings.should_setup_viewport_camera:
self.setup_viewport_camera()
self.setup_arrays()
self.update_progress(100)
bpy.context.window_manager.progress_end()
@@ -340,32 +326,83 @@ class IfcImporter:
return abs(coords[0]) > limit or abs(coords[1]) > limit or abs(coords[2]) > limit
def process_context_filter(self):
# Facetation is to accommodate broken Revit files
# See https://forums.buildingsmart.org/t/suggestions-on-how-to-improve-clarity-of-representation-context-usage-in-documentation/3663/6?u=moult
self.body_contexts = [
c.id()
for c in self.file.by_type("IfcGeometricRepresentationSubContext")
if c.ContextIdentifier in ["Body", "Facetation"]
]
# Ideally, all representations should be in a subcontext, but some BIM programs don't do this correctly
self.body_contexts.extend(
[
c.id()
for c in self.file.by_type("IfcGeometricRepresentationContext", include_subtypes=False)
if c.ContextType == "Model"
]
)
if self.body_contexts:
self.settings.set_context_ids(self.body_contexts)
# Annotation ContextType is to accommodate broken Revit files
# See https://github.com/Autodesk/revit-ifc/issues/187
self.plan_contexts = [
c.id()
for c in self.file.by_type("IfcGeometricRepresentationContext")
if c.ContextType in ["Plan", "Annotation"] or c.ContextIdentifier == "Annotation"
type_priority = ["Model", "Plan", "Annotation"]
identifier_priority = [
"Body",
"Body-FallBack",
"Facetation",
"FootPrint",
"Profile",
"Surface",
"Reference",
"Axis",
"Clearance",
"Box",
"Lighting",
"Annotation",
"CoG",
]
if self.plan_contexts:
self.settings_2d.set_context_ids(self.plan_contexts)
target_view_priority = [
"MODEL_VIEW",
"PLAN_VIEW",
"REFLECTED_PLAN_VIEW",
"ELEVATION_VIEW",
"SECTION_VIEW",
"GRAPH_VIEW",
"SKETCH_VIEW",
"USERDEFINED",
"NOTDEFINED",
]
def sort_context(context):
priority = []
if context.ContextType in type_priority:
priority.append(len(type_priority) - type_priority.index(context.ContextType))
else:
priority.append(0)
return tuple(priority)
def sort_subcontext(context):
priority = []
if context.ContextType in type_priority:
priority.append(len(type_priority) - type_priority.index(context.ContextType))
else:
priority.append(0)
if context.ContextIdentifier in identifier_priority:
priority.append(len(identifier_priority) - identifier_priority.index(context.ContextIdentifier))
else:
priority.append(0)
if context.TargetView in target_view_priority:
priority.append(len(target_view_priority) - target_view_priority.index(context.TargetView))
else:
priority.append(0)
priority.append(context.TargetScale or 0) # Big then small
return tuple(priority)
# Ideally, all representations should be in a subcontext, but some BIM programs don't do this correctly
self.contexts = sorted(
self.file.by_type("IfcGeometricRepresentationSubContext"), key=sort_subcontext, reverse=True
) + sorted(
self.file.by_type("IfcGeometricRepresentationContext", include_subtypes=False),
key=sort_context,
reverse=True,
)
for context in self.contexts:
settings = ifcopenshell.geom.settings()
settings.set_deflection_tolerance(self.ifc_import_settings.deflection_tolerance)
settings.set_angular_tolerance(self.ifc_import_settings.angular_tolerance)
settings.set(settings.STRICT_TOLERANCE, True)
settings.set(settings.INCLUDE_CURVES, True)
settings.set_context_ids([context.id()])
self.context_settings.append(settings)
def process_element_filter(self):
offset = self.ifc_import_settings.element_offset
@@ -436,13 +473,51 @@ class IfcImporter:
or getattr(element, "HasOpenings", None)
):
return
representations = self.get_transformed_body_representations(element.Representation.Representations)
representation = None
representation_priority = None
context = None
for rep in element.Representation.Representations:
if rep.ContextOfItems in self.contexts:
rep_priority = self.contexts.index(rep.ContextOfItems)
if representation is None or rep_priority < representation_priority:
representation = rep
representation_priority = rep_priority
context = rep.ContextOfItems
if not representation:
return
matrix = np.eye(4)
representation_id = None
rep = representation
while True:
if rep.Items 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
if representation_id is None:
representation_id = rep.id()
rep = rep.Items[0].MappingSource.MappedRepresentation
else:
if representation_id is None:
representation_id = rep.id()
break
resolved_representation = ifcopenshell.util.representation.resolve_representation(representation)
matrix[0][3] *= self.unit_scale
matrix[1][3] *= self.unit_scale
matrix[2][3] *= self.unit_scale
# Single swept disk solids (e.g. rebar) are better natively represented as beveled curves
if self.is_native_swept_disk_solid(element, representations):
if self.is_native_swept_disk_solid(element, resolved_representation):
self.native_data[element.GlobalId] = {
"representations": representations,
"representation": self.get_body_representation(element.Representation.Representations),
"matrix": matrix,
"context": context,
"geometry_id": representation_id,
"representation": resolved_representation,
"type": "IfcSweptDiskSolid",
}
return True
@@ -451,51 +526,52 @@ class IfcImporter:
return False # Performance improvements only occur on edge cases currently
# FacetedBreps (without voids) are meshes. See #841.
if self.is_native_faceted_brep(representations):
if self.is_native_faceted_brep(resolved_representation):
self.native_data[element.GlobalId] = {
"representations": representations,
"representation": self.get_body_representation(element.Representation.Representations),
"matrix": matrix,
"context": context,
"geometry_id": representation_id,
"representation": resolved_representation,
"type": "IfcFacetedBrep",
}
return True
if self.is_native_face_based_surface_model(representations):
if self.is_native_face_based_surface_model(resolved_representation):
self.native_data[element.GlobalId] = {
"representations": representations,
"representation": self.get_body_representation(element.Representation.Representations),
"matrix": matrix,
"context": context,
"geometry_id": representation_id,
"representation": resolved_representation,
"type": "IfcFaceBasedSurfaceModel",
}
return True
def is_native_swept_disk_solid(self, element, representations):
for representation in representations:
items = representation["raw"].Items or [] # Be forgiving of invalid IFCs because Revit :(
if len(items) == 1 and items[0].is_a("IfcSweptDiskSolid"):
if tool.Blender.Modifier.is_railing(element):
return False
return True
elif len(items) and ( # See #2508 why we accommodate for invalid IFCs here
items[0].is_a("IfcSweptDiskSolid")
and len({i.is_a() for i in items}) == 1
and len({i.Radius for i in items}) == 1
):
if tool.Blender.Modifier.is_railing(element):
return False
return True
def is_native_swept_disk_solid(self, element, representation):
items = representation.Items or [] # Be forgiving of invalid IFCs because Revit :(
if len(items) == 1 and items[0].is_a("IfcSweptDiskSolid"):
if tool.Blender.Modifier.is_railing(element):
return False
return True
elif len(items) and ( # See #2508 why we accommodate for invalid IFCs here
items[0].is_a("IfcSweptDiskSolid")
and len({i.is_a() for i in items}) == 1
and len({i.Radius for i in items}) == 1
):
if tool.Blender.Modifier.is_railing(element):
return False
return True
return False
def is_native_faceted_brep(self, representations):
for representation in representations:
for i in representation["raw"].Items:
if i.is_a() != "IfcFacetedBrep":
return False
def is_native_faceted_brep(self, representation):
for i in representation.Items:
if i.is_a() != "IfcFacetedBrep":
return False
return True
def is_native_face_based_surface_model(self, representations):
for representation in representations:
for i in representation["raw"].Items:
if i.is_a() != "IfcFaceBasedSurfaceModel":
return False
def is_native_face_based_surface_model(self, representation):
for i in representation.Items:
if i.is_a() != "IfcFaceBasedSurfaceModel":
return False
return True
def get_products_from_shape_representation(self, element):
@@ -507,6 +583,26 @@ class IfcImporter:
products.extend(self.get_products_from_shape_representation(inverse_element))
return products
def predict_dense_mesh(self):
threshold = 10000 # Just from experience.
faces = [len(e.CfsFaces) for e in self.file.by_type("IfcClosedShell")]
if faces and max(faces) > threshold:
self.ifc_import_settings.should_use_native_meshes = True
return
if self.file.schema == "IFC2X3":
return
faces = [len(e.Faces) for e in self.file.by_type("IfcPolygonalFaceSet")]
if faces and max(faces) > threshold:
self.ifc_import_settings.should_use_native_meshes = True
return
faces = [len(e.CoordIndex) for e in self.file.by_type("IfcTriangulatedFaceSet")]
if faces and max(faces) > threshold:
self.ifc_import_settings.should_use_native_meshes = True
def calculate_model_offset(self):
props = bpy.context.scene.BIMGeoreferenceProperties
if props.has_blender_offset:
@@ -573,7 +669,9 @@ class IfcImporter:
return
if not self.does_element_likely_have_geometry_far_away(element):
continue
shape = ifcopenshell.geom.create_shape(self.settings, element)
shape = self.create_generic_shape(element)
if not shape:
continue
m = shape.transformation.matrix.data
mat = np.array(
([m[0], m[3], m[6], m[9]], [m[1], m[4], m[7], m[10]], [m[2], m[5], m[8], m[11]], [0, 0, 0, 1])
@@ -645,7 +743,7 @@ class IfcImporter:
self.ifc_import_settings.logger.error("An invalid grid was found %s", grid)
continue
if grid.Representation:
shape = ifcopenshell.geom.create_shape(self.settings_2d, grid)
shape = self.create_generic_shape(grid)
grid_obj = self.create_product(grid, shape)
if bpy.context.preferences.addons["blenderbim"].preferences.lock_grids_on_import:
grid_obj.lock_location = (True, True, True)
@@ -664,7 +762,7 @@ class IfcImporter:
def create_grid_axes(self, axes, grid_collection, grid_obj):
for axis in axes:
shape = ifcopenshell.geom.create_shape(self.settings_2d, axis.AxisCurve)
shape = self.create_generic_shape(axis.AxisCurve)
mesh = self.create_mesh(axis, shape)
obj = bpy.data.objects.new(f"IfcGridAxis/{axis.AxisTag}", mesh)
if bpy.context.preferences.addons["blenderbim"].preferences.lock_grids_on_import:
@@ -684,27 +782,21 @@ class IfcImporter:
self.ifc_import_settings.logger.info("Creating object %s", element)
mesh = None
if self.ifc_import_settings.should_load_geometry:
representation = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
if not representation:
representation = ifcopenshell.util.representation.get_representation(element, "Plan", "Annotation")
if not representation:
representation = ifcopenshell.util.representation.get_representation(element, "Model", "Annotation")
if representation:
for context in self.contexts:
representation = ifcopenshell.util.representation.get_representation(element, context)
if not representation:
continue
mesh_name = "{}/{}".format(representation.ContextOfItems.id(), representation.id())
mesh = self.meshes.get(mesh_name)
if mesh is None:
shape = None
try:
shape = ifcopenshell.geom.create_shape(self.settings, representation)
except:
try:
shape = ifcopenshell.geom.create_shape(self.settings_2d, representation)
except:
self.ifc_import_settings.logger.error("Failed to generate shape for %s", element)
shape = self.create_generic_shape(representation)
if shape:
mesh = self.create_mesh(element, shape)
tool.Loader.link_mesh(shape, mesh)
self.meshes[mesh_name] = mesh
else:
self.ifc_import_settings.logger.error("Failed to generate shape for %s", element)
break
obj = bpy.data.objects.new(tool.Loader.get_name(element), mesh)
self.link_element(element, obj)
self.material_creator.create(element, obj, mesh)
@@ -728,20 +820,16 @@ class IfcImporter:
checkpoint = time.time()
self.incrementally_merge_objects()
native_data = self.native_data[element.GlobalId]
representation = native_data["representation"]
if not representation:
continue
context_id = representation.ContextOfItems.id() if hasattr(representation, "ContextOfItems") else 0
mesh_name = f"{context_id}/{representation.id()}"
mesh_name = f"{native_data['context'].id()}/{native_data['geometry_id']}"
mesh = self.meshes.get(mesh_name)
if mesh is None:
if native_data["type"] == "IfcSweptDiskSolid":
mesh = self.create_native_swept_disk_solid(element, mesh_name)
mesh = self.create_native_swept_disk_solid(element, mesh_name, native_data)
elif native_data["type"] == "IfcFacetedBrep":
mesh = self.create_native_faceted_brep(element, mesh_name)
mesh = self.create_native_faceted_brep(element, mesh_name, native_data)
elif native_data["type"] == "IfcFaceBasedSurfaceModel":
mesh = self.create_native_faceted_brep(element, mesh_name)
tool.Ifc.link(representation, mesh)
mesh = self.create_native_faceted_brep(element, mesh_name, native_data)
tool.Ifc.link(tool.Ifc.get().by_id(native_data["geometry_id"]), mesh)
mesh.name = mesh_name
self.meshes[mesh_name] = mesh
self.create_product(element, mesh=mesh)
@@ -753,21 +841,25 @@ class IfcImporter:
def create_elements(self):
self.create_generic_elements(self.elements)
def create_generic_shape(self, element):
for settings in self.context_settings:
try:
result = ifcopenshell.geom.create_shape(settings, element)
if result:
return result
except:
pass
def create_generic_elements(self, elements):
if isinstance(self.file, ifcopenshell.sqlite):
return self.create_generic_sqlite_elements(elements)
# Based on my experience in viewing BIM models, representations are prioritised as follows:
# 1. 3D Body, 2. 2D Body, 3. 2D Plans / annotations, 4. Point clouds, 5. No representation
# If an element has a representation that doesn't follow 1, 2, 3, or 4, it will not show by default.
# The user can load them later if they want to view them.
if self.ifc_import_settings.should_load_geometry:
products = self.create_products(elements)
elements -= products
products = self.create_products(elements, settings=self.settings_curve)
elements -= products
products = self.create_products(elements, settings=self.settings_2d)
elements -= products
for settings in self.context_settings:
if not elements:
break
products = self.create_products(elements, settings=settings)
elements -= products
products = self.create_pointclouds(elements)
elements -= products
@@ -825,9 +917,6 @@ class IfcImporter:
self.create_product(element, mesh=mesh)
def create_products(self, products, settings=None):
if settings is None:
settings = self.settings
results = set()
if not products:
return results
@@ -864,16 +953,8 @@ class IfcImporter:
shape = iterator.get()
if shape:
product = self.file.by_id(shape.id)
if self.body_contexts:
self.create_product(product, shape)
results.add(product)
else:
if shape.context not in ["Body", "Facetation"] and IfcStore.get_element(shape.id):
# We only load a single context, and we prioritise the Body context. See #1290.
pass
else:
self.create_product(product, shape)
results.add(product)
self.create_product(product, shape)
results.add(product)
if not iterator.next():
break
print("Done creating geometry")
@@ -901,10 +982,10 @@ class IfcImporter:
self.structural_collection.children.link(self.structural_connection_collection)
self.project["blender"].children.link(self.structural_collection)
self.create_products(self.file.by_type("IfcStructuralCurveMember"), settings=self.settings_2d)
self.create_products(self.file.by_type("IfcStructuralCurveConnection"), settings=self.settings_2d)
self.create_products(self.file.by_type("IfcStructuralSurfaceMember"), settings=self.settings_2d)
self.create_products(self.file.by_type("IfcStructuralSurfaceConnection"), settings=self.settings_2d)
self.create_generic_elements(set(self.file.by_type("IfcStructuralCurveMember")))
self.create_generic_elements(set(self.file.by_type("IfcStructuralCurveConnection")))
self.create_generic_elements(set(self.file.by_type("IfcStructuralSurfaceMember")))
self.create_generic_elements(set(self.file.by_type("IfcStructuralSurfaceConnection")))
self.create_structural_point_connections()
def create_structural_point_connections(self):
@@ -973,10 +1054,14 @@ class IfcImporter:
placement_matrix = self.get_element_matrix(product)
vertex_list = []
for item in representation.Items:
if item.is_a("IfcCartesianPointList"):
if item.is_a("IfcCartesianPointList3D"):
vertex_list.extend(
mathutils.Vector(list(coordinates)) * self.unit_scale for coordinates in item.CoordList
)
elif item.is_a("IfcCartesianPointList2D"):
vertex_list.extend(
mathutils.Vector(list(coordinates)).to_3d() * self.unit_scale for coordinates in item.CoordList
)
elif item.is_a("IfcCartesianPoint"):
vertex_list.append(mathutils.Vector(list(item.Coordinates)) * self.unit_scale)
@@ -1049,7 +1134,7 @@ class IfcImporter:
elif style.is_a("IfcPresentationStyleAssignment"):
styles.extend(style.Styles)
def create_native_faceted_brep(self, element, mesh_name):
def create_native_faceted_brep(self, element, mesh_name, native_data):
# TODO: georeferencing?
# co [x y z x y z x y z ...]
# vertex_index [i i i i i ...]
@@ -1066,10 +1151,11 @@ class IfcImporter:
"material_ids": [],
}
for representation in element.Representation.Representations:
if representation.ContextOfItems.id() not in self.body_contexts:
continue
self.convert_representation(representation)
for item in native_data["representation"].Items:
if item.is_a() == "IfcFacetedBrep":
self.convert_representation_item_faceted_brep(item)
elif item.is_a() == "IfcFaceBasedSurfaceModel":
self.convert_representation_item_face_based_surface_model(item)
mesh = bpy.data.meshes.new("Native")
@@ -1086,19 +1172,33 @@ class IfcImporter:
)
verts = [None] * len(self.mesh_data["co"])
for i in range(0, len(self.mesh_data["co"]), 3):
verts[i], verts[i + 1], verts[i + 2] = ifcopenshell.util.geolocation.enh2xyz(
self.mesh_data["co"][i] * self.unit_scale,
self.mesh_data["co"][i + 1] * self.unit_scale,
self.mesh_data["co"][i + 2] * self.unit_scale,
offset_point[0] * self.unit_scale,
offset_point[1] * self.unit_scale,
offset_point[2] * self.unit_scale,
float(props.blender_x_axis_abscissa),
float(props.blender_x_axis_ordinate),
verts[i], verts[i + 1], verts[i + 2], _ = native_data["matrix"] @ mathutils.Vector(
(
*ifcopenshell.util.geolocation.enh2xyz(
self.mesh_data["co"][i] * self.unit_scale,
self.mesh_data["co"][i + 1] * self.unit_scale,
self.mesh_data["co"][i + 2] * self.unit_scale,
offset_point[0] * self.unit_scale,
offset_point[1] * self.unit_scale,
offset_point[2] * self.unit_scale,
float(props.blender_x_axis_abscissa),
float(props.blender_x_axis_ordinate),
),
1,
)
)
mesh["has_cartesian_point_offset"] = True
else:
verts = [c * self.unit_scale for c in self.mesh_data["co"]]
verts = [None] * len(self.mesh_data["co"])
for i in range(0, len(self.mesh_data["co"]), 3):
verts[i], verts[i + 1], verts[i + 2], _ = native_data["matrix"] @ mathutils.Vector(
(
self.mesh_data["co"][i] * self.unit_scale,
self.mesh_data["co"][i + 1] * self.unit_scale,
self.mesh_data["co"][i + 2] * self.unit_scale,
1,
)
)
mesh["has_cartesian_point_offset"] = False
mesh.vertices.add(self.mesh_data["total_verts"])
@@ -1108,25 +1208,13 @@ class IfcImporter:
mesh.polygons.add(self.mesh_data["total_polygons"])
mesh.polygons.foreach_set("loop_start", self.mesh_data["loop_start"])
mesh.polygons.foreach_set("loop_total", self.mesh_data["loop_total"])
mesh.polygons.foreach_set("use_smooth", [0] * self.mesh_data["total_polygons"])
mesh.update()
mesh["ios_materials"] = self.mesh_data["materials"]
mesh["ios_material_ids"] = self.mesh_data["material_ids"]
return mesh
def convert_representation(self, representation):
for item in representation.Items:
self.convert_representation_item(item)
def convert_representation_item(self, item):
if item.is_a("IfcMappedItem"):
# mapping_target = matrix
self.convert_representation(item.MappingSource.MappedRepresentation)
elif item.is_a() == "IfcFacetedBrep":
self.convert_representation_item_faceted_brep(item)
elif item.is_a() == "IfcFaceBasedSurfaceModel":
self.convert_representation_item_face_based_surface_model(item)
def convert_representation_item_face_based_surface_model(self, item):
mesh = item.get_info_2(recursive=True)
for face_set in mesh["FbsmFaces"]:
@@ -1221,63 +1309,34 @@ class IfcImporter:
self.mesh_data["loop_start"].extend(loop_start)
# list(di1.keys())
def create_native_swept_disk_solid(self, element, mesh_name):
def create_native_swept_disk_solid(self, element, mesh_name, native_data):
# TODO: georeferencing?
curve = bpy.data.curves.new(mesh_name, type="CURVE")
curve.dimensions = "3D"
curve.resolution_u = 2
polyline = curve.splines.new("POLY")
for representation in self.native_data[element.GlobalId]["representations"]:
for item in representation["raw"].Items:
# TODO: support inner radius, start param, and end param
geometry = ifcopenshell.geom.create_shape(self.settings_native, 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)]
edges = [[e[i], e[i + 1]] for i in range(0, len(e), 2)]
v2 = None
for edge in edges:
v1 = vertices[edge[0]]
if v1 != v2:
polyline = curve.splines.new("POLY")
polyline.points[-1].co = representation["matrix"] @ mathutils.Vector(v1)
v2 = vertices[edge[1]]
polyline.points.add(1)
polyline.points[-1].co = representation["matrix"] @ mathutils.Vector(v2)
for item in native_data["representation"].Items:
# 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)]
edges = [[e[i], e[i + 1]] for i in range(0, len(e), 2)]
v2 = None
for edge in edges:
v1 = vertices[edge[0]]
if v1 != v2:
polyline = curve.splines.new("POLY")
polyline.points[-1].co = native_data["matrix"] @ mathutils.Vector(v1)
v2 = vertices[edge[1]]
polyline.points.add(1)
polyline.points[-1].co = native_data["matrix"] @ mathutils.Vector(v2)
curve.bevel_depth = self.unit_scale * item.Radius
curve.use_fill_caps = True
return curve
def create_native_annotation(self, element, mesh_name):
# TODO: georeferencing?
curve = bpy.data.curves.new(mesh_name, type="CURVE")
curve.dimensions = "3D"
curve.resolution_u = 2
polyline = curve.splines.new("POLY")
for representation in self.native_data[element.GlobalId]["representations"]:
for item in representation["raw"].Items:
# TODO: support inner radius, start param, and end param
geometry = ifcopenshell.geom.create_shape(self.settings_native, 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)]
edges = [[e[i], e[i + 1]] for i in range(0, len(e), 2)]
v2 = None
for edge in edges:
v1 = vertices[edge[0]]
if v1 != v2:
polyline = curve.splines.new("POLY")
polyline.points[-1].co = representation["matrix"] @ mathutils.Vector(v1)
v2 = vertices[edge[1]]
polyline.points.add(1)
polyline.points[-1].co = representation["matrix"] @ mathutils.Vector(v2)
curve.bevel_depth = self.unit_scale * item.Radius
return curve
def merge_by_class(self):
merge_set = {}
id_set = {}
@@ -1679,41 +1738,6 @@ class IfcImporter:
result[2][3] *= self.unit_scale
return result
def get_body_representation(self, representations):
for representation in representations:
if (
representation.RepresentationIdentifier == "Body"
and representation.RepresentationType == "MappedRepresentation"
):
if len(representation.Items) > 1:
return representation
return self.get_body_representation([representation.Items[0].MappingSource.MappedRepresentation])
elif representation.RepresentationIdentifier == "Body":
return representation
def get_transformed_body_representations(self, representations, matrix=None):
if matrix is None:
matrix = mathutils.Matrix()
results = []
for representation in representations:
if (
representation.RepresentationIdentifier == "Body"
and representation.RepresentationType == "MappedRepresentation"
):
for item in representation.Items:
# TODO: Confirm if this transformation is right
transform = self.get_axis2placement(item.MappingSource.MappingOrigin)
if item.MappingTarget:
transform = transform @ self.get_cartesiantransformationoperator(item.MappingTarget)
results.extend(
self.get_transformed_body_representations(
[item.MappingSource.MappedRepresentation], transform @ matrix
)
)
elif representation.RepresentationIdentifier == "Body":
results.append({"raw": representation, "matrix": self.scale_matrix(matrix)})
return results
def scale_matrix(self, matrix):
matrix[0][3] *= self.unit_scale
matrix[1][3] *= self.unit_scale
@@ -1896,12 +1920,28 @@ 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
tool.Geometry.record_object_position(obj)
def setup_viewport_camera(self):
context_override = tool.Blender.get_viewport_context()
with bpy.context.temp_override(**context_override):
bpy.ops.object.select_all(action="SELECT")
bpy.ops.view3d.view_selected()
bpy.ops.object.select_all(action="DESELECT")
def setup_arrays(self):
for element in self.file.by_type("IfcElement"):
pset_data = ifcopenshell.util.element.get_pset(element, "BBIM_Array")
if not pset_data or not pset_data.get("Data", None): # skip array children
continue
for i in range(len(json.loads(pset_data["Data"]))):
tool.Blender.Modifier.Array.set_children_lock_state(element, i, True)
tool.Blender.Modifier.Array.constrain_children_to_parent(element)
class IfcImportSettings:
def __init__(self):
@@ -1924,6 +1964,7 @@ class IfcImportSettings:
self.element_limit = 30000
self.has_filter = None
self.should_filter_spatial_elements = True
self.should_setup_viewport_camera = True
self.elements = set()
self.collection_mode = "DECOMPOSITION"
@@ -202,6 +202,7 @@ class BIM_OT_select_aggregate(bpy.types.Operator):
aggregate_obj = tool.Ifc.get_object(aggregate)
if aggregate_obj in context.selectable_objects:
aggregate_obj.select_set(True)
bpy.context.view_layer.objects.active = aggregate_obj
return {"FINISHED"}
@@ -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):
@@ -35,13 +35,16 @@ classes = (
operator.ViewBrickItem,
operator.SerializeBrick,
operator.AddBrickNamespace,
operator.SetBrickListRoot,
operator.RemoveBrickRelation,
prop.Brick,
prop.BIMBrickProperties,
ui.BIM_PT_brickschema,
ui.BIM_PT_ifc_brickschema_references,
ui.BIM_PT_brickschema_project_info,
ui.BIM_PT_brickschema_namespaces,
ui.BIM_PT_brickschema_create_entity,
ui.BIM_PT_brickschema_viewport,
ui.BIM_UL_bricks,
ui.BIM_PT_ifc_brickschema_references,
)
@@ -46,7 +46,7 @@ class BrickschemaData:
@classmethod
def get_is_loaded(cls):
return BrickStore.graph is not None
return BrickStore.graph is not None # `if BrickStore.graph` by itself takes ages.
@classmethod
def active_relations(cls):
@@ -67,45 +67,63 @@ class BrickschemaData:
PREFIX brick: <https://brickschema.org/schema/Brick#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
SELECT DISTINCT ?predicate ?object ?sp ?sv WHERE {
<{uri}> ?predicate ?object .
OPTIONAL {
{ ?predicate rdfs:range brick:TimeseriesReference . }
UNION
{ ?predicate a brick:EntityProperty . }
?object ?sp ?sv }
SELECT DISTINCT ?predicate ?object ?label ?sp ?sv WHERE {
<{uri}> ?predicate ?object .
OPTIONAL {
?object rdfs:label ?label .
}
OPTIONAL {
{ ?predicate rdfs:range brick:TimeseriesReference . }
UNION
{ ?predicate a brick:EntityProperty . }
?object ?sp ?sv .
}
}
GROUP BY ?object
""".replace(
"{uri}", uri
)
)
for row in query:
predicate = row.get("predicate")
predicate_name = predicate.toPython().split("#")[-1]
object = row.get("object")
object_name = object.toPython().split("#")[-1]
predicate_uri = row.get("predicate")
predicate_name = predicate_uri.toPython().split("#")[-1]
object_uri = row.get("object")
object_name = row.get("label")
if not object_name:
if isinstance(object_uri, BNode):
object_name = "[]"
else:
try:
object_name = object_uri.toPython().split("#")[-1]
except:
object_name = str(object_uri)
results.append(
{
"predicate": predicate,
"predicate_uri": predicate_uri,
"predicate_name": predicate_name,
"object": object,
"object_uri": object_uri,
"object_name": object_name,
"is_uri": isinstance(object, URIRef),
"object_uri": object.toPython(),
"is_globalid": predicate == "globalID",
"is_uri": isinstance(object_uri, URIRef),
"is_globalid": predicate_name == "ifcGlobalID",
}
)
# if isinstance(row.get("object"), BNode):
# for s, p, o in BrickStore.graph.triples((object, None, None)):
# results.append(
# {
# "predicate": predicate + ":" + p.toPython().split("#")[-1],
# "object": o.toPython().split("#")[-1],
# "is_uri": isinstance(o, URIRef),
# "object_uri": o.toPython(),
# "is_globalid": p.toPython().split("#")[-1] == "globalID",
# }
# )
if isinstance(object_uri, BNode):
for subject2, predicate2, object2 in BrickStore.graph.triples((object_uri, None, None)):
predicate2_name = predicate2.toPython().split("#")[-1]
try:
object2_name = object2.toPython().split("#")[-1]
except:
object2_name = str(object2)
results.append(
{
"predicate_uri": None,
"predicate_name": predicate_name + ":" + predicate2_name,
"object_uri": object2,
"object_name": object2_name,
"is_uri": isinstance(object2, URIRef),
"is_globalid": predicate2_name == "ifcGlobalID",
}
)
return results
@@ -131,7 +149,7 @@ class BrickschemaReferencesData:
for library in ifc.by_type("IfcLibraryInformation"):
if tool.Ifc.get_schema() == "IFC2X3":
results.append((str(library.id()), library.Name or "Unnamed", ""))
elif ".ttl" in library.Location:
elif library.Location and ".ttl" in library.Location:
results.append((str(library.id()), library.Name or "Unnamed", ""))
return results
@@ -37,6 +37,7 @@ class LoadBrickProject(bpy.types.Operator, Operator):
bl_idname = "bim.load_brick_project"
bl_label = "Load Brickschema Project"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Load in a Brick project from a file"
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
filter_glob: bpy.props.StringProperty(default="*.ttl", options={"HIDDEN"})
@@ -56,6 +57,7 @@ class ViewBrickClass(bpy.types.Operator, Operator):
bl_idname = "bim.view_brick_class"
bl_label = "View Brick Class"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Inspect the subclasses of this class"
brick_class: bpy.props.StringProperty(name="Brick Class")
split_screen: bpy.props.BoolProperty(name="Split Screen", default=False, options={"HIDDEN"})
@@ -67,17 +69,22 @@ class ViewBrickItem(bpy.types.Operator, Operator):
bl_idname = "bim.view_brick_item"
bl_label = "View Brick Item"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Inspect this entity in the viewer"
item: bpy.props.StringProperty(name="Brick Item")
split_screen: bpy.props.BoolProperty(name="Split Screen", default=False, options={"HIDDEN"})
def _execute(self, context):
core.view_brick_item(tool.Brick, item=self.item, split_screen=self.split_screen)
try:
core.view_brick_item(tool.Brick, item=self.item, split_screen=self.split_screen)
except:
self.report({'ERROR'}, f'Could not find {self.item}')
class RewindBrickClass(bpy.types.Operator, Operator):
bl_idname = "bim.rewind_brick_class"
bl_label = "Rewind Brick Class"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Go back to the previous list view"
split_screen: bpy.props.BoolProperty(name="Split Screen", default=False, options={"HIDDEN"})
def _execute(self, context):
@@ -88,6 +95,7 @@ class CloseBrickProject(bpy.types.Operator, Operator):
bl_idname = "bim.close_brick_project"
bl_label = "Close Brick Project"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Close the Brick project"
def _execute(self, context):
core.close_brick_project(tool.Brick)
@@ -97,6 +105,7 @@ class ConvertBrickProject(bpy.types.Operator, Operator):
bl_idname = "bim.convert_brick_project"
bl_label = "Convert Brick Project"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Create an Ifc library for this Brick project"
def _execute(self, context):
core.convert_brick_project(tool.Ifc, tool.Brick)
@@ -106,9 +115,18 @@ class AssignBrickReference(bpy.types.Operator, Operator):
bl_idname = "bim.assign_brick_reference"
bl_label = "Assign Brick Reference"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Assign the selected Ifc entity to the selected Brick entity"
def _execute(self, context):
if not context.active_object:
self.report({'ERROR'}, f'No Ifc selected')
return
props = context.scene.BIMBrickProperties
try:
props.bricks[props.active_brick_index]
except:
self.report({'ERROR'}, f'No Brick selected')
return
core.assign_brick_reference(
tool.Ifc,
tool.Brick,
@@ -122,19 +140,17 @@ class AddBrick(bpy.types.Operator, Operator):
bl_idname = "bim.add_brick"
bl_label = "Add Brick"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Create the Brick entity"
def _execute(self, context):
props = context.scene.BIMBrickProperties
library = None
if props.libraries:
library = tool.Ifc.get().by_id(int(props.libraries))
core.add_brick(
tool.Ifc,
tool.Brick,
element=tool.Ifc.get_entity(context.active_object) if context.selected_objects else None,
namespace=props.namespace,
brick_class=props.brick_entity_class,
library=library,
library=tool.Ifc.get().by_id(int(props.libraries)) if props.libraries else None,
label=props.new_brick_label,
)
@@ -143,6 +159,7 @@ class AddBrickRelation(bpy.types.Operator, Operator):
bl_idname = "bim.add_brick_relation"
bl_label = "Add Brick Relation"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Create the Brick relationship"
def _execute(self, context):
props = context.scene.BIMBrickProperties
@@ -152,7 +169,7 @@ class AddBrickRelation(bpy.types.Operator, Operator):
elif props.split_screen_toggled:
object = props.split_screen_bricks[props.split_screen_active_brick_index].uri
else:
object = props.new_brick_relation_namespace + props.new_brick_relation_object
object = props.namespace + props.new_brick_relation_object
core.add_brick_relation(
tool.Brick,
brick_uri=brick.uri,
@@ -165,6 +182,7 @@ class ConvertIfcToBrick(bpy.types.Operator, Operator):
bl_idname = "bim.convert_ifc_to_brick"
bl_label = "Convert IFC To Brick"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Convert Ifc entities and relations to Brick entities and relations"
def _execute(self, context):
props = context.scene.BIMBrickProperties
@@ -178,6 +196,7 @@ class NewBrickFile(bpy.types.Operator):
bl_idname = "bim.new_brick_file"
bl_label = "New Brick File"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Create a Brick project from scratch"
def execute(self, context):
IfcStore.begin_transaction(self)
@@ -210,16 +229,17 @@ class RefreshBrickViewer(bpy.types.Operator, Operator):
bl_idname = "bim.refresh_brick_viewer"
bl_label = "Refresh Brick Viewer"
bl_options = {"REGISTER", "UNDO"}
split_screen: bpy.props.BoolProperty(name="Split Screen", default=False, options={"HIDDEN"})
bl_description = "Refresh the list view"
def _execute(self, context):
core.refresh_brick_viewer(tool.Brick, split_screen=self.split_screen)
core.refresh_brick_viewer(tool.Brick)
class RemoveBrick(bpy.types.Operator, Operator):
bl_idname = "bim.remove_brick"
bl_label = "Remove Brick"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Delete this entity"
def _execute(self, context):
props = context.scene.BIMBrickProperties
@@ -262,32 +282,20 @@ class SerializeBrick(bpy.types.Operator):
class AddBrickNamespace(bpy.types.Operator, Operator):
bl_idname = "bim.add_brick_namespace"
bl_label = "Add Brick Namespace"
bl_description = "Bind a new namespace to the Brick project"
def _execute(self, context):
props = context.scene.BIMBrickProperties
alias = props.new_brick_namespace_alias
uri = props.new_brick_namespace_uri
core.add_namespace(tool.Brick, alias=alias, uri=uri)
class SetBrickListRoot(bpy.types.Operator, Operator):
bl_idname = "bim.set_brick_list_root"
bl_label = "Set Brick View Type"
bl_options = {"REGISTER", "UNDO"}
split_screen: bpy.props.BoolProperty(name="Split Screen", default=False, options={"HIDDEN"})
def _execute(self, context):
if self.split_screen:
root = context.scene.BIMBrickProperties.split_screen_brick_list_root
else:
root = context.scene.BIMBrickProperties.brick_list_root
core.set_brick_list_root(tool.Brick, brick_root=root, split_screen=self.split_screen)
core.add_brick_namespace(tool.Brick, alias=alias, uri=uri)
class RemoveBrickRelation(bpy.types.Operator, Operator):
bl_idname = "bim.remove_brick_relation"
bl_label = "Remove Relation"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Delete this relationship"
predicate: bpy.props.StringProperty(name="Relation")
object: bpy.props.StringProperty(name="Object")
@@ -30,6 +30,8 @@ from bpy.props import (
FloatVectorProperty,
CollectionProperty,
)
import blenderbim.core.brick as core
import blenderbim.tool.brick as tool
from blenderbim.tool.brick import BrickStore
def update_active_brick_index(self, context):
@@ -43,12 +45,12 @@ def get_libraries(self, context):
def get_namespaces(self, context):
return BrickStore.namespaces
return [(uri, f"{alias}: {uri}", "") for alias, uri in BrickStore.namespaces]
def get_brick_entity_classes(self, context):
entity = self.brick_entity_create_type
return BrickStore.entity_classes[entity]
return [(uri, uri.split("#")[-1], "") for uri in BrickStore.entity_classes[entity]]
def get_brick_roots(self, context):
@@ -56,13 +58,22 @@ def get_brick_roots(self, context):
def get_brick_relations(self, context):
def is_label(relation):
return relation["predicate_name"] == "label"
if not list(filter(is_label, BrickschemaData.data["active_relations"])):
new_relations = BrickStore.relationships.copy()
new_relations.append(("http://www.w3.org/2000/01/rdf-schema#label", "label", ""))
return new_relations
return BrickStore.relationships
relations = [(uri, uri.split("#")[-1], "") for uri in BrickStore.relationships]
for relation in BrickschemaData.data["active_relations"]:
if relation["predicate_name"] == "label":
return relations
relations.append(("http://www.w3.org/2000/01/rdf-schema#label", "label", ""))
return relations
def update_view(self, context):
root = context.scene.BIMBrickProperties.brick_list_root
core.set_brick_list_root(tool.Brick, brick_root=root, split_screen=False)
def split_screen_update_view(self, context):
root = context.scene.BIMBrickProperties.split_screen_brick_list_root
core.set_brick_list_root(tool.Brick, brick_root=root, split_screen=True)
class Brick(PropertyGroup):
@@ -79,10 +90,9 @@ class BIMBrickProperties(PropertyGroup):
active_brick_index: IntProperty(name="Active Brick Index", update=update_active_brick_index)
libraries: EnumProperty(name="Libraries", items=get_libraries)
set_list_root_toggled: BoolProperty(name="Set List Root Toggled", default=False)
brick_list_root: EnumProperty(name="Brick List Root", items=get_brick_roots)
brick_list_root: EnumProperty(name="Brick List Root", items=get_brick_roots, update=update_view)
# namespace manager
namespace: EnumProperty(name="Namespace", items=get_namespaces)
brick_settings_toggled: BoolProperty(name="Brick Settings Toggled", default=False)
new_brick_namespace_alias: StringProperty(name="New Brick Namespace Alias")
new_brick_namespace_uri: StringProperty(name="New Brick Namespace URI")
# create brick entity
@@ -93,13 +103,12 @@ class BIMBrickProperties(PropertyGroup):
brick_create_relations_toggled: BoolProperty(name="Brick Create Relations Toggled", default=False)
brick_edit_relations_toggled: BoolProperty(name="Brick Edit Relations Toggled", default=False)
new_brick_relation_type: EnumProperty(name="New Brick Relation Type", items=get_brick_relations)
new_brick_relation_namespace: EnumProperty(name="New Brick Relation Namespace", items=get_namespaces)
new_brick_relation_object: StringProperty(name="New Brick Relation Object")
add_relation_failed: BoolProperty(name="Add Relation Failed", default=False)
add_brick_relation_failed: BoolProperty(name="Add Relation Failed", default=False)
# create relations split screen
split_screen_toggled: BoolProperty(name="Split Screen Toggled", default=False)
split_screen_bricks: CollectionProperty(name="Split Screen Bricks", type=Brick)
split_screen_active_brick_index: IntProperty(name="Split Screen Active Brick Index", update=update_active_brick_index)
split_screen_active_brick_class: StringProperty(name="Split Screen Active Brick Class")
split_screen_brick_breadcrumbs: CollectionProperty(name="Split Screen Brick Breadcrumbs", type=StrProperty)
split_screen_brick_list_root: EnumProperty(name="Split Screen Brick List Root", items=get_brick_roots)
split_screen_brick_list_root: EnumProperty(name="Split Screen Brick List Root", items=get_brick_roots, update=split_screen_update_view)
+154 -85
View File
@@ -34,17 +34,36 @@ class BIM_PT_brickschema(Panel):
def draw(self, context):
if not BrickschemaData.is_loaded:
BrickschemaData.load()
self.props = context.scene.BIMBrickProperties
class BIM_PT_brickschema_project_info(Panel):
bl_label = "Project Info"
bl_idname = "BIM_PT_brickschema_project_info"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_brickschema"
def draw(self, context):
self.props = context.scene.BIMBrickProperties
if not BrickschemaData.data["is_loaded"]:
row = self.layout.row(align=True)
row.operator("bim.new_brick_file", text="Create Project")
row.operator("bim.load_brick_project", text="Load Project")
return
row = self.layout.row(align=True)
if BrickStore.path:
row = self.layout.row(align=True)
row.label(text=BrickStore.path, icon="FILEBROWSER")
else:
row.label(text="No file", icon="FILEBROWSER")
row = self.layout.row(align=True)
if BrickStore.last_saved:
row.label(text=BrickStore.last_saved, icon="TIME")
else:
row.label(text="Not saved", icon="TIME")
row = self.layout.row(align=True)
op = row.operator("bim.serialize_brick", icon="EXPORT", text="Save")
@@ -53,134 +72,173 @@ class BIM_PT_brickschema(Panel):
op.should_save_as = True
row.operator("bim.close_brick_project", text="", icon="CANCEL")
row = self.layout.row(align=True)
row.prop(data=self.props, property="brick_settings_toggled", text="", icon="PREFERENCES")
if self.props.brick_settings_toggled:
box = self.layout.box()
row = box.row(align=True)
row.label(text="Active Namespace:")
row = box.row(align=True)
prop_with_search(row, self.props, "namespace", text="")
class BIM_PT_brickschema_namespaces(Panel):
bl_label = "Namespaces"
bl_idname = "BIM_PT_brickschema_namespaces"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_brickschema"
row = box.row(align=True)
row.label(text="Bind New Namespace:")
@classmethod
def poll(cls, context):
return BrickStore.graph != None
row = box.row(align=True)
row.prop(data=self.props, property="new_brick_namespace_alias", text="")
col = row.column()
col.alignment = "CENTER"
col.scale_x = 1.1
col.label(text=":")
row.prop(data=self.props, property="new_brick_namespace_uri", text="")
row.operator("bim.add_brick_namespace", text="", icon="ADD")
def draw(self, context):
self.props = context.scene.BIMBrickProperties
row = self.layout.row(align=True)
row.label(text="Create Entity:")
row.label(text="Active Namespace:")
row = self.layout.row(align=True)
row.prop(data=self.props, property="brick_entity_create_type", text="")
prop_with_search(row, self.props, "namespace", text="")
row = self.layout.row(align=True)
row.prop(data=self.props, property="new_brick_label", text="")
prop_with_search(row, self.props, "brick_entity_class", text="")
row.operator("bim.add_brick", text="", icon="ADD")
# row.operator("bim.refresh_brick_viewer", text="", icon="FILE_REFRESH")
row.label(text="Bind New Namespace:")
row = self.layout.row(align=True)
row.prop(data=self.props, property="new_brick_namespace_alias", text="")
col = row.column()
col.alignment = "RIGHT"
col.alignment = "CENTER"
col.scale_x = 1.1
col.label(text=":")
row.prop(data=self.props, property="new_brick_namespace_uri", text="")
row.operator("bim.add_brick_namespace", text="", icon="ADD")
class BIM_PT_brickschema_create_entity(Panel):
bl_label = "Create Entity"
bl_idname = "BIM_PT_brickschema_create_entity"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_brickschema"
@classmethod
def poll(cls, context):
return BrickStore.graph != None
def draw(self, context):
self.props = context.scene.BIMBrickProperties
# TO DO: hide this if selected entity already has a reference, or something similar
row = self.layout.row(align=True)
row.prop(data=self.props, property="brick_entity_create_type", text="Class")
row = self.layout.row(align=True)
prop_with_search(row, self.props, "brick_entity_class", text="Type")
row = self.layout.row(align=True)
active = tool.Ifc.get_entity(context.active_object)
if active and context.selected_objects:
row.label(text="Label:")
row.label(text=active.Name if active.Name else "Unnamed")
else:
row.prop(data=self.props, property="new_brick_label", text="Label")
row = self.layout.row(align=True)
row.operator("bim.add_brick", text="Create Entity")
class BIM_PT_brickschema_viewport(Panel):
bl_label = "Viewport"
bl_idname = "BIM_PT_brickschema_viewport"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_brickschema"
@classmethod
def poll(cls, context):
return BrickStore.graph != None
def draw(self, context):
self.props = context.scene.BIMBrickProperties
row = self.layout.row(align=True)
row.column().alignment = "RIGHT"
row.prop(data=self.props, property="set_list_root_toggled", text="", icon="OUTLINER")
row.prop(data=self.props, property="split_screen_toggled", text="", icon="WINDOW")
row.operator("bim.refresh_brick_viewer", text="", icon="FILE_REFRESH")
grid = self.layout.grid_flow(even_columns=True)
grid1 = grid.column(align=True)
row = grid1.row(align=True)
grid_left = grid.column(align=True)
row = grid_left.row(align=True)
if len(self.props.brick_breadcrumbs):
op = row.operator("bim.rewind_brick_class", text="", icon="FRAME_PREV")
op.split_screen = False
row.prop(data=self.props, property="set_list_root_toggled", text="", icon="OUTLINER")
row.label(text=self.props.active_brick_class)
if self.props.set_list_root_toggled:
row = grid1.row(align=True)
op = row.operator("bim.set_brick_list_root", text="Set View")
op.split_screen = False
row = grid_left.row(align=True)
row.prop(data=self.props, property="brick_list_root", text="")
row = grid1.row()
row = grid_left.row()
BIM_UL_bricks.split_screen = False
row.template_list("BIM_UL_bricks", "", self.props, "bricks", self.props, "active_brick_index")
if self.props.split_screen_toggled:
grid2 = grid.column(align=True)
row = grid2.row(align=True)
grid_right = grid.column(align=True)
row = grid_right.row(align=True)
if len(self.props.split_screen_brick_breadcrumbs):
op = row.operator("bim.rewind_brick_class", text="", icon="FRAME_PREV")
op.split_screen = True
row.label(text=self.props.split_screen_active_brick_class)
if self.props.set_list_root_toggled:
row = grid2.row(align=True)
op = row.operator("bim.set_brick_list_root", text="Set View")
op.split_screen = True
row = grid_right.row(align=True)
row.prop(data=self.props, property="split_screen_brick_list_root", text="")
row = grid2.row()
row = grid_right.row()
BIM_UL_bricks.split_screen = True
row.template_list("BIM_UL_bricks", "", self.props, "split_screen_bricks", self.props, "split_screen_active_brick_index")
if BrickschemaData.data["active_relations"]:
row = self.layout.row(align=True)
col = row.column()
col.alignment = "RIGHT"
row.column().alignment = "RIGHT"
row.prop(data=self.props, property="brick_create_relations_toggled", text="", icon="PLUGIN")
row.prop(data=self.props, property="brick_edit_relations_toggled", text="", icon="TOOL_SETTINGS")
row.operator("bim.remove_brick", text="", icon="X")
row = self.layout.row(align=True)
row.label(text="Create Relation:")
if self.props.brick_create_relations_toggled and self.props.new_brick_relation_type == "http://www.w3.org/2000/01/rdf-schema#label":
if self.props.brick_create_relations_toggled and self.props.split_screen_toggled:
row = self.layout.row(align=True)
prop_with_search(row, self.props, "new_brick_relation_type", text="")
row.prop(data=self.props, property="new_brick_relation_object", text="")
row.operator("bim.add_brick_relation", text="", icon="ADD")
elif self.props.brick_create_relations_toggled and self.props.split_screen_toggled:
row = self.layout.row(align=True)
split_screen_selection = self.props.split_screen_bricks[self.props.split_screen_active_brick_index]
if split_screen_selection.total_items:
try:
split_screen_selection = self.props.split_screen_bricks[self.props.split_screen_active_brick_index]
except:
split_screen_selection = None
if not split_screen_selection or split_screen_selection.total_items:
row.label(text="No selection", icon="INFO")
else:
prop_with_search(row, self.props, "new_brick_relation_type", text="")
row.label(text=split_screen_selection.label if split_screen_selection.label else split_screen_selection.name)
row.operator("bim.add_brick_relation", text="", icon="ADD")
elif self.props.brick_create_relations_toggled:
row = self.layout.row(align=True)
prop_with_search(row, self.props, "new_brick_relation_namespace", text="")
elif self.props.brick_create_relations_toggled:
row = self.layout.row(align=True)
prop_with_search(row, self.props, "new_brick_relation_type", text="")
row.prop(data=self.props, property="new_brick_relation_object", text="")
row.operator("bim.add_brick_relation", text="", icon="ADD")
if self.props.brick_create_relations_toggled and self.props.add_relation_failed:
if self.props.brick_create_relations_toggled and self.props.add_brick_relation_failed:
row = self.layout.row(align=True)
row.label(text="Failed to find this entity!", icon="ERROR")
for relation in BrickschemaData.data["active_relations"]:
row = self.layout.row(align=True)
split = self.layout.split(factor=0.85, align=True)
row = split.row(align=True)
row.label(text=relation["predicate_name"])
row.separator()
row.label(text=relation["object_name"])
if self.props.brick_edit_relations_toggled and relation["predicate_name"] != "type":
row = split.row(align=True)
row.column().alignment = "RIGHT"
if self.props.brick_edit_relations_toggled and relation["predicate_uri"] and relation["predicate_name"] != "type":
op = row.operator("bim.remove_brick_relation", text="", icon="UNLINKED")
op.predicate = relation["predicate"]
op.object = relation["object"]
if relation["is_uri"] and relation["predicate_name"] != "type":
op.predicate = relation["predicate_uri"]
op.object = relation["object_uri"]
if relation["is_uri"] and relation["object_uri"].toPython().split("#")[-1] != self.props.active_brick_class:
op = row.operator("bim.view_brick_item", text="", icon="DISCLOSURE_TRI_RIGHT")
op.item = relation["object_uri"]
if relation["is_globalid"]:
@@ -188,6 +246,23 @@ class BIM_PT_brickschema(Panel):
op.global_id = relation["object_name"]
class BIM_UL_bricks(UIList):
split_screen = False
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
if item:
split = layout.split(factor=0.85, align=True)
row = split.row()
label = item.label if item.label else item.name
if item.total_items:
op = row.operator("bim.view_brick_class", text="", icon="DISCLOSURE_TRI_RIGHT", emboss=False)
op.brick_class = item.name
op.split_screen = self.split_screen
row.label(text=label)
if item.total_items:
row = split.row()
row.label(text=str(item.total_items))
class BIM_PT_ifc_brickschema_references(Panel):
bl_label = "Brickschema References"
bl_idname = "BIM_PT_ifc_brickschema_references"
@@ -211,15 +286,24 @@ class BIM_PT_ifc_brickschema_references(Panel):
row.label(text="No Brickschema Project Loaded")
return
if not BrickschemaReferencesData.data["libraries"]:
if not BrickschemaReferencesData.data["libraries"] and BrickStore.path:
row = self.layout.row(align=True)
row.label(text="No IFC Libraries")
row.operator("bim.convert_brick_project", text="", icon="ADD")
return
elif not BrickschemaReferencesData.data["libraries"]:
row = self.layout.row(align=True)
row.label(text="No IFC Libraries: save the Brick project to create a new library", icon="ERROR")
row = self.layout.row(align=True)
row.label(text="Libraries must have location pointing to a \".ttl\" file", icon="INFO")
return
row = self.layout.row(align=True)
prop_with_search(row, self.props, "libraries")
row.operator("bim.convert_brick_project", text="", icon="ADD")
if BrickStore.path:
row.operator("bim.convert_brick_project", text="", icon="ADD")
row = self.layout.row(align=True)
row.operator("bim.assign_brick_reference", icon="ADD")
@@ -234,19 +318,4 @@ class BIM_PT_ifc_brickschema_references(Panel):
row.label(text=reference["identification"], icon="ASSET_MANAGER")
row.label(text=reference["name"])
row.operator("bim.unassign_library_reference", text="", icon="X").reference = reference["id"]
row.operator("bim.view_brick_item", text="", icon="DISCLOSURE_TRI_RIGHT").item = reference["identification"]
class BIM_UL_bricks(UIList):
split_screen = False
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
if item:
row = layout.row(align=True)
label = item.label if item.label else item.name
if item.total_items:
op = row.operator("bim.view_brick_class", text="", icon="DISCLOSURE_TRI_RIGHT", emboss=False)
op.brick_class = item.name
op.split_screen = self.split_screen
label = label + " (" + str(item.total_items) + ")"
row.label(text=label)
row.operator("bim.view_brick_item", text="", icon="DISCLOSURE_TRI_RIGHT").item = reference["identification"]
@@ -0,0 +1,42 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2023 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BlenderBIM Add-on.
#
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BlenderBIM Add-on is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
from . import ui, prop, operator
classes = (
operator.GetBSDDClassificationProperties,
operator.LoadBSDDDomains,
operator.SearchBSDDClassifications,
operator.SetActiveBSDDDomain,
prop.BSDDDomain,
prop.BSDDClassification,
prop.BSDDPset,
prop.BIMBSDDProperties,
ui.BIM_UL_bsdd_domains,
ui.BIM_UL_bsdd_classifications,
ui.BIM_PT_bsdd,
)
def register():
bpy.types.Scene.BIMBSDDProperties = bpy.props.PointerProperty(type=prop.BIMBSDDProperties)
def unregister():
del bpy.types.Scene.BIMBSDDProperties
@@ -0,0 +1,132 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2023 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BlenderBIM Add-on.
#
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BlenderBIM Add-on is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import os
import bpy
import bsdd
import json
import ifcopenshell
import blenderbim.tool as tool
class LoadBSDDDomains(bpy.types.Operator):
bl_idname = "bim.load_bsdd_domains"
bl_label = "Load bSDD Domains"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
props = context.scene.BIMBSDDProperties
props.domains.clear()
client = bsdd.Client()
for domain in sorted(client.Domain(), key=lambda x: x["name"]):
new = props.domains.add()
new.name = domain["name"]
new.namespace_uri = domain["namespaceUri"]
new.default_language_code = domain["defaultLanguageCode"]
new.organization_name_owner = domain["organizationNameOwner"]
new.status = domain["status"]
new.version = domain["version"]
return {"FINISHED"}
class SetActiveBSDDDomain(bpy.types.Operator):
bl_idname = "bim.set_active_bsdd_domain"
bl_label = "Load bSDD Domains"
bl_options = {"REGISTER", "UNDO"}
name: bpy.props.StringProperty()
uri: bpy.props.StringProperty()
def execute(self, context):
props = context.scene.BIMBSDDProperties
props.active_domain = self.name
props.active_uri = self.uri
return {"FINISHED"}
class SearchBSDDClassifications(bpy.types.Operator):
bl_idname = "bim.search_bsdd_classifications"
bl_label = "Search bSDD Classifications"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
props = context.scene.BIMBSDDProperties
props.classifications.clear()
client = bsdd.Client()
related_ifc_entities = []
if len(props.keyword) < 3:
return {"FINISHED"}
if props.should_filter_ifc_class and context.active_object:
element = tool.Ifc.get_entity(context.active_object)
if element:
related_ifc_entities = [element.is_a()]
results = client.ClassificationSearchOpen(props.keyword, DomainNamespaceUris=[props.active_uri], RelatedIfcEntities=related_ifc_entities)
for result in sorted(results["classifications"], key=lambda x: x["referenceCode"]):
new = props.classifications.add()
new.name = result["name"]
new.reference_code = result["referenceCode"]
new.description = result.get("description", "")
new.namespace_uri = result["namespaceUri"]
new.domain_name = result["domainName"]
new.domain_namespace_uri = result["domainNamespaceUri"]
return {"FINISHED"}
class GetBSDDClassificationProperties(bpy.types.Operator):
bl_idname = "bim.get_bsdd_classification_properties"
bl_label = "Search bSDD Classifications"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
bprops = context.scene.BIMBSDDProperties
bprops.classification_psets.clear()
bsdd_classification = bprops.classifications[bprops.active_classification_index]
client = bsdd.Client()
data = client.Classification(bsdd_classification.namespace_uri)
properties = data.get("classificationProperties", None)
if not properties:
return {"FINISHED"}
psets = {}
for prop in properties:
if prop.get("propertyDomainName") != "IFC":
continue
pset = prop.get("propertySet", None)
if not pset:
continue
psets.setdefault(pset, {})
predefined_value = prop.get("predefinedValue")
if predefined_value:
possible_values = [predefined_value]
else:
possible_values = prop.get("possibleValues", []) or []
possible_values = [v["value"] for v in possible_values]
psets[pset][prop["name"]] = possible_values
for pset_name, pset in psets.items():
new = bprops.classification_psets.add()
new.name = pset_name
for name, values in pset.items():
new2 = new.properties.add()
new2.name = name
new2.enum_items = json.dumps(values)
new2.data_type = "enum"
return {"FINISHED"}
@@ -0,0 +1,66 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2023 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BlenderBIM Add-on.
#
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BlenderBIM Add-on is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
from bpy.types import PropertyGroup
from blenderbim.bim.prop import Attribute, StrProperty
from bpy.props import (
PointerProperty,
StringProperty,
EnumProperty,
BoolProperty,
IntProperty,
FloatProperty,
FloatVectorProperty,
CollectionProperty,
)
class BSDDDomain(PropertyGroup):
name: StringProperty(name="Name")
namespace_uri: StringProperty(name="URI")
default_language_code: StringProperty(name="Language")
organization_name_owner: StringProperty(name="Organization")
status: StringProperty(name="Status")
version: StringProperty(name="Version")
class BSDDClassification(PropertyGroup):
name: StringProperty(name="Name")
reference_code: StringProperty(name="Reference Code")
description: StringProperty(name="Description")
namespace_uri: StringProperty(name="Namespace URI")
domain_name: StringProperty(name="Domain Name")
domain_namespace_uri: StringProperty(name="Domain Namespace URI")
class BSDDPset(PropertyGroup):
name: StringProperty(name="Name")
properties: CollectionProperty(name="Properties", type=Attribute)
class BIMBSDDProperties(PropertyGroup):
active_domain: StringProperty(name="Active Domain")
active_uri: StringProperty(name="Active URI")
domains: CollectionProperty(name="Domains", type=BSDDDomain)
active_domain_index: IntProperty(name="Active Domain Index")
classifications: CollectionProperty(name="Classifications", type=BSDDClassification)
active_classification_index: IntProperty(name="Active Classification Index")
keyword: StringProperty(name="Keyword")
should_filter_ifc_class: BoolProperty(name="Filter Active IFC Class", default=True)
classification_psets: CollectionProperty(name="Classification Psets", type=BSDDPset)
@@ -0,0 +1,71 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2023 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BlenderBIM Add-on.
#
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BlenderBIM Add-on is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import blenderbim.tool as tool
from bpy.types import Panel, UIList
from blenderbim.bim.ifc import IfcStore
class BIM_PT_bsdd(Panel):
bl_label = "buildingSMART Data Dictionary"
bl_idname = "BIM_PT_bsdd"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_tab_project_setup"
def draw(self, context):
props = context.scene.BIMBSDDProperties
if props.active_domain:
row = self.layout.row()
row.label(text="Active: " + props.active_domain, icon="URL")
else:
row = self.layout.row()
row.label(text="No Active bSDD Domain", icon="ERROR")
if len(props.domains):
self.layout.template_list(
"BIM_UL_bsdd_domains",
"",
props,
"domains",
props,
"active_domain_index",
)
else:
row = self.layout.row()
row.operator("bim.load_bsdd_domains")
class BIM_UL_bsdd_domains(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
if item:
row = layout.row(align=True)
row.label(text=f"{item.name} ({item.organization_name_owner})")
op = row.operator("bim.set_active_bsdd_domain", text="", icon="RESTRICT_SELECT_OFF")
op.name = item.name
op.uri = item.namespace_uri
class BIM_UL_bsdd_classifications(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
if item:
row = layout.row(align=True)
row.label(text=item.reference_code)
row.label(text=item.name)
@@ -336,8 +336,15 @@ class SelectIfcClashResults(bpy.types.Operator):
else:
element_file = self.file
element = element_file.by_id(obj.BIMObjectProperties.ifc_definition_id)
if element.GlobalId in global_ids:
try:
element = element_file.by_id(obj.BIMObjectProperties.ifc_definition_id)
except:
continue
global_id = getattr(element, "GlobalId", None)
if not global_id:
continue
if global_id in global_ids:
obj.select_set(True)
return {"FINISHED"}
@@ -27,7 +27,7 @@ class BIM_PT_ifcclash(Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_tab_quality_control"
bl_parent_id = "BIM_PT_tab_clash_detection"
def draw(self, context):
layout = self.layout
@@ -102,9 +102,10 @@ class BIM_PT_clash_manager(Panel):
bl_idname = "BIM_PT_clash_manager"
bl_label = "Clash Manager"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
bl_category = "BlenderBIM"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_tab_clash_detection"
def draw(self, context):
layout = self.layout
@@ -21,7 +21,9 @@ from . import ui, prop, operator
classes = (
operator.AddClassification,
operator.AddClassificationFromBSDD,
operator.AddClassificationReference,
operator.AddClassificationReferenceFromBSDD,
operator.ChangeClassificationLevel,
operator.DisableEditingClassification,
operator.DisableEditingClassificationReference,
@@ -54,6 +54,25 @@ class AddClassification(bpy.types.Operator, tool.Ifc.Operator):
)
class AddClassificationFromBSDD(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.add_classification_from_bsdd"
bl_label = "Add Classification From bSDD"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
props = context.scene.BIMBSDDProperties
domain = [d for d in props.domains if d.name == props.active_domain][0]
for element in tool.Ifc.get().by_type("IfcClassification"):
if element.Name == props.active_domain or element.Location == domain.namespace_uri:
return
classification = ifcopenshell.api.run(
"classification.add_classification", tool.Ifc.get(), classification=props.active_domain
)
classification.Source = domain.organization_name_owner
classification.Location = domain.namespace_uri
classification.Edition = domain.version
class EnableEditingClassification(bpy.types.Operator):
bl_idname = "bim.enable_editing_classification"
bl_label = "Enable Editing Classification"
@@ -254,6 +273,70 @@ class AddClassificationReference(bpy.types.Operator, tool.Ifc.Operator):
)
class AddClassificationReferenceFromBSDD(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.add_classification_reference_from_bsdd"
bl_label = "Add Classification Reference From bSDD"
bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
obj_type: bpy.props.StringProperty()
def _execute(self, context):
if self.obj_type == "Object":
if context.selected_objects:
objects = [o.name for o in context.selected_objects]
else:
objects = [context.active_object.name]
else:
objects = [self.obj]
props = context.scene.BIMClassificationProperties
bprops = context.scene.BIMBSDDProperties
bsdd_classification = bprops.classifications[bprops.active_classification_index]
classification = None
for element in tool.Ifc.get().by_type("IfcClassification"):
if (
element.Name == bsdd_classification.domain_name
or element.Location == bsdd_classification.domain_namespace_uri
):
classification = element
break
if not classification:
classification = ifcopenshell.api.run(
"classification.add_classification", tool.Ifc.get(), classification=bsdd_classification.domain_name
)
classification.Location = bsdd_classification.domain_namespace_uri
for obj in objects:
ifc_definition_id = blenderbim.bim.helper.get_obj_ifc_definition_id(context, obj, self.obj_type)
if not ifc_definition_id:
continue
element = tool.Ifc.get().by_id(ifc_definition_id)
reference = ifcopenshell.api.run(
"classification.add_reference",
tool.Ifc.get(),
product=element,
classification=classification,
identification=bsdd_classification.reference_code,
name=bsdd_classification.name,
)
reference.Location = bsdd_classification.namespace_uri
for classification_pset in bprops.classification_psets:
pset = ifcopenshell.util.element.get_pset(element, classification_pset.name)
if pset:
pset = tool.Ifc.get().by_id(pset["id"])
else:
pset = ifcopenshell.api.run(
"pset.add_pset", tool.Ifc.get(), product=element, name=classification_pset.name
)
properties = {}
for prop in classification_pset.properties:
properties[prop.name] = prop.get_value()
ifcopenshell.api.run("pset.edit_pset", tool.Ifc.get(), pset=pset, properties=properties)
class ChangeClassificationLevel(bpy.types.Operator):
bl_idname = "bim.change_classification_level"
bl_label = "Change Classification Level"
@@ -48,6 +48,15 @@ class ClassificationReference(PropertyGroup):
class BIMClassificationProperties(PropertyGroup):
classification_source: EnumProperty(
items=[
("FILE", "IFC File", ""),
("BSDD", "buildingSMART Data Dictionary", ""),
("MANUAL", "Manual Entry", ""),
],
name="Classification Source",
default="FILE",
)
available_classifications: EnumProperty(items=get_available_classifications, name="Available Classifications")
classification_attributes: CollectionProperty(name="Classification Attributes", type=Attribute)
active_classification_id: IntProperty(name="Active Classification Id")
@@ -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):
@@ -48,6 +48,42 @@ class BIM_PT_classifications(Panel):
self.props = context.scene.BIMClassificationProperties
row = self.layout.row(align=True)
row.label(text="Source", icon="OUTLINER")
row.prop(self.props, "classification_source", text="")
if self.props.classification_source == "FILE":
self.draw_add_file_ui(context)
elif self.props.classification_source == "BSDD":
self.draw_add_bsdd_ui(context)
elif self.props.classification_source == "MANUAL":
self.draw_add_manual_ui(context)
for classification in ClassificationsData.data["classifications"]:
if self.props.active_classification_id == classification["id"]:
self.draw_editable_ui()
else:
self.draw_ui(classification)
def draw_add_manual_ui(self, context):
row = self.layout.row()
row.label(text="TODO", icon="ERROR")
def draw_add_bsdd_ui(self, context):
self.bprops = context.scene.BIMBSDDProperties
if not self.bprops.active_domain:
row = self.layout.row()
row.label(text="No Active bSDD Domain", icon="ERROR")
return
row = self.layout.row()
row.label(text="Active: " + self.bprops.active_domain, icon="URL")
row = self.layout.row()
row.operator("bim.add_classification_from_bsdd", icon="ADD")
def draw_add_file_ui(self, context):
if ClassificationsData.data["has_classification_file"]:
row = self.layout.row(align=True)
row.prop(self.props, "available_classifications", text="")
@@ -58,12 +94,6 @@ class BIM_PT_classifications(Panel):
row.label(text="No Active Classification Library")
row.operator("bim.load_classification_library", text="", icon="IMPORT")
for classification in ClassificationsData.data["classifications"]:
if self.props.active_classification_id == classification["id"]:
self.draw_editable_ui()
else:
self.draw_ui(classification)
def draw_editable_ui(self):
row = self.layout.row(align=True)
row.operator("bim.edit_classification", text="Save changes", icon="CHECKMARK")
@@ -84,6 +114,7 @@ class ReferenceUI:
obj = context.active_object
self.oprops = obj.BIMObjectProperties
self.sprops = context.scene.BIMClassificationProperties
self.bprops = context.scene.BIMBSDDProperties
self.props = obj.BIMClassificationReferenceProperties
self.file = IfcStore.get_file()
@@ -100,14 +131,76 @@ class ReferenceUI:
self.draw_reference_ui(reference)
def draw_add_ui(self, context):
row = self.layout.row(align=True)
row.label(text="Source", icon="OUTLINER")
row.prop(self.sprops, "classification_source", text="")
if self.sprops.classification_source == "FILE":
self.draw_add_file_ui(context)
elif self.sprops.classification_source == "BSDD":
self.draw_add_bsdd_ui(context)
elif self.sprops.classification_source == "MANUAL":
self.draw_add_manual_ui(context)
def draw_add_manual_ui(self, context):
row = self.layout.row()
row.label(text="TODO", icon="ERROR")
def draw_add_bsdd_ui(self, context):
if not self.bprops.active_domain:
row = self.layout.row()
row.label(text="No Active bSDD Domain", icon="ERROR")
return
row = self.layout.row()
row.label(text="Active: " + self.bprops.active_domain, icon="URL")
row = self.layout.row(align=True)
row.prop(self.bprops, "keyword", text="")
row.operator("bim.search_bsdd_classifications", text="", icon="VIEWZOOM")
row = self.layout.row()
row.prop(self.bprops, "should_filter_ifc_class")
if len(self.bprops.classifications):
self.layout.template_list(
"BIM_UL_bsdd_classifications",
"",
self.bprops,
"classifications",
self.bprops,
"active_classification_index",
)
else:
row = self.layout.row()
row.label(text="No Search Results")
if self.bprops.active_classification_index < len(self.bprops.classifications):
row = self.layout.row(align=True)
op = row.operator(
"bim.add_classification_reference_from_bsdd", text="Add Classification Reference", icon="ADD"
)
op.obj = self.obj
op.obj_type = self.obj_type
row.operator("bim.get_bsdd_classification_properties", text="", icon="COPY_ID")
if len(self.bprops.classification_psets):
for pset in self.bprops.classification_psets:
box = self.layout.box()
row = box.row()
row.label(text=pset.name, icon="COPY_ID")
blenderbim.bim.helper.draw_attributes(pset.properties, box)
def draw_add_file_ui(self, context):
if not self.data.data["active_classification_library"]:
row = self.layout.row(align=True)
row.label(text="No Active Classification Library")
row.label(text="No Active Classification Library", icon="ERROR")
row.operator("bim.load_classification_library", text="", icon="IMPORT")
return
row = self.layout.row(align=True)
row.label(text=f"Active Classification Library: {self.data.data['active_classification_library']}")
#row.prop(self.sprops, "available_classifications", text="")
# row.prop(self.sprops, "available_classifications", text="")
if not self.sprops.available_library_references:
op = row.operator("bim.change_classification_level", text="", icon="GREASEPENCIL")
op.parent_id = int(self.sprops.available_classifications)
@@ -1,124 +0,0 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BlenderBIM Add-on.
#
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BlenderBIM Add-on is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
import os
import logging
import ifcopenshell
import json
import webbrowser
import tempfile
from blenderbim.bim.ifc import IfcStore
class SelectCobieIfcFile(bpy.types.Operator):
bl_idname = "bim.select_cobie_ifc_file"
bl_label = "Select COBie IFC File"
bl_options = {"REGISTER", "UNDO"}
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def execute(self, context):
context.scene.COBieProperties.cobie_ifc_file = self.filepath
return {"FINISHED"}
def invoke(self, context, event):
context.window_manager.fileselect_add(self)
return {"RUNNING_MODAL"}
class SelectCobieJsonFile(bpy.types.Operator):
bl_idname = "bim.select_cobie_json_file"
bl_label = "Select COBie JSON File"
bl_options = {"REGISTER", "UNDO"}
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def execute(self, context):
context.scene.COBieProperties.cobie_json_file = self.filepath
return {"FINISHED"}
def invoke(self, context, event):
context.window_manager.fileselect_add(self)
return {"RUNNING_MODAL"}
class ExecuteIfcCobie(bpy.types.Operator):
bl_idname = "bim.execute_ifc_cobie"
bl_label = "Execute IFCCOBie"
file_format: bpy.props.StringProperty()
@classmethod
def poll(cls, context):
props = context.scene.COBieProperties
return props.should_load_from_memory or props.cobie_ifc_file
def execute(self, context):
from cobie import IfcCobieParser
props = context.scene.COBieProperties
if props.should_load_from_memory:
output_dir = tempfile.gettempdir()
else:
output_dir = os.path.dirname(props.cobie_ifc_file)
output = os.path.join(output_dir, "output")
logger = logging.getLogger("IFCtoCOBie")
fh = logging.FileHandler(os.path.join(output_dir, "cobie.log"))
fh.setLevel(logging.DEBUG)
fh.setFormatter(logging.Formatter("%(asctime)s : %(levelname)s : %(message)s"))
logger = logging.getLogger("IFCtoCOBie")
logger.addHandler(fh)
selector = ifcopenshell.util.selector.Selector()
if props.cobie_json_file:
with open(props.cobie_json_file, "r") as f:
custom_data = json.load(f)
else:
custom_data = {}
parser = IfcCobieParser(logger, selector)
ifc_file = IfcStore.get_file()
if not (ifc_file and props.should_load_from_memory):
ifc_file = props.cobie_ifc_file
parser.parse(
ifc_file,
props.cobie_types,
props.cobie_components,
custom_data,
)
if self.file_format == "xlsx":
from cobie import CobieXlsWriter
writer = CobieXlsWriter(parser, output)
writer.write()
webbrowser.open("file://" + output + "." + self.file_format)
elif self.file_format == "ods":
from cobie import CobieOdsWriter
writer = CobieOdsWriter(parser, output)
writer.write()
webbrowser.open("file://" + output + "." + self.file_format)
else:
from cobie import CobieCsvWriter
writer = CobieCsvWriter(parser, output_dir)
writer.write()
webbrowser.open("file://" + output_dir)
webbrowser.open("file://" + output_dir + "/cobie.log")
return {"FINISHED"}
@@ -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):
@@ -20,64 +20,65 @@ import bpy
from . import ui, prop, operator
classes = (
operator.AddCostColumn,
operator.AddCostItem,
operator.AddCostItemQuantity,
operator.AddCostSchedule,
operator.RemoveCostSchedule,
operator.EditCostSchedule,
operator.AddCostValue,
operator.AddCurrency,
operator.AddSummaryCostItem,
operator.AssignCostItemQuantity,
operator.AssignCostItemType,
operator.AssignCostValue,
operator.CalculateCostItemResourceValue,
operator.ChangeParentCostItem,
operator.ClearCostItemAssignments,
operator.ContractCostItem,
operator.ContractCostItemRate,
operator.ContractCostItems,
operator.CopyCostItem,
operator.CopyCostItemValues,
operator.DisableEditingCostItem,
operator.DisableEditingCostItemQuantity,
operator.DisableEditingCostItemValue,
operator.DisableEditingCostSchedule,
operator.EditCostItem,
operator.EditCostItemQuantity,
operator.EditCostItemValue,
operator.EditCostItemValueFormula,
operator.EnableEditingCostSchedule,
operator.EnableEditingCostItems,
operator.EditCostSchedule,
operator.EnableEditingCostItem,
operator.ExportCostSchedules,
operator.ExpandCostItems,
operator.EnableEditingCostItemQuantities,
operator.EnableEditingCostItemQuantity,
operator.EnableEditingCostItemValues,
operator.EnableEditingCostItems,
operator.EnableEditingCostItemValue,
operator.EnableEditingCostItemValueFormula,
operator.DisableEditingCostItem,
operator.DisableEditingCostSchedule,
operator.DisableEditingCostItemQuantity,
operator.DisableEditingCostItemValue,
operator.AddCostColumn,
operator.RemoveCostColumn,
operator.AddCostItem,
operator.AddSummaryCostItem,
operator.EnableEditingCostItemValues,
operator.EnableEditingCostSchedule,
operator.ExpandCostItem,
operator.ContractCostItem,
operator.ExpandCostItemRate,
operator.ExpandCostItems,
operator.ExportCostSchedules,
operator.HighlightProductCostItem,
operator.ImportCostScheduleCsv,
operator.LoadCostItemElementQuantities,
operator.LoadCostItemQuantities,
operator.LoadCostItemResourceQuantities,
operator.LoadCostItemTaskQuantities,
operator.LoadCostItemTypes,
operator.LoadProductCostItems,
operator.LoadScheduleOfRates,
operator.RemoveCostColumn,
operator.RemoveCostItem,
operator.AssignCostItemType,
operator.UnassignCostItemType,
operator.AssignCostItemQuantity,
operator.UnassignCostItemQuantity,
operator.AddCostItemQuantity,
operator.RemoveCostItemQuantity,
operator.AddCostValue,
operator.RemoveCostItemValue,
operator.CopyCostItemValues,
operator.RemoveCostSchedule,
operator.ReorderCostItem,
operator.SelectCostItemProducts,
operator.SelectCostScheduleProducts,
operator.ImportCostScheduleCsv,
operator.LoadCostItemQuantities,
operator.LoadCostItemTypes,
operator.AssignCostValue,
operator.LoadScheduleOfRates,
operator.ExpandCostItemRate,
operator.ContractCostItemRate,
operator.CalculateCostItemResourceValue,
operator.ClearCostItemAssignments,
operator.HighlightProductCostItem,
operator.LoadProductCostItems,
operator.ReorderCostItem,
operator.SelectUnassignedProducts,
operator.LoadCostItemElementQuantities,
operator.LoadCostItemTaskQuantities,
operator.LoadCostItemResourceQuantities,
operator.ChangeParentCostItem,
operator.CopyCostItem,
operator.AddCurrency,
operator.UnassignCostItemQuantity,
operator.UnassignCostItemType,
prop.CostItem,
prop.CostItemQuantity,
prop.CostItemType,
@@ -117,6 +117,7 @@ class CostSchedulesData:
data["TotalAppliedValue"] = 0.0
data["TotalCost"] = 0.0
has_unit_basis = False
is_sum = False
if root_element.is_a("IfcCostItem"):
values = root_element.CostValues
elif root_element.is_a("IfcConstructionResource"):
@@ -130,14 +131,18 @@ class CostSchedulesData:
data["UnitBasisValueComponent"] = cost_value_data["UnitBasis"]["ValueComponent"]
data["UnitBasisUnitSymbol"] = cost_value_data["UnitBasis"]["UnitSymbol"]
has_unit_basis = True
if has_unit_basis:
data["TotalCost"] = data["TotalAppliedValue"] / data["UnitBasisValueComponent"]
else:
if data["TotalCostQuantity"] is not None:
data["TotalCost"] = data["TotalAppliedValue"] * data["TotalCostQuantity"]
else:
data["TotalCost"] = data["TotalAppliedValue"]
data["TotalAppliedValue"] = None
data["UnitBasisValueComponent"] = 1
data["UnitBasisUnitSymbol"] = "U"
if cost_value.Category == "*":
is_sum = True
cost_quantity = data["TotalCostQuantity"] or 1
if has_unit_basis:
data["TotalCost"] = data["TotalAppliedValue"] * cost_quantity / data["UnitBasisValueComponent"]
else:
data["TotalCost"] = data["TotalAppliedValue"] * cost_quantity
if is_sum:
data["TotalAppliedValue"] = None
@classmethod
def _load_cost_item_quantities(cls, cost_item, data):
@@ -153,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"] = None
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
@@ -33,9 +33,26 @@ class AddCostSchedule(bpy.types.Operator, tool.Ifc.Operator):
bl_label = "Add Cost Schedule"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Add a new cost schedule"
name: bpy.props.StringProperty()
object_type: bpy.props.StringProperty()
def _execute(self, context):
core.add_cost_schedule(tool.Ifc, predefined_type=context.scene.BIMCostProperties.cost_schedule_predefined_types)
core.add_cost_schedule(
tool.Ifc,
name=self.name,
predefined_type=context.scene.BIMCostProperties.cost_schedule_predefined_types,
object_type=self.object_type,
)
def draw(self, context):
layout = self.layout
layout.prop(self, "name", text="Name")
layout.prop(context.scene.BIMCostProperties, "cost_schedule_predefined_types", text="Type")
if context.scene.BIMCostProperties.cost_schedule_predefined_types == "USERDEFINED":
layout.prop(self, "object_type", text="Object type")
def invoke(self, context, event):
return context.window_manager.invoke_props_dialog(self)
class EditCostSchedule(bpy.types.Operator, tool.Ifc.Operator):
@@ -156,6 +173,17 @@ class ContractCostItem(bpy.types.Operator, tool.Ifc.Operator):
core.contract_cost_item(tool.Cost, cost_item=tool.Ifc.get().by_id(self.cost_item))
class ContractCostItems(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.contract_cost_items"
bl_label = "Contract Cost Item"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Collapse cost item tree"
cost_item: bpy.props.IntProperty()
def _execute(self, context):
core.contract_cost_items(tool.Cost)
class RemoveCostItem(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.remove_cost_item"
bl_label = "Remove Cost Item"
@@ -635,7 +663,9 @@ class ExportCostSchedules(bpy.types.Operator):
def execute(self, context):
cost_schedule = tool.Ifc.get().by_id(self.cost_schedule) if self.cost_schedule else None
r = core.export_cost_schedules(tool.Cost, filepath=self.filepath, format=self.format, cost_schedule=cost_schedule)
r = core.export_cost_schedules(
tool.Cost, filepath=self.filepath, format=self.format, cost_schedule=cost_schedule
)
if isinstance(r, str):
self.report({"ERROR"}, r)
return {"FINISHED"}
+27 -33
View File
@@ -21,7 +21,6 @@ import blenderbim.bim.module.cost.prop as CostProp
from bpy.types import Panel, UIList
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.module.cost.data import CostSchedulesData
import blenderbim.tool as tool
class BIM_PT_cost_schedules(Panel):
@@ -30,30 +29,32 @@ class BIM_PT_cost_schedules(Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_tab_cost"
bl_options = {"HIDE_HEADER"}
@classmethod
def poll(cls, context):
return tool.Blender.is_tab(context, "SCHEDULING") and tool.Ifc.get() and tool.Ifc.get().schema != "IFC2X3"
file = IfcStore.get_file()
return file and hasattr(file, "schema") and file.schema != "IFC2X3"
def draw(self, context):
if not CostSchedulesData.is_loaded:
CostSchedulesData.load()
self.props = context.scene.BIMCostProperties
row = self.layout.row()
row = self.layout.row(align=True)
if not self.props.active_cost_schedule_id:
if CostSchedulesData.data["total_cost_schedules"]:
row.label(text=f"{CostSchedulesData.data['total_cost_schedules']} Cost Schedules Found", icon="TEXT")
row.operator("bim.export_cost_schedules", text="Export as spreadsheet", icon="EXPORT")
else:
row.label(text="No Cost Schedules found.", icon="COMMUNITY")
row = self.layout.row()
row.alignment = "RIGHT"
row.prop(self.props, "cost_schedule_predefined_types")
row.operator("bim.add_cost_schedule", icon="ADD", text="Add")
row.label(text="No Cost Schedules found.", icon="TEXT")
row.operator("bim.add_cost_schedule", icon="ADD", text="")
for schedule in CostSchedulesData.data["schedules"]:
self.draw_cost_schedule_ui(schedule)
row2 = self.layout.row()
row2.alignment = "RIGHT"
row2.operator("bim.export_cost_schedules", text="Export all", icon="EXPORT")
def draw_cost_schedule_ui(self, cost_schedule):
row = self.layout.row(align=True)
@@ -138,8 +139,8 @@ class BIM_PT_cost_schedules(Panel):
row = self.layout.row(align=True)
row.alignment = "RIGHT"
row.operator("bim.add_summary_cost_item", text="Add Summary Cost", icon="ADD")
row.operator("bim.expand_all_tasks", text="Expand All")
row.operator("bim.contract_all_tasks", text="Contract All")
row.operator("bim.expand_cost_items", text="Expand All")
row.operator("bim.contract_cost_items", text="Contract All")
row = self.layout.row(align=True)
row.alignment = "RIGHT"
if self.props.cost_items and self.props.active_cost_item_index < len(self.props.cost_items):
@@ -619,18 +620,22 @@ class BIM_UL_cost_items_trait:
else:
row.label(text="", icon="DOT")
def draw_total_cost_column(self, layout, cost_item):
format_numbers = "{0:,.2f}".format(cost_item["TotalCost"]).replace(",", " ")
currency = CostSchedulesData.data["currency"]
text = "{} {}".format(format_numbers, currency["name"]) if currency else format_numbers
layout.label(text=text)
def draw_quantity_column(self, layout, cost_item):
if CostSchedulesData.data["is_editing_rates"]:
self.draw_uom_column(layout, cost_item)
else:
self.draw_total_quantity_column(layout, cost_item)
def draw_uom_column(self, layout, cost_item):
layout.label(text=cost_item["UnitBasisUnitSymbol"])
def draw_total_quantity_column(self, layout, cost_item):
if cost_item["TotalCostQuantity"]:
label = "{0:.2f}".format(cost_item["TotalCostQuantity"]) + f" {cost_item['UnitSymbol'] or '-'}"
layout.label(text=label)
else:
layout.label(text="-")
def draw_value_column(self, layout, cost_item):
if cost_item["TotalAppliedValue"]:
text = "{0:,.2f}".format(cost_item["TotalAppliedValue"]).replace(",", " ")
@@ -642,8 +647,11 @@ class BIM_UL_cost_items_trait:
else:
layout.label(text="-")
def draw_uom_column(self, layout, cost_item):
layout.label(text=cost_item["UnitBasisUnitSymbol"] or "-" if cost_item["UnitBasisValueComponent"] else "-")
def draw_total_cost_column(self, layout, cost_item):
format_numbers = "{0:,.2f}".format(cost_item["TotalCost"]).replace(",", " ")
currency = CostSchedulesData.data["currency"]
text = "{} {}".format(format_numbers, currency["name"]) if currency else format_numbers
layout.label(text=text)
def draw_order_operator(self, row, ifc_definition_id, cost_item):
if cost_item["NestingIndex"] is not None:
@@ -656,20 +664,6 @@ class BIM_UL_cost_items_trait:
op.cost_item = ifc_definition_id
op.new_index = cost_item["NestingIndex"] - 1
def draw_total_quantity_column(self, layout, cost_item):
if cost_item["TotalCostQuantity"]:
label = "{0:.2f}".format(cost_item["TotalCostQuantity"]) + f" {cost_item['UnitSymbol'] or '-'}"
layout.label(text=label)
else:
layout.label(text="-")
# if cost_item["DerivedTotalCostQuantity"] not in [None, 0]:
# layout.label(text="{0:.2f}".format(cost_item["DerivedTotalCostQuantity"]) + f" {cost_item['DerivedUnitSymbol'] or '-'}")
# else:
# if cost_item["TotalCostQuantity"] == 0:
# layout.label(text="-")
# else:
# layout.label(text="{0:.2f}".format(cost_item["TotalCostQuantity"]) + f" {cost_item['UnitSymbol'] or '-'}")
class BIM_UL_cost_items(BIM_UL_cost_items_trait, UIList):
def __init__(self):
@@ -0,0 +1,41 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BlenderBIM Add-on.
#
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BlenderBIM Add-on is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
from . import prop, workspace
classes = (
prop.BIMCoveringProperties,
workspace.Hotkey,
)
def register():
if not bpy.app.background:
bpy.utils.register_tool(workspace.CoveringTool, after={"bim.structural_tool"}, separator=False, group=False)
bpy.types.Scene.BIMCoveringProperties = bpy.props.PointerProperty(type=prop.BIMCoveringProperties)
# bpy.types.Object.BIMObjectSpatialProperties = bpy.props.PointerProperty(type=prop.BIMObjectSpatialProperties)
# bpy.types.Scene.BIMSpatialManagerProperties = bpy.props.PointerProperty(type=prop.BIMSpatialManagerProperties)
def unregister():
if not bpy.app.background:
bpy.utils.unregister_tool(workspace.CoveringTool)
del bpy.types.Scene.BIMCoveringProperties
# del bpy.types.Object.BIMObjectSpatialProperties
# del bpy.types.Scene.BIMSpatialManagerProperties
@@ -17,6 +17,8 @@
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
from blenderbim.bim.prop import StrProperty, Attribute
#from blenderbim.bim.module.spatial.data import SpatialData
from bpy.types import PropertyGroup
from bpy.props import (
PointerProperty,
@@ -28,11 +30,10 @@ from bpy.props import (
FloatVectorProperty,
CollectionProperty,
)
#import blenderbim.tool as tool
#import blenderbim.core.geometry
#import ifcopenshell
class COBieProperties(PropertyGroup):
cobie_ifc_file: StringProperty(default="", name="COBie IFC File")
cobie_types: StringProperty(default=".COBieType", name="COBie Types")
cobie_components: StringProperty(default=".COBie", name="COBie Components")
cobie_json_file: StringProperty(default="", name="COBie JSON File")
should_load_from_memory: BoolProperty(default=False, name="Load from Memory")
class BIMCoveringProperties(PropertyGroup):
ceiling_height: bpy.props.FloatProperty(name="ceiling_height", default=2.7, subtype="DISTANCE", description="Ceiling height")
@@ -0,0 +1,262 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2023 @Andrej730
#
# This file is part of BlenderBIM Add-on.
#
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BlenderBIM Add-on is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import os
import bpy
import ifcopenshell
import blenderbim.tool as tool
from blenderbim.bim.helper import prop_with_search
from blenderbim.bim.module.model.data import AuthoringData
from bpy.types import WorkSpaceTool
from blenderbim.bim.ifc import IfcStore
import blenderbim.bim.handler
# declaring it here to avoid circular import problems
class Operator:
def execute(self, context):
IfcStore.execute_ifc_operator(self, context)
blenderbim.bim.handler.refresh_ui_data()
return {"FINISHED"}
class CoveringTool(WorkSpaceTool):
bl_space_type = "VIEW_3D"
bl_context_mode = "OBJECT"
bl_idname = "bim.covering_tool"
bl_label = "Covering Tool"
bl_description = "Create and edit coverings"
bl_icon = os.path.join(os.path.dirname(__file__), "ops.authoring.covering")
bl_widget = None
ifc_element_type = "IfcCoveringType"
bl_keymap = tool.Blender.get_default_selection_keypmap() + (
("bim.covering_hotkey", {"type": "A", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_A")]}),
("bim.covering_hotkey", {"type": "G", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_G")]}),
)
@classmethod
def draw_settings(cls, context, layout, ws_tool):
CoveringToolUI.draw(context, layout, ifc_element_type=cls.ifc_element_type)
def add_layout_hotkey(layout, text, hotkey, description):
args = ["covering", layout, text, hotkey, description]
tool.Blender.add_layout_hotkey_operator(*args)
class CoveringToolUI:
@classmethod
def draw(cls, context, layout, ifc_element_type = None):
cls.layout = layout
cls.props = context.scene.BIMModelProperties
cls.covering_props = context.scene.BIMCoveringProperties
row = cls.layout.row(align=True)
if not tool.Ifc.get():
row.label(text="No IFC Project", icon="ERROR")
return
if not AuthoringData.is_loaded:
AuthoringData.load(ifc_element_type)
elif AuthoringData.data["ifc_element_type"] != ifc_element_type:
AuthoringData.load(ifc_element_type)
if context.region.type == "TOOL_HEADER":
cls.draw_header_interface()
elif context.region.type in ("UI", "WINDOW"):
cls.draw_basic_bim_tool_interface()
cls.draw_default_interface()
@classmethod
def draw_header_interface(cls):
cls.draw_type_selection_interface()
@classmethod
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)
collection = bpy.context.view_layer.active_layer_collection.collection
collection_obj = collection.BIMCollectionProperties.obj
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()
# 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")
if tool.Ifc.get_entity(collection_obj):
if AuthoringData.data["predefined_type"] == "FLOORING":
op = row.operator("bim.add_instance_flooring_covering_from_cursor")
elif AuthoringData.data["predefined_type"] == "CEILING":
op = row.operator("bim.add_instance_ceiling_covering_from_cursor")
else:
op = row.operator("bim.add_constr_type_instance", text="Add")
op.from_invoke = True
if cls.props.relating_type_id.isnumeric():
op.relating_type_id = int(cls.props.relating_type_id)
else:
op = row.operator("bim.add_constr_type_instance", text="Add")
op.from_invoke = True
if cls.props.relating_type_id.isnumeric():
op.relating_type_id = int(cls.props.relating_type_id)
elif (AuthoringData.data["predefined_type"] == "FLOORING" and
type_material_usage == "IfcMaterialLayerSet" and
element and
bpy.context.selected_objects and
element.is_a("IfcWall")):
row = cls.layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="", icon="EVENT_A")
op = row.operator("bim.add_instance_flooring_coverings_from_walls")
elif (element and
bpy.context.selected_objects and
element.is_a("IfcCovering") and
# AuthoringData.data["predefined_type"] == "FLOORING" and
AuthoringData.data["active_material_usage"] == "LAYER3"):
row = cls.layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="", icon="EVENT_G")
op = row.operator("bim.regen_selected_covering_object")
# elif AuthoringData.data["predefined_type"] == "CEILING":
# row = cls.layout.row(align=True)
# row.prop(data=cls.props, property="ceiling_height", text="ceiling height")
# if element and bpy.context.selected_objects and element.is_a("IfcWall"):
# op = row.operator("bim.add_instance_ceiling_coverings_from_walls")
# elif element and bpy.context.selected_objects and element.is_a("IfcSpace"):
# op. = row.operator("bim.add_istance_flooring_from_spaces"):
# else:
# op = row.operator("bim.add_instance_ceiling_from_cursor")
# op = row.operator("bim.add_constr_type_instance", text="Add")
# op.from_invoke = True
# if cls.props.relating_type_id.isnumeric():
# op.relating_type_id = int(cls.props.relating_type_id)
else:
row = cls.layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="", icon="EVENT_A")
op = row.operator("bim.add_constr_type_instance", text="Add")
op.from_invoke = True
if cls.props.relating_type_id.isnumeric():
op.relating_type_id = int(cls.props.relating_type_id)
@classmethod
def draw_type_selection_interface(cls):
# shared by both sidebar and header
row = cls.layout.row(align=True)
if AuthoringData.data["ifc_classes"]:
row = cls.layout.row(align=True)
row.label(text="", icon="FILE_3D")
prop_with_search(row, cls.props, "relating_type_id", text="")
row.operator("bim.launch_type_manager", icon="LIGHTPROBE_GRID", text="")
else:
row.label(text=f"No {AuthoringData.data['ifc_element_type']} Found", icon="ERROR")
row = cls.layout.row()
row.operator("bim.launch_type_manager", icon="LIGHTPROBE_GRID", text="Launch Type Manager")
@classmethod
def draw_basic_bim_tool_interface(cls):
cls.draw_type_selection_interface()
if AuthoringData.data["ifc_classes"]:
if cls.props.ifc_class:
box = cls.layout.box()
if AuthoringData.data["type_thumbnail"]:
box.template_icon(icon_value=AuthoringData.data["type_thumbnail"], scale=5)
else:
op = box.operator("bim.load_type_thumbnails", text="Load Thumbnails", icon="FILE_REFRESH")
op.ifc_class = cls.props.ifc_class
class Hotkey(bpy.types.Operator, Operator):
bl_idname = "bim.covering_hotkey"
bl_label = "Hotkey"
bl_options = {"REGISTER", "UNDO"}
hotkey: bpy.props.StringProperty()
description: bpy.props.StringProperty()
@classmethod
def poll(cls, context):
return tool.Ifc.get()
@classmethod
def description(cls, context, operator):
return operator.description or ""
def _execute(self, context):
# self.props = context.scene.BIMCoveringProperties
getattr(self, f"hotkey_{self.hotkey}")()
def invoke(self, context, event):
# https://blender.stackexchange.com/questions/276035/how-do-i-make-operators-remember-their-property-values-when-called-from-a-hotkey
# self.props = context.scene.BIMSpatialProperties
return self.execute(context)
def draw(self, context):
pass
def hotkey_S_A(self):
active_obj = bpy.context.active_object
element = tool.Ifc.get_entity(active_obj)
collection = bpy.context.view_layer.active_layer_collection.collection
collection_obj = collection.BIMCollectionProperties.obj
if AuthoringData.data["predefined_type"] == "FLOORING":
if element and bpy.context.selected_objects and element.is_a("IfcWall"):
bpy.ops.bim.add_instance_flooring_coverings_from_walls()
elif tool.Ifc.get_entity(collection_obj):
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()
@@ -21,14 +21,15 @@ from . import ui, prop, operator
classes = (
operator.AddCsvAttribute,
operator.RemoveCsvAttribute,
operator.RemoveAllCsvAttributes,
operator.ExportIfcCsv,
operator.ImportIfcCsv,
operator.EyedropIfcCsv,
operator.SelectCsvIfcFile,
operator.ImportCsvAttributes,
operator.ExportCsvAttributes,
operator.ExportIfcCsv,
operator.ImportCsvAttributes,
operator.ImportIfcCsv,
operator.RemoveAllCsvAttributes,
operator.RemoveCsvAttribute,
operator.ReorderCsvAttribute,
operator.SelectCsvIfcFile,
prop.CsvAttribute,
prop.CsvProperties,
ui.BIM_PT_ifccsv,
)
@@ -16,16 +16,16 @@
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
import ifccsv
import ifcopenshell
import os
import logging
import bpy
import json
import webbrowser
import ifccsv
import logging
import tempfile
import ifcopenshell
import blenderbim.tool as tool
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.handler import purge_module_data
from blenderbim.bim.handler import refresh_ui_data
class AddCsvAttribute(bpy.types.Operator):
@@ -61,28 +61,46 @@ class RemoveAllCsvAttributes(bpy.types.Operator):
return {"FINISHED"}
class ReorderCsvAttribute(bpy.types.Operator):
bl_idname = "bim.reorder_csv_attribute"
bl_label = "Reorder CSV Attribute"
bl_options = {"REGISTER", "UNDO"}
old_index: bpy.props.IntProperty()
new_index: bpy.props.IntProperty()
def execute(self, context):
old = context.scene.CsvProperties.csv_attributes[self.old_index]
new = context.scene.CsvProperties.csv_attributes[self.new_index]
props = ["name", "header", "sort", "group", "varies_value", "summary"]
for prop in props:
value = getattr(new, prop)
setattr(new, prop, getattr(old, prop))
setattr(old, prop, value)
return {"FINISHED"}
class ImportCsvAttributes(bpy.types.Operator):
bl_idname = "bim.import_csv_attributes"
bl_label = "Import CSV Template"
bl_label = "Load CSV Settings"
bl_description = "Import a json template for CSV export"
bl_options = {"REGISTER", "UNDO"}
filter_glob: bpy.props.StringProperty(default="*.json", options={"HIDDEN"})
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def execute(self, context):
csv_props = context.scene.CsvProperties
csv_json = json.load(open(self.filepath))
expression = csv_json.get("expression", "")
if expression:
csv_props.ifc_selector = expression
attributes = csv_json.get("attributes", [])
if attributes:
csv_props.csv_attributes.clear()
for attribute in attributes:
csv_props.csv_attributes.add().name = attribute
props = context.scene.CsvProperties
data = json.load(open(self.filepath))
tool.Search.import_filter_query(data["query"], props.filter_groups)
props.csv_attributes.clear()
for attribute in data["attributes"]:
new = props.csv_attributes.add()
new.name = attribute["name"]
new.header = attribute["header"]
new.sort = attribute["sort"]
new.group = attribute["group"]
new.summary = attribute["summary"]
new.formatting = attribute["formatting"]
return {"FINISHED"}
def invoke(self, context, event):
@@ -92,7 +110,7 @@ class ImportCsvAttributes(bpy.types.Operator):
class ExportCsvAttributes(bpy.types.Operator):
bl_idname = "bim.export_csv_attributes"
bl_label = "Export CSV Template"
bl_label = "Save CSV Settings"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Save a json template for CSV export"
filename_ext = ".json"
@@ -100,22 +118,25 @@ class ExportCsvAttributes(bpy.types.Operator):
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def execute(self, context):
csv_props = context.scene.CsvProperties
props = context.scene.CsvProperties
csv_template = {}
expression = csv_props.ifc_selector
if expression:
csv_template["expression"] = expression
csv_attributes = []
for attribute in csv_props.csv_attributes:
attribute_name = attribute.name
csv_attributes.append(attribute_name)
if csv_attributes:
csv_template["attributes"] = csv_attributes
data = {
"query": tool.Search.export_filter_query(props.filter_groups),
"attributes": [
{
"name": a.name,
"header": a.header,
"sort": a.sort,
"group": a.group,
"summary": a.summary,
"formatting": a.formatting,
}
for a in props.csv_attributes
],
}
with open(self.filepath, "w") as outfile:
json.dump(csv_template, outfile)
json.dump(data, outfile)
return {"FINISHED"}
@@ -129,9 +150,8 @@ class ExportCsvAttributes(bpy.types.Operator):
class ExportIfcCsv(bpy.types.Operator):
bl_idname = "bim.export_ifccsv"
bl_label = "Export IFC"
#filename_ext = ".csv"
filename_ext = ".csv"
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def invoke(self, context, event):
props = context.scene.CsvProperties
@@ -149,19 +169,56 @@ class ExportIfcCsv(bpy.types.Operator):
ifc_file = IfcStore.get_file()
else:
ifc_file = ifcopenshell.open(props.csv_ifc_file)
selector = ifcopenshell.util.selector.Selector()
results = selector.parse(ifc_file, props.ifc_selector)
results = ifcopenshell.util.selector.filter_elements(
ifc_file, tool.Search.export_filter_query(props.filter_groups)
)
ifc_csv = ifccsv.IfcCsv()
attributes = [a.name for a in props.csv_attributes]
headers = [a.header for a in props.csv_attributes]
sort = []
groups = []
summaries = []
formatting = []
for attribute in props.csv_attributes:
if attribute.sort != "NONE":
sort.append({"name": attribute.name, "order": attribute.sort})
if attribute.group != "NONE":
groups.append({"name": attribute.name, "type": attribute.group, "varies_value": attribute.varies_value})
if attribute.summary != "NONE":
summaries.append({"name": attribute.name, "type": attribute.summary})
if attribute.formatting != "{{value}}" and "{{value}}" in attribute.formatting:
formatting.append({"name": attribute.name, "format": attribute.formatting})
sep = props.csv_custom_delimiter if props.csv_delimiter == "CUSTOM" else props.csv_delimiter
ifc_csv.export(ifc_file, results, attributes, output=self.filepath, format=props.format, delimiter=sep)
ifc_csv.export(
ifc_file,
results,
attributes,
headers=headers,
output=self.filepath,
format=props.format,
should_preserve_existing=props.should_preserve_existing,
delimiter=sep,
include_global_id=props.include_global_id,
null=props.null_value,
empty=props.empty_value,
bool_true=props.true_value,
bool_false=props.false_value,
sort=sort,
groups=groups,
summaries=summaries,
formatting=formatting,
)
return {"FINISHED"}
class ImportIfcCsv(bpy.types.Operator):
bl_idname = "bim.import_ifccsv"
bl_label = "Import CSV to IFC"
filename_ext = ".csv"
bl_label = "Import to IFC"
filter_glob: bpy.props.StringProperty(default="*.csv;*.ods;*.xlsx", options={"HIDDEN"})
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def invoke(self, context, event):
@@ -180,25 +237,20 @@ class ImportIfcCsv(bpy.types.Operator):
ifc_file = ifcopenshell.open(props.csv_ifc_file)
ifc_csv = ifccsv.IfcCsv()
sep = props.csv_custom_delimiter if props.csv_delimiter == "CUSTOM" else props.csv_delimiter
ifc_csv.Import(ifc_file, self.filepath, delimiter=sep)
attributes = [a.name for a in props.csv_attributes]
ifc_csv.Import(
ifc_file,
self.filepath,
attributes=attributes,
delimiter=sep,
null=props.null_value,
empty=props.empty_value,
bool_true=props.true_value,
bool_false=props.false_value,
)
if not props.should_load_from_memory:
ifc_file.write(props.csv_ifc_file)
purge_module_data()
return {"FINISHED"}
class EyedropIfcCsv(bpy.types.Operator):
bl_idname = "bim.eyedrop_ifccsv"
bl_label = "Query Selected Items"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
global_ids = []
self.file = IfcStore.get_file()
for obj in context.selected_objects:
if hasattr(obj, "BIMObjectProperties") and obj.BIMObjectProperties.ifc_definition_id:
global_ids.append("#" + self.file.by_id(obj.BIMObjectProperties.ifc_definition_id).GlobalId)
context.scene.CsvProperties.ifc_selector = "|".join(global_ids)
refresh_ui_data()
return {"FINISHED"}
@@ -17,6 +17,7 @@
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
from blenderbim.bim.module.search.prop import BIMFilterGroup
from blenderbim.bim.prop import StrProperty
from bpy.types import PropertyGroup
from bpy.props import (
@@ -31,10 +32,46 @@ from bpy.props import (
)
class CsvAttribute(PropertyGroup):
name: StringProperty(name="Query", default="class")
header: StringProperty(name="Header Value", default="IFC Class")
sort: EnumProperty(items=[("NONE", "None", ""), ("ASC", "Ascending", ""), ("DESC", "Descending", "")])
group: EnumProperty(
items=[
("NONE", "None", "Don't group any rows"),
("GROUP", "Group", "All rows where this value is identical will be merged"),
("CONCAT", "Concatenation", "Concatenate values if values vary within a group"),
("VARIES", "Varies", "Show a custom value if values vary within a group"),
("SUM", "Sum", "Sums the total value of rows in a group"),
("AVERAGE", "Average", "Averages the total value of rows in a group"),
("MIN", "Min", "Gets the minimum value of rows in a group"),
("MAX", "Max", "Gets the maximum value of rows in a group"),
]
)
varies_value: StringProperty(default="Varies", name="Varies Value")
summary: EnumProperty(
items=[
("NONE", "None", "Don't provide a summary row"),
("SUM", "Sum", "Sums the total value of all rows"),
("AVERAGE", "Average", "Averages the total value of all rows"),
("MIN", "Min", "Gets the minimum value of all rows"),
("MAX", "Max", "Gets the maximum value of all rows"),
]
)
formatting: StringProperty(default="{{value}}", name="Formatting")
class CsvProperties(PropertyGroup):
csv_ifc_file: StringProperty(default="", name="IFC File")
ifc_selector: StringProperty(default="", name="IFC Selector")
csv_attributes: CollectionProperty(name="CSV Attributes", type=StrProperty)
filter_groups: CollectionProperty(type=BIMFilterGroup, name="Filter Groups")
csv_attributes: CollectionProperty(name="CSV Attributes", type=CsvAttribute)
should_preserve_existing: BoolProperty(default=False, name="Preserve Existing")
include_global_id: BoolProperty(default=True, name="Include GlobalId")
null_value: StringProperty(default="N/A", name="Null Value")
empty_value: StringProperty(default="-", name="Empty String Value")
true_value: StringProperty(default="YES", name="True Value")
false_value: StringProperty(default="NO", name="False Value")
csv_delimiter: EnumProperty(
items=[
(";", ";", ""),
@@ -55,4 +92,9 @@ class CsvProperties(PropertyGroup):
default="csv",
)
csv_custom_delimiter: StringProperty(default="", name="Custom Delimiter")
should_show_settings: BoolProperty(default=False, name="Show Settings")
should_show_sort: BoolProperty(default=False, name="Show Sorting")
should_show_group: BoolProperty(default=False, name="Show Grouping")
should_show_summary: BoolProperty(default=False, name="Show Summary")
should_show_formatting: BoolProperty(default=False, name="Show Formatting")
should_load_from_memory: BoolProperty(default=False, name="Load from Memory")
+68 -27
View File
@@ -16,12 +16,14 @@
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import blenderbim.bim.helper
from bpy.types import Panel
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.module.search.data import SearchData
class BIM_PT_ifccsv(Panel):
bl_label = "CSV Import/Export"
bl_label = "Spreadsheet Import/Export"
bl_idname = "BIM_PT_ifccsv"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
@@ -36,45 +38,84 @@ class BIM_PT_ifccsv(Panel):
props = scene.CsvProperties
if IfcStore.get_file():
row = layout.row()
row = layout.row(align=True)
row.prop(props, "should_load_from_memory")
row.operator("bim.import_csv_attributes", icon="IMPORT", text="")
row.operator("bim.export_csv_attributes", icon="EXPORT", text="")
else:
row = layout.row(align=True)
row.alignment = "RIGHT"
row.operator("bim.import_csv_attributes", icon="IMPORT", text="")
row.operator("bim.export_csv_attributes", icon="EXPORT", text="")
row.prop(props, "should_show_settings", icon="PREFERENCES", text="")
if not IfcStore.get_file() or not props.should_load_from_memory:
row = layout.row(align=True)
row.prop(props, "csv_ifc_file")
row.operator("bim.select_csv_ifc_file", icon="FILE_FOLDER", text="")
row = layout.row(align=True)
row.prop(props, "ifc_selector")
row.operator("bim.eyedrop_ifccsv", icon="EYEDROPPER", text="")
layout.separator()
if props.should_show_settings:
layout.use_property_split = True
row = layout.row(align=True)
row.prop(props, "format")
if props.format == "csv":
row = layout.row(align=True)
row.prop(props, "csv_delimiter")
if props.csv_delimiter == "CUSTOM":
row = layout.row(align=True)
row.prop(props, "csv_custom_delimiter")
row = layout.row()
row.prop(props, "should_preserve_existing")
row = layout.row()
row.prop(props, "include_global_id")
row = layout.row()
row.prop(props, "null_value")
row = layout.row()
row.prop(props, "empty_value")
row = layout.row()
row.prop(props, "true_value")
row = layout.row()
row.prop(props, "false_value")
layout.use_property_split = False
blenderbim.bim.helper.draw_filter(self.layout, props, SearchData, "csv")
row = layout.row(align=True)
row.operator("bim.add_csv_attribute", icon="ADD")
row.operator("bim.remove_all_csv_attributes", icon="CANCEL")
row = layout.row(align=True)
row.operator("bim.import_csv_attributes", icon="IMPORT")
row.operator("bim.export_csv_attributes", icon="EXPORT")
row.prop(props, "should_show_sort", icon="SORTSIZE", text="")
row.prop(props, "should_show_group", icon="OUTLINER_COLLECTION", text="")
row.prop(props, "should_show_summary", icon="SYNTAX_ON", text="")
row.prop(props, "should_show_formatting", icon="CON_TRANSLIKE", text="")
total = len(props.csv_attributes)
for index, attribute in enumerate(props.csv_attributes):
row = layout.row(align=True)
row.prop(attribute, "name", text="")
row.prop(attribute, "header", text="")
if props.should_show_sort:
row.prop(attribute, "sort", text="")
if props.should_show_group:
row.prop(attribute, "group", text="")
if attribute.group == "VARIES":
row.prop(attribute, "varies_value", text="")
if props.should_show_summary:
row.prop(attribute, "summary", text="")
if props.should_show_formatting:
row.prop(attribute, "formatting", text="")
if total > 1:
if index != 0:
op = row.operator(f"bim.reorder_csv_attribute", icon="TRIA_UP", text="")
op.old_index = index
op.new_index = index - 1
if index + 1 != total:
op = row.operator(f"bim.reorder_csv_attribute", icon="TRIA_DOWN", text="")
op.old_index = index
op.new_index = index + 1
row.operator("bim.remove_csv_attribute", icon="X", text="").index = index
row = layout.row(align=True)
row.prop(props, "format")
if props.format == 'csv':
row = layout.row(align=True)
row.prop(props, "csv_delimiter")
if props.csv_delimiter == "CUSTOM":
row = layout.row(align=True)
row.prop(props, "csv_custom_delimiter")
row = layout.row()
split = row.split(factor=0.5)
c = split.column()
c.operator("bim.export_ifccsv", icon="EXPORT", text="Export IFC to " + props.format.upper())
c = split.column()
c.operator("bim.import_ifccsv", icon="IMPORT")
row.operator("bim.export_ifccsv", icon="EXPORT", text="Export IFC to " + props.format.upper())
row.operator("bim.import_ifccsv", icon="IMPORT")
@@ -20,23 +20,26 @@ import bpy
from . import ui, prop, operator
classes = (
operator.ConvertToBlender,
operator.CopyDebugInformation,
operator.CreateAllShapes,
operator.CreateShapeFromStepId,
operator.InspectFromObject,
operator.InspectFromStepId,
operator.OverrideDisplayType,
operator.ParseExpress,
operator.PrintIfcFile,
operator.PrintObjectPlacement,
operator.ProfileImportIFC,
operator.PurgeHdf5Cache,
operator.PurgeIfcLinks,
operator.ConvertToBlender,
operator.RewindInspector,
operator.SelectExpressFile,
operator.SelectHighPolygonMeshes,
operator.SelectHighestPolygonMeshes,
operator.ValidateIfcFile,
operator.PrintUnusedElementStats,
operator.PurgeUnusedElementsByClass,
prop.BIMDebugProperties,
ui.BIM_PT_debug,
)
@@ -116,7 +116,7 @@ class PurgeIfcLinks(bpy.types.Operator):
context.scene.BIMProperties.ifc_file = ""
context.scene.BIMDebugProperties.attributes.clear()
IfcStore.purge()
blenderbim.bim.handler.purge_module_data()
blenderbim.bim.handler.refresh_ui_data()
return {"FINISHED"}
@@ -136,7 +136,7 @@ class ConvertToBlender(bpy.types.Operator):
m.BIMMaterialProperties.ifc_style_id = False
bpy.context.scene.BIMProperties.ifc_file = ""
IfcStore.purge()
blenderbim.bim.handler.purge_module_data()
blenderbim.bim.handler.refresh_ui_data()
return {"FINISHED"}
@@ -154,6 +154,8 @@ class ValidateIfcFile(bpy.types.Operator):
logger = logging.getLogger("validate")
logger.setLevel(logging.DEBUG)
ifcopenshell.validate.validate(IfcStore.get_file(), logger, express_rules=True)
self.report({"INFO"}, "Check validation results in the system console.")
return {"FINISHED"}
@@ -216,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"}
@@ -441,3 +443,55 @@ class PurgeHdf5Cache(bpy.types.Operator):
def execute(self, context):
core.purge_hdf5_cache(tool.Debug)
return {"FINISHED"}
class OverrideDisplayType(bpy.types.Operator):
bl_idname = "bim.override_display_type"
bl_label = "Override Display Type"
display: bpy.props.StringProperty()
def execute(self, context):
for obj in context.selected_objects:
obj.display_type = self.display
return {"FINISHED"}
class PrintUnusedElementStats(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.print_unused_elements_stats"
bl_label = "Purge Unused Elements Stats"
bl_options = {"REGISTER", "UNDO"}
bl_description = (
"Print all unused elements in current IFC project in system console, not limited to the selected class"
)
ignore_contexts: bpy.props.BoolProperty(name="Ignore Contexts", default=True)
ignore_relationships: bpy.props.BoolProperty(name="Ignore Relationships", default=True)
ignore_types: bpy.props.BoolProperty(name="Ignore Types", default=True)
def _execute(self, context):
props = context.scene.BIMDebugProperties
# ignore some classes that could have zero 0 inverse references by their nature
ignore_classes = []
if self.ignore_contexts:
ignore_classes += ["IfcRepresentationContext"]
if self.ignore_relationships:
ignore_classes += ["IfcRelationship"]
if self.ignore_types:
ignore_classes += ["IfcTypeProduct"]
unused_elements = tool.Debug.print_unused_elements_stats(props.ifc_class_purge, ignore_classes)
self.report({"INFO"}, f"{unused_elements} unused elements found, check the system console for the details.")
class PurgeUnusedElementsByClass(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.purge_unused_elements_by_class"
bl_label = "Purge Unused Elements By Class"
bl_description = (
"Will find all elements of class that have no inverse refernces and will remove them, use very carefully."
)
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
props = context.scene.BIMDebugProperties
purged_elements = core.purge_unused_elements(tool.Ifc, tool.Debug, props.ifc_class_purge)
self.report({"INFO"}, f"{purged_elements} unused elements found and removed.")
@@ -40,3 +40,14 @@ class BIMDebugProperties(PropertyGroup):
inverse_attributes: CollectionProperty(name="Inverse Attributes", type=Attribute)
inverse_references: CollectionProperty(name="Inverse References", type=Attribute)
express_file: StringProperty(name="Express File")
display_type: EnumProperty(
items=[
("BOUNDS", "Bounds", ""),
("WIRE", "Wire", ""),
("SOLID", "Solid", ""),
("TEXTURED", "Textured", ""),
],
name="Display Type",
default="BOUNDS",
)
ifc_class_purge: StringProperty(name="Unused Elements IFC Class", default="")
@@ -83,6 +83,10 @@ class BIM_PT_debug(Panel):
).percentile = context.scene.BIMDebugProperties.percentile_of_polygons
row.prop(props, "percentile_of_polygons", text="")
row = layout.split(factor=0.5, align=True)
row.prop(props, "display_type", text="")
row.operator("bim.override_display_type").display = context.scene.BIMDebugProperties.display_type
if context.active_object and context.active_object.data:
mprops = context.active_object.data.BIMMeshProperties
row = layout.row()
@@ -22,6 +22,7 @@ import ifccsv
import ifcopenshell
import blenderbim.bim.handler
import blenderbim.tool as tool
from blenderbim.bim.ifc import IfcStore
class SelectDiffJsonFile(bpy.types.Operator):
@@ -51,8 +52,27 @@ class VisualiseDiff(bpy.types.Operator):
diff = json.load(file)
for obj in context.visible_objects:
obj.color = (1.0, 1.0, 1.0, 1.0)
element = tool.Ifc.get_entity(obj)
if not element:
if not obj.BIMObjectProperties.ifc_definition_id:
continue
ifc_file = ""
for scene in obj.users_scene:
if scene.BIMProperties.ifc_file:
ifc_file = scene.BIMProperties.ifc_file
if scene.library:
break
if ifc_file:
if ifc_file not in IfcStore.session_files:
IfcStore.session_files[ifc_file] = ifcopenshell.open(ifc_file)
element_file = IfcStore.session_files[ifc_file]
else:
element_file = ifc_file
try:
element = element_file.by_id(obj.BIMObjectProperties.ifc_definition_id)
except:
continue
global_id = getattr(element, "GlobalId", None)
if not global_id:
@@ -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):
@@ -92,10 +92,10 @@ classes = (
prop.BIMTextProperties,
prop.BIMAssignedProductProperties,
prop.BIMAnnotationProperties,
ui.BIM_PT_camera,
ui.BIM_PT_drawing_underlay,
ui.BIM_PT_sheets,
ui.BIM_PT_drawings,
ui.BIM_PT_camera,
ui.BIM_PT_drawing_underlay,
ui.BIM_PT_schedules,
ui.BIM_PT_references,
ui.BIM_PT_product_assignments,
@@ -16,11 +16,13 @@
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
import os
import blenderbim.tool as tool
from mathutils import Vector
import bpy
import math
import bmesh
import blenderbim.tool as tool
import ifcopenshell.util.element
from mathutils import Vector, Matrix
class Annotator:
@@ -90,6 +92,15 @@ class Annotator:
return obj
@staticmethod
def add_vertex_to_annotation(obj):
verts_world_space = Annotator.get_placeholder_coords()
vert_local = obj.matrix_world.inverted() @ verts_world_space[0]
bm = tool.Blender.get_bmesh_for_mesh(obj.data, clean=True)
bm.verts.new(vert_local)
tool.Blender.apply_bmesh(obj.data, bm, obj)
return obj
@staticmethod
def add_plane_to_annotation(obj, remove_face=False):
# default order = bot left, top left, bot right, top right
@@ -111,7 +122,7 @@ class Annotator:
def get_annotation_obj(drawing, object_type, data_type):
camera = tool.Ifc.get_object(drawing)
co1, _, _, _ = Annotator.get_placeholder_coords(camera)
matrix_world = camera.matrix_world.copy()
matrix_world = tool.Drawing.get_camera_matrix(camera)
matrix_world.translation = co1
collection = camera.BIMObjectProperties.collection
@@ -142,6 +153,8 @@ class Annotator:
data = bpy.data.curves.new(object_type, type="CURVE")
data.dimensions = "3D"
data.resolution_u = 2
elif data_type == "empty":
data = None
obj = bpy.data.objects.new(object_type, data)
obj.matrix_world = matrix_world
@@ -154,6 +167,13 @@ class Annotator:
camera = bpy.context.scene.camera
z_offset = camera.matrix_world.to_quaternion() @ Vector((0, 0, -1))
if (
ifcopenshell.util.element.get_pset(tool.Ifc.get_entity(camera), "EPset_Drawing", "TargetView")
== "REFLECTED_PLAN_VIEW"
):
z_offset *= -1
y = camera.data.ortho_scale / 4
res_x = bpy.context.scene.render.resolution_x
@@ -166,6 +186,7 @@ class Annotator:
center = camera.matrix_world.inverted() @ bpy.context.scene.cursor.location
center.z = 0
center = camera.matrix_world @ center
return (
center + z_offset,
@@ -123,6 +123,8 @@ class DrawingsData:
[(str(s.id()), s.Name or "Unnamed", "") for s in tool.Ifc.get().by_type("IfcBuildingStorey")]
)
return results
elif bpy.context.scene.DocProperties.target_view in ["MODEL_VIEW"]:
return [(h.upper(), h, "") for h in ["Orthographic", "Perspective"]]
return [(h.upper(), h, "") for h in ["North", "South", "East", "West"]]
@classmethod
@@ -52,23 +52,23 @@ class profile_consequential:
cls.test_name = test_name
@classmethod
def log(cls):
def log(cls, args=[]):
if cls.start_time is not None:
cls.lines.append(f"{cls.test_name}\t{timer() - cls.start_time:.10f}")
args = "" if not args else "\t" + "\t".join(args)
cls.lines.append(f"{cls.test_name}\t{timer() - cls.start_time:.10f}{args}")
@classmethod
def stop(cls):
cls.log()
cls.start_time = None
print("\n".join(cls.lines))
lines = "\n".join(cls.lines)
print(lines)
import pyperclip
pyperclip.copy(lines)
cls.lines = []
def ccw(A, B, C):
"""whether a-b-c located in counter-clockwise order in 2d space"""
return (C.y - A.y) * (B.x - A.x) > (B.y - A.y) * (C.x - A.x)
def worldspace_to_winspace(verts, context):
"""Convert world space verts to window space"""
region = context.region
@@ -248,8 +248,13 @@ class BaseDecorator:
if check_mode and obj.data.is_editmode:
return self.get_editmesh_geom(obj)
vertices = [obj.matrix_world @ v.co for v in obj.data.vertices]
indices = [e.vertices for e in obj.data.edges]
bm = bmesh.new()
bm.from_mesh(obj.data)
vertices = [obj.matrix_world @ v.co for v in bm.verts]
# In object mode, it's nicer to not show "internal edges". Most will be dissolved anyway.
indices = [[v.index for v in e.verts] for e in bm.edges if len(e.link_faces) != 2]
bm.free()
return vertices, indices
def get_editmesh_geom(self, obj):
@@ -520,7 +525,10 @@ class BaseDecorator:
return matrix.inverted()[i].to_3d().normalized()
text_dir_world_x_axis = get_basis_vector(obj.matrix_world)
text_dir = (camera.matrix_world.inverted().to_quaternion() @ text_dir_world_x_axis).to_2d().normalized()
camera_matrix = tool.Drawing.get_camera_matrix(camera)
text_dir = (camera_matrix.inverted().to_quaternion() @ text_dir_world_x_axis).to_2d().normalized()
pos = location_3d_to_region_2d(region, region3d, text_world_position)
props = obj.BIMTextProperties
@@ -742,7 +750,7 @@ class AngleDecorator(BaseDecorator):
except ZeroDivisionError:
continue
circle_angle = acos(cos_a)
counter_clockwise = ccw(v2, v1, v0)
counter_clockwise = tool.Cad.is_counter_clockwise_order(v2, v1, v0)
angle_circle = get_angle_circle(circle_start, circle_angle, counter_clockwise)
add_verts_sequence([v1 + v for v in angle_circle], start_i_arcs, **out_kwargs_arcs)
@@ -774,7 +782,7 @@ class AngleDecorator(BaseDecorator):
region3d = context.region_data
viewportDrawingScale = self.get_viewport_drawing_scale(context)
ANGLE_LABEL_OFFSET = 25 * viewportDrawingScale
ANGLE_LABEL_OFFSET = 20 * viewportDrawingScale
last_segment_i = len(indices) - 1
for edge_i, edge_vertices in enumerate(indices):
@@ -792,24 +800,24 @@ class AngleDecorator(BaseDecorator):
edge1_ws = v2 - v1
try:
cos_a = edge0_ws.dot(edge1_ws) / (edge0_ws.length * edge1_ws.length)
angle_rad = acos(cos_a)
angle = angle_rad / pi * 180
except ZeroDivisionError:
continue
circle_angle_rad = acos(cos_a)
circle_angle = circle_angle_rad / pi * 180
angle = 0
# calculate angle position
p0 = location_3d_to_region_2d(region, region3d, v0)
p1 = location_3d_to_region_2d(region, region3d, v1)
p2 = location_3d_to_region_2d(region, region3d, v2)
p0, p1, p2 = [location_3d_to_region_2d(region, region3d, p) for p in vertices[i0 : i1 + 2]]
edge0 = p0 - p1
edge1 = p2 - p1
base_edge = edge0 if ccw(p0, p1, p2) else edge1
text_offset = (Matrix.Rotation(-circle_angle_rad / 2, 2) @ base_edge).normalized() * ANGLE_LABEL_OFFSET
radius = min(edge0.length_squared, edge1.length_squared) ** 0.5
# TODO: can be helpful for svg positioning
base_edge = edge0 if tool.Cad.is_counter_clockwise_order(p0, p1, p2) else edge1
text_offset = (Matrix.Rotation(-angle_rad / 2, 2) @ base_edge).normalized() * (radius + ANGLE_LABEL_OFFSET)
label_position = p1 + text_offset
text = f"{int(circle_angle)}d"
text = f"{int(angle)}deg"
label_dir = Vector((1, 0))
self.draw_label(context, text, label_position, label_dir)
self.draw_label(context, text, label_position, label_dir, box_alignment="center")
class DiameterDecorator(DimensionDecorator):
@@ -1923,7 +1931,7 @@ class DecorationsHandler:
if cls.installed:
cls.uninstall()
handler = cls()
# NOTE: we USE POST_PIXEL here so that we can draw use both 3D_POLYLINE_UNIFORM_COLOR
# NOTE: we USE POST_PIXEL here so that we can use both 3D_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")
@@ -1941,6 +1949,7 @@ class DecorationsHandler:
self.decorators[object_type] = self.decorators["FALL"]
def get_objects_and_decorators(self, collection):
# TODO: do it in data instead of the handler for performance?
results = []
for obj in collection.all_objects:
@@ -18,6 +18,7 @@
import bpy
import blenderbim.bim.module.drawing.decoration as decoration
import blenderbim.tool as tool
from bpy.app.handlers import persistent
@@ -37,10 +38,28 @@ def depsgraph_update_pre_handler(scene):
def set_active_camera_resolution(scene):
if not scene.camera or "/" not in scene.camera.name or not scene.DocProperties.drawings:
return
props = scene.camera.data.BIMCameraProperties
ortho_scale = max((props.width, props.height))
aspect_ratio = props.width / props.height
if (
scene.render.resolution_x != scene.camera.data.BIMCameraProperties.raster_x
or scene.render.resolution_y != scene.camera.data.BIMCameraProperties.raster_y
(scene.camera.data.ortho_scale != ortho_scale)
or (scene.render.resolution_x / scene.render.resolution_y != aspect_ratio)
):
scene.render.resolution_x = scene.camera.data.BIMCameraProperties.raster_x
scene.render.resolution_y = scene.camera.data.BIMCameraProperties.raster_y
scene.camera.data.ortho_scale = ortho_scale
diagram_scale = tool.Drawing.get_diagram_scale(scene.camera)
scale_ratio = tool.Drawing.get_scale_ratio(diagram_scale["Scale"])
if props.width > props.height:
aspect_ratio = props.height / props.width
raster_x = ortho_scale * scale_ratio * props.dpi / 0.0254
raster_y = ortho_scale * aspect_ratio * scale_ratio * props.dpi / 0.0254
else:
aspect_ratio = props.width / props.height
raster_x = ortho_scale * aspect_ratio * scale_ratio * props.dpi / 0.0254
raster_y = ortho_scale * scale_ratio * props.dpi / 0.0254
scene.render.resolution_x = scene.camera.data.BIMCameraProperties.raster_x = int(raster_x)
scene.render.resolution_y = scene.camera.data.BIMCameraProperties.raster_y = int(raster_y)
current_drawing = scene.DocProperties.drawings[scene.DocProperties.current_drawing_index]
@@ -182,8 +182,12 @@ def format_distance(
# Check values and compose string
if inches == 12:
feet += 1
inches = 0
if unit_length == "INCHES":
inches = 12
if unit_length == "FEET":
feet += 1
inches = 0
if not isArea:
add_inches = bool(inches) or not suppress_zero_inches
@@ -26,7 +26,6 @@ import shutil
import hashlib
import shapely
import subprocess
import webbrowser
import numpy as np
import multiprocessing
import ifcopenshell
@@ -70,15 +69,6 @@ class profile:
print(self.task, timer() - self.start)
def open_with_user_command(user_command, path):
if user_command:
commands = eval(user_command)
for command in commands:
subprocess.Popen(command)
else:
webbrowser.open("file://" + path)
class Operator:
def execute(self, context):
IfcStore.execute_ifc_operator(self, context)
@@ -269,7 +259,7 @@ class CreateDrawing(bpy.types.Operator):
with profile("Combine SVG layers"):
svg_path = self.combine_svgs(context, underlay_svg, linework_svg, annotation_svg)
open_with_user_command(context.preferences.addons["blenderbim"].preferences.svg_command, svg_path)
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)
@@ -485,7 +475,7 @@ class CreateDrawing(bpy.types.Operator):
drawing_elements = tool.Drawing.get_drawing_elements(self.camera_element)
self.setup_serialiser(ifc)
self.setup_serialiser(ifc, target_view)
cache = IfcStore.get_cache()
[cache.remove(guid) for guid in invalidated_guids]
tree = ifcopenshell.geom.tree()
@@ -555,7 +545,7 @@ class CreateDrawing(bpy.types.Operator):
if self.camera.data.BIMCameraProperties.calculate_shapely_surfaces:
# shapely variant
group = root.findall(".//{http://www.w3.org/2000/svg}g")[0]
group = root.find("{http://www.w3.org/2000/svg}g")
nm = group.attrib["{http://www.ifcopenshell.org/ns}name"]
m4 = np.array(json.loads(group.attrib["{http://www.ifcopenshell.org/ns}plane"]))
m3 = np.array(json.loads(group.attrib["{http://www.ifcopenshell.org/ns}matrix3"]))
@@ -566,10 +556,21 @@ class CreateDrawing(bpy.types.Operator):
m44[1][3] = m3[1][2]
m44 = np.linalg.inv(m44)
projections = group.findall('.//{http://www.w3.org/2000/svg}g[@class="projection"]') or []
elements_with_faces = set()
for element in drawing_elements.copy():
obj = tool.Ifc.get_object(element)
if obj and obj.type == "MESH" and len(obj.data.polygons):
elements_with_faces.add(element.GlobalId)
projections = root.xpath(
".//svg:g[contains(@class, 'projection')]", namespaces={"svg": "http://www.w3.org/2000/svg"}
)
boundary_lines = []
for projection in projections:
boundary_lines = []
global_id = projection.attrib["{http://www.ifcopenshell.org/ns}guid"]
if global_id not in elements_with_faces:
continue
for path in projection.findall("./{http://www.w3.org/2000/svg}path"):
# Rounding is necessary to ensure coincident points are coincident
start, end = [[round(float(o), 1) for o in co[1:].split(",")] for co in path.attrib["d"].split()]
@@ -578,50 +579,55 @@ class CreateDrawing(bpy.types.Operator):
# Extension by 0.5mm is necessary to ensure lines overlap with other diagonal lines
start, end = tool.Drawing.extend_line(start, end, 0.5)
boundary_lines.append(shapely.LineString([start, end]))
unioned_boundaries = shapely.union_all(shapely.GeometryCollection(boundary_lines))
closed_polygons = shapely.polygonize(unioned_boundaries.geoms)
for polygon in closed_polygons.geoms:
# Less than 1mm2 is not worth styling on sheet
if polygon.area < 1:
continue
centroid = polygon.centroid
internal_point = centroid if polygon.contains(centroid) else polygon.representative_point()
if internal_point:
internal_point = [internal_point.x, internal_point.y]
a, b = self.drawing_to_model_co(m44, m4, internal_point, 0.0), self.drawing_to_model_co(
m44, m4, internal_point, -100.0
)
inside_elements = [e for e in tree.select(self.pythonize(a)) if not e.is_a("IfcAnnotation")]
if not inside_elements:
elements = [
e
for e in tree.select_ray(self.pythonize(a), self.pythonize(b - a))
if not e.instance.is_a("IfcAnnotation")
and tool.Cad.is_point_on_edge(
Vector(list(e.position)), (Vector(self.pythonize(a)), Vector(self.pythonize(b)))
)
]
if elements:
path = etree.Element("path")
d = (
"M"
+ " L".join(
[",".join([str(o) for o in co]) for co in polygon.exterior.coords[0:-1]]
)
unioned_boundaries = shapely.union_all(shapely.GeometryCollection(boundary_lines))
closed_polygons = shapely.polygonize(unioned_boundaries.geoms)
for polygon in closed_polygons.geoms:
# Less than 1mm2 is not worth styling on sheet
if polygon.area < 1:
continue
centroid = polygon.centroid
internal_point = centroid if polygon.contains(centroid) else polygon.representative_point()
if internal_point:
internal_point = [internal_point.x, internal_point.y]
a, b = self.drawing_to_model_co(m44, m4, internal_point, 0.0), self.drawing_to_model_co(
m44, m4, internal_point, -100.0
)
inside_elements = [e for e in tree.select(self.pythonize(a)) if not e.is_a("IfcAnnotation")]
if not inside_elements:
elements = [
e
for e in tree.select_ray(self.pythonize(a), self.pythonize(b - a))
if not e.instance.is_a("IfcAnnotation")
and tool.Cad.is_point_on_edge(
Vector(list(e.position)), (Vector(self.pythonize(a)), Vector(self.pythonize(b)))
)
]
if elements:
path = etree.Element("path")
d = (
"M"
+ " L".join([",".join([str(o) for o in co]) for co in polygon.exterior.coords[0:-1]])
+ " Z"
)
for interior in polygon.interiors:
d += (
" M"
+ " L".join([",".join([str(o) for o in co]) for co in interior.coords[0:-1]])
+ " Z"
)
for interior in polygon.interiors:
d += (
" M"
+ " L".join([",".join([str(o) for o in co]) for co in interior.coords[0:-1]])
+ " Z"
)
path.attrib["d"] = d
classes = self.get_svg_classes(ifc.by_id(elements[0].instance.id()))
classes.append("surface")
path.set("class", " ".join(list(classes)))
group.insert(0, path)
path.attrib["d"] = d
classes = self.get_svg_classes(ifc.by_id(elements[0].instance.id()))
classes.append(f"intpoint-{internal_point}")
classes.append(f"ab-{a}, {b}")
for i, ray_result in enumerate(elements):
classes.append(f"el{i}-{ray_result.instance.id()}")
classes.append(f"el{i}-pos-{list(ray_result.position)}")
classes.append(f"el{i}-dst-{ray_result.distance}")
classes.append("surface")
path.set("class", " ".join(list(classes)))
group.insert(0, path)
if self.camera.data.BIMCameraProperties.calculate_svgfill_surfaces:
results = etree.tostring(root).decode("utf8")
@@ -636,7 +642,7 @@ class CreateDrawing(bpy.types.Operator):
dom1 = parseString(svg_data_1)
svg1 = dom1.childNodes[0]
groups1 = [g for g in yield_groups(svg1) if g.getAttribute("class") == "projection"]
groups1 = [g for g in yield_groups(svg1) if "projection" in g.getAttribute("class")]
ls_groups = ifcopenshell.ifcopenshell_wrapper.svg_to_line_segments(results, "projection")
@@ -829,7 +835,7 @@ class CreateDrawing(bpy.types.Operator):
return svg_path
def setup_serialiser(self, ifc):
def setup_serialiser(self, ifc, target_view):
self.svg_settings = ifcopenshell.geom.settings(
DISABLE_TRIANGULATION=True, STRICT_TOLERANCE=True, INCLUDE_CURVES=True
)
@@ -840,7 +846,7 @@ class CreateDrawing(bpy.types.Operator):
self.serialiser.setPolygonal(True)
self.serialiser.setUseHlrPoly(True)
# Objects with more than these edges are rendered as wireframe instead of HLR for optimisation
self.serialiser.setProfileThreshold(1000)
self.serialiser.setProfileThreshold(10000)
self.serialiser.setUseNamespace(True)
self.serialiser.setAlwaysProject(True)
self.serialiser.setAutoElevation(False)
@@ -853,6 +859,10 @@ class CreateDrawing(bpy.types.Operator):
self.serialiser.setScale(self.scale)
self.serialiser.setSubtractionSettings(ifcopenshell.ifcopenshell_wrapper.ALWAYS)
self.serialiser.setUsePrefiltering(True) # See #3359
self.serialiser.setUnifyInputs(True)
self.serialiser.setSegmentProjection(True)
if target_view == "REFLECTED_PLAN_VIEW":
self.serialiser.setMirrorY(True)
# tree = ifcopenshell.geom.tree()
# This instructs the tree to explode BReps into faces and return
# the style of the face when running tree.select_ray()
@@ -887,6 +897,21 @@ class CreateDrawing(bpy.types.Operator):
)
return classes
def is_manifold(self, obj):
result = self.is_manifold_cache.get(obj.data.name, None)
if result is not None:
return result
bm = bmesh.new()
bm.from_mesh(obj.data)
for edge in bm.edges:
if not edge.is_manifold:
bm.free()
self.is_manifold_cache[obj.data.name] = False
return False
self.is_manifold_cache[obj.data.name] = True
return True
def merge_linework_and_add_metadata(self, root):
join_criteria = ifcopenshell.util.element.get_pset(self.camera_element, "EPset_Drawing", "JoinCriteria")
if join_criteria:
@@ -895,16 +920,27 @@ class CreateDrawing(bpy.types.Operator):
# Drawing convention states that same objects classes with the same material are merged when cut.
join_criteria = ["class", "material.Name", 'r"Pset.*Common"."Status"']
group = root.findall(".//{http://www.w3.org/2000/svg}g")[0]
group = root.find("{http://www.w3.org/2000/svg}g")
joined_paths = {}
self.is_manifold_cache = {}
ifc = tool.Ifc.get()
for el in root.findall(".//{http://www.w3.org/2000/svg}g[@{http://www.ifcopenshell.org/ns}guid]"):
element = ifc.by_guid(el.get("{http://www.ifcopenshell.org/ns}guid"))
classes = self.get_svg_classes(element)
classes.append("cut")
el.set("class", " ".join(classes))
if "projection" in el.get("class", "").split():
classes = self.get_svg_classes(element)
classes.append("projection")
el.set("class", " ".join(classes))
continue
else:
classes = self.get_svg_classes(element)
classes.append("cut")
el.set("class", " ".join(classes))
obj = tool.Ifc.get_object(element)
if not self.is_manifold(obj):
continue
# An element group will contain a bunch of paths representing the
# cut of that element. However IfcOpenShell may not correctly
@@ -1022,9 +1058,12 @@ class CreateDrawing(bpy.types.Operator):
# IfcConvert puts the projection afterwards which is not correct since
# projection should be drawn underneath the cut.
group = root.find("{http://www.w3.org/2000/svg}g")
projection = group.find("{http://www.w3.org/2000/svg}g[@class='projection']")
projection.getparent().remove(projection)
group.insert(0, projection)
projections = root.xpath(
".//svg:g[contains(@class, 'projection')]", namespaces={"svg": "http://www.w3.org/2000/svg"}
)
for projection in projections:
projection.getparent().remove(projection)
group.insert(0, projection)
def generate_annotation(self, context):
if not ifcopenshell.util.element.get_pset(self.drawing, "EPset_Drawing", "HasAnnotation"):
@@ -1035,7 +1074,9 @@ class CreateDrawing(bpy.types.Operator):
elements = tool.Drawing.get_group_elements(tool.Drawing.get_drawing_group(self.camera_element))
filtered_drawing_elements = tool.Drawing.get_drawing_elements(self.camera_element)
elements = [e for e in elements if e in filtered_drawing_elements]
filtered_drawing_annotations = {e for e in filtered_drawing_elements if e.is_a("IfcAnnotation")}
elements = {e for e in elements if e in filtered_drawing_elements}
elements = list(elements | filtered_drawing_annotations)
annotations = sorted(
elements, key=lambda a: (tool.Drawing.get_annotation_z_index(a), 1 if a.ObjectType == "TEXT" else 0)
@@ -1236,11 +1277,15 @@ class CreateSheets(bpy.types.Operator, Operator):
# These variables will be made available to the evaluated commands
svg = references["SHEET"]
basename = os.path.basename(svg)
path = os.path.dirname(svg)
pdf = os.path.splitext(svg)[0] + ".pdf"
eps = os.path.splitext(svg)[0] + ".eps"
dxf = os.path.splitext(svg)[0] + ".dxf"
replacements = {
"svg": svg,
"basename": os.path.basename(svg),
"path": os.path.dirname(svg),
"pdf": pdf,
"eps": os.path.splitext(svg)[0] + ".eps",
"dxf": os.path.splitext(svg)[0] + ".dxf",
}
has_sheet_reference = False
for reference in tool.Drawing.get_document_references(sheet):
@@ -1273,22 +1318,23 @@ class CreateSheets(bpy.types.Operator, Operator):
if svg2pdf_command:
# With great power comes great responsibility. Example:
# [['inkscape', svg, '-o', pdf]]
commands = eval(svg2pdf_command)
# [["inkscape", "svg", "-o", "pdf"]]
commands = json.loads(svg2pdf_command)
for command in commands:
subprocess.run(command)
subprocess.run([replacements.get(c, c) for c in command])
if svg2dxf_command:
# With great power comes great responsibility. Example:
# [['inkscape', svg, '-o', eps], ['pstoedit', '-dt', '-f', 'dxf:-polyaslines -mm', eps, dxf, '-psarg', '-dNOSAFER']]
commands = eval(svg2dxf_command)
# [["inkscape", "svg", "-o", "eps"], ["pstoedit", "-dt", "-f", "dxf:-polyaslines -mm", "eps", "dxf", "-psarg", "-dNOSAFER"]]
commands = json.loads(svg2dxf_command)
for command in commands:
subprocess.run(command)
command[0] = shutil.which(command[0]) or command[0]
subprocess.run([replacements.get(c, c) for c in command])
if svg2pdf_command:
open_with_user_command(context.preferences.addons["blenderbim"].preferences.pdf_command, pdf)
tool.Drawing.open_with_user_command(context.preferences.addons["blenderbim"].preferences.pdf_command, pdf)
else:
open_with_user_command(context.preferences.addons["blenderbim"].preferences.svg_command, svg)
tool.Drawing.open_with_user_command(context.preferences.addons["blenderbim"].preferences.svg_command, svg)
class SelectAllDrawings(bpy.types.Operator):
@@ -1355,7 +1401,9 @@ class OpenDrawing(bpy.types.Operator):
return {"CANCELLED"}
for drawing_uri in drawing_uris:
open_with_user_command(context.preferences.addons["blenderbim"].preferences.svg_command, drawing_uri)
tool.Drawing.open_with_user_command(
context.preferences.addons["blenderbim"].preferences.svg_command, drawing_uri
)
return {"FINISHED"}
@@ -1391,6 +1439,7 @@ class ActivateModel(bpy.types.Operator):
if view3d_context:
bpy.ops.object.hide_view_clear(view3d_context)
bpy.ops.bim.activate_status_filters()
subcontext = ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model", "Body", "MODEL_VIEW")
@@ -1512,11 +1561,17 @@ class RemoveDrawing(bpy.types.Operator, Operator):
tool.Ifc.get().by_id(d.ifc_definition_id) for d in context.scene.DocProperties.drawings if d.is_selected
]
else:
if not self.drawing:
self.report({"ERROR"}, "No drawing selected")
return {"CANCELLED"}
drawings = [tool.Ifc.get().by_id(self.drawing)]
removed_drawings = [drawing.id() for drawing in drawings]
for drawing in drawings:
sheet_references = tool.Drawing.get_sheet_references(drawing)
for reference in sheet_references:
bpy.ops.bim.remove_drawing_from_sheet(reference=reference.id())
core.remove_drawing(tool.Ifc, tool.Drawing, drawing=drawing)
@@ -1862,7 +1917,7 @@ class RemoveSchedule(bpy.types.Operator, Operator):
schedule: bpy.props.IntProperty()
def _execute(self, context):
core.remove_document(tool.Ifc, tool.Drawing, "SCHEDULE", schedule=tool.Ifc.get().by_id(self.schedule))
core.remove_document(tool.Ifc, tool.Drawing, "SCHEDULE", document=tool.Ifc.get().by_id(self.schedule))
class OpenSchedule(bpy.types.Operator, Operator):
@@ -312,7 +312,6 @@ class DocProperties(PropertyGroup):
should_use_underlay_cache: BoolProperty(name="Use Underlay Cache", default=False)
should_use_linework_cache: BoolProperty(name="Use Linework Cache", default=False)
should_use_annotation_cache: BoolProperty(name="Use Annotation Cache", default=False)
should_extract: BoolProperty(name="Should Extract", default=True)
is_editing_drawings: BoolProperty(name="Is Editing Drawings", default=False)
is_editing_schedules: BoolProperty(name="Is Editing Schedules", default=False)
is_editing_references: BoolProperty(name="Is Editing References", default=False)
@@ -375,6 +374,9 @@ class BIMCameraProperties(PropertyGroup):
custom_scale_denominator: bpy.props.StringProperty(default="100", update=update_diagram_scale)
raster_x: IntProperty(name="Raster X", default=1000)
raster_y: IntProperty(name="Raster Y", default=1000)
dpi: IntProperty(name="DPI", default=75)
width: FloatProperty(name="Width", default=50, subtype="DISTANCE")
height: FloatProperty(name="Height", default=50, subtype="DISTANCE")
is_nts: BoolProperty(name="Is NTS", update=update_is_nts)
active_drawing_style_index: IntProperty(name="Active Drawing Style Index")
@@ -502,6 +504,8 @@ ANNOTATION_TYPES_DATA = {
"PLAN_LEVEL": ("Level (Plan)", "", "SORTBYEXT", "curve"),
"SECTION_LEVEL": ("Level (Section)", "", "TRIA_DOWN", "curve"),
"BREAKLINE": ("Breakline", "", "FCURVE", "mesh"),
"SYMBOL": ("Symbol", "", "KEYFRAME", "empty"),
"MULTI_SYMBOL": ("Multi-Symbol", "", "OUTLINER_DATA_POINTCLOUD", "mesh"),
"LINEWORK": ("Line", "", "SNAP_MIDPOINT", "mesh"),
"BATTING": ("Batting", "Add batting annotation.\nThickness could be changed through Thickness property of BBIM_Batting property set", "FORCE_FORCE", "mesh"),
"REVISION_CLOUD":("Revision Cloud", "Add revision cloud", "VOLUME_DATA", "mesh"),
@@ -538,7 +542,7 @@ class BIMAnnotationProperties(PropertyGroup):
name="Create Representation For Type",
default=False,
description='Whether "Add type" should define a representation for the type \n'
"or allow occurences to have their own",
"or allow occurrences to have their own",
)
is_adding_type: bpy.props.BoolProperty(default=False)
type_name: bpy.props.StringProperty(name="Name", default="TYPEX")
@@ -118,6 +118,25 @@ class Scheduler:
if cell_style:
related_styles.append((style_name, cell_style))
# sometimes there are no column styles (e.g. in ODS from IfcCSV)
# and we just use some constant number for column widths
if not column_widths:
row_columns = []
for tr in table.getElementsByType(TableRow):
row_cols = 0
for td in tr.getElementsByType(TableCell):
column_span = td.getAttribute("numbercolumnsspanned")
column_span = int(column_span) if column_span else 1
col_repeat = td.getAttribute("numbercolumnsrepeated")
col_repeat = int(col_repeat) if col_repeat else 1
row_cols += column_span * col_repeat
row_columns.append(row_cols)
n_columns = max(row_columns)
column_widths = [25] * n_columns # some constant width value 👀
column_styles = [None] * n_columns
# collect rows height
row_heights = []
# TODO: never used yet because unsure about priority for row styles
@@ -351,7 +370,7 @@ class Scheduler:
wrap_text: if True, text will be wrapped to fit in cell
cell_width: width of cell, used for wrapping text
"""
text_lines = [str(p).upper() for p in p_tags]
text_lines = [str(p) for p in p_tags]
box_alignment_params = SvgWriter.get_box_alignment_parameters(box_alignment)
text_params = {
"font-size": font_size,
@@ -383,12 +402,27 @@ class Scheduler:
else:
wrapped_lines = text_lines
for line_number, text_line in enumerate(wrapped_lines[::-1]):
if box_alignment.startswith("top"):
dy_dir = 1
line_number = 0
elif box_alignment.startswith("bot"):
dy_dir = -1
line_number = 0
else: # middle row
# the idea is that the middle row should always stay in the center
# e.g. dy offset for 3 lines is: 1, 0, -1
# for 2 lines: 0.5, -0.5
dy_dir = -1
line_number = (len(wrapped_lines) - 1) / 2
for text_line in wrapped_lines[::dy_dir]:
# position has to be inserted at tspan to avoid x offset between tspans
tspan = self.svg.tspan(text_line, insert=(x, y), **text_params)
# doing it here and not in tspan constructor because constructor adds unnecessary spaces
tspan.update({"dy": f"-{line_number}em"})
tspan.update({"dy": f"{line_number}em"})
text_tag.add(tspan)
line_number += dy_dir
self.svg.add(text_tag)
def convert_to_mm(self, value):
@@ -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)
@@ -174,6 +175,8 @@ class SheetBuilder:
ET.register_namespace("", "http://www.w3.org/2000/svg")
layout_path = tool.Drawing.get_document_uri(sheet, "LAYOUT")
if not os.path.exists(layout_path):
return
layout_tree = ET.parse(layout_path)
layout_root = layout_tree.getroot()
@@ -343,8 +346,12 @@ class SheetBuilder:
def build_drawings(self, root, sheet):
for view in root.findall('{http://www.w3.org/2000/svg}g[@data-type="drawing"]'):
drawing_id = int(view.attrib["data-id"])
reference = tool.Ifc.get().by_id(int(view.attrib["data-id"]))
drawing = tool.Ifc.get().by_id(view.attrib["data-drawing"])
try:
reference = tool.Ifc.get().by_id(int(view.attrib["data-id"]))
drawing = tool.Ifc.get().by_id(view.attrib["data-drawing"])
except:
# Perhaps the SVG has outdated content or is edited externally which we cannot control.
continue
images = view.findall("{http://www.w3.org/2000/svg}image")
@@ -385,8 +392,12 @@ class SheetBuilder:
def build_schedules(self, root, sheet):
for view in root.findall('{http://www.w3.org/2000/svg}g[@data-type="schedule"]'):
reference = tool.Ifc.get().by_id(int(view.attrib["data-id"]))
schedule = tool.Ifc.get().by_id(int(view.attrib["data-schedule"]))
try:
reference = tool.Ifc.get().by_id(int(view.attrib["data-id"]))
schedule = tool.Ifc.get().by_id(int(view.attrib["data-schedule"]))
except:
# Perhaps the SVG has outdated content or is edited externally which we cannot control.
continue
images = view.findall("{http://www.w3.org/2000/svg}image")
@@ -414,7 +425,7 @@ class SheetBuilder:
view.remove(image)
def get_href(self, element):
return urllib.parse.unquote(element.attrib.get("{http://www.w3.org/1999/xlink}href"))
return urllib.parse.unquote(element.attrib.get("{http://www.w3.org/1999/xlink}href")).replace('\\','/')
def parse_embedded_svg(self, image, data):
group = ET.Element("g")
@@ -36,7 +36,7 @@ import blenderbim.bim.module.drawing.annotation as annotation
from blenderbim.bim.module.drawing.data import DecoratorData
from blenderbim.bim.ifc import IfcStore
from math import pi, ceil, atan, degrees
from math import pi, ceil, atan, degrees, acos
from mathutils import geometry, Vector
from bpy_extras import view3d_utils
@@ -326,11 +326,12 @@ class SvgWriter:
# We have to decide whether this should come from Blender or from IFC.
# For the moment, for convenience of experimenting with ideas, it comes
# from Blender. In the future, it should probably come from IFC.
if not isinstance(obj.data, bpy.types.Mesh):
return
classes = self.get_attribute_classes(obj)
if len(obj.data.vertices) and not len(obj.data.edges):
if obj.data is None:
return self.draw_empty_annotation(obj, classes)
elif not isinstance(obj.data, bpy.types.Mesh):
return
elif len(obj.data.vertices) and not len(obj.data.edges):
return self.draw_point_annotation(obj, classes)
elif len(obj.data.polygons) == 0:
return self.draw_edge_annotation(obj, classes)
@@ -774,6 +775,18 @@ class SvgWriter:
"text-anchor": text_anchor,
}
def add_fill_bg(self, element, copy=True):
if copy:
element = element.copy()
if hasattr(element, "xml"):
attrib = element.xml.attrib
elif isinstance(element, ET.Element):
attrib = element.attrib
else: # assuming it's svgwrite.base.BaseElement
attrib = element.attribs
attrib["filter"] = "url(#fill-background)"
return element
def draw_text_annotation(self, text_obj, position):
x_offset = self.raw_width / 2
y_offset = self.raw_height / 2
@@ -791,20 +804,21 @@ class SvgWriter:
return matrix.inverted()[i].to_3d().normalized()
text_dir_world_x_axis = get_basis_vector(text_obj.matrix_world)
text_dir = (self.camera.matrix_world.inverted().to_quaternion() @ text_dir_world_x_axis).to_2d().normalized()
# RCP cameras may be scaled, so reset scales.
camera_matrix = tool.Drawing.get_camera_matrix(self.camera)
text_dir = (camera_matrix.inverted().to_quaternion() @ text_dir_world_x_axis).to_2d().normalized()
angle = math.degrees(-text_dir.angle_signed(Vector((1, 0))))
classes = self.get_attribute_classes(text_obj)
classes_str = " ".join(classes)
fill_bg = "fill-bg" in classes
symbol = tool.Drawing.get_annotation_symbol(element)
template_text_fields = []
if not symbol:
text_transform = f"translate({text_position_svg_str}) rotate({angle})"
else:
if symbol:
# NOTE: for now we assume that scale is uniform
symbol_transform = f"translate({text_position_svg_str}) rotate({angle}) scale({text_obj.scale.x})"
text_transform = symbol_transform
symbol_svg = self.find_xml_symbol_by_id(symbol)
if symbol_svg:
@@ -815,10 +829,20 @@ class SvgWriter:
if template_text_fields:
symbol_xml.attrib["transform"] = symbol_transform
symbol_xml.attrib.pop("id")
# note: zip makes sure that we iterate over the shortest list
# NOTE: zip makes sure that we iterate over the shortest list
for field, text_literal in zip(template_text_fields, text_literals):
field.text = tool.Drawing.replace_text_literal_variables(text_literal.Literal, product)
field.text = tool.Drawing.replace_text_literal_variables(
text_literal.Literal, product or element
)
field.attrib["class"] = classes_str
if fill_bg:
symbol_copied = symbol_svg.copy()
for text_tag in symbol_copied.xml.findall("text"):
self.add_fill_bg(text_tag, copy=False)
# NOTE: in case we'll later need to add fill-bg for the entire symbol:
# self.add_fill_bg(symbol_svg, copy=False)
self.svg.add(symbol_copied)
self.svg.add(symbol_svg)
return None
@@ -827,20 +851,40 @@ class SvgWriter:
line_number = 0
for text_literal in text_literals:
text = tool.Drawing.replace_text_literal_variables(text_literal.Literal, product)
text = tool.Drawing.replace_text_literal_variables(text_literal.Literal, product or element)
text_tags = self.create_text_tag(
text,
text_position_svg,
angle,
text_literal.BoxAlignment,
classes_str,
fill_bg="fill-bg" in classes,
fill_bg=fill_bg,
line_number_start=line_number,
)
for tag in text_tags:
self.svg.add(tag)
line_number += len(tag.elements)
def draw_empty_annotation(self, obj, classes):
x_offset = self.raw_width / 2
y_offset = self.raw_height / 2
point = self.project_point_onto_camera(obj.matrix_world.translation)
element = tool.Ifc.get_entity(obj)
svg_id = ifcopenshell.util.element.get_pset(element, "EPset_Annotation", "Symbol")
if not svg_id:
# EPset_AnnotationSurveyArea is not standard! See bSI-4.3 proposal #660.
svg_id = ifcopenshell.util.element.get_pset(element, "EPset_AnnotationSurveyArea", "PointType")
if not svg_id:
svg_id = str(ifcopenshell.util.element.get_predefined_type(element))
if not svg_id:
return
point = Vector(((x_offset + point.x), (y_offset - point.y)))
symbol_position_svg = point * self.svg_scale
self.svg.add(self.svg.use(f"#{svg_id}", insert=symbol_position_svg))
def draw_point_annotation(self, obj, classes):
x_offset = self.raw_width / 2
y_offset = self.raw_height / 2
@@ -849,12 +893,14 @@ class SvgWriter:
projected_points = [self.project_point_onto_camera(matrix_world @ v.co) for v in obj.data.vertices]
element = tool.Ifc.get_entity(obj)
svg_id = str(ifcopenshell.util.element.get_predefined_type(element))
# EPset_AnnotationSurveyArea is not standard! See bSI-4.3 proposal #660.
point_type = ifcopenshell.util.element.get_pset(element, "EPset_AnnotationSurveyArea", "PointType")
if point_type:
svg_id += f"-{point_type}"
svg_id = ifcopenshell.util.element.get_pset(element, "EPset_Annotation", "Symbol")
if not svg_id:
# EPset_AnnotationSurveyArea is not standard! See bSI-4.3 proposal #660.
svg_id = ifcopenshell.util.element.get_pset(element, "EPset_AnnotationSurveyArea", "PointType")
if not svg_id:
svg_id = str(ifcopenshell.util.element.get_predefined_type(element))
if not svg_id:
return
for symbol_position in projected_points:
symbol_position = Vector(((x_offset + symbol_position.x), (y_offset - symbol_position.y)))
@@ -936,54 +982,54 @@ class SvgWriter:
def draw_angle_annotations(self, obj):
points = obj.data.splines[0].points
region = bpy.context.region
area = tool.Blender.get_viewport_context()["area"]
region_3d = area.spaces.active.region_3d
# [1, 2, 3, 4, 5] -> [[1, 2, 3], [2, 3, 4], [3, 4, 5]]
points_chunked = [points[i : i + 3] for i in range(len(points) - 2)]
for points_chunk in points_chunked:
points_2d = [view3d_utils.location_3d_to_region_2d(region, region_3d, p.co.xyz) for p in points_chunk]
points_chunk = [obj.matrix_world @ p.co.xyz for p in points_chunk]
self.draw_svg_3point_arc(obj, points_chunk)
edge0 = points_2d[0] - points_2d[1]
edge1 = points_2d[2] - points_2d[1]
angle_radius = min(edge0.length, edge1.length)
dir0 = edge0.normalized()
dir1 = edge1.normalized()
dir2 = ((dir0 + dir1) / 2).normalized()
# calculate p3 which is the center of the arc
# to use draw_svg_3point_arc()
p3 = points_2d[1] + dir2 * angle_radius
# make all edges the same radius
p0 = points_2d[1] + dir0 * angle_radius
p2 = points_2d[1] + dir1 * angle_radius
points_chunk = [view3d_utils.region_2d_to_origin_3d(region, region_3d, p) for p in [p0, p3, p2]]
# points = [p.co.xyz for p in bpy.context.active_object.data.splines[0].points[:3]]
bm = bmesh.new()
bm.verts.index_update()
bm.edges.index_update()
new_verts = [bm.verts.new(p) for p in points_chunk]
new_edges = [bm.edges.new((new_verts[e[0]], new_verts[e[1]])) for e in ((0, 1), (1, 2))]
self.draw_svg_3point_arc(obj, bm)
def draw_svg_3point_arc(self, obj, bm):
def draw_svg_3point_arc(self, obj, angle_points):
"""`angle_points` are expected to be already in world space"""
# This implementation uses an SVG arc, which means that it can only draw
# arcs that are orthogonal to the view (e.g. not arcs in 3D).
# Gosh this is bad code :(
points = [v.co for v in bm.verts][:3]
center = tool.Cad.get_center_of_arc(points, obj)
classes = self.get_attribute_classes(obj)
matrix_world = obj.matrix_world
x_offset = self.raw_width / 2
y_offset = self.raw_height / 2
bm.verts.ensure_lookup_table()
arc_end_verts = [v for v in bm.verts if len(v.link_edges) == 1]
arc_end_pts = [matrix_world @ v.co for v in arc_end_verts]
def position_on_svg(p):
p = self.project_point_onto_camera(p)
offset = Vector([self.raw_width / 2, self.raw_height / 2])
return (offset + p.xy * Vector((1, -1))) * self.svg_scale
# Probably need this when rewriting to use an SVG polyline instead of an arc
def get_angle_value():
"""points should be in world space"""
# calculate arc angle, need to make sure we do it in world space
v0, v1, v2 = angle_points
edge0_ws = v0 - v1
edge1_ws = v2 - v1
try:
cos_a = edge0_ws.dot(edge1_ws) / (edge0_ws.length * edge1_ws.length)
angle_rad = acos(cos_a)
angle = angle_rad / pi * 180
except ZeroDivisionError:
angle = 0
return angle
angle = get_angle_value()
angle_points = [position_on_svg(p) for p in angle_points]
# creating arc and making sure radius is consistent across the arc
edge0 = angle_points[0] - angle_points[1]
edge1 = angle_points[2] - angle_points[1]
angle_radius = min(edge0.length, edge1.length)
dir0 = edge0.normalized()
dir1 = edge1.normalized()
arc_mid_dir = ((dir0 + dir1) / 2).normalized()
arc_points = [angle_points[1] + direction * angle_radius for direction in [dir0, arc_mid_dir, dir1]]
arc_end_pts = [arc_points[0], arc_points[2]]
arc_mid_point = arc_points[1]
# The commented code below can be useful when we start using SVG polyline instead of an arc
# arc_path = [arc_end_verts[0]]
# while True:
# last_point = arc_end_verts[0]
@@ -996,79 +1042,44 @@ class SvgWriter:
# if not found_another_point:
# break
distance_between_end_verts = (arc_end_verts[0].co - arc_end_verts[1].co).length
arc_mid_vert = arc_end_verts[0].link_edges[0].other_vert(arc_end_verts[0])
is_reflex = 0 if (arc_mid_vert.co - arc_end_verts[1].co).length < distance_between_end_verts else 1
bm.free()
# Calculate the angle
# This is the true normal in 3D, whereas the camera projection we use is the drawing direction.
# This assumes (because we use SVG arcs) that the radius is always orthogonal to our view.
# When rewriting to use polylines, we should use this normal instead.
# center = tool.Cad.get_center_of_arc([p.to_3d() for p in arc_points], None).to_2d()
# normal = mathutils.geometry.normal([arc_end_pts[0], arc_end_pts[1], center])
normal = Vector(self.camera_projection)
dir1 = (arc_end_pts[0] - center).normalized()
dir2 = (arc_end_pts[1] - center).normalized()
# normal = Vector(self.camera_projection)
# dir1 = (arc_end_pts[0] - center).normalized()
# arc_mid_dir = (arc_end_pts[1] - center).normalized()
# Let's get the matrix that represents the coordinate system of the arc.
# This matrix allows us to get 2D vectors for calculating the signed arc angle.
z = normal
x = (arc_end_pts[0] - center).normalized()
y = z.cross(x)
arc_matrix = mathutils.Matrix([x, y, z]).transposed().to_4x4()
# z = normal
# x = (arc_end_pts[0] - center).normalized()
# y = z.cross(x)
# arc_matrix = mathutils.Matrix([x, y, z]).transposed().to_4x4()
dir1 = ((arc_matrix.inverted() @ arc_end_pts[0]) - (arc_matrix.inverted() @ center)).normalized()
dir2 = ((arc_matrix.inverted() @ arc_end_pts[1]) - (arc_matrix.inverted() @ center)).normalized()
angle = -dir1.xy.angle_signed(dir2.xy)
# if is_reflex:
# angle = angle % (math.pi * 2)
# Center of gravity of all vertices, used to help position the text
cog = Vector((0, 0, 0))
for point in points:
cog += point
cog = matrix_world @ (cog / len(points))
radius = ((matrix_world @ points[0]) - center).length
arc_midpoint = center + ((cog - center).normalized() * radius)
text_position = self.project_point_onto_camera(arc_midpoint)
text_position = Vector(
((x_offset + text_position.x) * self.svg_scale, (y_offset - text_position.y) * self.svg_scale)
)
center_projected = self.project_point_onto_camera(center)
center_position = Vector(
((x_offset + center_projected.x) * self.svg_scale, (y_offset - center_projected.y) * self.svg_scale)
)
text_offset = (text_position - center_position).xy.normalized() * 5
text_position += text_offset
# dir1 = ((arc_matrix.inverted() @ arc_end_pts[0]) - (arc_matrix.inverted() @ center)).normalized()
# dir2 = ((arc_matrix.inverted() @ arc_end_pts[1]) - (arc_matrix.inverted() @ center)).normalized()
# angle = -dir1.xy.angle_signed(dir2.xy)
# calculating text parameters and adding text
text_position = arc_mid_point + arc_mid_dir * 5
text_style = SvgWriter.get_box_alignment_parameters("center")
angle_text = abs(round(math.degrees(angle), 3))
if is_reflex:
angle_text = 360 - angle_text
self.svg.add(self.svg.text(f"{angle_text}deg", insert=tuple(text_position), class_="ANGLE", **text_style))
angle_text = f"{int(angle)}deg"
self.svg.add(self.svg.text(angle_text, insert=text_position, class_="ANGLE", **text_style))
# Draw SVG arc, see for details: http://xahlee.info/js/svg_circle_arc.html
arc_proj_end_pts = [self.project_point_onto_camera(v) for v in arc_end_pts]
p1 = Vector(
((x_offset + arc_proj_end_pts[0].x) * self.svg_scale, (y_offset - arc_proj_end_pts[0].y) * self.svg_scale)
)
p2 = Vector(
((x_offset + arc_proj_end_pts[1].x) * self.svg_scale, (y_offset - arc_proj_end_pts[1].y) * self.svg_scale)
)
r = radius * self.svg_scale
# reflex = 1 if angle > math.pi else 0
reflex = is_reflex
if reflex:
sense = 0 if angle > 0 else 1
else:
sense = 1 if angle > 0 else 0
p1, p2 = arc_end_pts
r = angle_radius
# reflex: 0 => arc < 180 degrees
# 1 => arc > 180 degrees
reflex = int(angle > 180)
# sense: 0 => moving at negative angles
# 1 => moving at positive angles
sense = int(tool.Cad.is_counter_clockwise_order(*arc_points))
d = f"M {p1.x} {p1.y} A {r} {r} 0 {reflex} {sense} {p2.x} {p2.y}"
classes = self.get_attribute_classes(obj)
path = self.svg.add(self.svg.path(d=d, class_=" ".join(classes)))
def draw_radius_annotations(self, obj):
@@ -1344,18 +1355,10 @@ class SvgWriter:
multiline_to_bottom=True,
fill_bg=False,
line_number_start=0,
_draw_fill_bg=False,
):
"""returns list of created text tags"""
text_tags = []
if fill_bg:
method_kwargs = locals() | {"_draw_fill_bg": True, "fill_bg": False}
del method_kwargs["self"]
del method_kwargs["text_tags"]
text_tags += self.create_text_tag(**method_kwargs)
base_text_attrs = SvgWriter.get_box_alignment_parameters(box_alignment)
base_text_attrs = base_text_attrs | ({"filter": "url(#fill-background)"} if _draw_fill_bg else {})
if not multiline:
transform_kwargs = {"transform": "rotate({} {} {})".format(angle, text_position.x, text_position.y)}
@@ -1392,6 +1395,11 @@ class SvgWriter:
# doing it here and not in tspan constructor because constructor adds unnecessary spaces
tspan.update({"dy": f"{line_number if multiline_to_bottom else -line_number}em"})
text_tag.add(tspan)
if fill_bg:
fill_bg_tags = [self.add_fill_bg(text_tag) for text_tag in text_tags]
text_tags = fill_bg_tags + text_tags
return text_tags
def project_point_onto_camera(self, point):
@@ -31,28 +31,28 @@ from blenderbim.bim.module.drawing.prop import ANNOTATION_TYPES_DATA
class BIM_PT_camera(Panel):
bl_label = "Drawing Generation"
bl_label = "Active Drawing"
bl_idname = "BIM_PT_camera"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "data"
@classmethod
def poll(cls, context):
return context.camera and hasattr(context.active_object.data, "BIMCameraProperties")
bl_context = "scene"
bl_parent_id = "BIM_PT_tab_drawings"
def draw(self, context):
layout = self.layout
if "/" not in context.active_object.name:
layout.label(text="This is not a BIM camera.")
if not (context.scene.camera and hasattr(context.scene.camera.data, "BIMCameraProperties")):
row = self.layout.row()
row.label(text="No Active Drawing", icon="ERROR")
return
layout.use_property_split = True
dprops = context.scene.DocProperties
props = context.active_object.data.BIMCameraProperties
if "/" not in context.scene.camera.name:
self.layout.label(text="This is not a BIM camera.")
return
col = layout.column(align=True)
self.layout.use_property_split = True
dprops = context.scene.DocProperties
props = context.scene.camera.data.BIMCameraProperties
col = self.layout.column(align=True)
row = col.row(align=True)
row.prop(props, "has_underlay", icon="OUTLINER_OB_IMAGE")
row.prop(dprops, "should_use_underlay_cache", text="", icon="FILE_REFRESH")
@@ -63,32 +63,36 @@ class BIM_PT_camera(Panel):
row.prop(props, "has_annotation", icon="MOD_EDGESPLIT")
row.prop(dprops, "should_use_annotation_cache", text="", icon="FILE_REFRESH")
row = layout.row()
row = self.layout.row()
row.prop(props, "calculate_shapely_surfaces")
row = layout.row()
row = self.layout.row()
row.prop(props, "calculate_svgfill_surfaces")
row = layout.row()
row.prop(dprops, "should_extract")
row = self.layout.row()
row.prop(props, "width")
row = self.layout.row()
row.prop(props, "height")
row = layout.row()
row.prop(props, "raster_x")
row = layout.row()
row.prop(props, "raster_y")
row = self.layout.row()
row.prop(context.scene.camera.data, "clip_end", text="Depth")
row = layout.row(align=True)
row.prop(props, "diagram_scale")
row = self.layout.row(align=True)
row.prop(props, "diagram_scale", text="Scale")
row.prop(props, "is_nts", text="", icon="MOD_EDGESPLIT")
if props.diagram_scale == "CUSTOM":
row = layout.row(align=True)
row = self.layout.row(align=True)
row.prop(props, "custom_scale_numerator", text="Custom Scale")
row.prop(props, "custom_scale_denominator", text="")
row = layout.row(align=True)
if props.has_underlay:
row = self.layout.row()
row.prop(props, "dpi")
row = self.layout.row(align=True)
row.operator("bim.create_drawing", text="Create Drawing", icon="OUTPUT")
op = row.operator("bim.open_drawing", icon="URL", text="")
op.view = context.active_object.name.split("/")[1]
op.view = context.scene.camera.name.split("/")[1]
class BIM_PT_drawing_underlay(Panel):
@@ -97,12 +101,12 @@ class BIM_PT_drawing_underlay(Panel):
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "data"
bl_context = "scene"
bl_parent_id = "BIM_PT_camera"
@classmethod
def poll(cls, context):
return context.camera and hasattr(context.active_object.data, "BIMCameraProperties")
return context.scene.camera and hasattr(context.active_object.data, "BIMCameraProperties")
def draw(self, context):
layout = self.layout
@@ -164,10 +168,8 @@ class BIM_PT_drawings(Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
@classmethod
def poll(cls, context):
return tool.Blender.is_tab(context, "DRAWINGS") and tool.Ifc.get()
bl_parent_id = "BIM_PT_tab_drawings"
bl_options = {"HIDE_HEADER"}
def draw(self, context):
if not DrawingsData.is_loaded:
@@ -245,10 +247,8 @@ class BIM_PT_schedules(Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
@classmethod
def poll(cls, context):
return tool.Blender.is_tab(context, "DRAWINGS") and tool.Ifc.get()
bl_parent_id = "BIM_PT_tab_schedules"
bl_options = {"HIDE_HEADER"}
def draw(self, context):
if not DocumentsData.is_loaded:
@@ -297,10 +297,8 @@ class BIM_PT_references(Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
@classmethod
def poll(cls, context):
return tool.Blender.is_tab(context, "DRAWINGS") and tool.Ifc.get()
bl_parent_id = "BIM_PT_tab_references"
bl_options = {"HIDE_HEADER"}
def draw(self, context):
if not DocumentsData.is_loaded:
@@ -314,7 +312,7 @@ class BIM_PT_references(Panel):
if not self.props.is_editing_references:
row = self.layout.row(align=True)
row.label(text=f"{DocumentsData.data['total_references']} References Found", icon="LONGDISPLAY")
row.label(text=f"{DocumentsData.data['total_references']} References Found", icon="OBJECT_HIDDEN")
row.operator("bim.load_references", text="", icon="IMPORT")
return
@@ -341,10 +339,8 @@ class BIM_PT_sheets(Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
@classmethod
def poll(cls, context):
return tool.Blender.is_tab(context, "DRAWINGS") and tool.Ifc.get()
bl_parent_id = "BIM_PT_tab_sheets"
bl_options = {"HIDE_HEADER"}
def draw(self, context):
if not SheetsData.is_loaded:
@@ -1,5 +1,5 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
# Copyright (C) 2023 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BlenderBIM Add-on.
#
@@ -20,17 +20,18 @@ import bpy
from . import ui, prop, operator
classes = (
operator.SelectCobieIfcFile,
operator.SelectCobieJsonFile,
operator.ExecuteIfcCobie,
prop.COBieProperties,
ui.BIM_PT_cobie,
operator.ExecuteIfcFM,
operator.ExecuteIfcFMFederate,
operator.SelectFMIfcFile,
operator.SelectFMSpreadsheetFiles,
prop.BIMFMProperties,
ui.BIM_PT_fm,
)
def register():
bpy.types.Scene.COBieProperties = bpy.props.PointerProperty(type=prop.COBieProperties)
bpy.types.Scene.BIMFMProperties = bpy.props.PointerProperty(type=prop.BIMFMProperties)
def unregister():
del bpy.types.Scene.COBieProperties
del bpy.types.Scene.BIMFMProperties
@@ -0,0 +1,157 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2023 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BlenderBIM Add-on.
#
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BlenderBIM Add-on is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import os
import bpy
import json
import ifcfm
import logging
import tempfile
import ifcopenshell
import blenderbim.tool as tool
class SelectFMIfcFile(bpy.types.Operator):
bl_idname = "bim.select_fm_ifc_file"
bl_label = "Select FM IFC File"
bl_options = {"REGISTER", "UNDO"}
filename_ext = ".ifc"
filter_glob: bpy.props.StringProperty(default="*.ifc;*.ifczip;*.ifcxml", options={"HIDDEN"})
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
files: bpy.props.CollectionProperty(name="File Path", type=bpy.types.OperatorFileListElement)
def execute(self, context):
props = context.scene.BIMFMProperties
props.ifc_files.clear()
dirname = os.path.dirname(self.filepath)
for f in self.files:
new = props.ifc_files.add()
new.name = os.path.join(dirname, f.name)
props.ifc_file = self.filepath
return {"FINISHED"}
def invoke(self, context, event):
context.window_manager.fileselect_add(self)
return {"RUNNING_MODAL"}
class ExecuteIfcFM(bpy.types.Operator):
bl_idname = "bim.execute_ifcfm"
bl_label = "Execute IfcFM"
file_format: bpy.props.StringProperty()
filter_glob: bpy.props.StringProperty(default="*.csv;*.ods;*.xlsx", options={"HIDDEN"})
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
@classmethod
def poll(cls, context):
props = context.scene.BIMFMProperties
return props.should_load_from_memory or props.ifc_file
def invoke(self, context, event):
props = context.scene.BIMFMProperties
self.filepath = bpy.path.ensure_ext(bpy.data.filepath, f".{props.format}")
WindowManager = context.window_manager
WindowManager.fileselect_add(self)
return {"RUNNING_MODAL"}
def execute(self, context):
props = context.scene.BIMFMProperties
ifc_file = tool.Ifc.get()
filepaths = []
if not (ifc_file and props.should_load_from_memory):
if len(props.ifc_files):
ifc_files = [ifcopenshell.open(f.name) for f in props.ifc_files]
filepaths = [f.name for f in props.ifc_files]
else:
ifc_files = [ifcopenshell.open(props.ifc_file)]
else:
ifc_files = [ifc_file]
for i, ifc_file in enumerate(ifc_files):
if filepaths:
dirname = os.path.dirname(self.filepath)
prefix, _ = os.path.splitext(os.path.basename(filepaths[i]))
basename = os.path.basename(self.filepath)
filepath = os.path.join(dirname, f"{prefix}-{basename}")
else:
filepath = self.filepath
parser = ifcfm.Parser(preset=props.engine)
parser.parse(ifc_file)
writer = ifcfm.Writer(parser)
writer.write()
if props.format == "csv":
writer.write_csv("tmp/")
elif props.format == "ods":
writer.write_ods(filepath)
elif props.format == "xlsx":
writer.write_xlsx(filepath)
return {"FINISHED"}
class SelectFMSpreadsheetFiles(bpy.types.Operator):
bl_idname = "bim.select_fm_spreadsheet_files"
bl_label = "Select FM Spreadsheet Files"
bl_options = {"REGISTER", "UNDO"}
filter_glob: bpy.props.StringProperty(default="*.ods;*.xlsx", options={"HIDDEN"})
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
files: bpy.props.CollectionProperty(name="File Path", type=bpy.types.OperatorFileListElement)
def execute(self, context):
props = context.scene.BIMFMProperties
props.spreadsheet_files.clear()
dirname = os.path.dirname(self.filepath)
for f in self.files:
new = props.spreadsheet_files.add()
new.name = os.path.join(dirname, f.name)
return {"FINISHED"}
def invoke(self, context, event):
context.window_manager.fileselect_add(self)
return {"RUNNING_MODAL"}
class ExecuteIfcFMFederate(bpy.types.Operator):
bl_idname = "bim.execute_ifcfm_federate"
bl_label = "Execute IfcFM"
file_format: bpy.props.StringProperty()
filter_glob: bpy.props.StringProperty(default="*.ods;*.xlsx", options={"HIDDEN"})
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
@classmethod
def poll(cls, context):
props = context.scene.BIMFMProperties
return props.spreadsheet_files
def invoke(self, context, event):
props = context.scene.BIMFMProperties
self.filepath = bpy.path.ensure_ext(bpy.data.filepath, f".{props.format}")
WindowManager = context.window_manager
WindowManager.fileselect_add(self)
return {"RUNNING_MODAL"}
def execute(self, context):
props = context.scene.BIMFMProperties
parser = ifcfm.Parser(preset=props.engine)
parser.federate([f.name for f in props.spreadsheet_files])
writer = ifcfm.Writer(parser)
writer.write()
if props.format == "ods":
writer.write_ods(self.filepath)
elif props.format == "xlsx":
writer.write_xlsx(self.filepath)
return {"FINISHED"}
@@ -0,0 +1,57 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2023 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BlenderBIM Add-on.
#
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BlenderBIM Add-on is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
from blenderbim.bim.prop import StrProperty
from bpy.types import PropertyGroup
from bpy.props import (
PointerProperty,
StringProperty,
EnumProperty,
BoolProperty,
IntProperty,
FloatProperty,
FloatVectorProperty,
CollectionProperty,
)
class BIMFMProperties(PropertyGroup):
ifc_file: StringProperty(default="", name="IFC File")
ifc_files: CollectionProperty(name="IFC Files", type=StrProperty)
spreadsheet_files: CollectionProperty(name="Spreadsheets", type=StrProperty)
should_load_from_memory: BoolProperty(default=False, name="Load from Memory")
engine: EnumProperty(
items=[
("aohbsem", "AOH-BSEM", ""),
("basic", "Basic", ""),
("cobie24", "COBie 2.4", ""),
("cobie3", "COBie 3", ""),
],
name="Engine",
default="cobie24",
)
format: EnumProperty(
items=[
("csv", "csv", ""),
("xlsx", "xlsx", ""),
("ods", "ods", ""),
],
name="Format",
default="ods",
)
@@ -1,5 +1,5 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
# Copyright (C) 2023 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BlenderBIM Add-on.
#
@@ -21,9 +21,9 @@ from bpy.types import Panel
from blenderbim.bim.ifc import IfcStore
class BIM_PT_cobie(Panel):
bl_label = "COBie"
bl_idname = "BIM_PT_cobie"
class BIM_PT_fm(Panel):
bl_label = "Facility Management"
bl_idname = "BIM_PT_fm"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
@@ -34,7 +34,7 @@ class BIM_PT_cobie(Panel):
layout.use_property_split = True
scene = context.scene
props = scene.COBieProperties
props = scene.BIMFMProperties
if IfcStore.get_file():
row = layout.row()
@@ -42,22 +42,23 @@ class BIM_PT_cobie(Panel):
if not IfcStore.get_file() or not props.should_load_from_memory:
row = layout.row(align=True)
row.prop(props, "cobie_ifc_file")
row.operator("bim.select_cobie_ifc_file", icon="FILE_FOLDER", text="")
if len(props.ifc_files) > 1:
row.label(text=f"{len(props.ifc_files)} Files Selected")
else:
row.prop(props, "ifc_file")
row.operator("bim.select_fm_ifc_file", icon="FILE_FOLDER", text="")
row = layout.row()
row.prop(props, "cobie_types")
row.prop(props, "engine")
row = layout.row()
row.prop(props, "cobie_components")
row.prop(props, "format")
row = layout.row()
op = row.operator("bim.execute_ifcfm", text="Convert To Spreadsheet")
row = layout.row(align=True)
row.prop(props, "cobie_json_file")
row.operator("bim.select_cobie_json_file", icon="FILE_FOLDER", text="")
row.label(text=f"{len(props.spreadsheet_files)} Spreadsheets Selected")
row.operator("bim.select_fm_spreadsheet_files", icon="FILE_FOLDER", text="")
row = layout.row()
op = row.operator("bim.execute_ifc_cobie", text="CSV")
op.file_format = "csv"
op = row.operator("bim.execute_ifc_cobie", text="ODS")
op.file_format = "ods"
op = row.operator("bim.execute_ifc_cobie", text="XLSX")
op.file_format = "xlsx"
op = row.operator("bim.execute_ifcfm_federate", text="Merge Spreadsheets")
@@ -22,34 +22,46 @@ from . import ui, prop, operator
classes = (
operator.AddRepresentation,
operator.CopyRepresentation,
operator.DisableEditingRepresentationItems,
operator.EditObjectPlacement,
operator.EnableEditingRepresentationItems,
operator.FlipObject,
operator.GetRepresentationIfcParameters,
operator.OverrideDelete,
operator.OverrideDuplicateMove,
operator.OverrideDuplicateMoveAggregate,
operator.OverrideDuplicateMoveAggregateMacro,
operator.OverrideDuplicateMoveLinked,
operator.OverrideDuplicateMoveLinkedMacro,
operator.OverrideDuplicateMoveMacro,
operator.OverrideDuplicateMoveAggregate,
operator.OverrideDuplicateMoveAggregateMacro,
operator.RefreshAggregate,
operator.OverrideJoin,
operator.OverrideMeshSeparate,
operator.OverrideModeSetEdit,
operator.OverrideModeSetObject,
operator.OverrideOriginSet,
operator.OverrideOutlinerDelete,
operator.OverridePasteBuffer,
operator.PurgeUnusedRepresentations,
operator.RefreshAggregate,
operator.RemoveConnection,
operator.RemoveRepresentation,
operator.SelectConnection,
operator.SwitchRepresentation,
operator.UpdateParametricRepresentation,
operator.UpdateRepresentation,
prop.RepresentationItem,
prop.BIMObjectGeometryProperties,
prop.BIMGeometryProperties,
ui.BIM_PT_derived_placements,
ui.BIM_PT_placement,
ui.BIM_PT_representations,
ui.BIM_PT_representation_items,
ui.BIM_PT_connections,
ui.BIM_PT_mesh,
ui.BIM_PT_derived_coordinates,
ui.BIM_PT_workarounds,
ui.BIM_MT_object_set_origin,
ui.BIM_MT_separate,
ui.BIM_UL_representation_items,
)
@@ -66,10 +78,10 @@ def register():
bpy.types.Object.BIMGeometryProperties = bpy.props.PointerProperty(type=prop.BIMObjectGeometryProperties)
bpy.types.Scene.BIMGeometryProperties = bpy.props.PointerProperty(type=prop.BIMGeometryProperties)
bpy.types.OBJECT_PT_transform.append(ui.BIM_PT_transform)
bpy.types.VIEW3D_MT_object.append(ui.object_menu)
bpy.types.OUTLINER_MT_object.append(ui.outliner_menu)
bpy.types.VIEW3D_MT_object_context_menu.append(ui.object_menu)
bpy.types.VIEW3D_MT_edit_mesh.append(ui.edit_mesh_menu)
wm = bpy.context.window_manager
if wm.keyconfigs.addon:
km = wm.keyconfigs.addon.keymaps.new(name="Object Mode", space_type="EMPTY")
@@ -110,9 +122,9 @@ def register():
def unregister():
bpy.types.VIEW3D_MT_object.remove(ui.object_menu)
bpy.types.OBJECT_PT_transform.remove(ui.BIM_PT_transform)
bpy.types.OUTLINER_MT_object.remove(ui.outliner_menu)
bpy.types.VIEW3D_MT_object_context_menu.remove(ui.outliner_menu)
bpy.types.VIEW3D_MT_edit_mesh.remove(ui.edit_mesh_menu)
del bpy.types.Scene.BIMGeometryProperties
del bpy.types.Object.BIMGeometryProperties
wm = bpy.context.window_manager
@@ -23,8 +23,10 @@ from mathutils import Vector
def refresh():
DerivedPlacementsData.is_loaded = False
PlacementData.is_loaded = False
DerivedCoordinatesData.is_loaded = False
RepresentationsData.is_loaded = False
RepresentationItemsData.is_loaded = False
ConnectionsData.is_loaded = False
@@ -91,13 +93,57 @@ class RepresentationsData:
return results
class RepresentationItemsData:
data = {}
is_loaded = False
@classmethod
def load(cls):
cls.data = {
"total_items": cls.total_items(),
"active_surface_style": cls.active_surface_style(),
"active_layer": cls.active_layer(),
}
cls.is_loaded = True
@classmethod
def total_items(cls):
active_representation_id = None
result = 0
if bpy.context.active_object.data and hasattr(bpy.context.active_object.data, "BIMMeshProperties"):
active_representation_id = bpy.context.active_object.data.BIMMeshProperties.ifc_definition_id
element = tool.Ifc.get().by_id(active_representation_id)
if not element.is_a("IfcShapeRepresentation"):
return 0
queue = list(element.Items)
while queue:
item = queue.pop()
if item.is_a("IfcMappedItem"):
queue.extend(item.MappingSource.MappedRepresentation.Items)
else:
result += 1
return result
@classmethod
def active_surface_style(cls):
props = bpy.context.active_object.BIMGeometryProperties
if props.active_item_index < len(props.items):
return props.items[props.active_item_index].surface_style
@classmethod
def active_layer(cls):
props = bpy.context.active_object.BIMGeometryProperties
if props.active_item_index < len(props.items):
return props.items[props.active_item_index].layer
class ConnectionsData:
data = {}
is_loaded = False
@classmethod
def load(cls):
cls.data = {"connections": cls.connections()}
cls.data = {"connections": cls.connections(), "is_connection_realization": cls.is_connection_realization()}
cls.is_loaded = True
@classmethod
@@ -114,7 +160,13 @@ class ConnectionsData:
else:
related_element = rel.RelatedElement
if element.is_a("IfcRelConnectsPathElements"):
realizing_elements = []
realizing_elements_connection_type = ""
if rel.is_a("IfcRelConnectsWithRealizingElements"):
realizing_elements.extend(rel.RealizingElements)
realizing_elements_connection_type = rel.ConnectionType
if rel.is_a("IfcRelConnectsPathElements"):
related_element_connection_type = rel.RelatedConnectionType
else:
related_element_connection_type = ""
@@ -125,6 +177,8 @@ class ConnectionsData:
"is_relating": True,
"Name": related_element.Name or "Unnamed",
"ConnectionType": related_element_connection_type,
"realizing_elements": realizing_elements,
"realizing_elements_connection_type": realizing_elements_connection_type,
}
)
@@ -134,7 +188,13 @@ class ConnectionsData:
else:
relating_element = rel.RelatingElement
if element.is_a("IfcRelConnectsPathElements"):
realizing_elements = []
realizing_elements_connection_type = ""
if rel.is_a("IfcRelConnectsWithRealizingElements"):
realizing_elements.extend(rel.RealizingElements)
realizing_elements_connection_type = rel.ConnectionType
if rel.is_a("IfcRelConnectsPathElements"):
relating_element_connection_type = rel.RelatingConnectionType
else:
relating_element_connection_type = ""
@@ -145,13 +205,32 @@ class ConnectionsData:
"is_relating": False,
"Name": relating_element.Name or "Unnamed",
"ConnectionType": relating_element_connection_type,
"realizing_elements": realizing_elements,
"realizing_elements_connection_type": realizing_elements_connection_type,
}
)
return results
@classmethod
def is_connection_realization(cls):
element = tool.Ifc.get_entity(bpy.context.active_object)
connections = element.IsConnectionRealization
if not connections:
return
class DerivedPlacementsData:
results = []
for rel in connections:
data = {
"realizing_elements_connection_type": rel.ConnectionType,
"connected_from": rel.RelatingElement,
"connected_to": rel.RelatedElement,
}
results.append(data)
return results
class DerivedCoordinatesData:
data = {}
is_loaded = False
@@ -230,6 +309,25 @@ class DerivedPlacementsData:
for i, storey in enumerate(storeys):
if storey[0] != element:
continue
if i >= len(storeys):
if i >= len(storeys) - 1:
return "N/A"
return "{0:.3f}".format(round(storeys[i + 1][1] - storey[1], 3))
class PlacementData:
data = {}
is_loaded = False
@classmethod
def load(cls):
cls.data = {
"has_placement": cls.has_placement(),
}
cls.is_loaded = True
@classmethod
def has_placement(cls):
element = tool.Ifc.get_entity(bpy.context.active_object)
if element and hasattr(element, "ObjectPlacement"):
return True
return False
@@ -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
@@ -57,14 +58,79 @@ class EditObjectPlacement(bpy.types.Operator, Operator):
core.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj)
class OverrideMeshSeparate(bpy.types.Operator, Operator):
bl_idname = "bim.override_mesh_separate"
bl_label = "IFC Mesh Separate"
bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
type: bpy.props.StringProperty()
def _execute(self, context):
obj = context.active_object
# You cannot separate meshes if the representation is mapped.
relating_type = tool.Root.get_element_type(tool.Ifc.get_entity(obj))
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.
tool.Blender.toggle_edit_mode(context)
bpy.ops.bim.unassign_type(related_object=obj.name)
tool.Blender.toggle_edit_mode(context)
selected_objects = context.selected_objects
bpy.ops.mesh.separate(type=self.type)
bpy.ops.object.mode_set(mode="OBJECT", toggle=False)
new_objs = [obj]
for new_obj in context.selected_objects:
if new_obj == obj:
continue
# This is not very efficient, it needlessly copies the representations first.
blenderbim.core.root.copy_class(tool.Ifc, tool.Collector, tool.Geometry, tool.Root, obj=new_obj)
new_objs.append(new_obj)
for new_obj in new_objs:
bpy.ops.bim.update_representation(obj=new_obj.name)
class OverrideOriginSet(bpy.types.Operator, Operator):
bl_idname = "bim.override_origin_set"
bl_label = "IFC Origin Set"
bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
origin_type: bpy.props.StringProperty()
def _execute(self, context):
objs = [bpy.data.objects.get(self.obj)] if self.obj else context.selected_objects
for obj in objs:
element = tool.Ifc.get_entity(obj)
if not element:
continue
if tool.Ifc.is_moved(obj):
core.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj)
representation = tool.Geometry.get_active_representation(obj)
if not representation:
continue
representation = ifcopenshell.util.representation.resolve_representation(representation)
if not tool.Geometry.is_meshlike(representation):
continue
bpy.ops.object.origin_set(type=self.origin_type)
bpy.ops.bim.update_representation(obj=obj.name)
class AddRepresentation(bpy.types.Operator, Operator):
bl_idname = "bim.add_representation"
bl_label = "Add Representation"
bl_options = {"REGISTER", "UNDO"}
representation_conversion_method: bpy.props.EnumProperty(
items=[
("OUTLINE", "Trace Outline", ""),
("BOX", "Bounding Box", ""),
("OUTLINE", "Trace Outline", "Traces outline by local XY axes, for Profile - by local XZ axes."),
(
"BOX",
"Bounding Box",
"Creates a bounding box representation.\n"
"For Plan context - 2D bounding box by local XY axes,\n"
"for Profile - 2D bounding box by local XZ axes.\n"
"For other contexts - bounding box is 3d.",
),
("PROJECT", "Full Representation", ""),
],
name="Representation Conversion Method",
@@ -145,18 +211,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(
@@ -185,6 +255,16 @@ class RemoveRepresentation(bpy.types.Operator, Operator):
)
class PurgeUnusedRepresentations(bpy.types.Operator, Operator):
bl_idname = "bim.purge_unused_representations"
bl_label = "Purge Unused Representations"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
purged_representations = core.purge_unused_representations(tool.Ifc, tool.Geometry)
self.report({"INFO"}, f"{purged_representations} representations were purged.")
class UpdateRepresentation(bpy.types.Operator, Operator):
bl_idname = "bim.update_representation"
bl_label = "Update Representation"
@@ -214,6 +294,10 @@ class UpdateRepresentation(bpy.types.Operator, Operator):
product = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
material = ifcopenshell.util.element.get_material(product, should_skip_usage=True)
if getattr(product, "HasOpenings", False) and obj.data.BIMMeshProperties.has_openings_applied:
# Meshlike things with openings can only be updated without openings applied.
return
if not product.is_a("IfcGridAxis"):
tool.Geometry.clear_cache(product)
@@ -231,8 +315,15 @@ class UpdateRepresentation(bpy.types.Operator, Operator):
core.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj)
if material and material.is_a() in ["IfcMaterialProfileSet", "IfcMaterialLayerSet"]:
# These objects are parametrically based on an axis and should not be modified as a mesh
return
if self.ifc_representation_class == "IfcTessellatedFaceSet":
# We are explicitly casting to a tessellation, so remove all parametric materials.
element_type = ifcopenshell.util.element.get_type(product)
if element_type: # Some invalid IFCs use material sets without a type.
ifcopenshell.api.run("material.unassign_material", tool.Ifc.get(), product=element_type)
ifcopenshell.api.run("material.unassign_material", tool.Ifc.get(), product=product)
else:
# These objects are parametrically based on an axis and should not be modified as a mesh
return
old_representation = self.file.by_id(obj.data.BIMMeshProperties.ifc_definition_id)
context_of_items = old_representation.ContextOfItems
@@ -285,7 +376,7 @@ class UpdateRepresentation(bpy.types.Operator, Operator):
"style.assign_representation_styles",
self.file,
shape_representation=new_representation,
styles=tool.Geometry.get_styles(obj),
styles=tool.Geometry.get_styles(obj, only_assigned_to_faces=True),
should_use_presentation_style_assignment=context.scene.BIMGeometryProperties.should_use_presentation_style_assignment,
)
tool.Geometry.record_object_materials(obj)
@@ -353,6 +444,8 @@ class GetRepresentationIfcParameters(bpy.types.Operator, Operator):
def _execute(self, context):
core.get_representation_ifc_parameters(tool.Geometry, obj=context.active_object)
parameters = context.active_object.data.BIMMeshProperties.ifc_parameters
self.report({"INFO"}, f"{len(parameters)} parameters found.")
class CopyRepresentation(bpy.types.Operator, Operator):
@@ -377,7 +470,7 @@ class CopyRepresentation(bpy.types.Operator, Operator):
if not element:
continue
bm.to_mesh(obj.data)
old_rep = self.get_representation_by_context(element, geometric_context)
old_rep = tool.Geometry.get_representation_by_context(element, geometric_context)
if old_rep:
ifcopenshell.api.run(
"geometry.unassign_representation", tool.Ifc.get(), product=element, representation=old_rep
@@ -394,16 +487,6 @@ class CopyRepresentation(bpy.types.Operator, Operator):
profile_set_usage=None,
)
def get_representation_by_context(self, element, context):
if element.is_a("IfcProduct") and element.Representation:
for r in element.Representation.Representations:
if r.ContextOfItems == context:
return r
elif element.is_a("IfcTypeProduct") and element.RepresentationMaps:
for r in element.RepresentationMaps:
if r.MappedRepresentation.ContextOfItems == context:
return r.MappedRepresentation
class OverrideDelete(bpy.types.Operator):
bl_idname = "bim.override_object_delete"
@@ -452,6 +535,8 @@ class OverrideDelete(bpy.types.Operator):
def _execute(self, context):
if self.is_batch:
ifcopenshell.util.element.batch_remove_deep2(tool.Ifc.get())
self.process_arrays(context)
for obj in context.selected_objects:
element = tool.Ifc.get_entity(obj)
if element:
@@ -461,6 +546,7 @@ class OverrideDelete(bpy.types.Operator):
tool.Geometry.delete_ifc_object(obj)
else:
bpy.data.objects.remove(obj)
if self.is_batch:
old_file = tool.Ifc.get()
old_file.end_transaction()
@@ -481,6 +567,30 @@ class OverrideDelete(bpy.types.Operator):
data["old_file"].redo()
tool.Ifc.set(data["new_file"])
def process_arrays(self, context):
selected_objects = set(context.selected_objects)
array_parents = set()
for obj in context.selected_objects:
element = tool.Ifc.get_entity(obj)
if not element:
continue
pset = ifcopenshell.util.element.get_pset(element, "BBIM_Array")
if not pset:
continue
array_parents.add(tool.Ifc.get().by_guid(pset["Parent"]))
for array_parent in array_parents:
array_parent_obj = tool.Ifc.get_object(array_parent)
data = [(i, data) for i, data in enumerate(tool.Blender.Modifier.Array.get_modifiers_data(array_parent))]
# NOTE: there is a way to remove arrays more precisely but it's more complex
for i, modifier_data in reversed(data):
children = set(tool.Blender.Modifier.Array.get_children_objects(modifier_data))
if children.issubset(selected_objects):
with context.temp_override(active_object=array_parent_obj):
bpy.ops.bim.remove_array(item=i)
else:
break # allows to remove only n last layers of an array
class OverrideOutlinerDelete(bpy.types.Operator):
bl_idname = "bim.override_outliner_delete"
@@ -491,7 +601,7 @@ class OverrideOutlinerDelete(bpy.types.Operator):
@classmethod
def poll(cls, context):
return len(context.selected_ids) > 0
return len(getattr(context, "selected_ids", [])) > 0
def execute(self, context):
# In this override, we don't check self.hierarchy. This effectively
@@ -606,6 +716,13 @@ class OverrideDuplicateMove(bpy.types.Operator):
return len(context.selected_objects) > 0
def execute(self, context):
return OverrideDuplicateMove.execute_duplicate_operator(self, context, linked=False)
def _execute(self, context):
return OverrideDuplicateMove.execute_ifc_duplicate_operator(self, context)
@staticmethod
def execute_duplicate_operator(self, context, linked=False):
# Deep magick from the dawn of time
if IfcStore.get_file():
IfcStore.execute_ifc_operator(self, context)
@@ -616,7 +733,7 @@ class OverrideDuplicateMove(bpy.types.Operator):
new_active_obj = None
for obj in context.selected_objects:
new_obj = obj.copy()
if obj.data:
if linked and obj.data:
new_obj.data = obj.data.copy()
if obj == context.active_object:
new_active_obj = new_obj
@@ -628,39 +745,111 @@ class OverrideDuplicateMove(bpy.types.Operator):
context.view_layer.objects.active = new_active_obj
return {"FINISHED"}
def _execute(self, context):
@staticmethod
def execute_ifc_duplicate_operator(self, context, linked=False):
objects_to_duplicate = set(context.selected_objects)
# handle arrays
arrays_to_duplicate, array_children = OverrideDuplicateMove.process_arrays(self, context)
objects_to_duplicate -= array_children
for child in array_children:
child.select_set(False)
self.new_active_obj = None
# Track decompositions so they can be recreated after the operation
relationships = tool.Root.get_decomposition_relationships(context.selected_objects)
relationships = tool.Root.get_decomposition_relationships(objects_to_duplicate)
old_to_new = {}
for obj in context.selected_objects:
for obj in objects_to_duplicate:
element = tool.Ifc.get_entity(obj)
if element and element.is_a("IfcAnnotation") and element.ObjectType == "DRAWING":
continue # For now, don't copy drawings until we stabilise a bit more. It's tricky.
linked_non_ifc_object = linked and not element
# Prior to duplicating, sync the object placement to make decomposition recreation more stable.
if tool.Ifc.is_moved(obj):
blenderbim.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj)
new_obj = obj.copy()
if obj.data:
new_obj.data = obj.data.copy()
temp_data = None
if obj.data and not linked_non_ifc_object:
# assure root.copy_class won't replace the previous mesh globally
temp_data = obj.data.copy()
new_obj.data = temp_data
if obj == context.active_object:
self.new_active_obj = new_obj
for collection in obj.users_collection:
collection.objects.link(new_obj)
obj.select_set(False)
new_obj.select_set(True)
if linked_non_ifc_object:
continue
# clear object's collection so it will be able to have it's own
new_obj.BIMObjectProperties.collection = None
# Copy the actual class
# copy the actual class
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:
tool.Blender.remove_data_block(temp_data)
if new:
array_pset = ifcopenshell.util.element.get_pset(new, "BBIM_Array")
if array_pset:
array_pset = tool.Ifc.get().by_id(array_pset["id"])
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), product=new, pset=array_pset)
old_to_new[tool.Ifc.get_entity(obj)] = [new]
# TODO: handle array data for other cases of duplication
array_data = arrays_to_duplicate.get(obj, None)
tool.Model.handle_array_on_copied_element(new, array_data)
if array_data:
for child in tool.Blender.Modifier.Array.get_all_children_objects(new):
child.select_set(True)
# TODO: add new array children to recreate their decomposition too
old_to_new[element] = [new]
if new.is_a("IfcRelSpaceBoundary"):
tool.Boundary.decorate_boundary(new_obj)
# Recreate decompositions
tool.Root.recreate_decompositions(relationships, old_to_new)
blenderbim.bim.handler.purge_module_data()
blenderbim.bim.handler.refresh_ui_data()
@staticmethod
def process_arrays(self, context):
selected_objects = set(context.selected_objects)
array_parents = set()
arrays_to_create = dict()
array_children = set() # will be ignored during the duplication
for obj in context.selected_objects:
element = tool.Ifc.get_entity(obj)
if not element:
continue
pset = ifcopenshell.util.element.get_pset(element, "BBIM_Array")
if not pset:
continue
array_parents.add(tool.Ifc.get().by_guid(pset["Parent"]))
for array_parent in array_parents:
array_parent_obj = tool.Ifc.get_object(array_parent)
if array_parent_obj not in selected_objects:
continue
array_data = []
for modifier_data in tool.Blender.Modifier.Array.get_modifiers_data(array_parent):
children = set(tool.Blender.Modifier.Array.get_children_objects(modifier_data))
if children.issubset(selected_objects):
modifier_data["children"] = []
array_data.append(modifier_data)
array_children.update(children)
else:
break # allows to duplicate only n first layers of an array
if array_data:
arrays_to_create[array_parent_obj] = array_data
return arrays_to_create, array_children
class OverrideDuplicateMoveLinkedMacro(bpy.types.Macro):
@@ -679,53 +868,10 @@ class OverrideDuplicateMoveLinked(bpy.types.Operator):
return len(context.selected_objects) > 0
def execute(self, context):
# Deep magick from the dawn of time
if IfcStore.get_file():
IfcStore.execute_ifc_operator(self, context)
if self.new_active_obj:
context.view_layer.objects.active = self.new_active_obj
return {"FINISHED"}
new_active_obj = None
for obj in context.selected_objects:
new_obj = obj.copy()
if obj == context.active_object:
new_active_obj = new_obj
for collection in obj.users_collection:
collection.objects.link(new_obj)
obj.select_set(False)
new_obj.select_set(True)
if new_active_obj:
context.view_layer.objects.active = new_active_obj
return {"FINISHED"}
return OverrideDuplicateMove.execute_duplicate_operator(self, context, linked=True)
def _execute(self, context):
self.new_active_obj = None
# Track decompositions so they can be recreated after the operation
relationships = tool.Root.get_decomposition_relationships(context.selected_objects)
old_to_new = {}
for obj in context.selected_objects:
new_obj = obj.copy()
if obj.data:
new_obj.data = obj.data.copy()
if obj == context.active_object:
self.new_active_obj = new_obj
for collection in obj.users_collection:
collection.objects.link(new_obj)
obj.select_set(False)
new_obj.select_set(True)
# Copy the actual class
new = blenderbim.core.root.copy_class(tool.Ifc, tool.Collector, tool.Geometry, tool.Root, obj=new_obj)
if new:
array_pset = ifcopenshell.util.element.get_pset(new, "BBIM_Array")
if array_pset:
array_pset = tool.Ifc.get().by_id(array_pset["id"])
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), product=new, pset=array_pset)
old_to_new[tool.Ifc.get_entity(obj)] = new
# Recreate decompositions
tool.Root.recreate_decompositions(relationships, old_to_new)
blenderbim.bim.handler.purge_module_data()
return {"FINISHED"}
return OverrideDuplicateMove.execute_ifc_duplicate_operator(self, context, linked=True)
class OverrideDuplicateMoveAggregateMacro(bpy.types.Macro):
@@ -745,28 +891,7 @@ class OverrideDuplicateMoveAggregate(bpy.types.Operator):
return len(context.selected_objects) > 0
def execute(self, context):
# Deep magick from the dawn of time
if IfcStore.get_file():
IfcStore.execute_ifc_operator(self, context)
if self.new_active_obj:
context.view_layer.objects.active = self.new_active_obj
return {"FINISHED"}
new_active_obj = None
for obj in context.selected_objects:
new_obj = obj.copy()
if obj.data:
new_obj.data = obj.data.copy()
if obj == context.active_object:
new_active_obj = new_obj
for collection in obj.users_collection:
collection.objects.link(new_obj)
obj.select_set(False)
new_obj.select_set(True)
if new_active_obj:
context.view_layer.objects.active = new_active_obj
return {"FINISHED"}
return OverrideDuplicateMove.execute_duplicate_operator(self, context, linked=False)
def _execute(self, context):
self.new_active_obj = None
@@ -964,12 +1089,7 @@ class OverrideDuplicateMoveAggregate(bpy.types.Operator):
)
if new_entity:
# Checks if the object belongs to an Ifc Array
array_pset = ifcopenshell.util.element.get_pset(new_entity, "BBIM_Array")
if array_pset:
array_pset = tool.Ifc.get().by_id(array_pset["id"])
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), product=new_entity, pset=array_pset)
tool.Model.handle_array_on_copied_element(new_entity)
blenderbim.core.aggregate.unassign_object(
tool.Ifc,
tool.Aggregate,
@@ -1004,6 +1124,17 @@ class OverrideDuplicateMoveAggregate(bpy.types.Operator):
recreate_data_structure(new_root_entity)
# Remove connections with old objects
for new in old_to_new.values():
for connection in new[0].ConnectedTo:
entity = connection.RelatedElement
if entity in old_to_new.keys():
core.remove_connection(tool.Geometry, connection=connection)
for connection in new[0].ConnectedFrom:
entity = connection.RelatingElement
if entity in old_to_new.keys():
core.remove_connection(tool.Geometry, connection=connection)
old_objs = []
for old, new in old_to_new.items():
old_objs.append(tool.Ifc.get_object(old))
@@ -1012,7 +1143,7 @@ class OverrideDuplicateMoveAggregate(bpy.types.Operator):
tool.Root.recreate_decompositions(relationships, old_to_new)
blenderbim.bim.handler.purge_module_data()
blenderbim.bim.handler.refresh_ui_data()
return {"FINISHED"}
@@ -1035,6 +1166,7 @@ class RefreshAggregate(bpy.types.Operator):
return {"FINISHED"}
def _execute(self, context):
refresh_start_time = time()
self.new_active_obj = None
old_to_new = {}
@@ -1067,9 +1199,64 @@ class RefreshAggregate(bpy.types.Operator):
else:
parts = ifcopenshell.util.element.get_parts(instance_entity)
for part in parts:
pset = ifcopenshell.util.element.get_pset(part, "BBIM_Aggregate_Data")
if part.is_a("IfcElementAssembly"):
if not pset:
data_children = {
"children": [],
"instance_of": [part.GlobalId],
}
data = [data_children]
pset = ifcopenshell.api.run(
"pset.add_pset", tool.Ifc.get(), product=part, name="BBIM_Aggregate_Data"
)
ifcopenshell.api.run(
"pset.edit_pset",
tool.Ifc.get(),
pset=pset,
properties={"Parent": instance_entity.GlobalId, "Data": json.dumps(data)},
)
part_obj = tool.Ifc.get_object(part)
new_part = duplicate_objects(part_obj)
blenderbim.core.aggregate.assign_object(
tool.Ifc,
tool.Aggregate,
tool.Collector,
relating_obj=tool.Ifc.get_object(entity),
related_obj=tool.Ifc.get_object(new_part),
)
duplicate_children(new_part)
for part in parts:
pset = ifcopenshell.util.element.get_pset(part, "BBIM_Aggregate_Data")
if part.is_a("IfcElementAssembly"):
pass
else:
if not pset:
pset = ifcopenshell.api.run(
"pset.add_pset", tool.Ifc.get(), product=part, name="BBIM_Aggregate_Data"
)
else:
pset = ifcopenshell.util.element.get_pset(part, "BBIM_Aggregate_Data")
pset = tool.Ifc.get().by_id(pset["id"])
data_children = {
"children": [],
"instance_of": [],
}
data = [data_children]
ifcopenshell.api.run(
"pset.edit_pset",
tool.Ifc.get(),
pset=pset,
properties={"Parent": instance_entity.GlobalId, "Data": json.dumps(data)},
)
part_obj = tool.Ifc.get_object(part)
new_part = duplicate_objects(part_obj)
blenderbim.core.aggregate.assign_object(
@@ -1091,47 +1278,58 @@ class RefreshAggregate(bpy.types.Operator):
obj.select_set(False)
new_obj.select_set(True)
# This is needed to make sure the new object gets unlink from
# the old object assembly collection
new_obj.BIMObjectProperties.collection = None
# Copy the actual class
new_entity = blenderbim.core.root.copy_class(
tool.Ifc, tool.Collector, tool.Geometry, tool.Root, obj=new_obj
)
if new_entity:
# Checks if the object belongs to an Ifc Array
array_pset = ifcopenshell.util.element.get_pset(new_entity, "BBIM_Array")
if array_pset:
array_pset = tool.Ifc.get().by_id(array_pset["id"])
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), product=new_entity, pset=array_pset)
tool.Model.handle_array_on_copied_element(new_entity)
blenderbim.core.aggregate.unassign_object(
tool.Ifc,
tool.Aggregate,
tool.Collector,
relating_obj=obj,
related_obj=tool.Ifc.get_object(new_entity),
)
if not new_entity.is_a("IfcElementAssembly"):
blenderbim.core.aggregate.unassign_object(
tool.Ifc,
tool.Aggregate,
tool.Collector,
relating_obj=obj,
related_obj=tool.Ifc.get_object(new_entity),
)
old_to_new[tool.Ifc.get_entity(obj)] = [new_entity]
return new_entity
if len(context.selected_objects) != 1:
self.report({"INFO"}, "Only 1 object need to be selected.")
return {"FINISHED"}
selected_root_obj = context.selected_objects[0]
selected_root_entity = tool.Ifc.get_entity(selected_root_obj)
if not selected_root_entity.is_a("IfcElementAssembly"):
if selected_root_entity.is_a("IfcElementAssembly"):
pass
elif selected_root_entity.Decomposes:
if selected_root_entity.Decomposes[0].RelatingObject.is_a("IfcElementAssembly"):
selected_root_entity = selected_root_entity.Decomposes[0].RelatingObject
selected_root_obj = tool.Ifc.get_object(selected_root_entity)
else:
self.report({"INFO"}, "Object is not part of a IfcElementAssembly.")
return {"FINISHED"}
pset = ifcopenshell.util.element.get_pset(selected_root_entity, "BBIM_Aggregate_Data")
if not pset:
self.report({"INFO"}, "Object is not part of an assembly aggregate.")
return {"FINISHED"}
pset_data = json.loads(pset["Data"])[0]
instance_of = pset_data["instance_of"][0]
original_root_entity = tool.Ifc.get().by_guid(instance_of)
if original_root_entity == selected_root_entity:
self.report({"INFO"}, "Cannot refresh original assembly. Select an assembly instance.")
return {"FINISHED"}
parents = remove_objects(selected_root_entity)
@@ -1144,13 +1342,24 @@ class RefreshAggregate(bpy.types.Operator):
for parent in parents:
duplicate_children(parent)
# Remove connections with old objects
for new in old_to_new.values():
for connection in new[0].ConnectedTo:
entity = connection.RelatedElement
if entity in old_to_new.keys():
core.remove_connection(tool.Geometry, connection=connection)
for connection in new[0].ConnectedFrom:
entity = connection.RelatingElement
if entity in old_to_new.keys():
core.remove_connection(tool.Geometry, connection=connection)
old_objs = []
for old, new in old_to_new.items():
old_objs.append(tool.Ifc.get_object(old))
new_obj = tool.Ifc.get_object(new[0])
matrix_diff = new_obj.matrix_world @ original_matrix
matrix_diff = Matrix.inverted(original_matrix) @ new_obj.matrix_world
new_matrix = selected_matrix @ matrix_diff
new_obj.matrix_world = new_matrix
@@ -1159,8 +1368,11 @@ class RefreshAggregate(bpy.types.Operator):
tool.Root.recreate_decompositions(relationships, old_to_new)
blenderbim.bim.handler.purge_module_data()
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"}
@@ -1317,8 +1529,13 @@ class OverrideModeSetEdit(bpy.types.Operator):
continue
is_profile = True
representation_class = tool.Geometry.get_ifc_representation_class(element, representation)
if usage_type == "PROFILE":
operator = lambda: bpy.ops.bim.hotkey(hotkey="A_E")
elif representation_class and "IfcCircleProfileDef" in representation_class:
self.report({"INFO"}, "Can't edit Circle Profile Extrusion")
obj.select_set(False)
continue
elif (
tool.Geometry.is_profile_based(obj.data)
or usage_type == "LAYER3"
@@ -1347,6 +1564,7 @@ class OverrideModeSetEdit(bpy.types.Operator):
continue
if tool.Geometry.is_meshlike(representation):
tool.Ifc.edit(obj)
if getattr(element, "HasOpenings", None):
# Mesh elements with openings must disable openings
# so that you can edit the original topology.
@@ -1365,8 +1583,9 @@ class OverrideModeSetEdit(bpy.types.Operator):
else:
obj.select_set(False)
continue
if not context.selected_objects or len(context.selected_objects) != len(selected_objs):
if len(selected_objs) > 1 and (
not context.selected_objects or len(context.selected_objects) != len(selected_objs)
):
# We are trying to edit at least one non-mesh-like object : Display a hint to the user
self.report({"INFO"}, "Only mesh-compatible representations may be edited concurrently in edit mode.")
@@ -1408,9 +1627,11 @@ class OverrideModeSetObject(bpy.types.Operator):
self.reload_representation(obj)
else:
self.reload_representation(obj)
tool.Ifc.finish_edit(obj)
for obj in self.unchanged_objs_with_openings:
self.reload_representation(obj)
tool.Ifc.finish_edit(obj)
return {"FINISHED"}
def reload_representation(self, obj):
@@ -1476,6 +1697,9 @@ class OverrideModeSetObject(bpy.types.Operator):
bpy.ops.bim.finish_editing_roof_path()
elif tool.Model.get_usage_type(element) == "PROFILE":
bpy.ops.bim.edit_extrusion_axis()
# if in the process of editing arbitrary profile
elif context.scene.BIMProfileProperties.active_arbitrary_profile_id:
bpy.ops.bim.edit_arbitrary_profile()
else:
bpy.ops.bim.edit_extrusion_profile()
return self.execute(context)
@@ -1490,7 +1714,76 @@ class OverrideModeSetObject(bpy.types.Operator):
self.edited_objs.append(obj)
elif getattr(element, "HasOpenings", None):
self.unchanged_objs_with_openings.append(obj)
else:
tool.Ifc.finish_edit(obj)
if self.edited_objs:
return context.window_manager.invoke_props_dialog(self)
return self.execute(context)
class FlipObject(bpy.types.Operator):
bl_idname = "bim.flip_object"
bl_label = "Flip Object"
bl_description = "Flip object's local axes, keep the position"
bl_options = {"REGISTER", "UNDO"}
flip_local_axes: bpy.props.EnumProperty(
name="Flip Local Axes", items=(("XY", "XY", ""), ("YZ", "YZ", ""), ("XZ", "XZ", "")), default="XY"
)
def execute(self, context):
for obj in context.selected_objects:
tool.Geometry.flip_object(obj, self.flip_local_axes)
return {"FINISHED"}
class EnableEditingRepresentationItems(bpy.types.Operator, Operator):
bl_idname = "bim.enable_editing_representation_items"
bl_label = "Enable Editing Representation Items"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
obj = context.active_object
props = obj.BIMGeometryProperties
props.is_editing = True
props.items.clear()
if bpy.context.active_object.data and hasattr(bpy.context.active_object.data, "BIMMeshProperties"):
active_representation_id = bpy.context.active_object.data.BIMMeshProperties.ifc_definition_id
element = tool.Ifc.get().by_id(active_representation_id)
if not element.is_a("IfcShapeRepresentation"):
return
queue = list(element.Items)
while queue:
item = queue.pop()
if item.is_a("IfcMappedItem"):
queue.extend(item.MappingSource.MappedRepresentation.Items)
else:
new = props.items.add()
new.name = item.is_a()
new.ifc_definition_id = item.id()
styles = []
for inverse in tool.Ifc.get().get_inverse(item):
if inverse.is_a("IfcStyledItem"):
styles = inverse.Styles
if styles and styles[0].is_a("IfcPresentationStyleAssignment"):
styles = styles[0].Styles
for style in styles:
if style.is_a("IfcSurfaceStyle"):
new.surface_style = style.Name or "Unnamed"
elif inverse.is_a("IfcPresentationLayerAssignment"):
new.layer = inverse.Name or "Unnamed"
class DisableEditingRepresentationItems(bpy.types.Operator, Operator):
bl_idname = "bim.disable_editing_representation_items"
bl_label = "Disable Editing Representation Items"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
obj = context.active_object
obj.BIMGeometryProperties.is_editing = False
@@ -38,8 +38,18 @@ def get_contexts(self, context):
return RepresentationsData.data["contexts"]
class RepresentationItem(PropertyGroup):
name: StringProperty(name="Name")
surface_style: StringProperty(name="Surface Style")
layer: StringProperty(name="Layer")
ifc_definition_id: IntProperty(name="IFC Definition ID")
class BIMObjectGeometryProperties(PropertyGroup):
contexts: EnumProperty(items=get_contexts, name="Contexts")
is_editing: BoolProperty(name="Is Editing", default=False)
items: CollectionProperty(name="Representation Items", type=RepresentationItem)
active_item_index: IntProperty(name="Active Representation Item Index")
class BIMGeometryProperties(PropertyGroup):
@@ -18,10 +18,16 @@
import bpy
import blenderbim.tool as tool
from bpy.types import Panel
from bpy.types import Panel, Menu, UIList
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.helper import prop_with_search
from blenderbim.bim.module.geometry.data import RepresentationsData, ConnectionsData, DerivedPlacementsData
from blenderbim.bim.module.geometry.data import (
RepresentationsData,
RepresentationItemsData,
ConnectionsData,
PlacementData,
DerivedCoordinatesData,
)
def object_menu(self, context):
@@ -29,6 +35,44 @@ def object_menu(self, context):
self.layout.operator("bim.override_object_duplicate_move", icon="PLUGIN")
self.layout.operator("bim.override_object_delete", icon="PLUGIN")
self.layout.operator("bim.override_paste_buffer", icon="PLUGIN")
self.layout.menu("BIM_MT_object_set_origin", icon="PLUGIN")
def edit_mesh_menu(self, context):
self.layout.separator()
self.layout.menu("BIM_MT_separate", icon="PLUGIN")
class BIM_MT_separate(Menu):
bl_idname = "BIM_MT_separate"
bl_label = "IFC Separate"
def draw(self, context):
self.layout.operator("bim.override_mesh_separate", icon="PLUGIN", text="IFC Selection").type = "SELECTED"
self.layout.operator("bim.override_mesh_separate", icon="PLUGIN", text="IFC By Material").type = "MATERIAL"
self.layout.operator("bim.override_mesh_separate", icon="PLUGIN", text="IFC By Loose Parts").type = "LOOSE"
class BIM_MT_object_set_origin(Menu):
bl_idname = "BIM_MT_object_set_origin"
bl_label = "IFC Set Origin"
def draw(self, context):
self.layout.operator(
"bim.override_origin_set", icon="PLUGIN", text="IFC Geometry to Origin"
).origin_type = "GEOMETRY_ORIGIN"
self.layout.operator(
"bim.override_origin_set", icon="PLUGIN", text="IFC Origin to Geometry"
).origin_type = "ORIGIN_GEOMETRY"
self.layout.operator(
"bim.override_origin_set", icon="PLUGIN", text="IFC Origin to 3D Cursor"
).origin_type = "ORIGIN_CURSOR"
self.layout.operator(
"bim.override_origin_set", icon="PLUGIN", text="IFC Origin to Center of Mass (Surface)"
).origin_type = "ORIGIN_CENTER_OF_MASS"
self.layout.operator(
"bim.override_origin_set", icon="PLUGIN", text="IFC Origin to Center of Mass (Volume)"
).origin_type = "ORIGIN_CENTER_OF_VOLUME"
def outliner_menu(self, context):
@@ -86,6 +130,53 @@ class BIM_PT_representations(Panel):
row.operator("bim.remove_representation", icon="X", text="").representation_id = representation["id"]
class BIM_PT_representation_items(Panel):
bl_label = "Representation Items"
bl_idname = "BIM_PT_representation_items"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_order = 1
bl_parent_id = "BIM_PT_tab_representations"
@classmethod
def poll(cls, context):
if not context.active_object:
return False
if not IfcStore.get_element(context.active_object.BIMObjectProperties.ifc_definition_id):
return False
return IfcStore.get_file()
def draw(self, context):
if not RepresentationItemsData.is_loaded:
RepresentationItemsData.load()
props = context.active_object.BIMGeometryProperties
row = self.layout.row(align=True)
row.label(text=f"{RepresentationItemsData.data['total_items']} Items Found")
if props.is_editing:
row.operator("bim.disable_editing_representation_items", icon="CANCEL", text="")
else:
row.operator("bim.enable_editing_representation_items", icon="IMPORT", text="")
return
self.layout.template_list("BIM_UL_representation_items", "", props, "items", props, "active_item_index")
row = self.layout.row()
if RepresentationItemsData.data["active_surface_style"]:
row.label(text=RepresentationItemsData.data["active_surface_style"], icon="MATERIAL")
else:
row.label(text="No Surface Style", icon="MESH_UVSPHERE")
row = self.layout.row()
if RepresentationItemsData.data["active_layer"]:
row.label(text=RepresentationItemsData.data["active_layer"], icon="STICKY_UVS_LOC")
else:
row.label(text="No Presentation Layer", icon="STICKY_UVS_LOC")
class BIM_PT_connections(Panel):
bl_label = "Connections"
bl_idname = "BIM_PT_connections"
@@ -111,7 +202,7 @@ class BIM_PT_connections(Panel):
layout = self.layout
props = context.active_object.BIMObjectProperties
if not ConnectionsData.data["connections"]:
if not ConnectionsData.data["connections"] and not ConnectionsData.data["is_connection_realization"]:
layout.label(text="No connections found")
for connection in ConnectionsData.data["connections"]:
@@ -123,6 +214,44 @@ class BIM_PT_connections(Panel):
op = row.operator("bim.remove_connection", icon="X", text="")
op.connection = connection["id"]
if connection["realizing_elements"]:
row = self.layout.row(align=True)
connection_type = connection["realizing_elements_connection_type"]
connection_type = f" ({connection_type})" if connection_type else ""
row.label(text=f"Realizing elements{connection_type}:")
for element in connection["realizing_elements"]:
row = self.layout.row(align=True)
obj = tool.Ifc.get_object(element)
row.operator("bim.select_entity", text="", icon="RESTRICT_SELECT_OFF").ifc_id = element.id()
row.label(text=obj.name)
# display connections where element is connection realization
connections = ConnectionsData.data["is_connection_realization"]
if not connections:
return
row = self.layout.row(align=True)
row.label(text="Element is connections realization:")
for connection in connections:
# NOTE: not displayed yet
connection_type = connection["realizing_elements_connection_type"]
connection_type = f" ({connection_type})" if connection_type else ""
row = self.layout.row(align=True)
connected_from = connection["connected_from"]
obj = tool.Ifc.get_object(connected_from)
row.operator("bim.select_entity", text="", icon="RESTRICT_SELECT_OFF").ifc_id = connected_from.id()
row.label(text=obj.name)
row.label(text="", icon="FORWARD")
connected_to = connection["connected_to"]
obj = tool.Ifc.get_object(connected_to)
row.operator("bim.select_entity", text="", icon="RESTRICT_SELECT_OFF").ifc_id = connected_to.id()
row.label(text=obj.name)
class BIM_PT_mesh(Panel):
bl_label = "Representation Utilities"
@@ -151,6 +280,9 @@ class BIM_PT_mesh(Panel):
layout = self.layout
row = layout.row()
row.operator("bim.update_representation", text="Manually Save Representation")
row = layout.row()
row.operator("bim.copy_representation", text="Copy Mesh From Active To Selected")
@@ -174,50 +306,99 @@ class BIM_PT_mesh(Panel):
op = row.operator("bim.update_representation", text="Convert To Arbitrary Extrusion With Voids")
op.ifc_representation_class = "IfcExtrudedAreaSolid/IfcArbitraryProfileDefWithVoids"
def BIM_PT_transform(self, context):
if context.active_object and context.active_object.BIMObjectProperties.ifc_definition_id:
row = self.layout.row(align=True)
row.label(text="Blender Offset")
row.label(text=context.active_object.BIMObjectProperties.blender_offset_type)
row = self.layout.row()
row.operator("bim.edit_object_placement")
if context.active_object and context.active_object.data:
mprops = context.active_object.data.BIMMeshProperties
row = layout.row()
row.operator("bim.get_representation_ifc_parameters")
for index, ifc_parameter in enumerate(mprops.ifc_parameters):
row = layout.row(align=True)
row.prop(ifc_parameter, "name", text="")
row.prop(ifc_parameter, "value", text="")
row.operator("bim.update_parametric_representation", icon="FILE_REFRESH", text="").index = index
class BIM_PT_derived_placements(Panel):
bl_label = "Derived Placements"
bl_idname = "BIM_PT_derived_placements"
bl_options = {"DEFAULT_CLOSED"}
class BIM_PT_placement(Panel):
bl_label = "Placement"
bl_idname = "BIM_PT_placement"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
bl_context = "scene"
bl_order = 1
bl_parent_id = "OBJECT_PT_transform"
bl_parent_id = "BIM_PT_tab_placement"
bl_options = {"HIDE_HEADER"}
@classmethod
def poll(cls, context):
return context.active_object and context.active_object.BIMObjectProperties.ifc_definition_id
def draw(self, context):
if not DerivedPlacementsData.is_loaded:
DerivedPlacementsData.load()
if not PlacementData.is_loaded:
PlacementData.load()
if PlacementData.data["has_placement"]:
row = self.layout.row()
row.prop(context.active_object, "location", text="Location")
row = self.layout.row()
row.prop(context.active_object, "rotation_euler", text="Rotation")
else:
row = self.layout.row()
row.label(text="No Object Placement Found")
if context.active_object.BIMObjectProperties.blender_offset_type != "NONE":
row = self.layout.row(align=True)
row.label(text="Blender Offset", icon="TRACKING_REFINE_FORWARDS")
row.label(text=context.active_object.BIMObjectProperties.blender_offset_type)
class BIM_PT_derived_coordinates(Panel):
bl_label = "Derived Coordinates"
bl_idname = "BIM_PT_derived_coordinates"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_order = 3
bl_parent_id = "BIM_PT_tab_placement"
bl_options = {"DEFAULT_CLOSED"}
@classmethod
def poll(cls, context):
return context.active_object is not None
def draw(self, context):
if not DerivedCoordinatesData.is_loaded:
DerivedCoordinatesData.load()
row = self.layout.row()
row.operator("bim.edit_object_placement", icon="EXPORT")
row = self.layout.row()
row.label(text="XYZ Dimensions")
row = self.layout.row(align=True)
row.enabled = False
row.prop(context.active_object, "dimensions", text="X", index=0, slider=True)
row.prop(context.active_object, "dimensions", text="Y", index=1, slider=True)
row.prop(context.active_object, "dimensions", text="Z", index=2, slider=True)
row = self.layout.row(align=True)
row.label(text="Min Global Z")
row.label(text=DerivedPlacementsData.data["min_global_z"])
row.label(text=DerivedCoordinatesData.data["min_global_z"])
row = self.layout.row(align=True)
row.label(text="Max Global Z")
row.label(text=DerivedPlacementsData.data["max_global_z"])
row.label(text=DerivedCoordinatesData.data["max_global_z"])
if DerivedPlacementsData.data["has_collection"]:
if DerivedCoordinatesData.data["has_collection"]:
row = self.layout.row(align=True)
row.label(text="Min Decomposed Z")
row.label(text=DerivedPlacementsData.data["min_decomposed_z"])
row.label(text=DerivedCoordinatesData.data["min_decomposed_z"])
row = self.layout.row(align=True)
row.label(text="Max Decomposed Z")
row.label(text=DerivedPlacementsData.data["max_decomposed_z"])
row.label(text=DerivedCoordinatesData.data["max_decomposed_z"])
if DerivedPlacementsData.data["is_storey"]:
if DerivedCoordinatesData.data["is_storey"]:
row = self.layout.row(align=True)
row.label(text="Storey Height")
row.label(text=DerivedPlacementsData.data["storey_height"])
row.label(text=DerivedCoordinatesData.data["storey_height"])
class BIM_PT_workarounds(Panel):
@@ -246,3 +427,13 @@ class BIM_PT_workarounds(Panel):
row.prop(props, "should_force_triangulation")
row = self.layout.row()
row.prop(props, "should_use_presentation_style_assignment")
class BIM_UL_representation_items(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
if item:
icon = "MATERIAL" if item.surface_style else "MESH_UVSPHERE"
row = layout.row(align=True)
row.label(text=item.name, icon=icon)
if item.layer:
row.label(text="", icon="STICKY_UVS_LOC")
@@ -34,6 +34,7 @@ classes = (
operator.GetCursorLocation,
operator.SetCursorLocation,
operator.ConvertAngleToCoordinates,
operator.ImportPlot,
prop.BIMGeoreferenceProperties,
ui.BIM_PT_gis,
ui.BIM_PT_gis_utilities,
@@ -56,7 +56,12 @@ class GeoreferenceData:
@classmethod
def map_conversion(cls):
if tool.Ifc.get_schema() == "IFC2X3":
return {}
project = tool.Ifc.get().by_type("IfcProject")[0]
map_conversion = ifcopenshell.util.element.get_pset(project, "ePSet_MapConversion")
if not map_conversion:
return {}
del map_conversion["id"]
return map_conversion
for context in tool.Ifc.get().by_type("IfcGeometricRepresentationContext", include_subtypes=False):
if context.HasCoordinateOperation:
@@ -72,8 +77,8 @@ class GeoreferenceData:
def map_derived_angle(cls):
if (
cls.data["map_conversion"]
and cls.data["map_conversion"]["XAxisAbscissa"] is not None
and cls.data["map_conversion"]["XAxisOrdinate"] is not None
and cls.data["map_conversion"].get("XAxisAbscissa", None) is not None
and cls.data["map_conversion"].get("XAxisOrdinate", None) is not None
):
return str(
round(
@@ -88,7 +93,12 @@ class GeoreferenceData:
@classmethod
def projected_crs(cls):
if tool.Ifc.get_schema() == "IFC2X3":
return {}
project = tool.Ifc.get().by_type("IfcProject")[0]
projected_crs = ifcopenshell.util.element.get_pset(project, "ePSet_ProjectedCRS")
if not projected_crs:
return {}
del projected_crs["id"]
return projected_crs
for context in tool.Ifc.get().by_type("IfcGeometricRepresentationContext", include_subtypes=False):
if context.HasCoordinateOperation:
@@ -76,7 +76,7 @@ class SetIfcGridNorth(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.set_ifc_grid_north"
bl_label = "Set IFC Grid North"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Set IFC grid north"
bl_description = "Set IFC grid north based on current Blender North Offset"
def _execute(self, context):
core.set_ifc_grid_north(tool.Georeference)
@@ -86,7 +86,7 @@ class SetBlenderGridNorth(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.set_blender_grid_north"
bl_label = "Set Blender Grid North"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Set Blender grid north"
bl_description = "Set Blender North Offset based on current IFC grid north"
def _execute(self, context):
core.set_blender_grid_north(tool.Georeference)
@@ -110,7 +110,7 @@ class SetCursorLocation(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.set_cursor_location"
bl_label = "Set Cursor Location"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Move curson location to the specified coordinates"
bl_description = "Move cursor location to the specified coordinates"
@classmethod
def poll(cls, context):
@@ -125,7 +125,7 @@ class SetIfcTrueNorth(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.set_ifc_true_north"
bl_label = "Set IFC True North"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Set IFC True north"
bl_description = "Set IFC true north based on current Blender North Offset"
def _execute(self, context):
core.set_ifc_true_north(tool.Georeference)
@@ -135,7 +135,7 @@ class SetBlenderTrueNorth(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.set_blender_true_north"
bl_label = "Set Blender True North"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Set Blender true north"
bl_description = "Set Blender North Offset based on current IFC true north"
def _execute(self, context):
core.set_blender_true_north(tool.Georeference)
@@ -187,3 +187,20 @@ class ConvertAngleToCoordinates(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
core.convert_angle_to_coord(tool.Georeference, type=self.type)
class ImportPlot(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.import_plot"
bl_label = "Import Plot"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Import plot"
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
filter_glob: bpy.props.StringProperty(default="*.csv", options={"HIDDEN"})
def execute(self, context):
core.import_plot(tool.Georeference, filepath=self.filepath)
return {"FINISHED"}
def invoke(self, context, event):
context.window_manager.fileselect_add(self)
return {"RUNNING_MODAL"}
@@ -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
@@ -78,10 +78,14 @@ class BIM_PT_gis(Panel):
def draw_ui(self, context):
props = context.scene.BIMGeoreferenceProperties
if tool.Ifc.get_schema() == "IFC2X3":
row = self.layout.row()
row.label(text="IFC2X3 Fallback In Use", icon="ERROR")
if not GeoreferenceData.data["projected_crs"]:
row = self.layout.row(align=True)
row.label(text="Not Georeferenced")
if tool.Ifc.get_schema != "IFC2X3":
if tool.Ifc.get_schema() != "IFC2X3":
row.operator("bim.add_georeferencing", icon="ADD", text="")
if props.has_blender_offset:
@@ -110,8 +114,9 @@ class BIM_PT_gis(Panel):
if GeoreferenceData.data["projected_crs"]:
row = self.layout.row(align=True)
row.label(text="Projected CRS", icon="WORLD")
row.operator("bim.enable_editing_georeferencing", icon="GREASEPENCIL", text="")
row.operator("bim.remove_georeferencing", icon="X", text="")
if tool.Ifc.get_schema() != "IFC2X3":
row.operator("bim.enable_editing_georeferencing", icon="GREASEPENCIL", text="")
row.operator("bim.remove_georeferencing", icon="X", text="")
for key, value in GeoreferenceData.data["projected_crs"].items():
if not value:
@@ -184,4 +189,4 @@ class BIM_PT_gis_utilities(Panel):
row = self.layout.row(align=True)
row.prop(props, "y_axis_abscissa_output", text="North Abscissa")
row = self.layout.row(align=True)
row.prop(props, "y_axis_ordinate_output", text="North Ordinate")
row.prop(props, "y_axis_ordinate_output", text="North Ordinate")
@@ -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):
@@ -63,7 +63,7 @@ class EnableEditingLayer(bpy.types.Operator):
def execute(self, context):
props = context.scene.BIMLayerProperties
props.layer_attributes.clear()
blenderbim.bim.helper.import_attributes(tool.Ifc.get().by_id(self.layer), props.layer_attributes)
blenderbim.bim.helper.import_attributes2(tool.Ifc.get().by_id(self.layer), props.layer_attributes)
props.active_layer_id = self.layer
return {"FINISHED"}
@@ -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 = {}
@@ -173,6 +228,8 @@ class ObjectMaterialData:
items = cls.material.MaterialProfiles
elif cls.material.is_a("IfcMaterialConstituentSet"):
items = cls.material.MaterialConstituents
elif cls.material.is_a("IfcMaterialList"):
items = cls.material.Materials
icon = "LAYER_ACTIVE"
if "Layer" in cls.material.is_a():
@@ -191,7 +248,9 @@ class ObjectMaterialData:
data["name"] = "No Profile"
if item.is_a("IfcMaterialLayer"):
data["name"] += f" ({item.LayerThickness})"
if not item.is_a("IfcMaterialList"):
if item.is_a("IfcMaterial"):
data["material"] = item.Name or "Unnamed"
else:
data["material"] = item.Material.Name or "Unnamed"
results.append(data)
return results
@@ -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()
@@ -174,7 +182,7 @@ class AssignMaterial(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
objects = [bpy.data.objects.get(self.obj)] if self.obj else tool.Blender.get_selected_objects()
core.assign_material(tool.Ifc, tool.Material, material_type= self.material_type , objects=objects )
core.assign_material(tool.Ifc, tool.Material, material_type=self.material_type, objects=objects)
class UnassignMaterial(bpy.types.Operator, tool.Ifc.Operator):
@@ -185,7 +193,7 @@ class UnassignMaterial(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
objects = [bpy.data.objects.get(self.obj)] if self.obj else tool.Blender.get_selected_objects()
core.unassign_material(tool.Ifc, tool.Material, objects=objects )
core.unassign_material(tool.Ifc, tool.Material, objects=objects)
class AddConstituent(bpy.types.Operator, tool.Ifc.Operator):
@@ -350,10 +358,8 @@ class AddListItem(bpy.types.Operator, tool.Ifc.Operator):
ifcopenshell.api.run(
"material.add_list_item",
self.file,
**{
"material_list": self.file.by_id(self.list_item_set),
"material": self.file.by_id(int(obj.BIMObjectMaterialProperties.material)),
},
material_list=self.file.by_id(self.list_item_set),
material=self.file.by_id(int(obj.BIMObjectMaterialProperties.material)),
)
@@ -709,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):
@@ -28,11 +28,11 @@ from blenderbim.bim.module.material.data import MaterialsData, ObjectMaterialDat
class BIM_PT_materials(Panel):
bl_label = "Materials"
bl_idname = "BIM_PT_materials"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_geometry"
bl_parent_id = "BIM_PT_tab_materials"
bl_options = {"HIDE_HEADER"}
@classmethod
def poll(cls, context):
@@ -45,11 +45,10 @@ class BIM_PT_materials(Panel):
self.props = context.scene.BIMMaterialProperties
row = self.layout.row(align=True)
row.label(text="{} Materials Found".format(MaterialsData.data["total_materials"]), icon="MATERIAL")
row.label(text="{} Materials".format(MaterialsData.data["total_materials"]), icon="NODE_MATERIAL")
if self.props.is_editing:
row.operator("bim.disable_editing_materials", text="", icon="CANCEL")
else:
row = self.layout.row(align=True)
prop_with_search(row, self.props, "material_type", text="")
row.operator("bim.load_materials", text="", icon="IMPORT")
return
@@ -58,21 +57,19 @@ class BIM_PT_materials(Panel):
row.alignment = "RIGHT"
if self.props.material_type == "IfcMaterial":
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:
row.operator("bim.add_material", text="", icon="ADD")
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
else:
row.operator("bim.add_material", text="", icon="ADD")
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):
@@ -84,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"
@@ -221,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()
@@ -269,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"]
@@ -285,9 +308,11 @@ class BIM_PT_object_material(Panel):
setattr(op, f"{ObjectMaterialData.data['set_item_name']}_index", index)
def draw_read_only_set_ui(self):
row = self.layout.row(align=True)
row.label(text="Name")
row.label(text=ObjectMaterialData.data["set"]["name"])
if ObjectMaterialData.data["material_class"] != "IfcMaterialList":
row = self.layout.row(align=True)
row.label(text="Name")
row.label(text=ObjectMaterialData.data["set"]["name"])
if ObjectMaterialData.data["set"]["description"]:
row = self.layout.row(align=True)
row.label(text="Description")
@@ -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")
@@ -22,6 +22,7 @@ import ifcopenshell
import blenderbim.bim.handler
import blenderbim.tool as tool
import blenderbim.core.misc as core
import blenderbim.core.geometry as core_geometry
from blenderbim.bim.ifc import IfcStore
from mathutils import Vector, Matrix, Euler
@@ -140,7 +141,51 @@ class SplitAlongEdge(bpy.types.Operator, Operator):
return context.selected_objects and tool.Ifc.get()
def _execute(self, context):
core.split_along_edge(tool.Misc, cutter=context.active_object, objs=context.selected_objects)
cutter = context.active_object
objs = [o for o in context.selected_objects if o != cutter]
# Splitting only works on meshes
for obj in objs:
# You cannot split meshes if the representation is mapped.
element = tool.Ifc.get_entity(obj)
if element:
relating_type = tool.Root.get_element_type(element)
if relating_type and tool.Root.does_type_have_representations(relating_type):
bpy.ops.bim.unassign_type(related_object=obj.name)
representation = tool.Geometry.get_active_representation(obj)
core_geometry.switch_representation(
tool.Ifc,
tool.Geometry,
obj=obj,
representation=representation,
should_reload=True,
is_global=True,
should_sync_changes_first=False,
apply_openings=False,
)
if not tool.Geometry.is_meshlike(representation):
bpy.ops.bim.update_representation(obj=obj.name, ifc_representation_class="IfcTessellatedFaceSet")
new_objs = tool.Misc.split_objects_with_cutter(objs, cutter)
for obj in new_objs:
blenderbim.core.root.copy_class(tool.Ifc, tool.Collector, tool.Geometry, tool.Root, obj=obj)
bpy.ops.bim.update_representation(obj=obj.name)
for obj in objs:
bpy.ops.bim.update_representation(obj=obj.name)
representation = tool.Geometry.get_active_representation(obj)
core_geometry.switch_representation(
tool.Ifc,
tool.Geometry,
obj=obj,
representation=representation,
should_reload=True,
is_global=True,
should_sync_changes_first=False,
apply_openings=True,
)
class GetConnectedSystemElements(bpy.types.Operator, Operator):
@@ -205,46 +250,50 @@ class DrawSystemArrows(bpy.types.Operator, Operator):
return context.selected_objects and tool.Ifc.get()
def _execute(self, context):
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
curve = bpy.data.objects.new("System Arrows", bpy.data.curves.new("System Arrows", "CURVE"))
curve.data.dimensions = "3D"
context.scene.collection.objects.link(curve)
sinks = []
sources = []
for obj in bpy.context.selected_objects:
if not obj.BIMObjectProperties.ifc_definition_id:
continue
element = tool.Ifc.get_entity(obj)
sources = []
sinks = []
for rel in getattr(element, "HasPorts", []) or []:
if rel.RelatingPort.FlowDirection == "SOURCE":
sources.append(
self.get_absolute_matrix(
ifcopenshell.util.placement.get_local_placement(rel.RelatingPort.ObjectPlacement)
)
)
elif rel.RelatingPort.FlowDirection == "SINK":
sinks.append(
self.get_absolute_matrix(
ifcopenshell.util.placement.get_local_placement(rel.RelatingPort.ObjectPlacement)
)
)
sources_current = []
sinks_current = []
for port in tool.System.get_ports(element):
local_placement = ifcopenshell.util.placement.get_local_placement(port.ObjectPlacement)
m = self.get_absolute_matrix(local_placement)
if port.FlowDirection == "SOURCE":
sources_current.append(m)
elif port.FlowDirection == "SINK":
sinks_current.append(m)
else:
sources.append(
self.get_absolute_matrix(
ifcopenshell.util.placement.get_local_placement(rel.RelatingPort.ObjectPlacement)
)
)
sinks.append(
self.get_absolute_matrix(
ifcopenshell.util.placement.get_local_placement(rel.RelatingPort.ObjectPlacement)
)
)
for sink in sinks:
for source in sources:
sources_current.append(m)
sinks_current.append(m)
if sinks_current or sources_current:
sinks.append(sinks_current)
sources.append(sources_current)
if not sinks:
self.report({"INFO"}, "No sinks/sources found for selected objects.")
return {"FINISHED"}
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
curve = bpy.data.objects.new("System Arrows", bpy.data.curves.new("System Arrows", "CURVE"))
curve.data.dimensions = "3D"
curve.show_in_front = True
context.scene.collection.objects.link(curve)
for i in range(len(sinks)):
for sink in sinks[i]:
for source in sources[i]:
polyline = curve.data.splines.new("POLY")
polyline.points.add(1)
polyline.points[0].co = (Matrix(sink).translation * unit_scale).to_4d()
polyline.points[1].co = (Matrix(source).translation * unit_scale).to_4d()
tool.Blender.select_and_activate_single_object(context, curve)
def get_absolute_matrix(self, matrix):
props = bpy.context.scene.BIMGeoreferenceProperties

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