Compare commits

...

287 Commits

Author SHA1 Message Date
Dion Moult 9a871f1459 Merging projects can now handle projects with different false origins
Warning: I haven't yet looked at rotations. Yikes. No CRS reprojection either.
2024-06-29 17:05:06 +10:00
Dion Moult 0d401c9272 Fix bug where migrating length units didn't change properties. Unit utility for checking attribute types now considers select types too
Changing properties is very critical for IFC2X3 georeferencing which is stored in a pset.
2024-06-29 17:04:27 +10:00
Dion Moult 2129350ab7 Fix bug where MergeProject didn't clean up duplicate CRS and coordinate operations 2024-06-29 17:03:26 +10:00
Dion Moult 43bc979114 The MergeProject recipe now accepts a file object, not always a filepath 2024-06-29 17:01:58 +10:00
Thomas Krijnen c7232a780a Remove debug print 2024-06-28 16:21:33 +02:00
Thomas Krijnen e96b4c11a2 Implement edge as top-level representation item #4924 2024-06-28 16:19:43 +02:00
Thomas Krijnen fecca6a8b2 What a bad idea to let ellipse inherit from circle" 2024-06-28 16:19:43 +02:00
Andrej730 1fc56585dd Support loading BlenderBIM in Blender 4.2+ as an extension
1) Used a hack to bring `blenderbim` into the main namespace instead of `bl_ext.repository.blenderbim`. This is not an official way to do it but this works for testing stuff (official way is to create a stub wheel that would redirect all `blenderbim` requests to `bl_ext.repository.blenderbim`).
2) AddonPreferences now require blender package name as it's bl_idname (e.g. bl_ext.repository.blenderbim)
3) Addressing preferences now also works through the blender package name. Created method in tool/Blender.py to incapsulate those details.
2024-06-28 18:46:08 +05:00
Andrej730 d1be0e8c1c create_shape - raise the same error for representations and rep items
To be consistent with how it's handled when product+representation is passed, previously it would fail silently.
2024-06-28 18:24:58 +05:00
Andrej730 dcc568f5a3 ifcopenshell.geom.create_shape - expose geometry_library argument 2024-06-28 18:24:58 +05:00
Andrej730 2e4a2712e9 ifcopenshell.geom - document available geometry libraries 2024-06-28 18:24:58 +05:00
Andrej730 d4b055aeb2 Open recent IFC project #3657
Example - https://imgur.com/a/EDfBTJS

Paths to the opened IFC projects are now stored and then you can open them from File -> Open Recent IFC Project menu. It's done in very similar way to how Blender does it, list of files is stored in `Blender\4.1\config\recent-ifc-projects.txt`.
2024-06-28 18:24:58 +05:00
Andrej730 0e2cc463c8 Fix issue loading curve representations #4924
Issue occurred in the process of solving #4843.
2024-06-28 18:16:32 +05:00
Andrej730 c3930a6573 black format 2024-06-28 18:16:32 +05:00
Andrej730 3c094b7bd2 typing 2024-06-28 18:16:32 +05:00
Dion Moult f8dffa1a9b Move new project wizard into its own panel and actually title it "New Project Wizard" for clarity 2024-06-28 22:50:02 +10:00
Dion Moult 03b6c08c50 Remove unload project button - superseded by Convert To Blender File operator 2024-06-28 22:50:02 +10:00
Thomas Krijnen 42b96d6290 Further work on trying to get orientation properly mapped 2024-06-28 13:41:01 +02:00
Thomas Krijnen c240f0582a Add TBB_INCLUDE_DIR 2024-06-28 13:40:19 +02:00
Thomas Krijnen 76b7748be1 Note some time points #4915 2024-06-28 13:39:58 +02:00
Thomas Krijnen a2334f4ce0 More 4X3 non-ADD2 permissiveness in param values on polynomials 2024-06-28 13:39:19 +02:00
Thomas Krijnen db50c87bf4 Fixed collections not being geom_items in mapped item handling 2024-06-28 13:38:56 +02:00
Thomas Krijnen dc8766cf0a Formatting improvements 2024-06-28 13:38:36 +02:00
Dion Moult 158f3d2962 Migrating calendar dates from IFC2X3 to IFC4 is now supported 2024-06-28 21:36:45 +10:00
Dion Moult 008fa70345 Fix failing collector tests to align with new behaviour 2024-06-28 21:15:53 +10:00
ay-ex e59cd80868 fix choco release (#4941)
* fix choco action

* fix choco action env vars

* fix choco action env vars

* fix choco action py version source regex
2024-06-28 19:33:58 +10:00
Dion Moult f0d7965d49 Fix #4804. Prevent possible execution of toggle edit mode if there is no active object.
Recent changes in the UI already make this very unlikely - we don't show the ability to toggle edit mode pre-emptively in the viewport tob bar but just in case...
2024-06-28 14:25:56 +10:00
Dion Moult b616cafa49 Minor typing hint and disable linear position elements from loading 2024-06-28 13:01:15 +10:00
Dion Moult 70bb0e5481 Update doc code examples to use static functions 2024-06-28 12:54:14 +10:00
Dion Moult 07060fc769 Replace all ifcopenshell.api.run with ifcopenshell.api static functions. 2024-06-28 12:36:31 +10:00
Dion Moult b5d613989e Merge pull request #4938 from Vanuan/documentation_geometry
[docs] Geometry related documentation
2024-06-28 07:05:46 +10:00
Vanuan f604aa8b6b Geometry related documentation 2024-06-27 23:40:39 +03:00
Andrej730 dc8912c128 -m ifcopenshell.validate not to print an empty line if file is valid 2024-06-27 18:26:49 +05:00
Andrej730 07a0b4fe2d -m ifcopenshell.validate to print a message if file is valid 2024-06-27 18:26:49 +05:00
Andrej730 eb67ffcbec Use ifc4 for enum entities in select attributes validation test #4927 2024-06-27 18:26:49 +05:00
Andrej730 4ec8345716 typing 2024-06-27 18:26:49 +05:00
Andrej730 56d3b2ca36 Use release ifc4x3 version for validation tests 2024-06-27 18:26:49 +05:00
Andrej730 dd06b1a6c4 small addition to 14dbe121f 2024-06-27 18:26:49 +05:00
Dion Moult 3469a9caad See #4727. Load alignments (linear elements, linear positioning elements, and referents) by default. 2024-06-27 21:49:44 +10:00
Thomas Krijnen 599ca56496 Mark piecewise_function as geom_item #4727 2024-06-27 09:33:04 +02:00
Andrej730 14dbe121f6 update serializer settings literals #4912 2024-06-27 10:59:35 +05:00
Dion Moult dee936b2c3 Fix beam Qto length and add more Qto mappings 2024-06-27 14:32:56 +10:00
mat a98cddf676 Add Text Literal re-ordering #3643 2024-06-27 12:41:33 +10:00
Dion Moult 48d058a32a Fix #4921. When upgrading to IFC4X3, preprocess the file to remove deprecated style assignments 2024-06-27 11:49:12 +10:00
Dion Moult 98afb72ba3 Prefer MapConversionScaled in IFC4X3 for SetFalseOrigin 2024-06-27 11:49:12 +10:00
Dion Moult 2d5be6cf89 Search replace 0.7.0 with 0.8.0 2024-06-27 11:49:12 +10:00
Thomas Krijnen 1299a8f512 Mark edge as geom_item 2024-06-26 21:21:47 +02:00
Thomas Krijnen eccbdfa76e Eval eigen auto #4727 2024-06-26 21:21:47 +02:00
Andrej730 24e16d97f3 deprecate non-batching api #4474 2024-06-26 18:11:07 +05:00
Andrej730 13d61f0135 rename sequence.assign_workplan for consistency 2024-06-26 17:17:34 +05:00
Andrej730 c56a4a6c9d Fix console errors from Representations UI if non-product element is selected 2024-06-26 17:17:34 +05:00
Andrej730 832dce462e spatial manager - fix bug with elevation not converted to si
Leading to issues if you would change it in non-meters project.
2024-06-26 17:17:34 +05:00
Andrej730 6f45373edd move switch_shading method from prop to tool 2024-06-26 17:06:30 +05:00
Andrej730 d5613e0e26 typing 2024-06-26 17:06:30 +05:00
Andrej730 d0ec4fe345 core tests - print unprocessed calls in pytest verbose mode
e.g. if you start test with pytest test_style.py -vv it will be easier to guess why it couldn't find a call
```
E               Exception: <class 'abc.Ifc'> was not called with run:
E                - {'type': 'SHOULD_BE_CALLED', 'number': None, 'call': {'name': 'run', 'args': ('style.add_surface_style',), 'kwargs': {'style': 'style', 'ifc_class': 'IfcSurfaceStyleShading', 'attributes': 'attributes'}}}
E               Unprocessed calls:
E                - {'name': 'run', 'args': ('style.add_style',), 'kwargs': {'name': 'name'}}
E                - {'name': 'link', 'args': ('element', 'obj'), 'kwargs': {}}
E                - {'name': 'run', 'args': ('style.add_surface_style',), 'kwargs': {'style': 'element', 'ifc_class': 'IfcSurfaceStyleShading', 'attributes': 'attributes'}}
```
2024-06-26 16:14:00 +05:00
Andrej730 665bf00f8b fix core.style tests #4843 2024-06-26 16:14:00 +05:00
Andrej730 2d2ca64a72 Do not use tool.Ifc.Operator if no ifc data is changed 2024-06-26 16:14:00 +05:00
Andrej730 60cb41a428 remove some props from Material.BIMStyleProperties as deprecated #4843 2024-06-26 16:13:59 +05:00
Andrej730 a69f3e8da9 remove unnecessary linking in IfcImporter.create_style #4843
As it's already linked by `self.link_element(style, blender_material)`
2024-06-26 16:13:59 +05:00
Andrej730 2f81cab5f1 deprecate Material.BIMMaterialProperties #4843
As it now stored just 1 property and it's now moved to the BIMStyleProperties.
2024-06-26 16:13:59 +05:00
Andrej730 78e81542d9 fix couple issues after ce9e4f285 #4843
1) BBIM was failing to unregister properly.
2) Failed to link materials on load.
3) Failed to purge IFC file.
4) Error changing style name
2024-06-26 16:13:59 +05:00
Andrej730 7bbc81b7c3 create_shape - include more types in the docs 2024-06-26 16:13:59 +05:00
ay-ex 6e6b256c22 fix use release tag for url, bump py version to 3.11 2024-06-26 19:37:37 +10:00
ay-ex d23eba9343 fix remove release needed check 2024-06-26 19:37:37 +10:00
ay-ex e899104d56 add simple check for "release needed?" 2024-06-26 19:37:37 +10:00
Dion Moult de00429df6 Make version in bot build download dynamic 2024-06-26 17:14:57 +10:00
Dion Moult 3aba82f608 Ensure all project feature tests pass 2024-06-26 17:14:39 +10:00
Dion Moult cb85b690e7 Bump bot build 2024-06-26 17:09:58 +10:00
Vanuan 527742ca93 Basic IFC concepts 2024-06-26 14:50:32 +10:00
Dion Moult b85dcdb437 Distribute IfcCityJSON on PyPI 2024-06-26 13:53:55 +10:00
Dion Moult f740ca3cf1 Distribute Ifc5D on PyPI 2024-06-26 13:02:59 +10:00
Dion Moult b9269b5a11 Distribute Ifc4D on PyPI 2024-06-26 13:02:18 +10:00
Dion Moult 16e98e1400 Distribute IfcFM on PyPI 2024-06-26 12:10:59 +10:00
Dion Moult 4976074b18 Distribute IfcDiff on PyPI 2024-06-26 12:03:08 +10:00
Dion Moult e670a7ca71 See #4826. If a Qto calculator returns none, don't fail
There can be valid reasons for this, such as if the calculator realises it cannot compute a value (e.g. no mass density when calculating weight)
2024-06-26 12:03:08 +10:00
Dion Moult d892ad956c See #4826. Fix Qto calculator which assumes that openings have a predefined type (in IFC2X3 they don't) 2024-06-26 12:03:08 +10:00
Vanuan ac42ed7bab Blender docs: More stubs, walls, doors, windows 2024-06-26 11:55:02 +10:00
Dion Moult bf677b839d Only get bot build binaries and use latest python to fix latest install issues 2024-06-26 10:24:32 +10:00
Dion Moult 464815b045 Update drawing generator to use v0.8.0 settings syntax 2024-06-26 09:20:46 +10:00
Dion Moult ae91c2b9f0 See #4919. Include pip install instructions for utils in docs. 2024-06-26 09:16:21 +10:00
Thomas Krijnen 39cd980008 Pin freetype version, incompatible change made 5d ago 2024-06-25 21:26:37 +02:00
Andrej730 f364643d7e fix assigning material usages / reloading reprs after c4ae9578e #4843 2024-06-25 18:30:52 +05:00
Andrej730 ef9bbd8de6 deprecate shading_checksum #4843
Changing diffuse color for Blender material now won't change the linked IFC style.

Since now all style parameters for styles are changed through styles UI and users shouldn't use Blender materials tab unless they want to do something advanced (though they are still currently can use it to assign material styles to the specific faces).

PS 1 less thing to sync 🥳
2024-06-25 18:30:52 +05:00
Andrej730 8172096a5b Do not directly access IfcStore.edited_objs 2024-06-25 18:30:52 +05:00
Andrej730 46d3d3afe1 calculate materials checksum in a common method 2024-06-25 18:30:52 +05:00
Andrej730 39cd4187ea Remove unused BIMMaterailProperties props #4843
attributes were deprecated in d219856
is_external and name were deprecated awhile ago in 3fe5c88 😁
2024-06-25 18:30:51 +05:00
Andrej730 d6a3bc3556 remove dead code for loading materials from external blend files
External blender materials are now supported from
2024-06-25 18:30:51 +05:00
Andrej730 ce9e4f2854 remove BIMObjectProperties from bpy.types.Material as deprecated #4843 2024-06-25 18:30:51 +05:00
Andrej730 7700e8016b ifcopenshell.geom.create_shape - document all return types 2024-06-25 18:30:51 +05:00
Andrej730 6e1ebb02ac file and entity - avoid infinite recursion if they fail to initialize
Example of the error:

Traceback (most recent call last):
  File "\test.py", line 133, in <module>
    ifc_file = ifcopenshell.file.from_string(ifc_str)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "\ifcopenshell\file.py", line 648, in from_string
    return file(ifcopenshell_wrapper.read(s))
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "\ifcopenshell\file.py", line 264, in __init__
    raise exc(msg)
ifcopenshell.SchemaError: Unsupported schema: IFC2X3
Exception ignored in: <function file.__del__ at 0x00000250896EB740>
Traceback (most recent call last):
  File "\ifcopenshell\file.py", line 282, in __del__
    del file_dict[self.file_pointer()]
                  ^^^^^^^^^^^^^^^^^
  File "\ifcopenshell\file.py", line 424, in __getattr__
    return getattr(self.wrapped_data, attr)
                   ^^^^^^^^^^^^^^^^^
  File "\ifcopenshell\file.py", line 424, in __getattr__
    return getattr(self.wrapped_data, attr)
                   ^^^^^^^^^^^^^^^^^
  File "\ifcopenshell\file.py", line 424, in __getattr__
    return getattr(self.wrapped_data, attr)
                   ^^^^^^^^^^^^^^^^^
  [Previous line repeated 996 more times]
RecursionError: maximum recursion depth exceeded

Simple way to trigger those recursions was:

import ifcopenshell
ifcopenshell.file(schema="IFC4x3_RC4_43c3555")
ifcopenshell.entity_instance(("IFC4X3", "IfcPresentationStyleAssignment"))
2024-06-25 18:30:51 +05:00
Andrej730 d2e7e3daaf move ifc file handling logic to ifcopenshell.file
to reuse the same logic for both ifcopenshell.open and ifcopenshell.file.from_string()
2024-06-25 18:30:51 +05:00
Andrej730 5d6215d889 bim.unlink_object - add description 2024-06-25 18:30:51 +05:00
Andrej730 17eb45fb4e Object metadata to double check element belongs to the current project
Just to be safe if it got from other Blender session and there is an IFC element with the same id in the current project. I guess we can't check it anywhere but information in "Object metadata" should very accurate as here user might decide to unlink it.
2024-06-25 18:30:51 +05:00
Andrej730 0a06d58c42 typing 2024-06-25 18:30:50 +05:00
Andrej730 a65c6b6d3f bim.unlink_object to skip popup if element is not found
If element is not found then it should always use `should_delete == False`, so we can just hide the popup in that case so users won't get confused about what should they choose.
2024-06-25 18:30:50 +05:00
Andrej730 c5262274eb bim.unlink_style - fix issue saving project after unlinking 2024-06-25 18:30:50 +05:00
Andrej730 65f94bcc0c Fix for console errors in a0e5d350b when no material active 2024-06-25 18:30:50 +05:00
Ziad-I 804bbc47c3 fix exception when global id is not included in csv data 2024-06-25 23:15:58 +10:00
Ziad-I a967665cbd slight html/js/css changes 2024-06-25 23:15:58 +10:00
Ziad-I cbd017aceb now sets active object too when selecting from web UI 2024-06-25 23:15:58 +10:00
Ziad-I 8d9d142bc2 reorganize JS code to be outside of document.ready 2024-06-25 23:15:58 +10:00
Ziad-I f6c30f2ddf improved send function logic to make there be 1 function only 2024-06-25 23:15:58 +10:00
Ziad-I d2a27999ba add handling for web operator selection
where if user click on item in the table it gets selected in blender
2024-06-25 23:15:58 +10:00
Ziad-I 2738b280a1 now shows ifc file name before table 2024-06-25 23:15:58 +10:00
Ziad-I c8946a19f2 edit html/js/css to use tabulator to show csv data 2024-06-25 23:15:58 +10:00
Ziad-I 6b1932c8d9 add event listeners in sioserver.py
added event listeners for sending csv data from blender and receiving operator data from web UI
2024-06-25 23:15:58 +10:00
Ziad-I a133b51dcc add new output format "web". might need to...
might need to rewrite the logic for the output format "web" at a later date if there is a better way
2024-06-25 23:15:58 +10:00
Ziad-I 6155edef0d made sending data take the event and namespace names as args 2024-06-25 23:15:58 +10:00
Thomas Krijnen 439595f04c Update build-all.py #3927 2024-06-25 11:44:26 +02:00
Thomas Krijnen c1fa9fe28e Option to read VERSION file in cmake #3927 2024-06-25 11:40:42 +02:00
Thomas Krijnen 54a0a5b454 Some more python mapping wrapping experiments 2024-06-24 21:57:16 +02:00
Thomas Krijnen bace73f854 Switch back to minkowski dilation for the time being 2024-06-24 21:56:57 +02:00
Thomas Krijnen 8da68f599b Move print() to header, distinguish properly between curve_sense and edge orientation, unwrap trimmed curves as edge bases, don't mutated cached edge in oriented edge processing, etc. #4895 2024-06-24 21:56:27 +02:00
Thomas Krijnen 278524ee3e Uninitialized use of context 2024-06-24 21:52:31 +02:00
Thomas Krijnen a9912f351f Don't dereference empty unit assignment list 2024-06-24 21:52:08 +02:00
Thomas Krijnen 1e5c51e5b2 Update main.py 2024-06-24 21:48:54 +02:00
Dion Moult 44100f3c46 Fix isolating drawing camera now that we have a new collection structure 2024-06-24 23:41:00 +10:00
Dion Moult 713e9c8e14 Minor fix in IfcFM to derive occurrences from types (and make it easier to override) 2024-06-24 23:41:00 +10:00
Dion Moult e6238a1f75 Georeference UI now lets you edit either a decimal degrees angle or an abscissa ordinate and calculates on the fly
Seriously no normal person uses abscissa and ordinate
2024-06-24 23:41:00 +10:00
Dion Moult cfd440dae9 Remove georeferencing orientation calculator UI because it's superseded by built-in angle calculators 2024-06-24 23:41:00 +10:00
Andrej730 a0e5d350bd Small UI in Blender material properties tab with unlinking #4843
Example - https://i.imgur.com/7VqIowz.png

This tab is needed only to indicate whether style is linked to some IFC element and to unlink it. Unlinking from Blender material makes much more sense as you can see from this tab what you're actually unlinking.
Unlinking for styles now works very similar to how it works for objects.
2024-06-24 18:37:55 +05:00
Andrej730 c37240cf96 pyproject.toml - disable pyright setting that conflicts with Blender api
See https://github.com/microsoft/pylance-release/issues/5457
2024-06-24 18:34:01 +05:00
Andrej730 115844279f fix removing objects with materials after c4ae9578e #4843
temporary fix, we probably should indeed just get rid of that listener
2024-06-24 18:34:00 +05:00
Andrej730 cbb7c5620d Fix unlinking objects with should_delete=False after 75f8e75
It was keeping a reference to that object in IfcStore which could lead to issues.
2024-06-24 18:34:00 +05:00
Andrej730 6d5d15793b record material checksum if styles were changed during material assignment
previously they wasn't recorded leading to the applying them to the representation items on save
2024-06-24 18:34:00 +05:00
Andrej730 8ac6f59394 typing 2024-06-24 18:34:00 +05:00
Andrej730 5d8addf1f8 remove optimization option deprecated after e3cb811 2024-06-24 18:34:00 +05:00
Andrej730 9b9301cbb0 ifc4x3 docs parser - use release ifc4x3 version 2024-06-24 15:22:02 +05:00
Andrej730 4d9b461776 Update ifc4x3 docs with ifc4x3_add2 release version 2024-06-24 15:22:02 +05:00
Andrej730 36f7cdacbd ifc4x3 docs - temporary fix for issue upstream
see https://github.com/buildingSMART/IFC4.3.x-development/issues/754
2024-06-24 15:22:02 +05:00
Andrej730 feb0e46257 More readable error if express file is missing 2024-06-24 15:22:02 +05:00
Andrej730 0b901bff5f typing 2024-06-24 15:22:02 +05:00
Dion Moult b45b0f2c81 There is now an explicit "remove true north" button to make it consistent with other buttons like remove georeferencing 2024-06-24 18:21:06 +10:00
Dion Moult 7a36c05c30 Implement WCS consideration in XYZ <-> ENH conversions. 2024-06-24 18:21:06 +10:00
Dion Moult ac58e6b8b2 Minor fix to new collection organisation for drawings 2024-06-24 18:21:06 +10:00
Dion Moult 68d3771a83 Change active layer collection when setting a new default container 2024-06-24 18:21:06 +10:00
Kristoffer Andersen 80349d70d8 Update ci-ifcopenshell-conda-cleaner.yml
fix missing env activation defaults
2024-06-24 08:41:46 +02:00
Dion Moult e295d596fe Split true north in API and Blender UI to be editable independent of georeferencing
They are two separate concepts, after all.
2024-06-23 23:19:27 +10:00
Dion Moult 013b138e5c Default to adding EPSG:3857 when adding georeferencing and let users choose an EPSG when adding. 2024-06-23 23:17:44 +10:00
Dion Moult 95d2d0af83 You can now see and edit WCS in the Blender UI (new API for it too) 2024-06-23 22:23:48 +10:00
Dion Moult 03ee826e67 Add tests for xaxis and yaxis angle conversions
This tricked me today, the "XAxis" and "YAxis" vectors are from different perspectives (yikes!). XAxis is from the perspective of grid north, and YAxis is from the perspective of project north. I'm following this convention because that's what buildingSMART docs do but just FYI they serve two different purposes!
2024-06-23 22:23:48 +10:00
Kristoffer Andersen 9f31815d9e add conda cron daily cleaner (#4910) 2024-06-23 09:11:17 +02:00
Ryan Schultz 1e9bfec655 Fix #4493 - Save drawing boundaries on bim.activate_drawing as well. 2024-06-22 11:44:42 -05:00
Dion Moult fd8cb7e8ed Implement Blender UI for handling new coordinate operations in IFC4X3 2024-06-22 23:32:25 +10:00
Dion Moult 4ef237e0f7 Implement purge map unit when removing georeferencing and removing 2X3 and 4X3 georeferencing in API 2024-06-22 22:13:20 +10:00
Dion Moult 3c48cfbc19 Add support for IfcRigidOperation and IfcMapConversionScaled in API 2024-06-22 21:59:09 +10:00
Dion Moult 177d43c30e Redesign georeference calculator UI to show units and be explicit about local vs map (instead of input vs output) 2024-06-22 16:29:36 +10:00
Dion Moult a6c3ae8446 Typing and update geolocation conversion utils to follow IFC4X3 rules about unit scaling 2024-06-22 15:52:00 +10:00
Dion Moult d0373da0ad Update XYZ <-> ENH geolocation conversion after the clarifications made on how to handle scale in IFC4X3 2024-06-22 14:47:42 +10:00
John Yani 7bb3de8371 Add documentation for toolbar (#4907) 2024-06-22 13:36:21 +10:00
Dion Moult 45e0045820 Fix bug where merge duplicate types would trigger listeners in an active Blender session. Stop using listeners, I guess? 2024-06-21 23:32:53 +10:00
Dion Moult 347b2b783d Minor doc fix to SetFalseOrigin code example 2024-06-21 23:32:53 +10:00
Dion Moult 2fc7cc7de2 Show georeferencing unit symbols to make it clearer what units things are in 2024-06-21 23:32:53 +10:00
Andrej730 4d39804045 docs code block formatting
`console` type code-block is designed mainly for examples of the console commands with the output.

If it's just a shell script with comments, then `console` only gets in the way - e.g. if you try to select a console code block manually, you won't be able to select comment symbol `#` at the beginning of the command making code unrunnable if you try to copy and run it. I've replaced it with `bash` or `bat` (for Windows) code blocks where `console` wasn't necessary.

I've also removed `$` symbols at the beginning of the commands if it's not a `console` code-block - since otherwise it just breaks "Copy Code Block" because you copy unrunnable code.
2024-06-21 18:28:59 +05:00
Andrej730 746051815d TesselateElement ifcpatch recipe to support product types
Previously it would just fail if you provide some IfcTypeProduct in the patch query
2024-06-21 17:50:22 +05:00
Andrej730 4385bf0bcc create_shape - note about IfcTypeProducts 2024-06-21 17:50:22 +05:00
Andrej730 1e092c140f add ifc2x3 support for 301cdf650 2024-06-21 17:50:22 +05:00
Andrej730 c4ae9578e9 fix styles update on material assignment/unassignment #4843 2024-06-21 17:50:22 +05:00
Andrej730 a834e5b4cd element.get_type doc note 2024-06-21 17:50:22 +05:00
Andrej730 e329b18e33 simplify process of loading blender materials #4843
We no longer traverse through representations searching for IfcSurfaceStyles and just rely on geometry.materials.
Possible after #4886 since:
- create_shape is now considering inherited materials
- material.instance_id is not returning IfcMaterial's style id instead of IfcMaterial id.
2024-06-21 17:50:22 +05:00
Andrej730 5731c4036a unresolve_type_representation - typing and small optimization 2024-06-21 17:49:02 +05:00
Andrej730 a594a6cfe2 typing 2024-06-21 17:49:02 +05:00
Andrej730 8b6235f42f fix TessellateElements ifcpatch recipe
TessellateElements patch was failing because it was relying on optionality of `edges` argument. Though argument itself is currently useless and not used in the code.
2024-06-21 17:49:02 +05:00
Andrej730 d443c5e2a6 element.get_type - check schema explicitly for a small optimization
Previously if product had no types it would still run through all element.IsDefinedBy and check if they are IfcRelDefinesByType even if it's not IFC2X3.
2024-06-21 17:49:02 +05:00
Andrej730 68d3539a86 mapping binding - never overwrite already set item->instance
1) this way code should be more straightforward
2) this allows us not to reimplement mapping.get_surface_style logic in IfcGeomWrapper as it will be already handled in map_impl(const IfcSchema::IfcStyledItem* inst) and the resulting item->instance is not overridden by IfcStyledItem.
2024-06-21 14:52:37 +05:00
Andrej730 301cdf6504 Fix creating representation with inherited materials
It should be considered as inherited material can also have a style.
Though occurrence's material is still prioritized.
2024-06-21 14:52:37 +05:00
Andrej730 882e0a4ae9 Fix instance_id in geometry.materials returning material id instead of style id 2024-06-21 14:52:37 +05:00
John Yani f921b65953 Major documentation refactoring (#4905) 2024-06-21 17:54:05 +10:00
Andrej730 1e26587bd3 geometry.import_representation to disable apply-default-materials #4832
as material creator is not designed for this option
2024-06-21 12:11:47 +05:00
Dion Moult bce6485b91 Support adding / editing georeferencing for IFC2X3 fallback psets 2024-06-21 11:21:41 +10:00
Dion Moult a0cb980a8f See #4896. Bump to IDS v1.0. 2024-06-20 17:37:14 +10:00
Dion Moult fc0e9e900f Fix #3970. Show passing elements in IDS test report. 2024-06-20 16:36:18 +10:00
Dion Moult d700262e7f Support IDS tests for nest relationships in IFC2X3 2024-06-20 15:53:28 +10:00
Dion Moult d92679223e Fix #4879. Include metadata about original requirement in JSON reports. 2024-06-20 15:29:10 +10:00
Dion Moult 7ac117cd8d Use a json encoder for IfcTester JSON reports to ensure everything is stringable 2024-06-20 15:00:55 +10:00
Dion Moult b87a6bc15c Fix #4874. Checking restrictions of the wrong data type will now returned a failed result instead of an exception. 2024-06-20 13:22:16 +10:00
Dion Moult fbefe8c555 PartOf facet tests now give more granular reports on failing predefined type requirements 2024-06-20 12:58:42 +10:00
Dion Moult 030119fd0e Fix #4893. IfcTester now requires lowercase booleans or 0/1.
This changed on May 5: https://github.com/buildingSMART/IDS/issues/290
2024-06-20 09:52:45 +10:00
Dion Moult fcf2c37432 New SetFalseOrigin patch recipe to make it easy to "Just Fix It!"
This combines the best practices of a few other patch recipes to make it easy to say "this point here is this world coordinate".
2024-06-19 23:41:46 +10:00
Dion Moult fab3ee70dd Fix bug where 2D angles weren't correctly parsed in OffsetObjectPlacements recipe 2024-06-19 23:37:01 +10:00
Dion Moult 4738528e69 We now can do cartesian point offsets regardless if there is a false session origin or not.
For example, it doesn't matter if you have a false origin set or not, there can still be scenarios where local verts are far away.
2024-06-19 23:33:16 +10:00
Thomas Krijnen 88c8bcf119 Pin OCCT serialization version to v2 #4393 2024-06-19 14:44:52 +02:00
Ziad-I 3c3e4dd047 add pip install python-socketio and aiohttp in makefile 2024-06-19 13:17:29 +03:00
Thomas Krijnen b44b1cb8b0 Fix segfault on gcc #4832 2024-06-19 11:56:56 +02:00
Ziad-I 6ad2bef05c add webui server and static files 2024-06-19 12:27:03 +03:00
Andrej730 8e0c92d5eb Fix issue using IfcImporter after 8246d2c
E.g. there was an error on creating a project using demo template
2024-06-19 10:57:34 +05:00
Andrej730 6bafbd92f3 fix ui bug after 114fca8be 2024-06-19 10:57:34 +05:00
Andrej730 03663eec3f include socketio to fix starting bbim after 65a1fbe 2024-06-19 10:57:34 +05:00
Thomas Krijnen a3d4642ea9 Fix boolean results on non-solid shells #4832 2024-06-18 21:30:00 +02:00
Thomas Krijnen 0e42130e9d Taxonomy curve upgrades #4832 2024-06-18 20:18:39 +02:00
Thomas Krijnen 691fe549d7 taxonony::cast throws exceptions now on invalid casts 2024-06-18 13:52:42 +02:00
Thomas Krijnen f5979be4d5 Keep old behaviour of subtractions, when failed use first operand #4832 2024-06-18 13:09:32 +02:00
Kristoffer Andersen 9fe40c3137 make necessary changes for v0.8 daily conda builds (#4884)
* make necessary changes for v0.8 daily ci conda builds

* fix conda find hdf5 portion of CMakelists.txt
2024-06-18 12:52:00 +02:00
Thomas Krijnen 82496e8126 m4 not referenced in IfcEllipseProfileDef 2024-06-18 12:32:00 +02:00
Thomas Krijnen 9c22e6ecc8 Support evaluation of faces directly, add identity matrix on unplaced circle profile curves #4832 2024-06-18 12:32:00 +02:00
Thomas Krijnen 17a433a97b Don't pass around nullptr in case of failed representation mapping 2024-06-18 12:32:00 +02:00
Ziad-I b90c1ee7c9 forgot to undo changes after moving web UI code out to its own module/tool
forgot to remove changes done to csv module and search tool after moving web UI to its own module/tool
changes such as:
unused imports
panel header option
2024-06-18 18:31:39 +10:00
Ziad-I bd30f73b3a fix no such directory error when running server
and added a 0.5 sleep time between disconnecting and killing server
2024-06-18 18:31:39 +10:00
Ziad-I 0aad3d99b5 add data.py for web module and sending data via ws connection 2024-06-18 18:31:39 +10:00
Ziad-I f7d55a4a4a fix open_web_browser operator 2024-06-18 18:31:39 +10:00
Ziad-I df2655e7ae add open browser button and operator 2024-06-18 18:31:39 +10:00
Ziad-I 9ec51e040e change ui to show buttons according to connected/running
and moved setting is_connected and is_running to inside the tool itself
2024-06-18 18:31:39 +10:00
Ziad-I 467602aec3 add sending messages to server in web tool 2024-06-18 18:31:39 +10:00
Ziad-I 88610e23b2 move thread and asyncio loop management to its own class
as manually managing the thread and asyncio loop was quite a hassle. especially, cleanup after closing them.
2024-06-18 18:31:39 +10:00
Ziad-I 89fb59307c add disconnection from server functionality
had to refactor how the server connects
2024-06-18 18:31:39 +10:00
Ziad-I 65a1fbea78 add connection to webserver logic 2024-06-18 18:31:38 +10:00
Ziad-I 78bc8fe977 add methods to show running and connected status 2024-06-18 18:31:38 +10:00
Ziad-I 6ab08a2f49 remove debug flag in flask run command
removed debug flag in flask run command as it leaves a zombie process after closing blender
2024-06-18 18:31:38 +10:00
Ziad-I de7d315eb3 add starting webserver and terminating webserver 2024-06-18 18:31:38 +10:00
Ziad-I 24662d75e4 add tool methods to generate port and check port availability 2024-06-18 18:31:38 +10:00
Ziad-I 76e6697bbc remove open_web_ui button and operator from csv module 2024-06-18 18:31:38 +10:00
Ziad-I 3c2fcffd0d moved web ui logic to web tool/core 2024-06-18 18:31:38 +10:00
Ziad-I 0188757197 add new web module 2024-06-18 18:31:38 +10:00
Ziad-I 444554a12d add print messages and opening of web UI 2024-06-18 18:31:38 +10:00
Ziad-I ba42f01db2 add internal logic to start the web-ui websocket server
added internal logic to the Search tool/core to start the websocket server for the webui
2024-06-18 18:31:38 +10:00
Ziad-I 5d91766f07 changed the icon for the button to URL icon 2024-06-18 18:31:38 +10:00
Ziad-I c4cbc577d8 add open web ui button and dummy operator 2024-06-18 18:31:38 +10:00
Dion Moult bfd9cc0cf0 Allow calling ifcpatch from code with no arguments 2024-06-18 13:55:19 +10:00
Dion Moult ac0b5abfc5 You can now choose whether to use a cache / force reload a linked model 2024-06-18 12:51:14 +10:00
Dion Moult ad61cc3c92 Update linking styles to v0.8.0 syntax 2024-06-18 10:59:10 +10:00
Dion Moult 6fa586a1b6 When linking IFCs into a vanilla Blender session with no IFC, the origin from the first IFC you link determines the origin of subsequent links 2024-06-18 00:15:55 +10:00
John Yani 4c09773211 Refactor Project Info panel to clarify distinct modes (#4880)
* Rename modes in Project Info documentation

* Refactor project info panel UI code
2024-06-17 23:38:05 +10:00
Dion Moult 2300411ce4 Fix #4510. Linking in georeferenced models now reuses the host model's georeferencing settings
In addition to reusing a constant false origin, because we now reuse the same logic as project loading, we also now get the ability to mix map-conversion and non-map-conversion coordinates and also link in rotated models, not just translated.
2024-06-17 17:10:14 +10:00
Dion Moult 9c61a9eed7 Linking in models now uses the same cartesian point offset strategy as loading models with a dynamic distance limit
Previously, it would have a hardcoded 1km distance limit and any "far away" object would be treated as a chunk instead of an occurrence. Now it has dynamic distance limits and benefit from occurrence instancing.
2024-06-17 17:03:42 +10:00
Dion Moult 743a197542 False origin is always in terms of map coordinates, not as an offset.
Previously, the false origin would be in terms of local coordinates. This means that the same false origin would give two different results on files with different map conversions. Now, false origin is ... well, a true map coordinate and works equally for both IFCs with and without a map conversion.
2024-06-16 22:28:48 +10:00
Dion Moult 99f329c13d When guessing false origins, only consider geometry, not placements
Placements may be misleading and are "invisible". A far away placement and local geometry can cancel each other out.
2024-06-16 15:12:11 +10:00
Dion Moult 623d487df7 Use gross shapes for guessing false origins to be faster 2024-06-16 15:11:24 +10:00
Dion Moult 348ef4023f False origins can now be explicitly auto, manual, or disabled 2024-06-16 14:50:33 +10:00
Dion Moult b253b74e88 Linking projects now shares context prioritisation logic with regular project loading
This means it can link in more interesting shapes.
2024-06-16 14:21:25 +10:00
Dion Moult a3b6a6ea39 Black geom/main.py 2024-06-15 23:50:37 +10:00
Dion Moult 8246d2c593 Aggressively refactor out functions relevant to guessing a false origin to tool.Loader 2024-06-15 23:50:27 +10:00
Dion Moult 3a6cf0e5ae Fix #4854. Make IFC edit mode toggle dynamic to prevent editing uneditable things. 2024-06-15 16:40:53 +10:00
Dion Moult 1db26aab15 Fix #4837. Make mathutils dependency optional.
In the long term, I'd like to replace mathutils with numpy.
2024-06-15 15:10:36 +10:00
John Yani a23e6dcd66 Expand Project Info documentation. Reorganize General Usage (#4875) 2024-06-15 14:10:00 +10:00
Dion Moult 9331292dd1 Fix bug where editing qtos was hardcoded to IFC4. 2024-06-15 14:07:27 +10:00
Dion Moult cad7db0162 See #4838. Consistently use schema_identifiers throughout the code instead of schema to be more explicit.
This also means in the future we are better shaped to be a prototyping platform for future IFC versions.
2024-06-15 14:05:37 +10:00
Dion Moult 9f356542bc Remove dead code where material psets used to be on a bpy.types.Material 2024-06-15 13:29:48 +10:00
Andrej730 1f7d80e6f1 remove unused argument 2024-06-14 19:14:20 +05:00
Andrej730 f3f1e8721c remove name update for ifc materials based on blender materials name #4843 2024-06-14 19:14:20 +05:00
Andrej730 114fca8be5 unassociate bim.add_material from blender materials #4843 2024-06-14 19:14:20 +05:00
Andrej730 52167a47b9 minor refactor for bim.add_boolean
1)  Couple logic simplifications and info messages
2) Removed unlink as obj2 is a blender object (we check this before with `tool.Ifc.get_entity(obj2) == None`) and doesn't need to be unlinked
2024-06-14 19:14:20 +05:00
Andrej730 d4ff2c8dda typing 2024-06-14 19:14:20 +05:00
Andrej730 75f8e75a8e ifc.unlink fixes after 3df75e9af
Also some changes related to #4843

add
2024-06-14 19:14:19 +05:00
Andrej730 d0f612afc2 IfcStore.get_element - more explicit checks for missing elements 2024-06-14 19:14:19 +05:00
Andrej730 b6b681c244 bim.add_boundary - info message if no relevant elements selected 2024-06-14 19:14:19 +05:00
Dion Moult 5911a602b9 Purge legacy code which used JSON files for schema descriptions and entities
In our early days, we didn't use IfcOpenShell to dynamically query the schema.
2024-06-14 21:59:53 +10:00
Dion Moult 453ef437d3 Fix linking IFC coordinates to v0.8.0 2024-06-14 16:30:31 +10:00
Dion Moult 88494d3379 Linking IFCs now also includes spatial elements 2024-06-14 16:16:08 +10:00
John Yani 1b41440aba Update Blender BIM Project Info documentation (#4871)
* Update property-editor.rst

* Add project overview screenshots

* Update property-editor.rst

* Add BlenderBIM project info screenshots

* Update property-editor.rst
2024-06-14 14:48:49 +10:00
Dion Moult 105434fe68 See #4854. Don't show IFC mode UI if there is no IFC model.
Still quite a bit of work to be done here, but this is better than nothing.
2024-06-14 13:01:43 +10:00
Dion Moult a0a505d64b Minor fix to not show placement if offset is not applicable 2024-06-14 13:00:09 +10:00
Dion Moult 3a4bdd623d Fix #4867. Accommmodate invalid IFC data from 12D. 2024-06-14 09:18:14 +10:00
Andrej730 99338187de prevent possible errors initializing swig wrapper's Settings #4861 2024-06-13 18:19:12 +05:00
Andrej730 9d7ef6ff47 BBIM - fix invalid representation item style assignment in ifc2x3
It wasn't considering IfcPresentationStyleAssignment.
2024-06-12 18:24:14 +05:00
Andrej730 ffd944f4f9 style->instance_id to consider possible IfcPresentationStyleAssignment in ifc2x3/ifc4 2024-06-12 13:52:44 +02:00
Thomas Krijnen ecef745c3b OCCT doesn't like null shapes even on symmetric bools 2024-06-12 11:47:27 +02:00
Thomas Krijnen 44507785ac Rerun codegen 2024-06-12 11:47:27 +02:00
Thomas Krijnen 79c0246e6a Don't sort enum names for correspondence with header 2024-06-12 11:47:27 +02:00
Dion Moult 3c659a342f BCF workflow now uses VERSION 2024-06-12 19:23:10 +10:00
Dion Moult d192217e78 bSDD workflow now uses VERSION 2024-06-12 18:44:51 +10:00
Dion Moult 6474552649 IfcClash workflow now uses VERSION 2024-06-12 18:25:05 +10:00
Dion Moult 4b454dd646 IfcCSV workflow now uses VERSION 2024-06-12 18:15:13 +10:00
Dion Moult 85e1786ce5 IfcPatch workflow now uses VERSION 2024-06-12 18:05:33 +10:00
Dion Moult 45b81ab814 IfcTester workflow now uses VERSION 2024-06-12 17:52:35 +10:00
Dion Moult 8241b557fe Minor fix - add "unstable" word to Sverchok release and fix importing WAxes 2024-06-12 10:26:20 +10:00
Dion Moult 1312a8e704 Purge deprecated feature to enable coordination mode / merge modes in favour of new link IFC feature 2024-06-12 09:53:58 +10:00
Thomas Krijnen 2a5e42e359 Don't log 'no op defined' for failed conversions 2024-06-11 20:09:37 +02:00
Thomas Krijnen dae7d86dad Fix style lookup in accordance with v0.7 #4844 2024-06-11 20:09:37 +02:00
Andrej730 9906149b0d do not load ifc materials as blender materials #4843
also deprecate blender_material style argument as it's not used anywhere now
2024-06-11 18:26:19 +05:00
Andrej730 2e2c116180 deprecate mixing up ifc materials and styles in one blender material on loading #4843
Part of the transition process disconnecting blender materials from IFC materials to make it less confusing.

E.g. previously if we would load an element A with material M and style S it would load it as blender material M that looks like S.
But also if you would load element B with just style S, it would add it as blender material M though B has nothing to do with material M.
2024-06-11 18:26:19 +05:00
Andrej730 13c6b9d673 do not add Blender materials to objects without styles #4843
If part of the geometry has no style, we'll use empty empty material slots since Blender does require some material slot to assign each polygon to.
2024-06-11 18:26:19 +05:00
Andrej730 2fbac57faf remove bim.unlink_material #4843 2024-06-11 18:26:19 +05:00
Andrej730 f803447651 more safe collector.assign
Noticed after error below trying to duplicate an element

  File "addons\blenderbim\bim\module\geometry\operator.py", line 769, in execute
    return OverrideDuplicateMove.execute_duplicate_operator(self, context, linked=False)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "addons\blenderbim\bim\module\geometry\operator.py", line 778, in execute_duplicate_operator
    IfcStore.execute_ifc_operator(self, context)
  File "addons\blenderbim\bim\ifc.py", line 381, in execute_ifc_operator
    result = getattr(operator, "_execute")(context)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "addons\blenderbim\bim\module\geometry\operator.py", line 772, in _execute
    return OverrideDuplicateMove.execute_ifc_duplicate_operator(self, context)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "addons\blenderbim\bim\module\geometry\operator.py", line 855, in execute_ifc_duplicate_operator
    new = blenderbim.core.root.copy_class(tool.Ifc, tool.Collector, tool.Geometry, tool.Root, obj=new_obj)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "addons\blenderbim\core\root.py", line 54, in copy_class
    collector.assign(obj)
  File "addons\blenderbim\tool\collector.py", line 62, in assign
    collection.objects.link(obj)
RuntimeError: Error: Object 'IfcActuator/Cube.001' already in collection 'IfcBuildingStorey/My Storey'
2024-06-11 18:26:19 +05:00
Andrej730 bec834c651 typing 2024-06-11 18:26:19 +05:00
Andrej730 3df75e9af3 IfcStore.unlink_element breaking change
Previously there was a problem with this method that it might confuse Blender objects copied from other sessions (or even from the current session but a few steps back) from current IFC objects and might unlink them accidentally.

Option to provide either element or obj was more of a convenience feature but now they have a different meaning to clearly distinguish between unlinking ifc element and unlinking some blender object without touching any ifc elements.
2024-06-11 18:26:19 +05:00
Andrej730 6630db21ee Set apply default materials to False #4833
Since it's now True by default in v0.8.0, resetting it to False as it was in v0.7.0
2024-06-11 18:26:19 +05:00
Andrej730 f54f45a051 geom.settings - add a tooltip for available settings #4832
example - https://i.imgur.com/eTdqv5J.png
added a test so we won't miss if some setting will be added/removed
2024-06-11 18:26:18 +05:00
Thomas Krijnen acd4274136 Don't blindly assumes openings have representations for the selected contexts. #4832 2024-06-11 15:25:03 +02:00
Thomas Krijnen d400a48a9d Remember your own conventions: lines run along positive Z 2024-06-11 14:14:47 +02:00
Thomas Krijnen 4331f4f785 Don't stall threads on occt failures 2024-06-11 14:14:11 +02:00
Thomas Krijnen 95e36979d5 Don't link against svgfill from python when building without cgal #4832 2024-06-11 12:32:11 +02:00
Thomas Krijnen 07827940cc Some defensiveness against null shapes (should look into this better) 2024-06-11 11:20:55 +02:00
Thomas Krijnen c2efcda7bb Don't use cached items in multi-threaded mode for now until we have proper immutability 2024-06-11 11:20:40 +02:00
Thomas Krijnen aa0fbd434e Add geometry library install targets #4842 2024-06-11 10:33:19 +02:00
Dion Moult 8cff046a46 Doc download links now point to explicitly versioned releases, not bot builds
This ensures that IOS-Python always 1) has the appropriate version and 2) contains exactly the same content as PyPI (i.e. with the latest Python files that might be outdated from the bot builds)
2024-06-11 17:32:30 +10:00
Dion Moult ff4154c2bd Forgot to distribution Windows 32 bit IfcOpenShell-Python 2024-06-11 16:09:00 +10:00
Dion Moult d7e497727c More build fixes. 2024-06-11 16:01:14 +10:00
948 changed files with 21415 additions and 40467 deletions
+1 -17
View File
@@ -1,24 +1,8 @@
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
@@ -54,4 +38,4 @@ jobs:
with:
user: __token__
password: ${{ secrets.PYPI_API_TOKEN }}
packages_dir: src/bcf/dist
packages_dir: src/bcf/dist
+16 -14
View File
@@ -33,7 +33,7 @@ jobs:
strategy:
fail-fast: false
matrix:
pyver: [py310]
pyver: [py311]
config:
- {
name: "Windows Build",
@@ -43,33 +43,37 @@ jobs:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2 # https://github.com/actions/setup-python
with:
python-version: '3.10.9' # Version range or exact version of a Python version to use, using SemVer's version range syntax
python-version: '3.11.9' # Version range or exact version of a Python version to use, using SemVer's version range syntax
architecture: 'x64' # optional x64 or x86. Defaults to x64 if not specified
- run: echo ${{ env.DATE }}
- name: Check in published releases if we should do a choco release
- name: Check in release tags if we should do a choco release
id: do_choco
run: |
echo "::set-output name=choco_release::$(python3 /home/runner/work/IfcOpenShell/IfcOpenShell/choco/blenderbim/check_repo_infos.py --do_choco_release?)"
cd /home/runner/work/IfcOpenShell/IfcOpenShell/choco/blenderbim/ &&
echo "::set-output name=choco_release::$(python3 check_release_needed.py)"
- name: Fill chocolatey scripts on win with latest blender python
if: ${{steps.do_choco.outputs.choco_release}} == 'do_choco_release'
run: |
cd /home/runner/work/IfcOpenShell/IfcOpenShell/choco/blenderbim/ &&
yesterday_non_iso="$(date --date='yesterday' +'%y%m%d' | tr -d '\n')" &&
target_os=${{ matrix.config.short_name }} &&
latest_blender_python_version_maj_min=$(python3 /home/runner/work/IfcOpenShell/IfcOpenShell/choco/blenderbim/check_repo_infos.py --latest_blender_python_version_maj_min?) &&
latest_blender_python_version_maj_min=$(python3 check_repo_infos.py --latest_blender_python_version_maj_min?) &&
echo "latest_blender_python_version_maj_min?: $latest_blender_python_version_maj_min" &&
pyver=$(python3 /home/runner/work/IfcOpenShell/IfcOpenShell/choco/blenderbim/check_repo_infos.py --pyver?) &&
export latest_blender_version_maj_min=$(python3 /home/runner/work/IfcOpenShell/IfcOpenShell/choco/blenderbim/check_repo_infos.py --latest_blender_release_maj_min?) &&
export latest_blender_version_maj_min_pat=$(python3 /home/runner/work/IfcOpenShell/IfcOpenShell/choco/blenderbim/check_repo_infos.py --latest_blender_release_maj_min_pat?) &&
export latest_blender_version_maj_min=$(python3 check_repo_infos.py --latest_blender_release_maj_min?) &&
export latest_blender_version_maj_min_pat=$(python3 check_repo_infos.py --latest_blender_release_maj_min_pat?) &&
echo latest_blender_version_maj_min_pat?: $latest_blender_version_maj_min_pat &&
export blenderbim_build_version="${{ env.major }}.${{ env.minor }}.$yesterday_non_iso" &&
export url_blenderbim_py310_win_zip="https://github.com/IfcOpenShell/IfcOpenShell/releases/download/blenderbim-$yesterday_non_iso/blenderbim-$yesterday_non_iso-$pyver-$target_os.zip" &&
wget $url_blenderbim_py310_win_zip --no-verbose &&
export sha256sum_blenderbim_py310_win_zip=$( sha256sum blenderbim-$yesterday_non_iso-$pyver-$target_os.zip --tag | cut -d ' ' -f 4 | tr -d '\n') &&
echo sha256sum_blenderbim_py310_win_zip: $sha256sum_blenderbim_py310_win_zip &&
python3 choco/blenderbim/fill_dynamic_parameters.py &&
export url_blenderbim_py3x_win_zip="https://github.com/IfcOpenShell/IfcOpenShell/releases/download/$target_release_tag/$target_release_tag-$pyver-$target_os.zip" &&
wget $url_blenderbim_py3x_win_zip --no-verbose &&
export sha256sum_blenderbim_py3x_win_zip=$( sha256sum $target_release_tag-$pyver-$target_os.zip --tag | cut -d ' ' -f 4 | tr -d '\n') &&
echo sha256sum_blenderbim_py3x_win_zip: $sha256sum_blenderbim_py3x_win_zip &&
python3 fill_dynamic_parameters.py &&
echo __build choco with mono &&
mkdir /tmp/choco/ &&
cd /tmp/choco/ &&
wget "https://github.com/chocolatey/choco/archive/refs/tags/$choco_version.tar.gz" --quiet &&
tar -xzf "$choco_version.tar.gz" &&
cd choco-$choco_version &&
@@ -78,7 +82,6 @@ jobs:
cp -r build_output/chocolatey /opt/chocolatey &&
cd /home/runner/work/IfcOpenShell/IfcOpenShell/choco/blenderbim/ &&
echo __choco pack &&
mono /opt/chocolatey/choco.exe --version &&
mono /opt/chocolatey/choco.exe pack --allow-unofficial &&
echo __choco set apiKey &&
mono /opt/chocolatey/choco.exe setapikey --key="$CHOCO_TOKEN" --source="https://push.chocolatey.org/" --allow-unofficial # &&
@@ -89,4 +92,3 @@ jobs:
if: ${{steps.do_choco.outputs.choco_release}} != 'do_choco_release'
run: |
echo "no releases found today ${{ env.DATE }}, therefore choco packaging is skipped."
-13
View File
@@ -1,21 +1,8 @@
name: ci-bsdd-pypi
on:
schedule:
# ┌───────────── minute (0 - 59)
# │ ┌───────────── hour (0 - 23)
# │ │ ┌───────────── day of the month (1 - 31)
# │ │ │ ┌───────────── month (1 - 12 or JAN-DEC)
# │ │ │ │ ┌───────────── day of the week (0 - 6 or SUN-SAT)
# * * * * *
- cron: "0 0 18 * *"
workflow_dispatch:
env:
major: 0
minor: 0
name: ifcopenshell
jobs:
activate:
runs-on: ubuntu-latest
+35
View File
@@ -0,0 +1,35 @@
name: ci-ifc4d-pypi
on:
workflow_dispatch:
jobs:
activate:
runs-on: ubuntu-latest
if: |
github.repository == 'IfcOpenShell/IfcOpenShell'
steps:
- name: Set env
run: echo ok go
build:
needs: activate
runs-on: ubuntu-latest
strategy:
fail-fast: false
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2 # https://github.com/actions/setup-python
with:
python-version: '3.11' # Version range or exact version of a Python version to use, using SemVer's version range syntax
- name: Compile
run: |
pip install build
cd src/ifc4d &&
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/ifc4d/dist
+35
View File
@@ -0,0 +1,35 @@
name: ci-ifc5d-pypi
on:
workflow_dispatch:
jobs:
activate:
runs-on: ubuntu-latest
if: |
github.repository == 'IfcOpenShell/IfcOpenShell'
steps:
- name: Set env
run: echo ok go
build:
needs: activate
runs-on: ubuntu-latest
strategy:
fail-fast: false
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2 # https://github.com/actions/setup-python
with:
python-version: '3.11' # Version range or exact version of a Python version to use, using SemVer's version range syntax
- name: Compile
run: |
pip install build
cd src/ifc5d &&
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/ifc5d/dist
@@ -0,0 +1,35 @@
name: ci-ifccityjson-pypi
on:
workflow_dispatch:
jobs:
activate:
runs-on: ubuntu-latest
if: |
github.repository == 'IfcOpenShell/IfcOpenShell'
steps:
- name: Set env
run: echo ok go
build:
needs: activate
runs-on: ubuntu-latest
strategy:
fail-fast: false
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2 # https://github.com/actions/setup-python
with:
python-version: '3.11' # Version range or exact version of a Python version to use, using SemVer's version range syntax
- name: Compile
run: |
pip install build
cd src/ifccityjson &&
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/ifccityjson/dist
-13
View File
@@ -1,21 +1,8 @@
name: ci-ifcclash-pypi
on:
schedule:
# ┌───────────── minute (0 - 59)
# │ ┌───────────── hour (0 - 23)
# │ │ ┌───────────── day of the month (1 - 31)
# │ │ │ ┌───────────── month (1 - 12 or JAN-DEC)
# │ │ │ │ ┌───────────── day of the week (0 - 6 or SUN-SAT)
# * * * * *
- cron: "0 0 18 * *"
workflow_dispatch:
env:
major: 0
minor: 0
name: ifcopenshell
jobs:
activate:
runs-on: ubuntu-latest
-13
View File
@@ -1,21 +1,8 @@
name: ci-ifccsv-pypi
on:
schedule:
# ┌───────────── minute (0 - 59)
# │ ┌───────────── hour (0 - 23)
# │ │ ┌───────────── day of the month (1 - 31)
# │ │ │ ┌───────────── month (1 - 12 or JAN-DEC)
# │ │ │ │ ┌───────────── day of the week (0 - 6 or SUN-SAT)
# * * * * *
- cron: "0 0 18 * *"
workflow_dispatch:
env:
major: 0
minor: 0
name: ifcopenshell
jobs:
activate:
runs-on: ubuntu-latest
+35
View File
@@ -0,0 +1,35 @@
name: ci-ifcdiff-pypi
on:
workflow_dispatch:
jobs:
activate:
runs-on: ubuntu-latest
if: |
github.repository == 'IfcOpenShell/IfcOpenShell'
steps:
- name: Set env
run: echo ok go
build:
needs: activate
runs-on: ubuntu-latest
strategy:
fail-fast: false
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2 # https://github.com/actions/setup-python
with:
python-version: '3.11' # Version range or exact version of a Python version to use, using SemVer's version range syntax
- name: Compile
run: |
pip install build
cd src/ifcdiff &&
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/ifcdiff/dist
+35
View File
@@ -0,0 +1,35 @@
name: ci-ifcfm-pypi
on:
workflow_dispatch:
jobs:
activate:
runs-on: ubuntu-latest
if: |
github.repository == 'IfcOpenShell/IfcOpenShell'
steps:
- name: Set env
run: echo ok go
build:
needs: activate
runs-on: ubuntu-latest
strategy:
fail-fast: false
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2 # https://github.com/actions/setup-python
with:
python-version: '3.11' # Version range or exact version of a Python version to use, using SemVer's version range syntax
- name: Compile
run: |
pip install build
cd src/ifcfm &&
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/ifcfm/dist
@@ -0,0 +1,101 @@
name: ci-ifcopenshell-conda-daily-cleaner
on:
workflow_dispatch:
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: "00 23 * * *" # 11min before utc midnight every day
env:
ANACONDA_TOKEN: ${{ secrets.ANACONDA_TOKEN }}
NUM_SUPPORTED_VERSIONS: 5
jobs:
activate:
runs-on: ubuntu-latest
defaults:
run:
shell: bash -l {0}
if: |
github.repository == 'IfcOpenShell/IfcOpenShell'
steps:
- uses: mamba-org/setup-micromamba@v1 # https://github.com/mamba-org/setup-micromamba
with:
environment-name: test-env
create-args: >-
python=3.11
anaconda-client=1.12.3
- name: Run conda cleaner
run: |
python - << EOF
import os
from datetime import datetime, timedelta
from binstar_client.utils import get_server_api
from binstar_client.errors import BinstarError
# Configuration
api_token = os.environ.get('ANACONDA_TOKEN')
pkg_name = 'ifcopenshell'
channel_name = 'ifcopenshell'
# Authenticate with Anaconda
aserver_api = get_server_api(token=api_token)
# Get the list of packages in the channel
def get_package(filter_package_name: str = None):
try:
user_packages = aserver_api.user_packages(channel_name)
if filter_package_name:
user_packages = [pkg for pkg in user_packages if pkg['name'] == filter_package_name]
if len(user_packages) == 0:
print(f"No packages found for {filter_package_name}.")
if len(user_packages) > 1:
raise ValueError(f"Found {len(user_packages)} package for {filter_package_name}. Will only support 1 package.")
return user_packages[0]
except BinstarError as err:
raise ValueError(f"Failed to fetch packages: {err}")
# Delete a package version
def delete_package(package_name, version):
try:
aserver_api.remove_release(channel_name, package_name, version)
print(f"Deleted {package_name} version {version}")
except BinstarError as err:
print(f"Failed to delete {package_name} version {version}: {err}")
# Main logic
def main():
package = get_package(pkg_name)
if not package:
print("No packages found.")
return
number_of_supported_versions = ${{ env.NUM_SUPPORTED_VERSIONS }}
package_name = package['name']
versions = package["versions"]
if len(versions) <= number_of_supported_versions:
print(f"Number of versions {len(versions)} is less than or equal to {number_of_supported_versions}.")
return
# sort the versions in descending order
versions = sorted(versions, reverse=True)
releases = versions[number_of_supported_versions:]
for release in releases:
delete_package(package_name, release)
main()
EOF
@@ -2,21 +2,44 @@ name: ci-ifcopenshell-conda-daily-v0.8.0
on:
workflow_dispatch:
push:
branches:
- v0.8.0
schedule:
# ┌───────────── minute (0 - 59)
# │ ┌───────────── hour (0 - 23)
# │ │ ┌───────────── day of the month (1 - 31)
# │ │ │ ┌───────────── month (1 - 12 or JAN-DEC)
# │ │ │ │ ┌───────────── day of the week (0 - 6 or SUN-SAT)
# * * * * *
- cron: "49 23 * * *" # 11min before utc midnight every day
jobs:
activate:
runs-on: ubuntu-latest
if: |
github.repository == 'IfcOpenShell/IfcOpenShell'
outputs:
version: ${{ steps.version.outputs.version }}
date: ${{ steps.date.outputs.date }}
verdate: ${{ steps.verdate.outputs.verdate }}
steps:
- uses: actions/checkout@v4
- name: Set env
run: echo ok go
- name: Get current version
id: version
run: echo "version=$(cat VERSION)" >> $GITHUB_OUTPUT
- name: Get current date
id: date
run: echo "date=$(date +'%y%m%d')" >> $GITHUB_OUTPUT
- name: Version + date str
id: verdate
run: echo "verdate='${{ steps.version.outputs.version }}.${{ steps.date.outputs.date }}'" >> $GITHUB_OUTPUT
test:
name: ${{ matrix.platform.distver }}-${{ matrix.pyver.name }}
name: ${{ matrix.platform.distver }}-${{ matrix.pyver.name }}-${{ matrix.variant }}
needs: activate
runs-on: ${{ matrix.platform.distver }}
defaults:
@@ -25,8 +48,9 @@ jobs:
strategy:
fail-fast: false
matrix:
variant: [novtk, all]
pyver: [
{ name: py311, distver: '3.11'},
# { name: py311, distver: '3.11'},
{ name: py312, distver: '3.12'}
]
platform: [
@@ -35,24 +59,28 @@ jobs:
{ name: macOS, distver: macos-latest, pkg_dir: 'osx-64' }
]
steps:
- uses: actions/checkout@v3
- name: Set up swap space
if: runner.os == 'Linux'
uses: pierotofy/set-swap-space@v1.0
with:
swap-size-gb: 10
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Download and extract MacOSX SDK
if: ${{ matrix.platform.name == 'macOS' }}
if: ${{ runner.os == 'macOS' }}
run: |
curl -o MacOSX10.13.sdk.tar.xz -L https://github.com/phracker/MacOSX-SDKs/releases/download/11.3/MacOSX10.13.sdk.tar.xz && \
tar xf MacOSX10.13.sdk.tar.xz && \
sudo mv -v MacOSX10.13.sdk /opt/ && \
ls /opt/
- uses: seanmiddleditch/gha-setup-ninja@master
curl -L https://github.com/phracker/MacOSX-SDKs/releases/download/11.3/MacOSX10.13.sdk.tar.xz | tar -xvJf - -C /Users/runner/work/
- uses: mamba-org/setup-micromamba@v1 # https://github.com/mamba-org/setup-micromamba
with:
environment-file: conda/environment.build.yml
environment-name: test-env
create-args: >-
python=${{ matrix.pyver.distver }}
python=3.11
anaconda-client
boa
- name: create conda package dist dir
run: |
@@ -60,8 +88,10 @@ jobs:
- name: build & test ifcopenshell
run: |
boa build . --python ${{ matrix.pyver.distver }} --no-remove-work-dir --output-folder '${{ github.workspace }}/dist'
conda mambabuild . --python ${{ matrix.pyver.distver }} --no-remove-work-dir --output-folder '${{ github.workspace }}/dist' --variants='{variant: ${{ matrix.variant }}}'
working-directory: ./conda
env:
VERSION_OVERRIDE: ${{ needs.activate.outputs.verdate }}
- name: upload to anaconda
if: ${{ matrix.platform.name == 'win' }}
@@ -71,4 +101,4 @@ jobs:
- name: upload to anaconda
if: ${{ matrix.platform.name != 'win' }}
run: |
anaconda -t ${{ secrets.ANACONDA_TOKEN }} upload --force --user ifcopenshell ${{ github.workspace }}/dist/${{ matrix.platform.pkg_dir }}/*.tar.bz2
anaconda -t ${{ secrets.ANACONDA_TOKEN }} upload --force --user ifcopenshell ${{ github.workspace }}/dist/${{ matrix.platform.pkg_dir }}/*.tar.bz2
@@ -6,7 +6,6 @@ on:
tags:
- v0.**
jobs:
activate:
@@ -27,19 +27,19 @@ jobs:
pyver: [py39, py310, py311, py312]
config:
- {
name: "Windows Build",
name: "Windows 64bit",
short_name: win64,
}
- {
name: "Linux Build",
name: "Linux 64bit",
short_name: linux64
}
- {
name: "MacOS Build",
name: "MacOS Intel 64bit",
short_name: macos64
}
- {
name: "MacOS ARM Build",
name: "MacOS Silicon 64bit",
short_name: macosm164
}
steps:
+8 -4
View File
@@ -22,19 +22,23 @@ jobs:
pyver: [py39, py310, py311, py312]
config:
- {
name: "Windows Build",
name: "Windows 64bit",
short_name: win64,
}
- {
name: "Linux Build",
name: "Windows 32bit",
short_name: win32,
}
- {
name: "Linux 64bit",
short_name: linux64
}
- {
name: "MacOS Build",
name: "MacOS Intel 64bit",
short_name: macos64
}
- {
name: "MacOS ARM Build",
name: "MacOS Silicon 64bit",
short_name: macosm164
}
steps:
-13
View File
@@ -1,21 +1,8 @@
name: ci-ifcpatch-pypi
on:
schedule:
# ┌───────────── minute (0 - 59)
# │ ┌───────────── hour (0 - 23)
# │ │ ┌───────────── day of the month (1 - 31)
# │ │ │ ┌───────────── month (1 - 12 or JAN-DEC)
# │ │ │ │ ┌───────────── day of the week (0 - 6 or SUN-SAT)
# * * * * *
- cron: "0 0 18 * *"
workflow_dispatch:
env:
major: 0
minor: 0
name: ifcopenshell
jobs:
activate:
runs-on: ubuntu-latest
+1 -1
View File
@@ -46,6 +46,6 @@ jobs:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: src/ifcsverchok/dist/ifcsverchok-${{steps.version.outputs.version}}.${{steps.date.outputs.date}}.zip
asset_name: ifcsverchok-${{steps.version.outputs.version}}.${{steps.date.outputs.date}}.zip
release_name: "ifcsverchok-${{steps.version.outputs.version}}.${{steps.date.outputs.date}}"
release_name: "ifcsverchok-${{steps.version.outputs.version}}.${{steps.date.outputs.date}} (unstable)"
tag: "ifcsverchok-${{steps.version.outputs.version}}.${{steps.date.outputs.date}}"
overwrite: true
-13
View File
@@ -1,21 +1,8 @@
name: ci-ifctester-pypi
on:
schedule:
# ┌───────────── minute (0 - 59)
# │ ┌───────────── hour (0 - 23)
# │ │ ┌───────────── day of the month (1 - 31)
# │ │ │ ┌───────────── month (1 - 12 or JAN-DEC)
# │ │ │ │ ┌───────────── day of the week (0 - 6 or SUN-SAT)
# * * * * *
- cron: "0 0 18 * *"
workflow_dispatch:
env:
major: 0
minor: 0
name: ifcopenshell
jobs:
activate:
runs-on: ubuntu-latest
@@ -1,34 +0,0 @@
name: Restart failed daily conda builds
on:
workflow_run:
workflows: [ ci-ifcopenshell-conda-daily ]
types:
- completed
env:
REPO_OWNER: IfcOpenShell/IfcOpenShell
MY_WORKFLOW: ci-ifcopenshell-conda-daily
jobs:
restart-failed-runs:
runs-on: ubuntu-latest
if: ${{ github.event.workflow_run.conclusion == 'failure' }}
steps:
- name: checkout
uses: actions/checkout@v3
- name: Use python 3.11
uses: actions/setup-python@v2
with:
python-version: 3.11
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install requests
- name: Check for failed runs
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
WORKFLOW_RUN_ID: ${{ github.event.workflow_run.id }}
run: |
python check_repo_ci_jobs.py
working-directory: .github/workflows
+1 -1
View File
@@ -30,7 +30,7 @@ For more information, see:
| Service | Status |
| ----------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| Anaconda Daily Build | [![Anaconda-Server Badge](https://img.shields.io/conda/vn/ifcopenshell/ifcopenshell)](https://anaconda.org/ifcopenshell/ifcopenshell) |
| Anaconda v0.7.0 Stable | [![Anaconda-Server Badge](https://img.shields.io/conda/vn/conda-forge/ifcopenshell)](https://anaconda.org/conda-forge/ifcopenshell) |
| Anaconda v0.8.0 Stable | [![Anaconda-Server Badge](https://img.shields.io/conda/vn/conda-forge/ifcopenshell)](https://anaconda.org/conda-forge/ifcopenshell) |
| PyPi Daily Build | [![PyPi Badge](https://img.shields.io/pypi/v/ifcopenshell)](https://pypi.org/project/ifcopenshell/) |
| ArchLinux AUR Package Stable | [![AUR Badge](https://img.shields.io/aur/version/ifcopenshell)](https://aur.archlinux.org/packages/ifcopenshell) |
| ArchLinux AUR Package git | [![AUR Badge](https://img.shields.io/aur/version/ifcopenshell-git)](https://aur.archlinux.org/packages/ifcopenshell-git) |
+1 -1
View File
@@ -12,7 +12,7 @@
<projectUrl>https://github.com/IfcOpenShell/IfcOpenShell</projectUrl>
<iconUrl>https://rawcdn.githack.com/IfcOpenShell/IfcOpenShell/c6ee1d1679d1298de0d7447ff108c22709c68e54/choco/blenderbim/blenderbim.png</iconUrl>
<!-- <copyright>Year Software Vendor</copyright> -->
<licenseUrl>https://github.com/IfcOpenShell/IfcOpenShell/blob/v0.7.0/COPYING</licenseUrl>
<licenseUrl>https://github.com/IfcOpenShell/IfcOpenShell/blob/v0.8.0/COPYING</licenseUrl>
<requireLicenseAcceptance>true</requireLicenseAcceptance>
<projectSourceUrl>https://github.com/IfcOpenShell/IfcOpenShell</projectSourceUrl>
<docsUrl>https://docs.blenderbim.org/</docsUrl>
+23
View File
@@ -0,0 +1,23 @@
import datetime
import os
def get_repo_tag_names():
git_return = os.popen("git tag -l").read()
return [tag_name for tag_name in git_return.split("\n") if tag_name]
def set_github_env_var(key: str, value: str):
env_file_path = os.getenv('GITHUB_ENV')
with open(env_file_path, "a") as env_file:
_ = env_file.write(f"{key}={value}" + "\n")
blenderbim_date_yesterday = (datetime.datetime.now() - datetime.timedelta(days=1)).strftime("%y%m%d")
for tag_name in get_repo_tag_names():
if blenderbim_date_yesterday in tag_name:
set_github_env_var(key="choco_release", value="do_choco_release")
set_github_env_var(key="target_release_tag", value=tag_name)
print("do_choco_release", end="")
break
+3 -14
View File
@@ -1,4 +1,4 @@
import datetime
import os
import re
import sys
from urllib import request
@@ -24,24 +24,14 @@ def get_latest_blender_version() -> list:
return re.findall(RE_BLENDER_VERSION_MIN_MAJ_PAT, html_txt)
URL_IFCOS_RELEASES = "https://github.com/IfcOpenShell/IfcOpenShell/releases"
URL_CHOCO_PACKAGE = "https://community.chocolatey.org/packages/blender"
URL_BLENDER_CMAKE = "https://raw.githubusercontent.com/blender/blender/{}/build_files/cmake/Modules/FindPythonLibsUnix.cmake"
RE_BLENDER_VERSION_MIN_MAJ = r"Latest Version.+<span>Blender (\d+\.\d+)\..+</span>"
RE_BLENDER_VERSION_MIN_MAJ_PAT = r"Latest Version.+<span>Blender (\d+\.\d+\.\d+)</span>"
RE_BLENDER_PYTHON_VERSION_MAJ_MIN = r"(?:set|SET)\(_PYTHON_VERSION_SUPPORTED (\d+\.\d+)\)"
RE_BLENDER_PYTHON_VERSION_MAJ_MIN = r"\(_PYTHON_VERSION_SUPPORTED (\d+\.\d+)\)"
if sys.argv[1] == "--do_choco_release?":
now = datetime.datetime.now()
blenderbim_date = (now - datetime.timedelta(days=1)).strftime("%y%m%d")
resp = request_repo_info(URL_IFCOS_RELEASES)
text = str(resp.read())
if blenderbim_date in text:
print("do_choco_release", end="")
elif sys.argv[1] == "--latest_blender_release_maj_min_pat?":
if sys.argv[1] == "--latest_blender_release_maj_min_pat?":
latest_blender_version = get_latest_blender_version()
if latest_blender_version:
print(latest_blender_version[0], end="")
@@ -86,4 +76,3 @@ elif sys.argv[1] == "--pyver?":
else:
print("[ERROR] could not determine pyver")
quit(1)
+2 -2
View File
@@ -17,8 +17,8 @@ topics = {
"install": {
"path": HERE_DIR / "tools" / "chocolateyinstall.ps1",
"env_vars": {
"url_blenderbim_py310_win_zip",
"sha256sum_blenderbim_py310_win_zip",
"url_blenderbim_py3x_win_zip",
"sha256sum_blenderbim_py3x_win_zip",
"latest_blender_version_maj_min",
},
},
+2 -2
View File
@@ -1,7 +1,7 @@
$ErrorActionPreference = 'Stop'
$url64 = 'url_blenderbim_py310_win_zip'
$checksum64 = 'sha256sum_blenderbim_py310_win_zip'
$url64 = 'url_blenderbim_py3x_win_zip'
$checksum64 = 'sha256sum_blenderbim_py3x_win_zip'
$checksumType64 = 'sha256'
$appDataUserDir = [System.Environment]::GetEnvironmentVariable('appdata')
+46 -30
View File
@@ -27,8 +27,6 @@ if(CCACHE_FOUND)
set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ccache)
endif()
project(IfcOpenShell VERSION 0.8.0)
cmake_policy(SET CMP0048 NEW)
cmake_policy(SET CMP0074 NEW)
cmake_policy(SET CMP0078 OLD)
@@ -82,6 +80,17 @@ option(WITH_RELATIONSHIP_VALIDATION "Build IfcConvert with option to validate ge
option(USERSPACE_PYTHON_PREFIX "Installs IfcPython for the current user only instead of system-wide." OFF)
option(ADD_COMMIT_SHA "Add commit sha and branch in version number, warning results in many rebuilds, requires git" OFF)
option(VERSION_OVERRIDE "Override the version defined in IfcParse.h with the file VERSION in the repository root" OFF)
if (VERSION_OVERRIDE)
file(READ "../VERSION" "RELEASE_VERSION_")
string(STRIP "${RELEASE_VERSION_}" RELEASE_VERSION)
message(STATUS "Detected version '${RELEASE_VERSION}'")
else()
set(RELEASE_VERSION "0.8.0")
endif()
project(IfcOpenShell VERSION ${RELEASE_VERSION})
if(MINIMAL_BUILD)
message(STATUS "Setting options for minimal build")
@@ -590,25 +599,27 @@ if(HDF5_SUPPORT)
else()
message(STATUS "Packaging hdf5 and zlib for conda distribution")
if(WIN32)
# Windows
set(zlib_post zlib)
set(lib_ext lib)
elseif(${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
# macOS
set(zlib_post libz)
set(lib_ext dylib)
else()
# linux
set(zlib_post libz)
set(lib_ext so)
endif()
set(HDF5_LIBRARIES
set(HDF5_LIBRARIES
"${HDF5_LIBRARY_DIR}/libhdf5_cpp.${lib_ext}"
"${HDF5_LIBRARY_DIR}/libhdf5.${lib_ext}"
"${HDF5_LIBRARY_DIR}/${zlib_post}.${lib_ext}"
)
else()
# linux and windows
# Find HDF5 package
find_package(HDF5 REQUIRED COMPONENTS C CXX)
# Find ZLIB package
find_package(ZLIB REQUIRED)
# Include directories
include_directories(${HDF5_INCLUDE_DIRS} ${ZLIB_INCLUDE_DIRS})
# Link libraries
set(HDF5_LIBRARIES ${HDF5_LIBRARIES} ${ZLIB_LIBRARIES})
message(STATUS "HDF5 libraries: ${HDF5_LIBRARIES}")
endif()
endif()
endif()
@@ -741,6 +752,7 @@ endif(MSVC)
include_directories(${INCLUDE_DIRECTORIES} ${OCC_INCLUDE_DIR} ${OPENCOLLADA_INCLUDE_DIRS}
${Boost_INCLUDE_DIRS} ${LIBXML2_INCLUDE_DIR} ${JSON_INCLUDE_DIR} ${HDF5_INCLUDE_DIR}
${EIGEN_DIR} ${CGAL_INCLUDE_DIR} ${GMP_INCLUDE_DIR} ${MPFR_INCLUDE_DIR} ${USD_INCLUDE_DIR}
${TBB_INCLUDE_DIR}
)
if(NOT SCHEMA_VERSIONS)
@@ -1095,22 +1107,26 @@ if(ADD_COMMIT_SHA)
find_package(Git)
if(GIT_FOUND)
message("git found: ${GIT_EXECUTABLE} with version ${GIT_VERSION_STRING}")
execute_process(
COMMAND ${GIT_EXECUTABLE} branch -a --contains HEAD
OUTPUT_VARIABLE git_branches
OUTPUT_STRIP_TRAILING_WHITESPACE
)
string(REPLACE "\n" ";" git_branch_list "${git_branches}")
if (VERSION_OVERRIDE)
set (git_branch ${RELEASE_VERSION})
else()
message("git found: ${GIT_EXECUTABLE} with version ${GIT_VERSION_STRING}")
execute_process(
COMMAND ${GIT_EXECUTABLE} branch -a --contains HEAD
OUTPUT_VARIABLE git_branches
OUTPUT_STRIP_TRAILING_WHITESPACE
)
string(REPLACE "\n" ";" git_branch_list "${git_branches}")
foreach(git_branch_candidate IN ITEMS ${git_branch_list})
string(REPLACE "*" "" git_branch_candidate_temp "${git_branch_candidate}")
string(STRIP "${git_branch_candidate_temp}" git_branch_candidate_2)
if(NOT git_branch_candidate_2 MATCHES "^HEAD$")
string(REPLACE "/" ";" git_branch_candidate_2_list "${git_branch_candidate_2}")
list(GET git_branch_candidate_2_list -1 git_branch)
endif()
endforeach()
foreach(git_branch_candidate IN ITEMS ${git_branch_list})
string(REPLACE "*" "" git_branch_candidate_temp "${git_branch_candidate}")
string(STRIP "${git_branch_candidate_temp}" git_branch_candidate_2)
if(NOT git_branch_candidate_2 MATCHES "^HEAD$")
string(REPLACE "/" ";" git_branch_candidate_2_list "${git_branch_candidate_2}")
list(GET git_branch_candidate_2_list -1 git_branch)
endif()
endforeach()
endif()
execute_process(
COMMAND ${GIT_EXECUTABLE} rev-parse --short HEAD
@@ -1177,7 +1193,7 @@ if(BUILD_IFCGEOM)
DESTINATION ${INCLUDEDIR}/ifcgeom_schema_agnostic
)
install(TARGETS ${IFCGEOM_SCHEMA_LIBRARIES} IfcGeom
install(TARGETS ${IFCGEOM_SCHEMA_LIBRARIES} ${kernel_libraries} IfcGeom
ARCHIVE DESTINATION ${LIBDIR}
LIBRARY DESTINATION ${LIBDIR}
RUNTIME DESTINATION ${BINDIR}
-1
View File
@@ -1 +0,0 @@
.env
+8 -6
View File
@@ -1,10 +1,13 @@
mkdir build && cd build
REM Remove dot from PY_VER for use in library name
REM From https://github.com/tpaviot/pythonocc-core/blob/master/ci/conda/bld.bat
set MY_PY_VER=%PY_VER:.=%
rem set LIBXML2="%LIBRARY_PREFIX%/lib/libxml2.lib"
set LIBXML2="%LIBRARY_PREFIX%/lib/libxml2.lib"
cmake -G "Ninja" ^
-D SCHEMA_VERSIONS="2x3;4;4x1;4x3;4x3_add1" ^
-D SCHEMA_VERSIONS="2x3;4;4x1;4x3_add2" ^
-D CMAKE_BUILD_TYPE:STRING=Release ^
-D CMAKE_INSTALL_PREFIX:FILEPATH="%LIBRARY_PREFIX%" ^
-D CMAKE_PREFIX_PATH:FILEPATH="%LIBRARY_PREFIX%" ^
@@ -39,11 +42,10 @@ cmake -G "Ninja" ^
-D Boost_INCLUDE_DIR:FILEPATH="%LIBRARY_PREFIX%\include" ^
-D Boost_USE_STATIC_LIBS:BOOL=OFF ^
-D CITYJSON_SUPPORT:BOOL=OFF ^
%SRC_DIR%/cmake
../cmake
if errorlevel 1 exit 1
:: Build and install
cmake --build . -- install
ninja install -j 1
if errorlevel 1 exit 1
+3 -5
View File
@@ -6,16 +6,14 @@
if [ "$(uname)" == "Darwin" ]; then
export FSUFFIX=dylib
export CFLAGS="$CFLAGS -Wl,-flat_namespace,-undefined,suppress"
export CXXFLAGS="$CXXFLAGS -Wl,-flat_namespace,-undefined,suppress"
export LDFLAGS="$LDFLAGS -Wl,-flat_namespace,-undefined,suppress"
elif [ "$(expr substr $(uname -s) 1 5)" == "Linux" ]; then
export FSUFFIX=so
fi
cmake -G Ninja \
-DSCHEMA_VERSIONS="2x3;4;4x1;4x3;4x3_add1" \
cmake ${CMAKE_ARGS} -G Ninja \
-DSCHEMA_VERSIONS="2x3;4;4x1;4x3_add2" \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX=$PREFIX \
${CMAKE_PLATFORM_FLAGS[@]} \
@@ -48,4 +46,4 @@ cmake -G Ninja \
ninja
ninja install -j 1
ninja install -j 1
+53 -6
View File
@@ -1,11 +1,58 @@
CONDA_BUILD_SYSROOT: # used for github actions conda daily build
- /Users/runner/work/MacOSX10.13.sdk # [osx]
variant:
- novtk
- all
occt:
- 7.7.1
- 7.8.1
c_compiler:
- vs2022 # [win]
- vs2022 # [win]
- gcc # [linux]
- clang # [osx]
c_stdlib:
- vs # [win]
- sysroot # [linux]
- macosx_deployment_target # [osx]
cxx_compiler:
- vs2022 # [win]
- vs2022 # [win]
- gcc # [linux]
- clangxx # [osx]
c_compiler_version:
- '12' # [linux]
- '16' # [osx]
cxx_compiler_version:
- '12' # [linux]
- '16' # [osx]
c_stdlib_version:
- '2.12' # [linux]
- '10.13' # [osx]
hdf5:
- 1.14.3
libboost_devel:
- '1.84'
libxml2:
- '2'
mpfr:
- '4'
gmp:
- '6' # [not win]
pin_run_as_build:
python:
min_pin: x.x
max_pin: x.x
zlib:
- '1'
target_platform:
- win-64 # [win]
- linux-64 # [linux]
- osx-64 # [osx]
macos_machine:
- x86_64-apple-darwin13.4.0
MACOSX_DEPLOYMENT_TARGET:
- '10.13'
MACOSX_SDK_VERSION:
- '10.13'
CONDA_BUILD_SYSROOT:
- "/Users/runner/work/MacOSX10.13.sdk" # [osx]
-58
View File
@@ -1,58 +0,0 @@
@echo off
:: This is a batch file to set the environment variables for the project
:: It is not necessary for conda compilation, but it provides you with type hints when working with the c++ libraries
:: OpenCascade, CGAL, Eigen, etc distributed using conda-forge in your IDE.
::
:: mamba env update -f environment.build.yml --prune
:: mamba activate ifcopenshell-build
::
:: Note!
:: You have to add a .env file next to this env.bat file where you set PREFIX=<path to your conda env>
set CONDA_BUILD=1
set MY_PY_VER=311
:: set this file's parent directory as a variable
set THIS_DIR=%~dp0
:: read the .env file located in THIS_DIR and set the environment variables.
:: the .env file should contain a line like this:
:: PREFIX=C:\Users\your_user_name\mambaforge3\envs\ifcopenshell-build
for /f "tokens=*" %%i in (%THIS_DIR%.env) do set %%i
set LIBRARY_PREFIX=%PREFIX%/Library
set CMAKE_PREFIX_PATH=%PREFIX%;%LIBRARY_PREFIX%/include;%LIBRARY_PREFIX%/lib;%LIBRARY_PREFIX%/bin
set OCC_LIBRARY_DIR=%LIBRARY_PREFIX%/lib
set OCC_INCLUDE_DIR=%LIBRARY_PREFIX%/include/opencascade
set CGAL_DIR=%LIBRARY_PREFIX%/lib/cmake/CGAL
set CGAL_INCLUDE_DIR=%LIBRARY_PREFIX%/include
set EIGEN_DIR=%LIBRARY_PREFIX%/include/eigen3
set LIBXML2_INCLUDE_DIR=%LIBRARY_PREFIX%/include/libxml2
set LIBXML2_LIBRARIES=%LIBRARY_PREFIX%/lib/libxml2.lib
set Boost_LIBRARY_DIR=%LIBRARY_PREFIX%/lib
set Boost_INCLUDE_DIR=%LIBRARY_PREFIX%/include
set Boost_USE_STATIC_LIBS=OFF
set GMP_INCLUDE_DIR=%LIBRARY_PREFIX%/include
set GMP_LIBRARY_DIR=%LIBRARY_PREFIX%/lib
set MPFR_LIBRARY_DIR=%LIBRARY_PREFIX%/lib
set HDF5_INCLUDE_DIR=%LIBRARY_PREFIX%/include
set HDF5_LIBRARY_DIR=%LIBRARY_PREFIX%/lib
set JSON_INCLUDE_DIR=%LIBRARY_PREFIX%/include
set HDF5_SUPPORT=ON
set BUILD_IFCPYTHON=ON
set BUILD_IFCGEOM=ON
set COLLADA_SUPPORT=OFF
set BUILD_EXAMPLES=OFF
set BUILD_GEOMSERVER=OFF
set GLTF_SUPPORT=ON
set BUILD_CONVERT=ON
set BUILD_IFCMAX=OFF
set IFCXML_SUPPORT=ON
set PYTHON_EXECUTABLE=%PREFIX%/python.exe
set PYTHON_LIBRARY=%PREFIX%/libs/python%MY_PY_VER%.lib
-17
View File
@@ -1,17 +0,0 @@
name: ifcopenshell-build
channels:
- conda-forge
dependencies:
- boa
- boost-cpp
- occt
- libxml2
- cgal-cpp
- hdf5
- mpfr
- nlohmann_json
- swig
- zlib
# These needs to be installed per platform
#- sel(unix): gmp
#- sel(win): mpir
+303
View File
@@ -0,0 +1,303 @@
{% set version = environ.get('VERSION_OVERRIDE', '0.8.0') %}
{% set build = 0 %}
# Higher number -> Always prioritize "novtk" variant over "all" variant
{% set build = build + 200 %} # [variant == "novtk"]
{% set build = build + 100 %} # [variant == "all"]
package:
name: ifcopenshell
version: {{ version }}
source:
path: ../
build:
number: {{ build }}
skip: true # [py<39]
binary_relocation: false # [osx]
string: py{{ CONDA_PY }}_{{ variant }}_h{{ PKG_HASH }}_{{ build }}
run_exports:
- {{ pin_subpackage('ifcopenshell', max_pin='x.x.x') }} *{{ variant }}*
requirements:
build:
- cmake
- ninja
- swig >=4.1.1
- {{ stdlib("c") }}
- {{ compiler('c') }}
- {{ compiler('cxx') }}
host:
- python
- libboost-devel
- occt *=*{{ variant }}*
- libxml2
- cgal-cpp
- hdf5
- eigen
- mpfr
- nlohmann_json
- gmp # [unix]
- mpir # [win]
- zlib
run:
- python
- {{ pin_compatible('occt', max_pin='x.x.x') }} *{{ variant }}*
- {{ pin_compatible('cgal-cpp', max_pin='x.x') }}
test:
imports:
- ifcopenshell
about:
home: https://ifcopenshell.org
license: LGPL-3.0-or-later
license_file: COPYING
summary: 'IfcOpenShell is a library to support the IFC file format'
description: |
IfcOpenShell
============
IfcOpenShell is an open source ([LGPL]) software library for working with Industry Foundation Classes ([IFC]). Complete
parsing support is provided for [IFC2x3 TC1], [IFC4 Add2 TC1], IFC4x1, IFC4x3, and IFC4x3. Extensive geometric support
is implemented for the IFC releases [IFC2x3 TC1] and [IFC4 Add2 TC1]. Extending with support for arbitrary IFC schemas
is possible at compile-time when using C++ and at run-time when using Python.
In addition to a C++ and Python API, IfcOpenShell comes with an ecosystem of tools, notably including IfcConvert (an
application to convert IFC models to
other formats), the BlenderBIM Add-on (an add-on to Blender providing a graphical IFC authoring platform), and many
other libraries, CLI apps, and more. Support is also provided for auxiliary standards such as BCF and IDS.
For more information, see:
* [IfcOpenShell Website](http://ifcopenshell.org)
* [IfcOpenShell Documentation](http://blenderbim.org/docs-python)
* [IfcOpenShell C++ Installation](https://blenderbim.org/docs-python/ifcopenshell/installation.html)
* [IfcOpenShell Python Installation](https://blenderbim.org/docs-python/ifcopenshell-python/installation.html)
* [IfcOpenShell Python Hello World Tutorial](https://blenderbim.org/docs-python/ifcopenshell-python/hello_world.html)
* [BlenderBIM Add-on Website](https://blenderbim.org)
* [BlenderBIM Add-on Documentation](http://blenderbim.org/docs)
* [Add-on Installation](https://blenderbim.org/docs/users/installation.html)
* [Exploring an IFC model](https://blenderbim.org/docs/users/exploring_an_ifc_model.html)
<table>
<thead>
<tr>
<th>Service</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<tr>
<td>Anaconda Daily Build</td>
<td><a href="https://anaconda.org/ifcopenshell/ifcopenshell"><img src="https://img.shields.io/conda/vn/ifcopenshell/ifcopenshell" alt="Anaconda-Server Badge"></a></td>
</tr>
<tr>
<td>Anaconda v0.8.0 Stable</td>
<td><a href="https://anaconda.org/conda-forge/ifcopenshell"><img src="https://img.shields.io/conda/vn/conda-forge/ifcopenshell" alt="Anaconda-Server Badge"></a></td>
</tr>
<tr>
<td>PyPi Daily Build</td>
<td><a href="https://pypi.org/project/ifcopenshell/"><img src="https://img.shields.io/pypi/v/ifcopenshell" alt="PyPi Badge"></a></td>
</tr>
<tr>
<td>ArchLinux AUR Package Stable</td>
<td><a href="https://aur.archlinux.org/packages/ifcopenshell"><img src="https://img.shields.io/aur/version/ifcopenshell" alt="AUR Badge"></a></td>
</tr>
<tr>
<td>ArchLinux AUR Package git</td>
<td><a href="https://aur.archlinux.org/packages/ifcopenshell-git"><img src="https://img.shields.io/aur/version/ifcopenshell-git" alt="AUR Badge"></a></td>
</tr>
<tr>
<td>BlenderBIM Add-on Chocolatey (under moderation)</td>
<td><a href="https://community.chocolatey.org/packages/blenderbim-nightly/"><img src="https://img.shields.io/chocolatey/v/blenderbim-nightly" alt="Chocolatey Badge"></a></td>
</tr>
<tr>
<td>Sponsor development on OpenCollective</td>
<td><a href="https://opencollective.com/opensourcebim/"><img src="https://opencollective.com/opensourcebim/tiers/badge.svg" alt="Financial Contributors"></a></td>
</tr>
<tr>
<td>Docker hub</td>
<td><a href="https://hub.docker.com/r/aecgeeks/ifcopenshell"><img src="https://img.shields.io/docker/pulls/aecgeeks/ifcopenshell" alt="Docker Pulls"></a></td>
</tr>
</tbody>
</table>
Contents
--------
Those marked with an asterisk are part of IfcOpenShell.
<table>
<thead>
<tr>
<th>Name</th>
<th>Description</th>
<th>License</th>
</tr>
</thead>
<tbody>
<tr>
<td>bcf</td>
<td>Library to read and write BCF-XML and query OpenCDE BCF-API modules</td>
<td>LGPL-3.0-or-later</td>
</tr>
<tr>
<td>blenderbim</td>
<td>Add-on to Blender providing a graphical native IFC authoring platform</td>
<td>GPL-3.0-or-later</td>
</tr>
<tr>
<td>bsdd</td>
<td>Library to query the bSDD API</td>
<td>LGPL-3.0-or-later</td>
</tr>
<tr>
<td>ifc2ca</td>
<td>Utility to convert IFC structural analysis models to Code_Aster</td>
<td>LGPL-3.0-or-later</td>
</tr>
<tr>
<td>ifc4d</td>
<td>Convert to and from IFC and project management software</td>
<td>LGPL-3.0-or-later</td>
</tr>
<tr>
<td>ifc5d</td>
<td>Report and optimise cost information from IFC</td>
<td>LGPL-3.0-or-later</td>
</tr>
<tr>
<td>ifcbimtester</td>
<td>Wrapper for Gherkin based unit testing for IFC models</td>
<td>LGPL-3.0-or-later</td>
</tr>
<tr>
<td>ifcblender</td>
<td>Historic Blender IFC import add-on</td>
<td>LGPL-3.0-or-later*</td>
</tr>
<tr>
<td>ifccityjson</td>
<td>Convert CityJSON to IFC</td>
<td>LGPL-3.0-or-later</td>
</tr>
<tr>
<td>ifcclash</td>
<td>Clash detection library and CLI app</td>
<td>LGPL-3.0-or-later</td>
</tr>
<tr>
<td>ifccobie</td>
<td>Extract IFC data for COBie handover requirements</td>
<td>LGPL-3.0-or-later</td>
</tr>
<tr>
<td>ifcconvert</td>
<td>CLI app to convert IFC to many other formats</td>
<td>LGPL-3.0-or-later*</td>
</tr>
<tr>
<td>ifccsv</td>
<td>Library and CLI app to export and import schedules from IFC</td>
<td>LGPL-3.0-or-later</td>
</tr>
<tr>
<td>ifcdiff</td>
<td>Compare changes between IFC models</td>
<td>LGPL-3.0-or-later</td>
</tr>
<tr>
<td>ifcfm</td>
<td>Extract IFC data for FM handover requirements</td>
<td>LGPL-3.0-or-later</td>
</tr>
<tr>
<td>ifcgeom</td>
<td>Internal library for IfcOpenShell</td>
<td>LGPL-3.0-or-later*</td>
</tr>
<tr>
<td>ifcgeom_schema_agnostic</td>
<td>Internal library for IfcOpenShell</td>
<td>LGPL-3.0-or-later*</td>
</tr>
<tr>
<td>ifcgeomserver</td>
<td>Internal library for IfcOpenShell</td>
<td>LGPL-3.0-or-later*</td>
</tr>
<tr>
<td>ifcjni</td>
<td>Internal library for IfcOpenShell</td>
<td>LGPL-3.0-or-later*</td>
</tr>
<tr>
<td>ifcmax</td>
<td>Historic extension for IFC support in 3DS Max</td>
<td>LGPL-3.0-or-later*</td>
</tr>
<tr>
<td>ifcopenshell-python</td>
<td>Python library for IFC manipulation</td>
<td>LGPL-3.0-or-later*</td>
</tr>
<tr>
<td>ifcparse</td>
<td>Internal library for IfcOpenShell</td>
<td>LGPL-3.0-or-later*</td>
</tr>
<tr>
<td>ifcpatch</td>
<td>Utility to run pre-packaged scripts to manipulate IFCs</td>
<td>LGPL-3.0-or-later</td>
</tr>
<tr>
<td>ifcsverchok</td>
<td>Blender Add-on for visual node programming with IFC</td>
<td>GPL-3.0-or-later</td>
</tr>
<tr>
<td>ifctester</td>
<td>Library, CLI and webapp for IDS model auditing</td>
<td>LGPL-3.0-or-later</td>
</tr>
<tr>
<td>ifcwrap</td>
<td>Internal library for IfcOpenShell</td>
<td>LGPL-3.0-or-later*</td>
</tr>
<tr>
<td>qtviewer</td>
<td>Internal library for IfcOpenShell</td>
<td>LGPL-3.0-or-later*</td>
</tr>
<tr>
<td>serializers</td>
<td>Internal library for IfcOpenShell</td>
<td>LGPL-3.0-or-later*</td>
</tr>
</tbody>
</table>
[LGPL]: https://github.com/IfcOpenShell/IfcOpenShell/tree/master/COPYING.LESSER "LGPL-3.0-or-later"
[IFC]: https://technical.buildingsmart.org/standards/ifc/ "IFC"
[IFC2x3 TC1]: https://standards.buildingsmart.org/IFC/RELEASE/IFC2x3/TC1/HTML/ "IFC2x3 TC1"
[IFC4 Add2 TC1]: https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/ "IFC4 Add2 TC1"
[Visual Studio]: https://www.visualstudio.com/ "Visual Studio"
[Visual C++ Build Tools]: http://landinghub.visualstudio.com/visual-cpp-build-tools "Visual C++ Build Tools"
[MSYS2]: https://msys2.github.io/ "MSYS2"
[win/readme.md]: https://github.com/IfcOpenShell/IfcOpenShell/tree/master/win/readme.md "win/readme.md"
[nix/build-all.py]: https://github.com/IfcOpenShell/IfcOpenShell/tree/master/nix/build-all.py "nix/build-all.py"
doc_url: https://ifcopenshell.org/
dev_url: https://github.com/IfcOpenShell/IfcOpenShell
extra:
recipe-maintainers:
- adrianinsaval
- looooo
- Krande
-64
View File
@@ -1,64 +0,0 @@
context:
name: ifcopenshell
version: 0.8.0alpha1
package:
name: '{{ name|lower }}'
version: '{{ version }}'
source:
path: ..
build:
number: 1
requirements:
build:
- "{{ compiler('c') }}"
- "{{ compiler('cxx') }}"
- ninja >=1.10.2
- cmake
- swig 4.1.1
host:
- python
- boost-cpp
- occt ==7.7.0
- libxml2
- cgal-cpp
- hdf5
- eigen
- mpfr
- sel(unix): gmp
- sel(win): mpir
- nlohmann_json
- zlib
run:
- python
- {{ 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
- hdf5
- eigen
- mpfr
- sel(unix): gmp
- sel(win): mpir
- nlohmann_json
- zlib
test:
imports:
- ifcopenshell
about:
home: https://ifcopenshell.org
license: LGPL-3.0-or-later
license_file: COPYING
summary: 'IfcOpenShell is a library to support the IFC file format'
description: |
IfcOpenShell is an open source (LGPL) software library for
working with the Industry Foundation Classes (IFC) file format.
doc_url: https://blenderbim.org/docs-python/
dev_url: https://github.com/IfcOpenShell/IfcOpenShell
+3 -1
View File
@@ -576,6 +576,7 @@ if "freetype" in targets:
download_url = "https://github.com/freetype/freetype",
download_name = "freetype2",
download_tool=download_tool_git,
revision="VER-2-11-1"
)
if USE_OCCT and "occ" in targets:
@@ -830,7 +831,8 @@ cmake_args = [
"-DJSON_INCLUDE_DIR=" f"{DEPS_DIR}/install/json",
"-DEIGEN_DIR=" f"{DEPS_DIR}/install/eigen-3.3.9",
"-DBoost_NO_BOOST_CMAKE=" "On",
"-DADD_COMMIT_SHA=" +("On" if ADD_COMMIT_SHA else "Off")
"-DADD_COMMIT_SHA=" +("On" if ADD_COMMIT_SHA else "Off"),
"-DVERSION_OVERRIDE=" +("On" if ADD_COMMIT_SHA else "Off")
]
if "wasm" in flags:
+1 -1
View File
@@ -1,6 +1,6 @@
package:
name: ifcopenshell
version: 0.7.0
version: 0.8.0
source:
path: IfcOpenShell
+1 -1
View File
@@ -1,7 +1,7 @@
from setuptools import setup, find_packages
setup(name='IfcOpenShell',
version='0.7.0',
version='0.8.0',
description='IfcOpenShell is an open source (LGPL) software library for working with the Industry Foundation Classes (IFC) file format.',
author='Thomas Krijnen',
author_email='thomas@aecgeeks.com',
+4 -1
View File
@@ -1,2 +1,5 @@
[tool.black]
line-length = 120
line-length = 120
[tool.pyright]
reportInvalidTypeForm = false
+1 -1
View File
@@ -10,7 +10,7 @@ type: software
authors:
- name: "IfcOpenShell contributors"
repository-code: >-
https://github.com/IfcOpenShell/IfcOpenShell/tree/v0.7.0/src/bcf
https://github.com/IfcOpenShell/IfcOpenShell/tree/v0.8.0/src/bcf
abstract: >-
Library to read and write BCF-XML and query OpenCDE
BCF-API modules
+26 -4
View File
@@ -1,4 +1,22 @@
VERSION:=`date '+%y%m%d'`
# BCF - BCF Python library
# Copyright (C) 2021 Prabhat Singh <singh01prabhat@gmail.com>
#
# This file is part of BCF.
#
# BCF is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BCF 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with BCF. If not, see <http://www.gnu.org/licenses/>.
VERSION:=$(shell cat ../../VERSION)
SED:=sed -i
ifeq ($(UNAME_S),Darwin)
SED:=sed -i '' -e
@@ -21,10 +39,14 @@ models:
.PHONY: dist
dist:
rm -rf dist
$(SED) "s/999999/$(VERSION)/" pyproject.toml
cp pyproject.toml pyproject.toml.bak
cp src/bcf/__init__.py __init__.py.bak
$(SED) 's/version = "0.0.0"/version = "$(VERSION)"/' pyproject.toml
$(SED) 's/version = "0.0.0"/version = "$(VERSION)"/' src/bcf/__init__.py
python -m build
$(SED) "s/$(VERSION)/999999/" pyproject.toml
mv pyproject.toml.bak pyproject.toml
mv __init__.py.bak src/bcf/__init__.py
# .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
+2 -2
View File
@@ -17,7 +17,7 @@ dependencies = [
"numpy",
"ifcopenshell",
]
version = "0.0.999999"
version = "0.0.0"
classifiers = [
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
"Operating System :: OS Independent",
@@ -138,4 +138,4 @@ max-attributes = 10
[tool.pylint.format]
expected-line-ending-format = "LF"
max-line-length = 120
max-line-length = 120
+2
View File
@@ -15,3 +15,5 @@
#
# You should have received a copy of the GNU Lesser General Public License
# along with BCF. If not, see <http://www.gnu.org/licenses/>.
__version__ = version = "0.0.0"
+1 -1
View File
@@ -10,7 +10,7 @@ type: software
authors:
- name: "IfcOpenShell contributors"
repository-code: >-
https://github.com/IfcOpenShell/IfcOpenShell/tree/v0.7.0/src/blenderbim
https://github.com/IfcOpenShell/IfcOpenShell/tree/v0.8.0/src/blenderbim
url: 'https://blenderbim.org/'
repository-artifact: 'https://blenderbim.org/download.html'
abstract: >-
+10 -9
View File
@@ -87,7 +87,6 @@ bump:
cd . && $(SED) "s/$(OLD)/$(NEW)/" Makefile
cd ../ifcopenshell-python/ && $(SED) "s/$(OLD)/$(NEW)/" Makefile
cd ../ifcopenshell-python/docs/ifcconvert/ && $(SED) "s/$(OLD)/$(NEW)/" installation.rst
cd ../ifcopenshell-python/docs/ifcopenshell-python/ && $(SED) "s/$(OLD)/$(NEW)/" installation.rst
.PHONY: dist
dist:
@@ -100,7 +99,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.8.0-90ae709-$(PLATFORM)64.zip
cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-$(PYNUMBER)-v$(VERSION)-39cd980-$(PLATFORM)64.zip
cd dist/working && unzip ifcopenshell-python*
cp -r dist/working/ifcopenshell dist/blenderbim/libs/site/packages/
@@ -113,13 +112,11 @@ endif
mkdir dist/working
cd dist/working && wget https://github.com/IfcOpenShell/IfcOpenShell/archive/refs/heads/v0.8.0.zip
cd dist/working && unzip v0.8.0.zip
# IfcOpenBot sometimes lags behind, so we hotfix the Python utilities
rm -rf dist/blenderbim/libs/site/packages/ifcopenshell/util/
rm -rf dist/blenderbim/libs/site/packages/ifcopenshell/api/
cp -r dist/working/IfcOpenShell-0.8.0/src/ifcopenshell-python/ifcopenshell/util dist/blenderbim/libs/site/packages/ifcopenshell/
cp -r dist/working/IfcOpenShell-0.8.0/src/ifcopenshell-python/ifcopenshell/api dist/blenderbim/libs/site/packages/ifcopenshell/
cp dist/working/IfcOpenShell-0.8.0/src/ifcopenshell-python/ifcopenshell/*.py dist/blenderbim/libs/site/packages/ifcopenshell/
cp dist/working/IfcOpenShell-0.8.0/src/ifcopenshell-python/ifcopenshell/express/rule_executor.py dist/blenderbim/libs/site/packages/ifcopenshell/express/
# IfcOpenBot sometimes lags behind, so we only use its binaries
mv dist/blenderbim/libs/site/packages/ifcopenshell/*ifcopenshell_wrapper* dist/working/
rm -rf dist/blenderbim/libs/site/packages/ifcopenshell
cp -r dist/working/IfcOpenShell-0.8.0/src/ifcopenshell-python/ifcopenshell dist/blenderbim/libs/site/packages/
mv dist/working/*ifcopenshell_wrapper* dist/blenderbim/libs/site/packages/ifcopenshell/
# Provides bcf functionality
cp -r dist/working/IfcOpenShell-0.8.0/src/bcf/src/bcf dist/blenderbim/libs/site/packages/
# Provides IFCClash functionality
@@ -213,6 +210,10 @@ endif
cd dist/working && . env/bin/activate && $(PIP) install lark --target=./site-packages
# Required by IFC4D
cd dist/working && . env/bin/activate && $(PIP) install PyP6Xer --target=./site-packages
# Required by Web module
cd dist/working && . env/bin/activate && $(PIP) install python-socketio[asyncio_client] --target=./site-packages
cd dist/working && . env/bin/activate && $(PIP) install aiohttp --target=./site-packages
cp -r dist/working/site-packages/* dist/blenderbim/libs/site/packages/
rm -rf dist/working
+15 -2
View File
@@ -27,12 +27,14 @@ import sys
IN_BLENDER = sys.modules.get("bpy", None)
if IN_BLENDER:
import bpy
import addon_utils
import platform
import traceback
import webbrowser
import addon_utils
from collections import deque
# NOTE: bl_info is superseded by blender_manifest.toml
# if addon is installed as an extension (Blender 4.2+)
bl_info = {
"name": "BlenderBIM",
"description": "Transforms Blender into a native Building Information Model authoring platform using IFC.",
@@ -92,7 +94,6 @@ if IN_BLENDER:
sys.path.insert(0, os.path.join(os.path.dirname(os.path.realpath(__file__)), "libs", "site", "packages"))
try:
import blenderbim.bim
import ifcopenshell.api
def log_api(usecase_path, ifc_file, settings):
@@ -107,9 +108,21 @@ if IN_BLENDER:
ifcopenshell.api.add_pre_listener("*", "action_logger", log_api)
def register():
global BLENDER_PACKAGE_NAME, __name__, __package__
BLENDER_PACKAGE_NAME = __name__
if bpy.app.version >= (4, 2, 0):
sys.modules["blenderbim"] = sys.modules[__name__]
__name__ = "blenderbim"
# Only renaming __name__ is actually required to make imports work.
# We renaming __package__ just for consistency.
__package__ = "blenderbim"
import blenderbim.bim
blenderbim.bim.register()
def unregister():
import blenderbim.bim
blenderbim.bim.unregister()
except:
+2 -5
View File
@@ -84,6 +84,7 @@ modules = {
"debug": None,
"ifcgit": None,
"covering": None,
"web": 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,
@@ -130,7 +131,6 @@ classes = [
prop.GlobalId,
prop.BIMObjectProperties,
prop.BIMCollectionProperties,
prop.BIMMaterialProperties,
prop.BIMMeshProperties,
prop.BIMFacet,
prop.BIMFilterGroup,
@@ -141,6 +141,7 @@ classes = [
# Tabs panel
ui.BIM_PT_tabs,
# Project overview
ui.BIM_PT_tab_new_project_wizard,
ui.BIM_PT_tab_project_info,
ui.BIM_PT_tab_spatial_decomposition,
ui.BIM_PT_tab_project_setup,
@@ -223,8 +224,6 @@ def register():
bpy.types.Screen.BIMTabProperties = bpy.props.PointerProperty(type=prop.BIMTabProperties)
bpy.types.Collection.BIMCollectionProperties = bpy.props.PointerProperty(type=prop.BIMCollectionProperties)
bpy.types.Object.BIMObjectProperties = bpy.props.PointerProperty(type=prop.BIMObjectProperties)
bpy.types.Material.BIMObjectProperties = bpy.props.PointerProperty(type=prop.BIMObjectProperties)
bpy.types.Material.BIMMaterialProperties = bpy.props.PointerProperty(type=prop.BIMMaterialProperties)
bpy.types.Mesh.BIMMeshProperties = bpy.props.PointerProperty(type=prop.BIMMeshProperties)
bpy.types.Curve.BIMMeshProperties = bpy.props.PointerProperty(type=prop.BIMMeshProperties)
bpy.types.Camera.BIMMeshProperties = bpy.props.PointerProperty(type=prop.BIMMeshProperties)
@@ -283,8 +282,6 @@ def unregister():
del bpy.types.Scene.BIMProperties
del bpy.types.Collection.BIMCollectionProperties
del bpy.types.Object.BIMObjectProperties
del bpy.types.Material.BIMObjectProperties
del bpy.types.Material.BIMMaterialProperties
del bpy.types.Mesh.BIMMeshProperties
del bpy.types.Curve.BIMMeshProperties
del bpy.types.Camera.BIMMeshProperties
@@ -0,0 +1,103 @@
import argparse
from aiohttp import web
import socketio
import pystache
import json
sio_port = 8080 # default port
sio = socketio.AsyncServer(
cors_allowed_origins="*",
async_mode="aiohttp",
)
# sio.instrument(
# auth={
# "username": "admin",
# "password": "admin",
# }
# )
app = web.Application()
sio.attach(app)
blender_messages = {}
# Web namespace
class WebNamespace(socketio.AsyncNamespace):
def __init__(self, namespace):
super().__init__(namespace)
async def on_connect(self, sid, environ):
print(f"Web client connected: {sid}")
async def on_disconnect(self, sid):
print(f"Web client disconnected: {sid}")
async def on_web_operator(self, sid, data):
await sio.emit("web_operator", data["operator"], namespace="/blender", room=data["blenderId"])
# Blender namespace
class BlenderNamespace(socketio.AsyncNamespace):
def __init__(self, namespace):
super().__init__(namespace)
async def on_connect(self, sid, environ):
print(f"Blender client connected: {sid}")
# Notify web client about new connection
blender_messages[sid] = {}
await sio.emit("blender_connect", sid, namespace="/web")
async def on_disconnect(self, sid):
print(f"Blender client disconnected: {sid}")
# Remove client message and notify web client about disconnection
if sid in blender_messages:
del blender_messages[sid]
await sio.emit("blender_disconnect", sid, namespace="/web")
async def on_data(self, sid, data):
print(f"Data from Blender client {sid}")
# blender_messages[sid]["default_data"] = data
# await sio.emit("default_data", {"blenderId": sid, "data": data}, namespace="/web")
async def on_csv_data(self, sid, data):
print(f"CSV data from Blender client {sid}")
# Store the message and forward it to the web client
blender_messages[sid]["csv_data"] = data
await sio.emit("csv_data", {"blenderId": sid, "data": data}, namespace="/web")
# Attach namespaces
sio.register_namespace(WebNamespace("/web"))
sio.register_namespace(BlenderNamespace("/blender"))
# Define a route to render the index.html template
async def index(request):
with open("templates/index.html", "r") as f:
template = f.read()
html_content = pystache.render(template, {"port": sio_port})
return web.Response(text=html_content, content_type="text/html")
app.router.add_get("/", index)
app.router.add_static("/static/", path="./static", name="static")
def main():
global sio_port
parser = argparse.ArgumentParser(description="SocketIO server")
parser.add_argument("--host", type=str, default="127.0.0.1", help="Host to run the server on")
parser.add_argument("--port", type=int, default=8080, help="Port to run the server on")
args = parser.parse_args()
sio_port = args.port
web.run_app(app, host=args.host, port=sio_port)
if __name__ == "__main__":
main()
# web.run_app(app, host="127.0.0.1", port=sio_port)
@@ -0,0 +1,72 @@
body {
background-color: #252525;
color: #e0e0e0;
margin: 0;
font-family: Arial, sans-serif;
}
#container {
margin-top: 20px;
margin-left: 10px;
margin-right: 10px;
margin-bottom: 20px
}
.table-container {
margin-bottom: 40px;
}
.csv-table {
margin-top: 10px;
}
h3 {
margin-top: 0;
}
nav {
background-color: #121212;
padding: 1em 0;
display: flex;
align-items: center;
position: relative;
}
nav .logo {
margin-left: 40px;
height: 40px;
/* Adjust height as needed */
}
nav ul {
list-style-type: none;
margin: 0;
margin-left: 1px;
display: flex;
flex-grow: 1;
justify-content: left;
}
nav ul li {
margin-right: 50px;
}
nav ul li a {
text-decoration: none;
color: #fff;
font-size: 1.2em;
}
nav ul li a:hover,
nav ul li a.active {
color: #3fb449;
}
nav .bottom-bar {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 2px;
background-color: #25682a;
}
@@ -0,0 +1,220 @@
// keeps track of blenders connected in form of
// {shown:bool, filename:string, headers:array}
const connectedClients = {};
let socket;
// Document ready function
$(document).ready(function () {
connectSocket();
});
// Function to connect to Socket.IO server
function connectSocket() {
const url = "ws://localhost:" + SOCKET_PORT + "/web";
socket = io(url);
console.log("socket: ", socket);
// Register socket event handlers
socket.on("blender_connect", handleBlenderConnect);
socket.on("blender_disconnect", handleBlenderDisconnect);
socket.on("csv_data", handleCsvData);
}
// Function to handle 'blender_connect' event
function handleBlenderConnect(blenderId) {
console.log("blender_connect: ", blenderId);
if (!connectedClients.hasOwnProperty(blenderId)) {
connectedClients[blenderId] = { shown: false, ifc_file: "" };
}
}
// Function to handle 'blender_disconnect' event
function handleBlenderDisconnect(blenderId) {
console.log("blender_disconnect: ", blenderId);
if (connectedClients.hasOwnProperty(blenderId)) {
delete connectedClients[blenderId];
removeTableElement(blenderId);
}
}
// Function to handle 'csv_data' event
function handleCsvData(data) {
const blenderId = data["blenderId"];
const csvData = data["data"]["data"];
const filename = data["data"]["IFC_File"];
console.log(data);
if (connectedClients.hasOwnProperty(blenderId)) {
if (!connectedClients[blenderId].shown) {
connectedClients[blenderId] = {
shown: true,
ifc_file: filename,
headers: [],
};
addTableElement(blenderId, csvData, filename);
} else {
updateTableElement(blenderId, csvData, filename);
}
} else {
connectedClients[blenderId] = {
shown: true,
ifc_file: filename,
headers: [],
};
addTableElement(blenderId, csvData, filename);
}
}
// Function to add a new table with data and filename
function addTableElement(blenderId, csvData, filename) {
// store headers of the csv data
const firstLine = csvData.indexOf("\n");
const csvHeaders = csvData.substring(0, firstLine).split(",");
connectedClients[blenderId].headers = csvHeaders;
console.log(connectedClients[blenderId]);
const tableContainer = $("<div></div>")
.addClass("table-container")
.attr("id", "container-" + blenderId);
const tableTitle = $("<h3></h3>")
.attr("id", "title-" + blenderId)
.text(filename)
.css("margin-bottom", "10px");
const tableDiv = $("<div></div>")
.addClass("csv-table")
.attr("id", "table-" + blenderId);
tableContainer.append(tableTitle);
tableContainer.append(tableDiv);
$("#container").append(tableContainer);
function createHeaderMenu(definitions) {
var menu = [];
for (let column of definitions) {
// Create text element for checkbox
if (!column.hasOwnProperty("key")) {
column.visible = true;
}
let checkbox = document.createElement("span");
checkbox.textContent = column.visible ? "\u2611" : "\u2610"; // ☑ or ☐
// Build label
let label = document.createElement("span");
let title = document.createElement("span");
title.textContent = " " + column.title;
label.appendChild(checkbox);
label.appendChild(title);
// Create menu item
menu.push({
label: label,
action: function (e) {
// Prevent menu closing
e.stopPropagation();
// Toggle current column visibility
column.visible = !column.visible;
console.log(column.title, column.visible);
// Change menu item checkbox
if (column.visible) {
table.showColumn(column.title);
checkbox.textContent = "\u2611"; // ☑
} else {
table.hideColumn(column.title);
checkbox.textContent = "\u2610"; // ☐
}
},
});
}
return menu;
}
var table = new Tabulator("#table-" + blenderId, {
height: "400px",
resizableColumnGuide: true,
index: "GlobalId",
data: csvData,
importFormat: "csv",
autoColumns: true,
selectableRows: 1,
layout: "fitColumns",
autoColumnsDefinitions: function (definitions) {
menu = createHeaderMenu(definitions, table);
definitions.forEach((column) => {
column.visible = true;
column.headerMenu = menu;
});
return definitions;
},
});
table.on("rowSelected", function (row) {
var index = row.getIndex(); // the guid of the object
if (!index) {
console.log("No Global ID found");
return;
}
// table id is usually "table-BlenderId" so blender id is always
// after the first 6 characters
var tableId = row.getTable().element.id.substr(6);
const msg = {
blenderId: tableId,
operator: {
type: "selection",
GlobalId: index,
},
};
socket.emit("web_operator", msg);
});
}
// Function to update table and filename
function updateTableElement(blenderId, csvData, filename) {
// check if the headers are the same
// if yes, just replace the data
// else, replace the whole data and column definations
const firstLine = csvData.indexOf("\n");
const newHeaders = csvData.substring(0, firstLine).split(",");
const sameHeaders = compareHeaders(
connectedClients[blenderId].headers,
newHeaders
);
const table = Tabulator.findTable("#table-" + blenderId)[0];
if (table) {
if (sameHeaders) {
table.replaceData(csvData, { importFormat: "csv" });
} else {
table.setData(csvData);
connectedClients[blenderId].headers = newHeaders;
}
$("#title-" + blenderId).text(filename);
}
}
// Function to remove table element
function removeTableElement(blenderId) {
$("#container-" + blenderId).remove();
}
// Utility function to compare two csv header
function compareHeaders(headers1, headers2) {
if (headers1.length !== headers2.length) {
return false;
}
for (let i = 0; i < headers1.length; i++) {
if (headers1[i] !== headers2[i]) {
return false;
}
}
return true;
}
@@ -0,0 +1,38 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Web Client</title>
<link rel="stylesheet" href="/static/css/styles.css" />
<link
rel="stylesheet"
href="https://unpkg.com/tabulator-tables/dist/css/tabulator_site_dark.min.css"
/>
<script
type="text/javascript"
src="https://code.jquery.com/jquery-3.6.0.min.js"
></script>
<script
type="text/javascript"
src="https://unpkg.com/tabulator-tables/dist/js/tabulator.min.js"
></script>
<script
type="text/javascript"
src="https://cdn.socket.io/4.0.0/socket.io.min.js"
></script>
<script>
var SOCKET_PORT = {{port}};
</script>
<script defer src="./static/js/index.js"></script>
</head>
<body>
<nav>
<ul>
<li><a href="/">IFC Data</a></li>
</ul>
<div class="bottom-bar"></div>
</nav>
<div id="container"></div>
</body>
</html>
+5 -5
View File
@@ -89,7 +89,7 @@ class IfcExporter:
self.get_application_name(), tool.Blender.get_blenderbim_version()
)
def sync_all_objects(self, skip_unlinking=False) -> list[ifcopenshell.entity_instance]:
def sync_all_objects(self) -> list[ifcopenshell.entity_instance]:
results: list[ifcopenshell.entity_instance] = []
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(self.file)
for ifc_definition_id in list(IfcStore.id_map.keys()):
@@ -118,8 +118,8 @@ class IfcExporter:
continue
try:
if isinstance(obj, bpy.types.Material):
if tool.Ifc.has_changed_shading(obj):
blenderbim.core.style.update_style_colours(tool.Ifc, tool.Style, obj=obj)
# TODO: do we add materials to edited_objs?
continue
else:
element = tool.Ifc.get_entity(obj)
if element:
@@ -137,9 +137,9 @@ class IfcExporter:
return
bpy.ops.bim.update_representation(obj=obj.name)
def has_changed_materials(self, obj):
def has_changed_materials(self, obj: bpy.types.Object) -> bool:
checksum = obj.data.BIMMeshProperties.material_checksum
return checksum != str([s.id() for s in tool.Geometry.get_styles(obj) if s])
return checksum != tool.Geometry.get_material_checksum(obj)
def sync_object_placement(self, obj: bpy.types.Object) -> Union[ifcopenshell.entity_instance, None]:
element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
+8 -12
View File
@@ -51,10 +51,8 @@ def name_callback(obj, data):
return
if isinstance(obj, bpy.types.Material):
if obj.BIMObjectProperties.ifc_definition_id:
IfcStore.get_file().by_id(obj.BIMObjectProperties.ifc_definition_id).Name = obj.name
if obj.BIMMaterialProperties.ifc_style_id:
IfcStore.get_file().by_id(obj.BIMMaterialProperties.ifc_style_id).Name = obj.name
if ifc_definition_id := obj.BIMStyleProperties.ifc_definition_id:
IfcStore.get_file().by_id(ifc_definition_id).Name = obj.name
refresh_ui_data()
return
@@ -86,11 +84,6 @@ def name_callback(obj, data):
refresh_ui_data()
def color_callback(obj, data):
if obj.BIMMaterialProperties.ifc_style_id:
tool.Ifc.edit(obj)
def active_object_callback():
refresh_ui_data()
update_bim_tool_props()
@@ -193,6 +186,8 @@ def refresh_ui_data():
tool.Ifc.get().clear_cache()
bpy.context.scene.DocProperties.should_draw_decorations = bpy.context.scene.DocProperties.should_draw_decorations
if bpy.context.scene.WebProperties.is_connected:
tool.Web.send_webui_data()
@persistent
@@ -296,12 +291,13 @@ def load_post(scene):
ifcopenshell.api.owner.settings.get_application = get_application
AuthoringData.type_thumbnails = {}
if not bpy.context.preferences.addons["blenderbim"].preferences.should_setup_toolbar:
preferences = tool.Blender.get_addon_preferences()
if not preferences.should_setup_toolbar:
tool.Blender.unregister_toolbar()
if bpy.context.preferences.addons["blenderbim"].preferences.should_setup_workspace:
if preferences.should_setup_workspace:
if "BIM" in bpy.data.workspaces:
if bpy.context.preferences.addons["blenderbim"].preferences.activate_workspace:
if preferences.activate_workspace:
bpy.context.window.workspace = bpy.data.workspaces["BIM"]
else:
bpy.ops.workspace.append_activate(idname="BIM", filepath=os.path.join(cwd, "data", "workspace.blend"))
+98 -71
View File
@@ -32,12 +32,29 @@ import blenderbim.bim.handler
import blenderbim.tool as tool
from pathlib import Path
from blenderbim.tool.brick import BrickStore
from typing import Set, Union, Optional
from typing import Set, Union, Optional, TypedDict, Callable
IFC_CONNECTED_TYPE = Union[bpy.types.Material, bpy.types.Object]
class OperationData(TypedDict):
id: int
guid: Union[str, None]
obj: str
class Operation(TypedDict):
rollback: Callable
commit: Callable
data: OperationData
class TransactionStep(TypedDict):
key: str
operations: list[Operation]
class IfcStore:
path: str = ""
file: Optional[ifcopenshell.file] = None
@@ -55,9 +72,9 @@ class IfcStore:
library_file: Optional[ifcopenshell.file] = None
current_transaction = ""
last_transaction = ""
history = []
future = []
schema_identifiers = ["IFC4", "IFC2X3", "IFC4X3"]
history: list[TransactionStep] = []
future: list[TransactionStep] = []
schema_identifiers = ["IFC4", "IFC2X3", "IFC4X3_ADD2"]
session_files: dict[str, ifcopenshell.file] = {}
@staticmethod
@@ -77,7 +94,7 @@ class IfcStore:
IfcStore.last_transaction = ""
IfcStore.history = []
IfcStore.future = []
IfcStore.schema_identifiers = ["IFC4", "IFC2X3", "IFC4X3"]
IfcStore.schema_identifiers = ["IFC4", "IFC2X3", "IFC4X3_ADD2"]
IfcStore.session_files = {}
@staticmethod
@@ -150,7 +167,7 @@ class IfcStore:
IfcStore.file = ifcopenshell.open(path)
@staticmethod
def get_schema():
def get_schema() -> ifcopenshell.ifcopenshell_wrapper.schema_definition:
if IfcStore.file is None:
IfcStore.schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(
bpy.context.scene.BIMProjectProperties.export_schema
@@ -162,14 +179,13 @@ class IfcStore:
@staticmethod
def get_element(id_or_guid: Union[int, str]) -> IFC_CONNECTED_TYPE:
if isinstance(id_or_guid, int):
map_object = IfcStore.id_map
obj = IfcStore.id_map.get(id_or_guid)
else:
map_object = IfcStore.guid_map
try:
obj = map_object[id_or_guid]
obj.name # In case the object has been deleted, this triggers an exception
except:
return
obj = IfcStore.guid_map.get(id_or_guid)
if obj is None:
return None
if not tool.Blender.is_valid_data_block(obj):
return None
return obj
@staticmethod
@@ -189,21 +205,26 @@ class IfcStore:
def relink_object(obj: IFC_CONNECTED_TYPE) -> None:
if not obj:
return
if obj.BIMObjectProperties.ifc_definition_id:
try:
element = IfcStore.get_file().by_id(obj.BIMObjectProperties.ifc_definition_id)
except:
if isinstance(obj, bpy.types.Object):
ifc_definition_id = obj.BIMObjectProperties.ifc_definition_id
if not ifc_definition_id:
return
data = {"id": element.id(), "obj": obj.name}
try:
element = IfcStore.get_file().by_id(ifc_definition_id)
except RuntimeError:
return
data = OperationData(id=element.id(), obj=obj.name)
if hasattr(element, "GlobalId"):
data["guid"] = element.GlobalId
IfcStore.commit_link_element(data)
if hasattr(obj, "BIMMaterialProperties") and obj.BIMMaterialProperties.ifc_style_id:
return
else: # bpy.types.Material
ifc_definition_id = obj.BIMStyleProperties.ifc_definition_id
try:
element = IfcStore.get_file().by_id(obj.BIMMaterialProperties.ifc_style_id)
except:
element = IfcStore.get_file().by_id(ifc_definition_id)
except RuntimeError:
return
data = {"id": element.id(), "obj": obj.name}
data = OperationData(id=element.id(), obj=obj.name)
IfcStore.commit_link_element(data)
@staticmethod
@@ -218,44 +239,40 @@ class IfcStore:
existing_obj = IfcStore.id_map.get(element.id(), None)
if existing_obj == obj:
return
elif existing_obj:
try:
existing_obj.name
IfcStore.unlink_element(element=element, obj=existing_obj)
except:
pass
# TODO: When does this occur?
elif existing_obj and tool.Blender.is_valid_data_block(existing_obj):
IfcStore.unlink_element(obj=existing_obj)
IfcStore.id_map[element.id()] = obj
if hasattr(element, "GlobalId"):
IfcStore.guid_map[element.GlobalId] = obj
if element.is_a("IfcSurfaceStyle"):
obj.BIMMaterialProperties.ifc_style_id = element.id()
obj.BIMStyleProperties.ifc_definition_id = element.id()
else:
obj.BIMObjectProperties.ifc_definition_id = element.id()
blenderbim.bim.handler.subscribe_to(obj, "name", blenderbim.bim.handler.name_callback)
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):
if isinstance(obj, bpy.types.Object):
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}
data = OperationData(id=element.id(), guid=getattr(element, "GlobalId", None), obj=obj.name)
IfcStore.history[-1]["operations"].append(
{"rollback": IfcStore.rollback_link_element, "commit": IfcStore.commit_link_element, "data": data}
Operation(rollback=IfcStore.rollback_link_element, commit=IfcStore.commit_link_element, data=data)
)
@staticmethod
def rollback_link_element(data):
def rollback_link_element(data: OperationData) -> None:
del IfcStore.id_map[data["id"]]
if data["guid"]:
del IfcStore.guid_map[data["guid"]]
@staticmethod
def commit_link_element(data):
def commit_link_element(data: OperationData) -> None:
obj = bpy.data.objects.get(data["obj"])
if not obj:
obj = bpy.data.materials.get(data["obj"])
@@ -263,9 +280,7 @@ class IfcStore:
if "guid" in data:
IfcStore.guid_map[data["guid"]] = obj
blenderbim.bim.handler.subscribe_to(obj, "name", blenderbim.bim.handler.name_callback)
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):
if isinstance(obj, bpy.types.Object):
blenderbim.bim.handler.subscribe_to(
obj, "active_material_index", blenderbim.bim.handler.active_material_index_callback
)
@@ -273,7 +288,7 @@ class IfcStore:
# TODO We're handling id_map and guid_map, but what about edited_objs? This might cause big problems.
@staticmethod
def rollback_unlink_element(data) -> None:
def rollback_unlink_element(data: OperationData) -> None:
if "id" not in data or "obj" not in data:
return
obj = bpy.data.objects.get(data["obj"])
@@ -282,7 +297,7 @@ class IfcStore:
IfcStore.guid_map[data["guid"]] = obj
@staticmethod
def commit_unlink_element(data) -> None:
def commit_unlink_element(data: OperationData) -> None:
del IfcStore.id_map[data["id"]]
if data["guid"]:
del IfcStore.guid_map[data["guid"]]
@@ -291,50 +306,62 @@ class IfcStore:
def unlink_element(
element: Optional[ifcopenshell.entity_instance] = None, obj: Optional[IFC_CONNECTED_TYPE] = None
) -> None:
if element is None:
try:
element = tool.Ifc.get_entity(obj)
except:
pass
"""Unlink IFC `element` or Blender `obj`.
If element is provided then it will be unlinked from the related Blender object.
Blender object's IFC information is also will be purged.
If Blender object is provided then all IFC information will be purged from this object.
Method won't be searching for related IFC elements as Blender object might come from
different Blender session and there might be an ids/guids clash.
Only one argument must be provided and other should be omitted as they have different meaning.
:raises TypeError: If both arguments or no arguments provided.
"""
if not bool(element) ^ bool(obj):
raise TypeError("Only one argument must be provided - element or obj.")
if obj:
IfcStore.purge_blender_ifc_data(obj)
return
assert element # Type checker.
if obj is None:
try:
potential_obj = IfcStore.id_map[element.id()]
potential_obj.name
obj = potential_obj
if tool.Blender.is_valid_data_block(potential_obj):
obj = potential_obj
IfcStore.purge_blender_ifc_data(obj)
except:
pass
try:
if element:
del IfcStore.id_map[element.id()]
else:
del IfcStore.id_map[obj.BIMObjectProperties.ifc_definition_id]
del IfcStore.id_map[element.id()]
except:
pass
try:
if element and hasattr(element, "GlobalId"):
del IfcStore.guid_map[element.GlobalId]
except:
pass
if element and element.is_a("IfcSurfaceStyle"):
obj.BIMMaterialProperties.ifc_style_id = 0
elif obj:
obj.BIMObjectProperties.ifc_definition_id = 0
if global_id := getattr(element, "GlobalId", None):
try:
del IfcStore.guid_map[global_id]
except:
pass
if IfcStore.history:
data = {}
if element:
data["id"] = element.id()
data["guid"] = getattr(element, "GlobalId", None)
data = OperationData(id=element.id(), guid=global_id)
if obj:
data["obj"] = obj.name
IfcStore.history[-1]["operations"].append(
{"rollback": IfcStore.rollback_unlink_element, "commit": IfcStore.commit_unlink_element, "data": data}
Operation(rollback=IfcStore.rollback_unlink_element, commit=IfcStore.commit_unlink_element, data=data)
)
@staticmethod
def purge_blender_ifc_data(obj: IFC_CONNECTED_TYPE) -> None:
if isinstance(obj, bpy.types.Material):
obj.BIMStyleProperties.ifc_definition_id = 0
else: # bpy.types.Object
obj.BIMObjectProperties.ifc_definition_id = 0
@staticmethod
def execute_ifc_operator(operator: bpy.types.Operator, context: bpy.types.Context, is_invoke=False):
blenderbim.last_actions.append({"type": "operator", "name": operator.bl_idname})
@@ -393,15 +420,15 @@ class IfcStore:
rollback = rollback or getattr(operator, "rollback", lambda data: True)
commit = commit or getattr(operator, "commit", lambda data: True)
if IfcStore.history and IfcStore.history[-1]["key"] == key:
IfcStore.history[-1]["operations"].append({"rollback": rollback, "commit": commit, "data": data})
IfcStore.history[-1]["operations"].append(OperationData(rollback=rollback, commit=commit, data=data))
else:
IfcStore.history.append(
{"key": key, "operations": [{"rollback": rollback, "commit": commit, "data": data}]}
TransactionStep(key=key, operations=[OperationData(rollback=rollback, commit=commit, data=data)])
)
IfcStore.future = []
@staticmethod
def undo(until_key=None) -> None:
def undo(until_key: str = None) -> None:
BrickStore.undo()
if not IfcStore.history:
return
@@ -416,7 +443,7 @@ class IfcStore:
IfcStore.future.append(event)
@staticmethod
def redo(until_key=None) -> None:
def redo(until_key: str = None) -> None:
BrickStore.redo()
if not IfcStore.future:
File diff suppressed because it is too large Load Diff
@@ -49,7 +49,7 @@ class BoundaryDecorator:
batch.draw(shader)
def __call__(self, context):
self.addon_prefs = context.preferences.addons["blenderbim"].preferences
self.addon_prefs = tool.Blender.get_addon_preferences()
selected_elements_color = self.addon_prefs.decorator_color_selected
unselected_elements_color = self.addon_prefs.decorator_color_unselected
special_elements_color = self.addon_prefs.decorator_color_special
@@ -551,9 +551,9 @@ class HideBoundaries(bpy.types.Operator, tool.Ifc.Operator):
for boundary in element.BoundedBy or []:
boundary_obj = tool.Ifc.get_object(boundary)
if boundary_obj:
to_delete.add(boundary_obj)
for boundary_obj in to_delete:
tool.Ifc.unlink(obj=boundary_obj)
to_delete.add((boundary, boundary_obj))
for boundary, boundary_obj in to_delete:
tool.Ifc.unlink(element=boundary)
bpy.data.objects.remove(boundary_obj)
context.scene.BIMBoundaryProperties.boundaries.clear()
return {"FINISHED"}
@@ -602,6 +602,11 @@ class AddBoundary(bpy.types.Operator, tool.Ifc.Operator):
parent_boundaries = []
objs = context.selected_objects
if not any((element:=tool.Ifc.get_entity(obj)) and element.is_a("IfcSpace") for obj in objs):
self.report({"INFO"}, "No IfcSpace elements selected.")
return {"FINISHED"}
if len(objs) == 2:
# The user may select two objects, a space and its related building element
for obj in objs:
@@ -55,7 +55,7 @@ class ClashDecorator:
batch.draw(shader)
def draw_text(self, context):
self.addon_prefs = context.preferences.addons["blenderbim"].preferences
self.addon_prefs = tool.Blender.get_addon_preferences()
selected_elements_color = self.addon_prefs.decorator_color_selected
unselected_elements_color = self.addon_prefs.decorator_color_unselected
special_elements_color = self.addon_prefs.decorator_color_special
@@ -75,7 +75,7 @@ class ClashDecorator:
blf.draw(font_id, text) # Set your text here
def draw_geometry(self, context):
self.addon_prefs = context.preferences.addons["blenderbim"].preferences
self.addon_prefs = tool.Blender.get_addon_preferences()
selected_elements_color = self.addon_prefs.decorator_color_selected
unselected_elements_color = self.addon_prefs.decorator_color_unselected
special_elements_color = self.addon_prefs.decorator_color_special
@@ -185,6 +185,8 @@ class ExportIfcCsv(bpy.types.Operator):
def invoke(self, context, event):
props = context.scene.CsvProperties
if props.format == "web":
return self.execute(context)
self.filepath = bpy.path.ensure_ext(bpy.data.filepath, f".{props.format}")
WindowManager = context.window_manager
WindowManager.fileselect_add(self)
@@ -222,6 +224,10 @@ class ExportIfcCsv(bpy.types.Operator):
if attribute.formatting != "{{value}}" and "{{value}}" in attribute.formatting:
formatting.append({"name": attribute.name, "format": attribute.formatting})
file_format = props.format
if props.format == "web":
file_format = "pd"
sep = props.csv_custom_delimiter if props.csv_delimiter == "CUSTOM" else props.csv_delimiter
ifc_csv.export(
ifc_file,
@@ -229,7 +235,7 @@ class ExportIfcCsv(bpy.types.Operator):
attributes,
headers=headers,
output=self.filepath,
format=props.format,
format=file_format,
should_preserve_existing=props.should_preserve_existing,
delimiter=sep,
include_global_id=props.include_global_id,
@@ -247,6 +253,8 @@ class ExportIfcCsv(bpy.types.Operator):
if props.format != "csv" and props.should_generate_svg:
schedule_creator = scheduler.Scheduler()
schedule_creator.schedule(self.filepath, tool.Drawing.get_path_with_ext(self.filepath, "svg"))
if props.format == "web":
tool.Web.send_webui_data(extra_data=ifc_csv.dataframe.to_csv(index=False), event="csv_data")
self.report({"INFO"}, f"Data is exported to {props.format.upper()}.")
return {"FINISHED"}
@@ -88,6 +88,7 @@ class CsvProperties(PropertyGroup):
("csv", "csv", ""),
("xlsx", "xlsx", ""),
("ods", "ods", ""),
("web", "web", ""),
],
name="Output format",
default="csv",
@@ -90,12 +90,11 @@ class ConvertToBlender(bpy.types.Operator):
def execute(self, context):
for obj in bpy.data.objects:
if obj.type in {"MESH", "EMPTY"}:
obj.BIMObjectProperties.ifc_definition_id = 0
tool.Ifc.unlink(obj=obj)
if obj.data:
obj.data.BIMMeshProperties.ifc_definition_id = 0
for material in bpy.data.materials:
material.BIMObjectProperties.ifc_definition_id = 0
material.BIMMaterialProperties.ifc_style_id = 0
tool.Ifc.unlink(obj=material)
context.scene.BIMProperties.ifc_file = ""
context.scene.BIMDebugProperties.attributes.clear()
IfcStore.purge()
@@ -188,7 +188,6 @@ class ExecuteIfcDiff(bpy.types.Operator):
tool.Ifc.set(ifc_diff.old)
ifc_importer.file = ifc_diff.old
ifc_importer.process_context_filter()
ifc_importer.create_materials()
ifc_importer.create_styles()
elements = {ifc_diff.old.by_guid(guid) for guid in ifc_diff.deleted_elements}
@@ -204,7 +203,6 @@ class ExecuteIfcDiff(bpy.types.Operator):
tool.Ifc.set(ifc_diff.new)
ifc_importer.file = ifc_diff.new
ifc_importer.process_context_filter()
ifc_importer.create_materials()
ifc_importer.create_styles()
elements = {ifc_diff.new.by_guid(guid) for guid in ifc_diff.added_elements}
@@ -71,6 +71,8 @@ classes = (
operator.OpenReference,
operator.OpenSchedule,
operator.OpenSheet,
operator.OrderTextLiteralDown,
operator.OrderTextLiteralUp,
operator.ReloadDrawingStyles,
operator.RemoveDrawing,
operator.RemoveDrawingFromSheet,
@@ -335,7 +335,7 @@ class BaseDecorator:
"""`verts` should be in winspace with `(0,0,0)` in the screen left bottom corner, not in the center"""
region = context.region
if not color:
color = context.preferences.addons["blenderbim"].preferences.decorations_colour
color = tool.Blender.get_addon_preferences().decorations_colour
self.line_shader.bind()
# POLYLINE_UNIFORM_COLOR specific uniforms
@@ -404,7 +404,7 @@ class BaseDecorator:
font_id = self.font_id
color = context.preferences.addons["blenderbim"].preferences.decorations_colour
color = tool.Blender.get_addon_preferences().decorations_colour
ang = -Vector((1, 0)).angle_signed(text_dir)
cos = math.cos(ang)
@@ -812,7 +812,7 @@ class AngleDecorator(BaseDecorator):
arcs_color = None
edges_color = UNSPECIAL_ELEMENT_COLOR
if context.active_object == obj and obj.data.is_editmode:
arcs_color = context.preferences.addons["blenderbim"].preferences.decorator_color_special
arcs_color = tool.Blender.get_addon_preferences().decorator_color_special
edges_color = None
self.draw_lines(
@@ -1093,7 +1093,7 @@ class HiddenDecorator(BaseDecorator):
else:
verts, idxs = self.get_mesh_geom(obj)
winspace_verts = worldspace_to_winspace(verts, context)
color = [i for i in context.preferences.addons["blenderbim"].preferences.decorations_colour]
color = [i for i in tool.Blender.get_addon_preferences().decorations_colour]
color[3] = 0.5
self.draw_lines(context, obj, winspace_verts, idxs, color)
@@ -1409,7 +1409,7 @@ class GridDecorator(BaseDecorator):
# stem with gaps for circles
add_verts_sequence([v0 + gap, v1 - gap], start_i, **out_kwargs)
color = [i for i in context.preferences.addons["blenderbim"].preferences.decorations_colour]
color = [i for i in tool.Blender.get_addon_preferences().decorations_colour]
color[3] = 0.5
self.draw_lines(context, obj, output_verts, output_edges, color)
self.draw_labels(context, obj, verts)
@@ -1591,7 +1591,7 @@ class CutDecorator:
cls.installed = None
def __call__(self, context):
self.addon_prefs = context.preferences.addons["blenderbim"].preferences
self.addon_prefs = tool.Blender.get_addon_preferences()
selected_elements_color = self.addon_prefs.decorator_color_selected
all_vertices = []
@@ -279,7 +279,7 @@ class CreateDrawing(bpy.types.Operator):
svg_path = self.combine_svgs(context, underlay_svg, linework_svg, annotation_svg)
tool.Drawing.open_with_user_command(
context.preferences.addons["blenderbim"].preferences.svg_command, svg_path
tool.Blender.get_addon_preferences().svg_command, svg_path
)
if self.print_all:
@@ -475,15 +475,15 @@ class CreateDrawing(bpy.types.Operator):
with profile(f"Processing {context_type} context"):
if not context or not drawing_elements:
continue
geom_settings = ifcopenshell.geom.settings(
DISABLE_TRIANGULATION=True, INCLUDE_CURVES=True
)
geom_settings = ifcopenshell.geom.settings()
geom_settings.set("dimensionality", ifcopenshell.ifcopenshell_wrapper.CURVES_SURFACES_AND_SOLIDS)
geom_settings.set("iterator-output", ifcopenshell.ifcopenshell_wrapper.NATIVE)
if ifc.by_id(context[0]).ContextType == "Plan" and "PLAN_VIEW" in target_view:
offset = ifcopenshell.ifcopenshell_wrapper.float_array_3()
# A 2mm Z offset to combat Z-fighting in plan or RCPs
offset[2] = 0.002 if target_view == "PLAN_VIEW" else -0.002
geom_settings.offset = offset
geom_settings.set_context_ids(context)
geom_settings.set("context-ids", context)
it = ifcopenshell.geom.iterator(
geom_settings, ifc, multiprocessing.cpu_count(), include=drawing_elements
)
@@ -507,7 +507,7 @@ class CreateDrawing(bpy.types.Operator):
# All very hackish whilst prototyping
exporter = blenderbim.bim.export_ifc.IfcExporter(None)
exporter.file = tool.Ifc.get()
invalidated_elements = exporter.sync_all_objects(skip_unlinking=True)
invalidated_elements = exporter.sync_all_objects()
invalidated_elements += exporter.sync_edited_objects()
invalidated_guids = [e.GlobalId for e in invalidated_elements if hasattr(e, "GlobalId")]
cache = IfcStore.get_cache()
@@ -561,7 +561,8 @@ class CreateDrawing(bpy.types.Operator):
if tool.Ifc.get() == ifc and self.camera_element not in drawing_elements:
with profile("Camera element"):
# The camera must always be included, regardless of any include/exclude filters.
geom_settings = ifcopenshell.geom.settings(DISABLE_TRIANGULATION=True)
geom_settings = ifcopenshell.geom.settings()
geom_settings.set("iterator-output", ifcopenshell.ifcopenshell_wrapper.NATIVE)
it = ifcopenshell.geom.iterator(geom_settings, ifc, include=[self.camera_element])
for elem in it:
self.serialiser.write(elem)
@@ -874,11 +875,14 @@ class CreateDrawing(bpy.types.Operator):
return svg_path
def setup_serialiser(self, target_view):
self.svg_settings = ifcopenshell.geom.settings(
DISABLE_TRIANGULATION=True, INCLUDE_CURVES=True
)
self.svg_settings = ifcopenshell.geom.settings()
self.svg_settings.set("dimensionality", ifcopenshell.ifcopenshell_wrapper.CURVES_SURFACES_AND_SOLIDS)
self.svg_settings.set("iterator-output", ifcopenshell.ifcopenshell_wrapper.NATIVE)
self.svg_buffer = ifcopenshell.geom.serializers.buffer()
self.serialiser = ifcopenshell.geom.serializers.svg(self.svg_buffer, self.svg_settings)
self.serialiser_settings = ifcopenshell.geom.serializer_settings()
self.serialiser = ifcopenshell.geom.serializers.svg(
self.svg_buffer, self.svg_settings, self.serialiser_settings
)
self.serialiser.setWithoutStoreys(True)
self.serialiser.setPolygonal(True)
self.serialiser.setUseHlrPoly(True)
@@ -982,7 +986,7 @@ class CreateDrawing(bpy.types.Operator):
obj = tool.Ifc.get_object(element)
if not obj: # This is a linked model object. For now, do nothing.
if not obj: # This is a linked model object. For now, do nothing.
continue
if not self.is_manifold(obj):
@@ -1370,8 +1374,8 @@ class CreateSheets(bpy.types.Operator, Operator):
),
)
svg2pdf_command = context.preferences.addons["blenderbim"].preferences.svg2pdf_command
svg2dxf_command = context.preferences.addons["blenderbim"].preferences.svg2dxf_command
svg2pdf_command = tool.Blender.get_addon_preferences().svg2pdf_command
svg2dxf_command = tool.Blender.get_addon_preferences().svg2dxf_command
if svg2pdf_command:
# With great power comes great responsibility. Example:
@@ -1389,9 +1393,9 @@ class CreateSheets(bpy.types.Operator, Operator):
subprocess.run([replacements.get(c, c) for c in command])
if svg2pdf_command:
tool.Drawing.open_with_user_command(context.preferences.addons["blenderbim"].preferences.pdf_command, pdf)
tool.Drawing.open_with_user_command(tool.Blender.get_addon_preferences().pdf_command, pdf)
else:
tool.Drawing.open_with_user_command(context.preferences.addons["blenderbim"].preferences.svg_command, svg)
tool.Drawing.open_with_user_command(tool.Blender.get_addon_preferences().svg_command, svg)
class SelectAllDrawings(bpy.types.Operator):
@@ -1459,7 +1463,7 @@ class OpenDrawing(bpy.types.Operator):
for drawing_uri in drawing_uris:
tool.Drawing.open_with_user_command(
context.preferences.addons["blenderbim"].preferences.svg_command, drawing_uri
tool.Blender.get_addon_preferences().svg_command, drawing_uri
)
return {"FINISHED"}
@@ -1553,6 +1557,14 @@ class ActivateDrawing(bpy.types.Operator):
core.sync_references(tool.Ifc, tool.Collector, tool.Drawing, drawing=tool.Ifc.get().by_id(self.drawing))
CutDecorator.install(context)
tool.Drawing.show_decorations()
# Save drawing bounds to the .ifc file
camera = context.scene.camera
camera_props = camera.data.BIMCameraProperties
if camera_props.update_representation(camera):
bpy.ops.bim.update_representation(obj=camera.name, ifc_representation_class="")
return {"FINISHED"}
@@ -2340,6 +2352,32 @@ class RemoveTextLiteral(bpy.types.Operator):
return {"FINISHED"}
class OrderTextLiteralUp(bpy.types.Operator):
bl_idname = "bim.order_text_literal_up"
bl_label = "Move Text Literal Up"
bl_options = {"REGISTER", "UNDO"}
literal_prop_id: bpy.props.IntProperty()
def execute(self, context):
obj = context.active_object
obj.BIMTextProperties.literals.move(self.literal_prop_id, self.literal_prop_id - 1)
return {"FINISHED"}
class OrderTextLiteralDown(bpy.types.Operator):
bl_idname = "bim.order_text_literal_down"
bl_label = "Move Text Literal Down"
bl_options = {"REGISTER", "UNDO"}
literal_prop_id: bpy.props.IntProperty()
def execute(self, context):
obj = context.active_object
obj.BIMTextProperties.literals.move(self.literal_prop_id, self.literal_prop_id + 1)
return {"FINISHED"}
class AssignSelectedObjectAsProduct(bpy.types.Operator):
bl_idname = "bim.assign_selected_as_product"
bl_label = "Assign Selected Object As Product"
@@ -2738,7 +2776,7 @@ class AddReferenceImage(bpy.types.Operator, Operator):
obj.material_slots[0].material = material
bpy.ops.bim.add_style()
style = ifc_file.by_id(material.BIMMaterialProperties.ifc_style_id)
style = ifc_file.by_id(material.BIMStyleProperties.ifc_definition_id)
tool.Style.assign_style_to_object(style, obj)
# TODO: IfcSurfaceStyleRendering is unnecessary here, added it only because
@@ -525,6 +525,10 @@ class BIM_PT_text(Panel):
row = box.row(align=True)
row.label(text=f"Literal[{i}]:")
if i > 0:
row.operator("bim.order_text_literal_up", icon="TRIA_UP", text="").literal_prop_id = i
if i < len(props.literals) - 1:
row.operator("bim.order_text_literal_down", icon="TRIA_DOWN", text="").literal_prop_id = i
row.operator("bim.remove_text_literal", icon="X", text="").literal_prop_id = i
# skip BoxAlignment since we're going to format it ourselves
@@ -24,6 +24,7 @@ from mathutils import Vector
def refresh():
ViewportData.is_loaded = False
PlacementData.is_loaded = False
DerivedCoordinatesData.is_loaded = False
RepresentationsData.is_loaded = False
@@ -31,6 +32,28 @@ def refresh():
ConnectionsData.is_loaded = False
class ViewportData:
data = {}
is_loaded = False
@classmethod
def load(cls):
cls.is_loaded = True
cls.data = {"mode": cls.mode()}
@classmethod
def mode(cls):
obj = bpy.context.active_object
obj_mode = [("OBJECT", "IFC Object Mode", "", "OBJECT_DATAMODE", 0)]
mesh_modes = [
("OBJECT", "IFC Object Mode", "", "OBJECT_DATAMODE", 0),
("EDIT", "IFC Edit Mode", "", "EDITMODE_HLT", 1),
]
if not obj or not tool.Blender.is_editable(obj):
return obj_mode
return mesh_modes
class RepresentationsData:
data = {}
is_loaded = False
@@ -39,7 +62,10 @@ class RepresentationsData:
def load(cls):
cls.data = {"representations": cls.representations()}
cls.data["contexts"] = cls.contexts()
# Only after cls.representations().
cls.data["shape_aspects"] = cls.shape_aspects()
cls.is_loaded = True
@classmethod
@@ -95,7 +121,11 @@ class RepresentationsData:
return results
@classmethod
def shape_aspects(cls):
def shape_aspects(cls) -> list[tuple[str, str, str]]:
"""Get list of current's representation shape aspects for prop's enum_items."""
# Ignore objects without representations, e.g. IfcRelSpaceBoundary.
if not cls.data["representations"]:
return []
obj = bpy.context.active_object
if not obj.data:
return []
@@ -334,11 +364,13 @@ class PlacementData:
obj = bpy.context.active_object
if obj and props.has_blender_offset:
xyz = cls.original_xyz(obj)
cls.data.update({
"original_x": str(xyz[0]),
"original_y": str(xyz[1]),
"original_z": str(xyz[2]),
})
cls.data.update(
{
"original_x": str(xyz[0]),
"original_y": str(xyz[1]),
"original_z": str(xyz[2]),
}
)
cls.is_loaded = True
@classmethod
@@ -324,7 +324,7 @@ class UpdateRepresentation(bpy.types.Operator, Operator):
if not obj.data:
continue
self.update_obj_mesh_representation(context, obj)
IfcStore.edited_objs.discard(obj)
tool.Ifc.finish_edit(obj)
return {"FINISHED"}
def update_obj_mesh_representation(self, context, obj):
@@ -1126,7 +1126,6 @@ class DuplicateMoveLinkedAggregate(bpy.types.Operator):
location_diff = new_obj.location - base_obj_location
new_obj.location = context.scene.cursor.location + location_diff
if len(context.selected_objects) != 1:
return {"FINISHED"}
@@ -1531,7 +1530,7 @@ class OverrideModeSetEdit(bpy.types.Operator):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
selected_objs = context.selected_objects or ([context.active_object] if context.active_object else [])
selected_objs = tool.Blender.get_selected_objects()
active_obj = context.active_object
if context.active_object:
@@ -1542,23 +1541,13 @@ class OverrideModeSetEdit(bpy.types.Operator):
return bpy.ops.bim.enable_editing_boundary_geometry()
for obj in selected_objs:
if not obj:
continue
obj_supports_edit_mode, message = tool.Blender.object_supports_edit_mode(obj)
if not obj_supports_edit_mode:
self.report({"INFO"}, message)
if not tool.Blender.is_editable(obj):
self.report({"INFO"}, f"Object cannot be edited: {obj.name}")
obj.select_set(False)
continue
element = tool.Ifc.get_entity(obj)
if not element:
continue
usage_type = tool.Model.get_usage_type(element)
if usage_type is not None and usage_type not in ("PROFILE", "LAYER3"):
# Parametric objects shall not be edited as meshes as they
# can be modified to be incompatible with the parametric
# constraints.
obj.select_set(False)
continue
representation = tool.Geometry.get_active_representation(obj)
if not representation:
continue
@@ -1566,6 +1555,7 @@ class OverrideModeSetEdit(bpy.types.Operator):
obj.select_set(False)
continue
usage_type = tool.Model.get_usage_type(element)
is_profile = True
representation_class = tool.Geometry.get_ifc_representation_class(element, representation)
if usage_type == "PROFILE":
@@ -1663,6 +1653,8 @@ class OverrideModeSetObject(bpy.types.Operator):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
if not context.active_object:
return {"FINISHED"}
for obj in self.edited_objs:
if self.should_save:
bpy.ops.bim.update_representation(obj=obj.name, ifc_representation_class="")
@@ -1694,6 +1686,8 @@ class OverrideModeSetObject(bpy.types.Operator):
return IfcStore.execute_ifc_operator(self, context, is_invoke=True)
def _invoke(self, context, event):
if not context.active_object:
return {"FINISHED"}
self.is_valid = True
bpy.ops.object.mode_set(mode="EDIT", toggle=True)
@@ -19,7 +19,7 @@
import bpy
import blenderbim.tool as tool
from blenderbim.bim.prop import StrProperty, Attribute
from blenderbim.bim.module.geometry.data import RepresentationsData
from blenderbim.bim.module.geometry.data import RepresentationsData, ViewportData
from bpy.types import PropertyGroup
from bpy.props import (
PointerProperty,
@@ -48,6 +48,12 @@ def update_mode(self, context):
bpy.ops.bim.override_mode_set_edit("INVOKE_DEFAULT")
def get_mode(self, context):
if not ViewportData.is_loaded:
ViewportData.load()
return ViewportData.data["mode"]
def get_styles(self, context):
# postponed import to avoid circular import
from blenderbim.bim.module.material.data import MaterialsData
@@ -129,13 +135,5 @@ class BIMGeometryProperties(PropertyGroup):
# Navisworks workaround
should_force_triangulation: BoolProperty(name="Force Triangulation", default=False)
is_changing_mode: BoolProperty(name="Is Changing Mode", default=False)
mode: EnumProperty(
default="OBJECT",
items=[
("OBJECT", "IFC Object Mode", "", "OBJECT_DATAMODE", 0),
("EDIT", "IFC Edit Mode", "", "EDITMODE_HLT", 1),
],
name="IFC Interaction Mode",
update=update_mode,
)
mode: EnumProperty(items=get_mode, name="IFC Interaction Mode", update=update_mode)
representation_from_object: PointerProperty(type=bpy.types.Object)
@@ -33,6 +33,8 @@ from blenderbim.bim.module.layer.data import LayersData
def mode_menu(self, context):
if not tool.Ifc.get():
return
row = self.layout.row(align=True)
if context.scene.BIMGeometryProperties.mode == "EDIT":
row.operator("bim.override_mode_set_object", icon="CANCEL", text="Discard Changes").should_save = False
@@ -419,10 +421,11 @@ class BIM_PT_placement(Panel):
row.label(text="Blender Offset", icon="TRACKING_REFINE_FORWARDS")
row.label(text=context.active_object.BIMObjectProperties.blender_offset_type)
row = self.layout.row(align=True)
row.label(text=PlacementData.data["original_x"], icon="EMPTY_AXIS")
row.label(text=PlacementData.data["original_y"])
row.label(text=PlacementData.data["original_z"])
if context.active_object.BIMObjectProperties.blender_offset_type != "NOT_APPLICABLE":
row = self.layout.row(align=True)
row.label(text=PlacementData.data["original_x"], icon="EMPTY_AXIS")
row.label(text=PlacementData.data["original_y"])
row.label(text=PlacementData.data["original_z"])
class BIM_PT_derived_coordinates(Panel):
@@ -20,23 +20,31 @@ import bpy
from . import ui, prop, operator
classes = (
operator.EnableEditingGeoreferencing,
operator.DisableEditingGeoreferencing,
operator.EditGeoreferencing,
operator.SetIfcGridNorth,
operator.SetBlenderGridNorth,
operator.SetIfcTrueNorth,
operator.SetBlenderTrueNorth,
operator.RemoveGeoreferencing,
operator.AddGeoreferencing,
operator.ConvertLocalToGlobal,
operator.ConvertGlobalToLocal,
operator.GetCursorLocation,
operator.SetCursorLocation,
operator.ConvertAngleToCoordinates,
operator.ConvertGlobalToLocal,
operator.ConvertLocalToGlobal,
operator.DisableEditingGeoreferencing,
operator.DisableEditingTrueNorth,
operator.DisableEditingWCS,
operator.EditGeoreferencing,
operator.EditTrueNorth,
operator.EditWCS,
operator.EnableEditingGeoreferencing,
operator.EnableEditingTrueNorth,
operator.EnableEditingWCS,
operator.GetCursorLocation,
operator.ImportPlot,
operator.RemoveGeoreferencing,
operator.RemoveTrueNorth,
operator.SetBlenderGridNorth,
operator.SetBlenderTrueNorth,
operator.SetIfcGridNorth,
operator.SetIfcTrueNorth,
prop.BIMGeoreferenceProperties,
ui.BIM_PT_gis,
ui.BIM_PT_gis_wcs,
ui.BIM_PT_gis_true_north,
ui.BIM_PT_gis_calculator,
)
@@ -18,8 +18,10 @@
import bpy
import ifcopenshell.util.geolocation
import numpy as np
import blenderbim.tool as tool
import ifcopenshell.util.geolocation
from ifcopenshell.util.doc import get_entity_doc
def refresh():
@@ -32,14 +34,28 @@ class GeoreferenceData:
@classmethod
def load(cls):
cls.data["coordinate_operation_class"] = cls.coordinate_operation_class()
cls.data["blender_derived_angle"] = cls.blender_derived_angle()
cls.data["map_conversion"] = cls.map_conversion()
cls.data["coordinate_operation"] = cls.coordinate_operation()
cls.data["map_derived_angle"] = cls.map_derived_angle()
cls.data["projected_crs"] = cls.projected_crs()
cls.data["true_north"] = cls.true_north()
cls.data["true_derived_angle"] = cls.true_derived_angle()
cls.data["local_unit_symbol"] = cls.local_unit_symbol()
cls.data["map_unit_symbol"] = cls.map_unit_symbol()
cls.data["world_coordinate_system"] = cls.world_coordinate_system()
cls.is_loaded = True
@classmethod
def coordinate_operation_class(cls):
if tool.Ifc.get_schema() == "IFC2X3":
return []
declaration = tool.Ifc.schema().declaration_by_name("IfcCoordinateOperation")
declarations = ifcopenshell.util.schema.get_subtypes(declaration)
names = [d.name() for d in declarations]
version = tool.Ifc.get_schema()
return [(c, c, get_entity_doc(version, c).get("description", "")) for c in sorted(names)]
@classmethod
def blender_derived_angle(cls):
props = bpy.context.scene.BIMGeoreferenceProperties
@@ -54,36 +70,37 @@ class GeoreferenceData:
)
@classmethod
def map_conversion(cls):
def coordinate_operation(cls):
if tool.Ifc.get_schema() == "IFC2X3":
project = tool.Ifc.get().by_type("IfcProject")[0]
map_conversion = ifcopenshell.util.element.get_pset(project, "ePSet_MapConversion")
if not map_conversion:
coordinate_operation = ifcopenshell.util.element.get_pset(project, "ePSet_MapConversion")
if not coordinate_operation:
return {}
del map_conversion["id"]
return map_conversion
del coordinate_operation["id"]
coordinate_operation["type"] = "ePSet_MapConversion"
return coordinate_operation
for context in tool.Ifc.get().by_type("IfcGeometricRepresentationContext", include_subtypes=False):
if context.HasCoordinateOperation:
map_conversion = context.HasCoordinateOperation[0].get_info()
del map_conversion["id"]
del map_conversion["type"]
del map_conversion["SourceCRS"]
del map_conversion["TargetCRS"]
return map_conversion
coordinate_operation = context.HasCoordinateOperation[0].get_info()
del coordinate_operation["id"]
del coordinate_operation["SourceCRS"]
del coordinate_operation["TargetCRS"]
return coordinate_operation
return {}
@classmethod
def map_derived_angle(cls):
if (
cls.data["map_conversion"]
and cls.data["map_conversion"].get("XAxisAbscissa", None) is not None
and cls.data["map_conversion"].get("XAxisOrdinate", None) is not None
cls.data["coordinate_operation"]
and cls.data["coordinate_operation"].get("XAxisAbscissa", None) is not None
and cls.data["coordinate_operation"].get("XAxisOrdinate", None) is not None
):
return str(
round(
ifcopenshell.util.geolocation.xaxis2angle(
cls.data["map_conversion"]["XAxisAbscissa"], cls.data["map_conversion"]["XAxisOrdinate"]
cls.data["coordinate_operation"]["XAxisAbscissa"],
cls.data["coordinate_operation"]["XAxisOrdinate"],
),
3,
)
@@ -130,4 +147,36 @@ class GeoreferenceData:
@classmethod
def true_derived_angle(cls):
if cls.data["true_north"]:
return str(round(ifcopenshell.util.geolocation.yaxis2angle(*cls.data["true_north"][0:2]), 3))
return str(round(ifcopenshell.util.geolocation.yaxis2angle(*cls.data["true_north"][:2]), 3))
@classmethod
def local_unit_symbol(cls):
if not (unit := ifcopenshell.util.unit.get_project_unit(tool.Ifc.get(), "LENGTHUNIT")):
return "n/a"
return ifcopenshell.util.unit.get_unit_symbol(unit)
@classmethod
def map_unit_symbol(cls):
if tool.Ifc.get().schema == "IFC2X3":
return cls.local_unit_symbol()
if crs := tool.Ifc.get().by_type("IfcProjectedCRS"):
crs = crs[0]
if not (unit := crs.MapUnit):
return cls.local_unit_symbol()
return ifcopenshell.util.unit.get_unit_symbol(unit)
return "n/a"
@classmethod
def world_coordinate_system(cls):
wcs = ifcopenshell.util.geolocation.get_wcs(tool.Ifc.get())
result = {}
if wcs is None:
result["has_transformation"] = False
return result
if np.allclose(wcs, np.eye(4)):
result["has_transformation"] = False
else:
result["has_transformation"] = True
result["rotation"] = str(round(ifcopenshell.util.geolocation.yaxis2angle(*wcs[:, 1][:2]), 3))
result["x"], result["y"], result["z"] = wcs[:, 3][:3]
return result
@@ -29,7 +29,7 @@ class AddGeoreferencing(bpy.types.Operator, tool.Ifc.Operator):
bl_description = "Add a new georeference"
def _execute(self, context):
core.add_georeferencing(tool.Ifc)
core.add_georeferencing(tool.Georeference)
class EnableEditingGeoreferencing(bpy.types.Operator, tool.Ifc.Operator):
@@ -106,21 +106,6 @@ class GetCursorLocation(bpy.types.Operator, tool.Ifc.Operator):
core.get_cursor_location(tool.Georeference)
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 cursor location to the specified coordinates"
@classmethod
def poll(cls, context):
props = context.scene.BIMGeoreferenceProperties
return tool.Ifc.get() and props.coordinate_output.count(",") == 2
def _execute(self, context):
core.set_cursor_location(tool.Georeference)
class SetIfcTrueNorth(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.set_ifc_true_north"
bl_label = "Set IFC True North"
@@ -151,7 +136,7 @@ class ConvertLocalToGlobal(bpy.types.Operator, tool.Ifc.Operator):
def poll(cls, context):
file = tool.Ifc.get()
props = context.scene.BIMGeoreferenceProperties
return file and props.coordinate_input.count(",") == 2
return file and props.local_coordinates.count(",") == 2
def _execute(self, context):
core.convert_local_to_global(tool.Georeference)
@@ -167,7 +152,7 @@ class ConvertGlobalToLocal(bpy.types.Operator, tool.Ifc.Operator):
def poll(cls, context):
file = tool.Ifc.get()
props = context.scene.BIMGeoreferenceProperties
return file and file.by_type("IfcUnitAssignment") and props.coordinate_input.count(",") == 2
return file and file.by_type("IfcUnitAssignment") and props.local_coordinates.count(",") == 2
def _execute(self, context):
core.convert_global_to_local(tool.Georeference)
@@ -204,3 +189,73 @@ class ImportPlot(bpy.types.Operator, tool.Ifc.Operator):
def invoke(self, context, event):
context.window_manager.fileselect_add(self)
return {"RUNNING_MODAL"}
class EnableEditingWCS(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.enable_editing_wcs"
bl_label = "Enable Editing WCS"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Enable editing WCS"
def _execute(self, context):
core.enable_editing_wcs(tool.Georeference)
class EditWCS(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.edit_wcs"
bl_label = "Edit WCS"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Edit the WCS"
def _execute(self, context):
core.edit_wcs(tool.Ifc, tool.Georeference)
class DisableEditingWCS(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.disable_editing_wcs"
bl_label = "Disable Editing WCS"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Close editing panel"
def _execute(self, context):
core.disable_editing_wcs(tool.Georeference)
class EnableEditingTrueNorth(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.enable_editing_true_north"
bl_label = "Enable Editing True North"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Enable editing True North"
def _execute(self, context):
core.enable_editing_true_north(tool.Georeference)
class EditTrueNorth(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.edit_true_north"
bl_label = "Edit True North"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Edit the True North"
def _execute(self, context):
core.edit_true_north(tool.Ifc, tool.Georeference)
class DisableEditingTrueNorth(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.disable_editing_true_north"
bl_label = "Disable Editing True North"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Close editing panel"
def _execute(self, context):
core.disable_editing_true_north(tool.Georeference)
class RemoveTrueNorth(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.remove_true_north"
bl_label = "Remove True North"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Remove true north"
def _execute(self, context):
core.remove_true_north(tool.Ifc)
@@ -17,7 +17,8 @@
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
from blenderbim.bim.prop import StrProperty, Attribute
import ifcopenshell.util.geolocation
from blenderbim.bim.prop import Attribute
from bpy.types import PropertyGroup
from bpy.props import (
PointerProperty,
@@ -29,26 +30,104 @@ from bpy.props import (
FloatVectorProperty,
CollectionProperty,
)
from blenderbim.bim.module.georeference.data import GeoreferenceData
def get_coordinate_operation_class(self, context):
if not GeoreferenceData.is_loaded:
GeoreferenceData.load()
return GeoreferenceData.data["coordinate_operation_class"]
def update_true_north_angle(self, context):
if self.is_changing_angle:
return
self.is_changing_angle = True
try:
x, y = ifcopenshell.util.geolocation.angle2yaxis(float(self.true_north_angle))
self.true_north_abscissa = str(x)
self.true_north_ordinate = str(y)
except:
pass
self.is_changing_angle = False
def update_true_north_vector(self, context):
if self.is_changing_angle:
return
self.is_changing_angle = True
try:
x = float(self.true_north_abscissa)
y = float(self.true_north_ordinate)
self.true_north_angle = str(round(ifcopenshell.util.geolocation.yaxis2angle(x, y), 7))
except:
pass
self.is_changing_angle = False
def update_grid_north_angle(self, context):
if self.is_changing_angle:
return
self.is_changing_angle = True
try:
x, y = ifcopenshell.util.geolocation.angle2xaxis(float(self.grid_north_angle))
self.x_axis_abscissa = str(x)
self.x_axis_ordinate = str(y)
self.x_axis_is_null = False
except:
pass
self.is_changing_angle = False
def update_grid_north_vector(self, context):
if self.is_changing_angle:
return
self.is_changing_angle = True
try:
x = float(self.x_axis_abscissa)
y = float(self.x_axis_ordinate)
self.grid_north_angle = str(round(ifcopenshell.util.geolocation.xaxis2angle(x, y), 7))
self.x_axis_is_null = False
except:
pass
self.is_changing_angle = False
class BIMGeoreferenceProperties(PropertyGroup):
coordinate_operation_class: bpy.props.EnumProperty(
items=get_coordinate_operation_class, name="Coordinate Operation Class"
)
is_changing_angle: BoolProperty(name="Is Changing Angle", default=False)
is_editing: BoolProperty(name="Is Editing")
map_conversion: CollectionProperty(name="Map Conversion", type=Attribute)
is_editing_wcs: BoolProperty(name="Is Editing WCS")
is_editing_true_north: BoolProperty(name="Is Editing True North")
coordinate_operation: CollectionProperty(name="Coordinate Operation", type=Attribute)
projected_crs: CollectionProperty(name="Projected CRS", type=Attribute)
coordinate_input: StringProperty(name="Coordinate Input", description='Formatted "x,y,z" (without quotes)')
coordinate_output: StringProperty(name="Coordinate Output", description='Formatted "x,y,z" (without quotes)')
angle_degree_input_x: FloatProperty(name="Angle Degree Input", description="Angle (in degrees) rel to Easting")
angle_degree_input_y: FloatProperty(name="Angle Degree Input", description="Angle (in degrees) rel to +Y")
x_axis_abscissa_output: StringProperty(name="X Axis Abscissa Ordinate Output", description="X axis abscissa and ordinate", )
x_axis_ordinate_output: StringProperty(name="X Axis Abscissa Ordinate Output", description="X axis abscissa and ordinate", )
y_axis_abscissa_output: StringProperty(name="Y Axis Abscissa Ordinate Output", description="Y axis abscissa and ordinate", )
y_axis_ordinate_output: StringProperty(name="Y Axis Abscissa Ordinate Output", description="Y axis abscissa and ordinate", )
local_coordinates: StringProperty(
name="Local Coordinates", description='Formatted "x,y,z" (without quotes)', default="0,0,0"
)
map_coordinates: StringProperty(
name="Map Coordinates", description='Formatted "x,y,z" (without quotes)', default="0,0,0"
)
grid_north_angle: StringProperty(name="Grid North Angle", update=update_grid_north_angle)
x_axis_abscissa: StringProperty(name="X Axis Abscissa", update=update_grid_north_vector)
x_axis_ordinate: StringProperty(name="X Axis Ordinate", update=update_grid_north_vector)
x_axis_is_null: BoolProperty(name="X Axis Is Null")
host_model_origin: StringProperty(name="Host Model Origin")
model_origin: StringProperty(name="Model Origin")
has_blender_offset: BoolProperty(name="Has Blender Offset")
blender_eastings: StringProperty(name="Blender Eastings", default="0")
blender_northings: StringProperty(name="Blender Northings", default="0")
blender_orthogonal_height: StringProperty(name="Blender Orthogonal Height", default="0")
blender_x_axis_abscissa: StringProperty(name="Blender X Axis Abscissa", default="1")
blender_x_axis_ordinate: StringProperty(name="Blender X Axis Ordinate", default="0")
has_true_north: BoolProperty(name="Has True North", default=True)
true_north_abscissa: StringProperty(name="True North Abscissa")
true_north_ordinate: StringProperty(name="True North Ordinate")
blender_offset_x: StringProperty(name="Blender Offset X", default="0")
blender_offset_y: StringProperty(name="Blender Offset Y", default="0")
blender_offset_z: StringProperty(name="Blender Offset Z", default="0")
true_north_angle: StringProperty(name="True North Angle", update=update_true_north_angle)
true_north_abscissa: StringProperty(name="True North Abscissa", update=update_true_north_vector)
true_north_ordinate: StringProperty(name="True North Ordinate", update=update_true_north_vector)
wcs_x: StringProperty(name="WCS X", default="0")
wcs_y: StringProperty(name="WCS Y", default="0")
wcs_z: StringProperty(name="WCS Z", default="0")
wcs_rotation: StringProperty(name="WCS Rotation", default="0")
@@ -40,8 +40,9 @@ class BIM_PT_gis(Panel):
GeoreferenceData.load()
if props.is_editing:
return self.draw_editable_ui(context)
self.draw_ui(context)
self.draw_editable_ui(context)
else:
self.draw_ui(context)
def draw_editable_ui(self, context):
props = context.scene.BIMGeoreferenceProperties
@@ -53,53 +54,54 @@ class BIM_PT_gis(Panel):
draw_attributes(props.projected_crs, self.layout)
row = self.layout.row()
row.label(text="Map Conversion", icon="GRID")
row.label(text="Coordinate Operation", icon="GRID")
for attribute in props.map_conversion:
if attribute.name == "Scale" and hasattr(context.scene, "sun_pos_properties"):
for attribute in props.coordinate_operation:
if attribute.name == "XAxisAbscissa":
row = self.layout.row(align=True)
row.operator("bim.set_ifc_grid_north", text="Set IFC North")
row.operator("bim.set_blender_grid_north", text="Set Blender North")
draw_attribute(attribute, self.layout.row())
row = self.layout.row()
row.label(text="True North", icon="LIGHT_SUN")
row = self.layout.row()
row.prop(props, "has_true_north")
row = self.layout.row()
row.prop(props, "true_north_abscissa")
row = self.layout.row()
row.prop(props, "true_north_ordinate")
if hasattr(context.scene, "sun_pos_properties"):
row = self.layout.row(align=True)
row.operator("bim.set_ifc_true_north", text="Set IFC North")
row.operator("bim.set_blender_true_north", text="Set Blender North")
row.prop(props, "grid_north_angle", text="Angle")
row.prop(props, "x_axis_is_null", icon="RADIOBUT_OFF" if props.x_axis_is_null else "RADIOBUT_ON", text="")
row = self.layout.row(align=True)
row.prop(props, "x_axis_abscissa", text="XAxis Abscissa")
row.prop(props, "x_axis_is_null", icon="RADIOBUT_OFF" if props.x_axis_is_null else "RADIOBUT_ON", text="")
elif attribute.name == "XAxisOrdinate":
row = self.layout.row(align=True)
row.prop(props, "x_axis_ordinate", text="XAxis Ordinate")
row.prop(props, "x_axis_is_null", icon="RADIOBUT_OFF" if props.x_axis_is_null else "RADIOBUT_ON", text="")
if hasattr(context.scene, "sun_pos_properties"):
row = self.layout.row(align=True)
row.operator("bim.set_ifc_grid_north", text="Set IFC North")
row.operator("bim.set_blender_grid_north", text="Set Blender North")
else:
draw_attribute(attribute, self.layout.row())
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")
row.label(text="IFC2X3 Fallback In Use", icon="INFO")
if not GeoreferenceData.data["projected_crs"]:
row = self.layout.row(align=True)
row.label(text="Not Georeferenced")
row = self.layout.row()
row.label(text="Not Georeferenced", icon="ERROR")
if tool.Ifc.get_schema() != "IFC2X3":
row = self.layout.row(align=True)
row.prop(props, "coordinate_operation_class", text="")
row.operator("bim.add_georeferencing", icon="ADD", text="")
if props.has_blender_offset:
row = self.layout.row()
row.label(text="Blender Offset", icon="TRACKING_REFINE_FORWARDS")
row.label(text="Blender Session Origin", icon="TRACKING_REFINE_FORWARDS")
row = self.layout.row(align=True)
row.label(text="Eastings")
row.label(text=f"Eastings ({GeoreferenceData.data['local_unit_symbol']})")
row.label(text=props.blender_eastings)
row = self.layout.row(align=True)
row.label(text="Northings")
row.label(text=f"Northings ({GeoreferenceData.data['local_unit_symbol']})")
row.label(text=props.blender_northings)
row = self.layout.row(align=True)
row.label(text="OrthogonalHeight")
row.label(text=f"OrthogonalHeight ({GeoreferenceData.data['local_unit_symbol']})")
row.label(text=props.blender_orthogonal_height)
row = self.layout.row(align=True)
row.label(text="XAxisAbscissa")
@@ -125,30 +127,140 @@ class BIM_PT_gis(Panel):
row.label(text=key)
row.label(text=str(value))
if GeoreferenceData.data["map_conversion"]:
if GeoreferenceData.data["coordinate_operation"]:
row = self.layout.row(align=True)
row.label(text="Map Conversion", icon="GRID")
row.label(text="Coordinate Operation", icon="GRID")
for key, value in GeoreferenceData.data["map_conversion"].items():
for key, value in GeoreferenceData.data["coordinate_operation"].items():
if value is None:
continue
if key == "type":
key = "Type"
row = self.layout.row(align=True)
row.label(text=key)
if key in ("Eastings", "Northings", "OrthogonalHeight"):
row.label(text=f"{key} ({GeoreferenceData.data['map_unit_symbol']})")
else:
row.label(text=key)
row.label(text=str(value))
if key == "XAxisOrdinate":
row = self.layout.row(align=True)
row.label(text="Derived Angle")
row.label(text=GeoreferenceData.data["map_derived_angle"])
if GeoreferenceData.data["true_north"]:
row = self.layout.row()
row.label(text="True North", icon="LIGHT_SUN")
class BIM_PT_gis_true_north(Panel):
bl_idname = "BIM_PT_gis_true_north"
bl_label = "True North"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_gis"
def draw(self, context):
if not GeoreferenceData.is_loaded:
GeoreferenceData.load()
self.props = context.scene.BIMGeoreferenceProperties
if self.props.is_editing_true_north:
self.draw_editable_ui(context)
else:
self.draw_ui()
def draw_editable_ui(self, context):
row = self.layout.row()
row.prop(self.props, "true_north_angle", text="Angle")
row = self.layout.row()
row.prop(self.props, "true_north_abscissa", text="Abscissa")
row = self.layout.row()
row.prop(self.props, "true_north_ordinate", text="Ordinate")
if hasattr(context.scene, "sun_pos_properties"):
row = self.layout.row(align=True)
row.label(text="Vector")
row.label(text=str(GeoreferenceData.data["true_north"][0:2])[1:-1])
row.operator("bim.set_ifc_true_north", text="Set IFC North")
row.operator("bim.set_blender_true_north", text="Set Blender North")
row = self.layout.row(align=True)
row.operator("bim.edit_true_north", icon="CHECKMARK")
row.operator("bim.disable_editing_true_north", icon="CANCEL", text="")
def draw_ui(self):
if GeoreferenceData.data["true_north"]:
row = self.layout.row(align=True)
row.label(text="Abscissa")
row.label(text=str(GeoreferenceData.data["true_north"][0]))
row = self.layout.row(align=True)
row.label(text="Ordinate")
row.label(text=str(GeoreferenceData.data["true_north"][1]))
row.operator("bim.enable_editing_true_north", icon="GREASEPENCIL", text="")
row.operator("bim.remove_true_north", icon="X", text="")
row = self.layout.row(align=True)
row.label(text="Derived Angle")
row.label(text=GeoreferenceData.data["true_derived_angle"])
else:
row = self.layout.row(align=True)
row.label(text="No True North Found", icon="LIGHT_SUN")
row.operator("bim.enable_editing_true_north", icon="GREASEPENCIL", text="")
class BIM_PT_gis_wcs(Panel):
bl_idname = "BIM_PT_gis_wcs"
bl_label = "World Coordinate System"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_gis"
def draw(self, context):
if not GeoreferenceData.is_loaded:
GeoreferenceData.load()
props = context.scene.BIMGeoreferenceProperties
if props.is_editing_wcs:
self.draw_editable_ui(context)
else:
self.draw_ui()
def draw_ui(self):
if GeoreferenceData.data["world_coordinate_system"]["has_transformation"]:
row = self.layout.row(align=True)
row.label(text="Unrecommended Transformation Found", icon="ERROR")
row.operator("bim.enable_editing_wcs", icon="GREASEPENCIL", text="")
row = self.layout.row(align=True)
row.label(text=f"X ({GeoreferenceData.data['local_unit_symbol']})")
row.label(text=str(GeoreferenceData.data["world_coordinate_system"]["x"]))
row = self.layout.row(align=True)
row.label(text=f"Y ({GeoreferenceData.data['local_unit_symbol']})")
row.label(text=str(GeoreferenceData.data["world_coordinate_system"]["y"]))
row = self.layout.row(align=True)
row.label(text=f"Z ({GeoreferenceData.data['local_unit_symbol']})")
row.label(text=str(GeoreferenceData.data["world_coordinate_system"]["z"]))
row = self.layout.row(align=True)
row.label(text="Rotation")
row.label(text=str(GeoreferenceData.data["world_coordinate_system"]["rotation"]))
else:
row = self.layout.row()
row.label(text="No WCS Transformation", icon="CHECKMARK")
row.operator("bim.enable_editing_wcs", icon="GREASEPENCIL", text="")
def draw_editable_ui(self, context):
props = context.scene.BIMGeoreferenceProperties
row = self.layout.row(align=True)
row.label(text="World Coordinate System", icon="EMPTY_ARROWS")
row.operator("bim.edit_wcs", icon="CHECKMARK", text="")
row.operator("bim.disable_editing_wcs", icon="CANCEL", text="")
row = self.layout.row()
row.prop(props, "wcs_x")
row = self.layout.row()
row.prop(props, "wcs_y")
row = self.layout.row()
row.prop(props, "wcs_z")
row = self.layout.row()
row.prop(props, "wcs_rotation")
class BIM_PT_gis_calculator(Panel):
@@ -161,33 +273,17 @@ class BIM_PT_gis_calculator(Panel):
bl_parent_id = "BIM_PT_gis"
def draw(self, context):
if not GeoreferenceData.is_loaded:
GeoreferenceData.load()
props = context.scene.BIMGeoreferenceProperties
row = self.layout.row(align=True)
row.prop(props, "coordinate_input", text="Input")
row.prop(props, "local_coordinates", text=f"Local ({GeoreferenceData.data['local_unit_symbol']})")
row.operator("bim.get_cursor_location", text="", icon="TRACKER")
row = self.layout.row(align=True)
row.prop(props, "coordinate_output", text="Output")
row.operator("bim.set_cursor_location", text="", icon="TRACKER")
row = self.layout.row()
row.prop(props, "map_coordinates", text=f"Map ({GeoreferenceData.data['map_unit_symbol']})")
row = self.layout.row(align=True)
row.operator("bim.convert_local_to_global", text="Local to Global")
row.operator("bim.convert_global_to_local", text="Global to Local")
row = self.layout.row(align=True)
row.label(text="Orientation Calculator", icon="TRACKING_REFINE_FORWARDS")
row = self.layout.row(align=True)
row.prop(props, "angle_degree_input_x", text="Angle (°) rel to Easting")
row.operator("bim.convert_angle_to_coord", text="", icon="FILE_REFRESH").type = "rel_x"
row = self.layout.row(align=True)
row.prop(props, "x_axis_abscissa_output", text="XAxis Abscissa")
row = self.layout.row(align=True)
row.prop(props, "x_axis_ordinate_output", text="XAxis Ordinate")
row = self.layout.row(align=True)
row.prop(props, "angle_degree_input_y", text="Angle (°) rel to to +Y")
row.operator("bim.convert_angle_to_coord", text="", icon="FILE_REFRESH").type = "rel_y"
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")
@@ -58,7 +58,6 @@ classes = (
operator.SelectByMaterial,
operator.UnassignMaterial,
operator.UnassignMaterialStyle,
operator.UnlinkMaterial,
prop.Material,
prop.BIMMaterialProperties,
prop.BIMObjectMaterialProperties,
@@ -118,7 +118,6 @@ class AddMaterial(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.add_material"
bl_label = "Add Material"
bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty(name="Material Name")
name: bpy.props.StringProperty(default="Default")
category: bpy.props.StringProperty(default="")
description: bpy.props.StringProperty(default="")
@@ -135,8 +134,7 @@ class AddMaterial(bpy.types.Operator, tool.Ifc.Operator):
row.prop(self, "category", text="Category")
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, name=self.name, category=self.category, description=self.description)
core.add_material(tool.Ifc, tool.Material, name=self.name, category=self.category, description=self.description)
material_prop_purge()
@@ -182,7 +180,7 @@ class RemoveMaterial(bpy.types.Operator, tool.Ifc.Operator):
material: bpy.props.IntProperty()
def _execute(self, context):
res = core.remove_material(tool.Ifc, tool.Material, tool.Style, material=tool.Ifc.get().by_id(self.material))
res = core.remove_material(tool.Ifc, tool.Material, material=tool.Ifc.get().by_id(self.material))
if not res:
self.report({"ERROR"}, "Material is used in material sets and cannot be removed.")
return {"CANCELLED"}
@@ -198,16 +196,6 @@ class RemoveMaterialSet(bpy.types.Operator, tool.Ifc.Operator):
core.remove_material_set(tool.Ifc, tool.Material, material=tool.Ifc.get().by_id(self.material))
class UnlinkMaterial(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.unlink_material"
bl_label = "Unlink Material"
bl_options = {"REGISTER", "UNDO"}
material: bpy.props.IntProperty(name="Material ID")
def _execute(self, context):
core.unlink_material(tool.Ifc, obj=tool.Ifc.get_object(tool.Ifc.get().by_id(self.material)))
class AssignMaterial(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.assign_material"
bl_label = "Assign Material"
@@ -69,8 +69,6 @@ class BIM_PT_materials(Panel):
op.material = material.ifc_definition_id
op = row.operator("bim.enable_editing_material_style", text="", icon="SHADING_RENDERED")
op.material = material.ifc_definition_id
op = row.operator("bim.unlink_material", icon="UNLINKED", text="")
op.material = material.ifc_definition_id
row.operator("bim.remove_material", text="", icon="X").material = material.ifc_definition_id
self.draw_editing_ui()
@@ -152,7 +150,7 @@ class BIM_PT_object_material(Panel):
if not ObjectMaterialData.data["materials"]:
row = self.layout.row(align=True)
row.label(text="No Materials Available")
row.operator("bim.add_material", icon="ADD", text="").obj = ""
row.operator("bim.add_material", icon="ADD", text="")
return
if ObjectMaterialData.data["type_material"]:
@@ -78,7 +78,7 @@ class ProfileDecorator:
self.draw_batch("TRIS", vertices_coords, faces_color, face_indices)
def __call__(self, context, get_custom_bmesh=None, draw_faces=False, exit_edit_mode_callback=None):
self.addon_prefs = context.preferences.addons["blenderbim"].preferences
self.addon_prefs = tool.Blender.get_addon_preferences()
selected_elements_color = self.addon_prefs.decorator_color_selected
unselected_elements_color = self.addon_prefs.decorator_color_unselected
special_elements_color = self.addon_prefs.decorator_color_special
@@ -224,7 +224,7 @@ class FilledOpeningGenerator:
else:
opening_obj = tool.Ifc.get_object(opening)
if opening_obj:
tool.Ifc.unlink(obj=opening_obj)
tool.Ifc.unlink(element=opening)
bpy.data.objects.remove(opening_obj)
filling_obj = tool.Ifc.get_object(filling)
@@ -541,20 +541,23 @@ class AddBoolean(Operator, tool.Ifc.Operator):
obj1, obj2 = context.selected_objects
element1 = tool.Ifc.get_entity(obj1)
element2 = tool.Ifc.get_entity(obj2)
if element1 and element2:
return {"FINISHED"}
if not element1 and not element2:
if not (bool(element1) ^ bool(element2)):
self.report({"INFO"}, "One of the selected objects should be blender object and another IFC object.")
return {"FINISHED"}
# element1 - IFC object, element2 - blender object.
if element2 and not element1:
obj1, obj2 = obj2, obj1
element1, element2 = element2, element1
if not obj1.data or not hasattr(obj1.data, "BIMMeshProperties"):
representation = tool.Geometry.get_active_representation(obj1)
if not representation:
self.report({"INFO"}, "No representation found for the selected IFC object.")
return {"FINISHED"}
representation = tool.Ifc.get().by_id(obj1.data.BIMMeshProperties.ifc_definition_id)
if not obj2.data or len(obj2.data.polygons) <= 4: # It takes 4 faces to create a closed solid
mesh_data = {"type": "IfcHalfSpaceSolid", "matrix": obj1.matrix_world.inverted() @ obj2.matrix_world}
elif obj2.data:
else:
mesh_data = {"type": "Mesh", "blender_obj": obj1, "blender_void": obj2}
booleans = ifcopenshell.api.run(
@@ -574,7 +577,6 @@ class AddBoolean(Operator, tool.Ifc.Operator):
should_sync_changes_first=False,
)
tool.Ifc.unlink(obj=obj2)
bpy.data.objects.remove(obj2)
return {"FINISHED"}
@@ -772,9 +774,9 @@ class HideOpenings(Operator, tool.Ifc.Operator):
for opening in openings:
opening_obj = tool.Ifc.get_object(opening)
if opening_obj:
to_delete.add(opening_obj)
for opening_obj in to_delete:
tool.Ifc.unlink(obj=opening_obj)
to_delete.add((opening, opening_obj))
for opening, opening_obj in to_delete:
tool.Ifc.unlink(element=opening)
bpy.data.objects.remove(opening_obj)
tool.Model.clear_scene_openings()
return {"FINISHED"}
@@ -824,7 +826,7 @@ class EditOpenings(Operator, tool.Ifc.Operator):
building_objs.update(
self.get_all_building_objects_of_similar_openings(opening)
) # NB this has nothing to do with clone similar_opening
tool.Ifc.unlink(element=opening, obj=opening_obj)
tool.Ifc.unlink(element=opening)
bpy.data.objects.remove(opening_obj)
tool.Model.reload_body_representation(building_objs)
@@ -902,7 +904,7 @@ class DecorationsHandler:
batch.draw(shader)
def __call__(self, context):
self.addon_prefs = context.preferences.addons["blenderbim"].preferences
self.addon_prefs = tool.Blender.get_addon_preferences()
selected_elements_color = self.addon_prefs.decorator_color_selected
unselected_elements_color = self.addon_prefs.decorator_color_unselected
special_elements_color = self.addon_prefs.decorator_color_special
@@ -18,6 +18,7 @@
import bpy
import mathutils
import numpy as np
from functools import reduce
import ifcopenshell
import ifcopenshell.api
@@ -39,7 +40,7 @@ from mathutils import Vector, Matrix
from bpy_extras.object_utils import AddObjectHelper
from . import prop
import json
from typing import Any, Union
from typing import Any, Union, Optional, assert_never
class EnableAddType(bpy.types.Operator, tool.Ifc.Operator):
@@ -513,88 +514,49 @@ def regenerate_profile_usage(usecase_path, ifc_file, settings):
def ensure_material_assigned(usecase_path: str, ifc_file: ifcopenshell.file, settings: dict[str, Any]) -> None:
if usecase_path == "material.assign_material":
if not settings.get("material", None):
return
elements = settings["products"]
elements = settings["products"]
material = settings.get("material")
if material:
assigned_material = settings["material"]
else:
elements = []
for rel in ifc_file.by_type("IfcRelAssociatesMaterial"):
if rel.RelatingMaterial == settings["material"] or [
e for e in ifc_file.traverse(rel.RelatingMaterial) if e == settings["material"]
]:
elements.extend(rel.RelatedObjects)
material_type: ifcopenshell.util.element.MATERIAL_TYPE = settings["type"]
element = elements[0]
if material_type == "IfcMaterial":
assigned_material = ifcopenshell.util.element.get_material(element, should_inherit=False)
assert assigned_material # Type checker.
# Material usages just inherit the style from the type material, so can't override it.
elif material_type in ("IfcMaterialLayerSetUsage", "IfcMaterialProfileSetUsage"):
return
# If type is Set and no material argument were provided, then Set was just created
# and not yet have any IfcMaterials.
elif material_type in ("IfcMaterialConstituentSet", "IfcMaterialLayerSet", "IfcMaterialProfileSet"):
return
elif material_type == "IfcMaterialList":
assert False, "Current assign_material implementation requires 'material' argument for IfcMaterialList."
else:
assert_never(material_type)
update_blender_ifc_materials(elements)
for element in elements[:]:
if element.is_a("IfcElementType"):
elements.extend(tool.Model.get_occurrences_without_material_override(element))
tool.Model.apply_ifc_material_changes(elements, assigned_material=settings["material"])
def ensure_material_unassigned(usecase_path: str, ifc_file: ifcopenshell.file, settings: dict[str, Any]) -> None:
elements = settings["products"]
if elements[0].is_a("IfcElementType"):
elements.extend(ifcopenshell.util.element.get_types(elements[0]))
update_blender_ifc_materials(elements)
# unassign_material could be called when product is about to get removed
# and representation might be already removed.
elements = [
e
for e in elements
if (obj := tool.Ifc.get_object(e))
and obj.data
and tool.Ifc.get_entity_by_id(obj.data.BIMMeshProperties.ifc_definition_id)
]
def update_blender_ifc_materials(elements: list[ifcopenshell.entity_instance]) -> None:
"""update mesh blender materials that have ifc material connected to them
by replacing them with `blender_material`"""
# since different elements can share meshes (e.g. occurrecnes without openings)
# we need to make sure not to affect them accidentally
meshes_users: dict[bpy.types.Mesh, set[bpy.types.Object]] = dict()
for obj in bpy.data.objects:
if not obj.data:
continue
meshes_users.setdefault(obj.data, set()).add(obj)
objects: set[bpy.types.Object] = set()
for element in elements:
obj: bpy.types.Object = tool.Ifc.get_object(element)
if not obj or not obj.data:
continue
objects.add(obj)
meshes: set[bpy.types.Mesh] = {obj.data for obj in objects}
for mesh in meshes:
mesh_users = meshes_users[mesh]
if not mesh_users.issubset(objects):
continue
# NOTE: we need `obj` as removing materials and appending them to `mesh.materials`
# will mess up mesh faces material indices
# NOTE: we make an assumption here that all mesh users
# have the same material - they either inherit it from the type
# or type doesn't have a material.
#
# If we add option to UI to add materials overriding type materials
# then this assumption won't be safe anymore
obj = next(iter(mesh_users))
element = tool.Ifc.get_entity(obj)
current_material = ifcopenshell.util.element.get_material(element)
if current_material:
current_material = tool.Ifc.get_object(current_material)
material_replaced = False
for material_slot in obj.material_slots:
material = material_slot.material
if material is None:
continue
ifc_material = tool.Ifc.get_entity(material)
# it's blender material for style, so ignore it
if not ifc_material:
continue
if ifc_material == current_material:
continue
material_slot.material = current_material
material_replaced = True
if not material_replaced and current_material:
mesh.materials.append(current_material)
# clear empty slots
for i, material in reversed(list(enumerate(mesh.materials[:]))):
if material is None:
mesh.materials.pop(index=i)
for element in elements[:]:
if element.is_a("IfcElementType"):
elements.extend(tool.Model.get_occurrences_without_material_override(element))
tool.Model.apply_ifc_material_changes(elements)
@@ -108,7 +108,7 @@ def update_railing_modifier_ifc_data(context):
tool.Model.replace_object_ifc_representation(body, obj, model_representation)
# hacky way to ensure tha ifc representation won't get tessellated at project save
IfcStore.edited_objs.discard(obj)
tool.Ifc.finish_edit(obj)
elif props.railing_type == "FRAMELESS_PANEL":
tool.Ifc.edit(obj)
@@ -17,34 +17,34 @@
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
import ifcopenshell.file
import blenderbim.bim.handler
import blenderbim.tool as tool
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.ifc import IFC_CONNECTED_TYPE
from typing import Any
def sync_name(usecase_path, ifc_file, settings):
def sync_name(usecase_path: str, ifc_file: ifcopenshell.file, settings: dict[str, Any]) -> None:
if usecase_path == "attribute.edit_attributes":
element = settings["product"]
elif usecase_path == "style.edit_presentation_style":
element = settings["style"]
else:
raise Exception(f"Unsupported usecase: '{usecase_path}'.")
element: ifcopenshell.entity_instance
if "Name" not in settings["attributes"]:
return
obj = tool.Ifc.get_object(element)
if not obj:
return
obj: IFC_CONNECTED_TYPE
if isinstance(obj, bpy.types.Object):
new_name = "{}/{}".format(element.is_a(), settings["attributes"]["Name"] or "Unnamed")
collection = obj.BIMObjectProperties.collection
if collection:
collection.name = new_name
elif isinstance(obj, bpy.types.Material):
new_name = settings["attributes"]["Name"] or "Unnamed"
material = tool.Ifc.get_entity(obj)
if material and material != element:
material.Name = new_name
style = tool.Style.get_style(obj)
if style and style != element:
style.Name = new_name
collection = obj.BIMObjectProperties.collection
if collection:
collection.name = new_name
obj.name = new_name
blenderbim.bim.handler.refresh_ui_data()
@@ -30,6 +30,7 @@ classes = (
operator.ChangeLibraryElement,
operator.CreateClippingPlane,
operator.CreateProject,
operator.ClearRecentIFCProjects,
operator.DisableCulling,
operator.DisableEditingHeader,
operator.EditHeader,
@@ -58,7 +59,6 @@ classes = (
operator.UnassignLibraryDeclaration,
operator.UnlinkIfc,
operator.UnloadLink,
operator.UnloadProject,
workspace.ExploreHotkey,
prop.LibraryElement,
prop.FilterCategory,
@@ -66,6 +66,8 @@ classes = (
prop.BIMProjectProperties,
ui.BIM_MT_new_project,
ui.BIM_MT_project,
ui.BIM_PT_new_project_wizard,
ui.BIM_MT_recent_projects,
ui.BIM_PT_project,
ui.BIM_PT_project_library,
ui.BIM_PT_links,
@@ -43,7 +43,7 @@ class ProjectData:
@classmethod
def get_export_schema(cls):
return [(s, s, "") for s in IfcStore.schema_identifiers]
return [(s, "IFC4X3" if s == "IFC4X3_ADD2" else s, "") for s in IfcStore.schema_identifiers]
@classmethod
def library_file(cls):
@@ -64,7 +64,7 @@ class ProjectDecorator:
batch.draw(shader)
def __call__(self, context):
self.addon_prefs = context.preferences.addons["blenderbim"].preferences
self.addon_prefs = tool.Blender.get_addon_preferences()
selected_elements_color = self.addon_prefs.decorator_color_selected
unselected_elements_color = self.addon_prefs.decorator_color_unselected
special_elements_color = self.addon_prefs.decorator_color_special
@@ -128,7 +128,7 @@ class ClippingPlaneDecorator:
batch.draw(shader)
def __call__(self, context):
self.addon_prefs = context.preferences.addons["blenderbim"].preferences
self.addon_prefs = tool.Blender.get_addon_preferences()
selected_elements_color = self.addon_prefs.decorator_color_selected
unselected_elements_color = self.addon_prefs.decorator_color_unselected
special_elements_color = self.addon_prefs.decorator_color_special
@@ -19,10 +19,12 @@
import os
import bpy
import time
import json
import logging
import tempfile
import traceback
import subprocess
import datetime
import numpy as np
import ifcopenshell
import ifcopenshell.api
@@ -43,12 +45,11 @@ from blenderbim.bim.ui import IFCFileSelector
from blenderbim.bim import import_ifc
from blenderbim.bim import export_ifc
from collections import defaultdict
import json
from math import radians
from pathlib import Path
from mathutils import Vector, Matrix
from bpy.app.handlers import persistent
from ifcopenshell.geom import ShapeElementType, ShapeType
from ifcopenshell.geom import ShapeElementType
from blenderbim.bim.module.project.data import LinksData
from blenderbim.bim.module.project.decorator import ProjectDecorator, ClippingPlaneDecorator
from typing import Union
@@ -116,7 +117,6 @@ class CreateProject(bpy.types.Operator):
def _execute(self, context):
props = context.scene.BIMProjectProperties
template = None if props.template_file == "0" else props.template_file
blenderbim.bim.schema.reload(props.export_schema)
if tool.Blender.is_default_scene():
for obj in bpy.data.objects:
bpy.data.objects.remove(obj)
@@ -125,6 +125,7 @@ class CreateProject(bpy.types.Operator):
for mat in bpy.data.materials:
bpy.data.materials.remove(mat)
core.create_project(tool.Ifc, tool.Project, tool.Spatial, schema=props.export_schema, template=template)
blenderbim.bim.schema.reload(tool.Ifc.get().schema_identifier)
tool.Blender.register_toolbar()
def rollback(self, data):
@@ -470,8 +471,7 @@ class AppendLibraryElement(bpy.types.Operator):
ifc_import_settings = import_ifc.IfcImportSettings.factory(context, IfcStore.path, logger)
ifc_importer = import_ifc.IfcImporter(ifc_import_settings)
ifc_importer.file = self.file
blender_material = ifc_importer.create_material(element)
self.import_material_styles(blender_material, element, ifc_importer)
self.import_material_styles(element, ifc_importer)
def import_product_from_ifc(self, element: ifcopenshell.entity_instance, context: bpy.types.Context) -> None:
self.file = IfcStore.get_file()
@@ -515,8 +515,7 @@ class AppendLibraryElement(bpy.types.Operator):
for material in ifcopenshell.util.element.get_materials(element):
if IfcStore.get_element(material.id()):
continue
blender_material = ifc_importer.create_material(material)
self.import_material_styles(blender_material, material, ifc_importer)
self.import_material_styles(material, ifc_importer)
def import_styles(self, element: ifcopenshell.entity_instance, ifc_importer: import_ifc.IfcImporter) -> None:
if element.is_a("IfcTypeProduct"):
@@ -533,7 +532,6 @@ class AppendLibraryElement(bpy.types.Operator):
def import_material_styles(
self,
blender_material: bpy.types.Material,
material: ifcopenshell.entity_instance,
ifc_importer: import_ifc.IfcImporter,
) -> None:
@@ -541,7 +539,7 @@ class AppendLibraryElement(bpy.types.Operator):
return
for element in self.file.traverse(material.HasRepresentation[0]):
if element.is_a("IfcSurfaceStyle") and not IfcStore.get_element(element.id()):
ifc_importer.create_style(element, blender_material)
ifc_importer.create_style(element)
class EnableEditingHeader(bpy.types.Operator):
@@ -658,6 +656,46 @@ class LoadProject(bpy.types.Operator, IFCFileSelector):
is_advanced: bpy.props.BoolProperty(name="Enable Advanced Mode", default=False)
use_relative_path: bpy.props.BoolProperty(name="Use Relative Path", default=False)
should_start_fresh_session: bpy.props.BoolProperty(name="Should Start Fresh Session", default=True)
use_detailed_tooltip: bpy.props.BoolProperty(default=False, options={"HIDDEN"})
@classmethod
def description(cls, context, properties):
tooltip = cls.bl_description
if not properties.use_detailed_tooltip:
return tooltip
filepath = properties.filepath
if not filepath:
return tooltip
filepath = Path(filepath)
tooltip += f".\n\n"
if not filepath.exists():
tooltip += "File does not exist"
return tooltip
def get_modified_date(st_mtime: float) -> str:
mod_time = datetime.datetime.fromtimestamp(st_mtime)
today = datetime.date.today()
if mod_time.date() == today:
return f"Today {mod_time.strftime('%I:%M %p')}"
elif mod_time.date() == today - datetime.timedelta(days=1):
return f"Yesterday {mod_time.strftime('%I:%M %p')}"
return mod_time.strftime("%d %b %Y")
def get_file_size(size_bytes: float) -> str:
if size_bytes < 1024 * 1024: # Less than 1 MiB
size = size_bytes / 1024
return f"{size:.0f} KiB"
else:
size = size_bytes / (1024 * 1024)
return f"{size:.1f} MiB"
file_stat = filepath.stat()
tooltip += f"Modified: {get_modified_date(file_stat.st_mtime)}"
tooltip += f"\nSize: {get_file_size(file_stat.st_size)}"
return tooltip
def execute(self, context):
@persistent
@@ -686,10 +724,13 @@ class LoadProject(bpy.types.Operator, IFCFileSelector):
for obj in bpy.data.objects:
bpy.data.objects.remove(obj)
context.scene.BIMProperties.ifc_file = self.get_filepath()
filepath = Path(self.get_filepath())
context.scene.BIMProperties.ifc_file = str(filepath)
context.scene.BIMProjectProperties.is_loading = True
context.scene.BIMProjectProperties.total_elements = len(tool.Ifc.get().by_type("IfcElement"))
tool.Blender.register_toolbar()
tool.Blender.add_recent_ifc_project(filepath.absolute())
if not self.is_advanced:
bpy.ops.bim.load_project_elements()
except:
@@ -706,16 +747,13 @@ class LoadProject(bpy.types.Operator, IFCFileSelector):
IFCFileSelector.draw(self, context)
class UnloadProject(bpy.types.Operator):
bl_idname = "bim.unload_project"
bl_label = "Unload Project"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Unload the IFC project"
class ClearRecentIFCProjects(bpy.types.Operator):
bl_idname = "bim.clear_recent_ifc_projects"
bl_label = "Clear Recent IFC Projects List"
bl_options = {"REGISTER"}
def execute(self, context):
IfcStore.purge()
context.scene.BIMProperties.ifc_file = ""
context.scene.BIMProjectProperties.is_loading = False
tool.Blender.clear_recent_ifc_projects()
return {"FINISHED"}
@@ -859,7 +897,19 @@ class LinkIfc(bpy.types.Operator):
directory: bpy.props.StringProperty(subtype="DIR_PATH")
filter_glob: bpy.props.StringProperty(default="*.ifc", options={"HIDDEN"})
use_relative_path: bpy.props.BoolProperty(name="Use Relative Path", default=False)
false_origin: bpy.props.StringProperty(name="False Origin", default="0,0,0")
use_cache: bpy.props.BoolProperty(name="Use Cache", default=True)
def draw(self, context):
pprops = context.scene.BIMProjectProperties
row = self.layout.row()
row.prop(self, "use_relative_path")
row = self.layout.row()
row.prop(self, "use_cache")
row = self.layout.row()
row.prop(pprops, "false_origin_mode")
if pprops.false_origin_mode == "MANUAL":
row = self.layout.row()
row.prop(pprops, "false_origin")
def execute(self, context):
start = time.time()
@@ -876,7 +926,7 @@ class LinkIfc(bpy.types.Operator):
except:
pass # Perhaps on another drive or something
new.name = filepath
status = bpy.ops.bim.load_link(filepath=filepath, false_origin=self.false_origin)
status = bpy.ops.bim.load_link(filepath=filepath, use_cache=self.use_cache)
if status == {"CANCELLED"}:
error_msg = (
f'Error processing IFC file "{self.filepath}" '
@@ -954,7 +1004,7 @@ class LoadLink(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
bl_description = "Load the selected file"
filepath: bpy.props.StringProperty()
false_origin: bpy.props.StringProperty(name="False Origin", default="0,0,0")
use_cache: bpy.props.BoolProperty(name="Use Cache", default=True)
def execute(self, context):
self.filepath = self.filepath.replace("\\", "/")
@@ -986,12 +1036,34 @@ class LoadLink(bpy.types.Operator):
blend_filepath = self.filepath + ".cache.blend"
h5_filepath = self.filepath + ".cache.h5"
if not self.use_cache and os.path.exists(blend_filepath):
os.remove(blend_filepath)
if not os.path.exists(blend_filepath):
pprops = bpy.context.scene.BIMProjectProperties
gprops = bpy.context.scene.BIMGeoreferenceProperties
tool.Loader.calculate_model_origin(tool.Ifc.get())
code = f"""
import bpy
def run():
bpy.ops.bim.load_linked_project(filepath="{self.filepath}", false_origin="{self.false_origin}")
gprops = bpy.context.scene.BIMGeoreferenceProperties
# Our model origin becomes their host model origin
gprops.host_model_origin = "{gprops.model_origin}"
gprops.has_blender_offset = {gprops.has_blender_offset}
gprops.blender_eastings = "{gprops.blender_eastings}"
gprops.blender_northings = "{gprops.blender_northings}"
gprops.blender_orthogonal_height = "{gprops.blender_orthogonal_height}"
gprops.blender_offset_x = "{gprops.blender_offset_x}"
gprops.blender_offset_y = "{gprops.blender_offset_y}"
gprops.blender_offset_z = "{gprops.blender_offset_z}"
gprops.blender_x_axis_abscissa = "{gprops.blender_x_axis_abscissa}"
gprops.blender_x_axis_ordinate = "{gprops.blender_x_axis_ordinate}"
pprops = bpy.context.scene.BIMProjectProperties
pprops.distance_limit = {pprops.distance_limit}
pprops.false_origin_mode = "{pprops.false_origin_mode}"
pprops.false_origin = "{pprops.false_origin}"
bpy.ops.bim.load_linked_project(filepath="{self.filepath}")
bpy.ops.wm.save_as_mainfile(filepath="{blend_filepath}")
try:
@@ -1011,8 +1083,26 @@ except Exception as e:
if not os.path.exists(blend_filepath) or os.stat(blend_filepath).st_mtime < t:
return {"CANCELLED"}
self.set_model_origin_from_link()
self.link_blend(blend_filepath)
def set_model_origin_from_link(self):
if tool.Ifc.get():
return # The current model's coordinates always take priority.
json_filepath = self.filepath + ".cache.json"
if not os.path.exists(json_filepath):
return
with open(json_filepath, "r") as f:
data = json.load(f)
gprops = bpy.context.scene.BIMGeoreferenceProperties
for prop in ("model_origin", "blender_x_axis_abscissa", "blender_x_axis_ordinate"):
if (value := data.get(prop, None)) is not None:
setattr(gprops, prop, value)
class ReloadLink(bpy.types.Operator):
bl_idname = "bim.reload_link"
@@ -1248,7 +1338,6 @@ class LoadLinkedProject(bpy.types.Operator):
bl_label = "Load a project for viewing only."
bl_options = {"REGISTER", "UNDO"}
filepath: bpy.props.StringProperty()
false_origin: bpy.props.StringProperty(name="False Origin", default="0,0,0")
def execute(self, context):
import ifcpatch
@@ -1257,6 +1346,9 @@ class LoadLinkedProject(bpy.types.Operator):
start = time.time()
pprops = bpy.context.scene.BIMProjectProperties
gprops = bpy.context.scene.BIMGeoreferenceProperties
self.filepath = self.filepath.replace("\\", "/")
print("Processing", self.filepath)
@@ -1272,32 +1364,56 @@ class LoadLinkedProject(bpy.types.Operator):
print("Finished writing property database")
logger = logging.getLogger("ImportIFC")
ifc_import_settings = import_ifc.IfcImportSettings.factory(context, IfcStore.path, logger)
ifc_importer = import_ifc.IfcImporter(ifc_import_settings)
ifc_importer.file = self.file
ifc_importer.process_context_filter()
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(self.file)
tool.Loader.set_unit_scale(self.unit_scale)
tool.Loader.set_settings(import_ifc.IfcImportSettings.factory(context, IfcStore.path, logger))
tool.Loader.settings.contexts = ifcopenshell.util.representation.get_prioritised_contexts(self.file)
tool.Loader.settings.context_settings = tool.Loader.create_settings()
self.elements = set(self.file.by_type("IfcElement"))
if self.file.schema in ("IFC2X3", "IFC4"):
self.elements |= set(self.file.by_type("IfcProxy"))
self.elements |= set(self.file.by_type("IfcSite"))
if self.file.schema == "IFC2X3":
self.elements |= set(self.file.by_type("IfcSpatialStructureElement"))
else:
self.elements |= set(self.file.by_type("IfcSpatialElement"))
self.elements -= set(self.file.by_type("IfcFeatureElement"))
self.elements = list(self.elements)
model_origin = np.array(ifcopenshell.util.geolocation.auto_xyz2enh(self.file, 0, 0, 0))
false_origin = np.array([float(o.strip()) for o in self.false_origin.split(",")])
model_offset = model_origin - false_origin
zero_origin = np.array((0, 0, 0))
has_model_offset = not np.allclose(model_offset, zero_origin)
if tool.Loader.settings.false_origin_mode == "MANUAL" and tool.Loader.settings.false_origin:
tool.Loader.set_manual_blender_offset(self.file)
elif tool.Loader.settings.false_origin_mode == "AUTOMATIC":
if gprops.host_model_origin:
tool.Loader.settings.false_origin = list(map(float, gprops.host_model_origin.split(",")))
tool.Loader.set_manual_blender_offset(self.file)
else:
tool.Loader.guess_false_origin(self.file)
for settings in ifc_importer.context_settings:
settings = ifcopenshell.geom.settings()
tool.Loader.calculate_model_origin(self.file)
self.json_filepath = self.filepath + ".cache.json"
data = {
"host_model_origin": gprops.host_model_origin,
"model_origin": gprops.model_origin,
"has_blender_offset": gprops.has_blender_offset,
"blender_eastings": gprops.blender_eastings,
"blender_northings": gprops.blender_northings,
"blender_orthogonal_height": gprops.blender_orthogonal_height,
"blender_offset_x": gprops.blender_offset_x,
"blender_offset_y": gprops.blender_offset_y,
"blender_offset_z": gprops.blender_offset_z,
"blender_x_axis_abscissa": gprops.blender_x_axis_abscissa,
"blender_x_axis_ordinate": gprops.blender_x_axis_ordinate,
"distance_limit": pprops.distance_limit,
"false_origin_mode": pprops.false_origin_mode,
"false_origin": pprops.false_origin,
}
with open(self.json_filepath, "w") as f:
json.dump(data, f)
if has_model_offset:
offset = ifcopenshell.ifcopenshell_wrapper.float_array_3()
offset[0], offset[1], offset[2] = model_offset
settings.offset = offset
for settings in tool.Loader.settings.context_settings:
if not self.elements:
break
results = set()
iterator = ifcopenshell.geom.iterator(
settings, self.file, multiprocessing.cpu_count(), include=self.elements
)
@@ -1313,16 +1429,17 @@ class LoadLinkedProject(bpy.types.Operator):
chunked_materials = []
chunked_material_ids = []
material_offset = 0
max_slot_index = 0
chunk_size = 10000
r4 = np.array([[0, 0, 0, 1]])
offset = 0
ci = 0
if iterator.initialize():
while True:
shape = iterator.get()
if len(shape.geometry.faces) > 1000 and self.is_local(shape): # 333 tris
results.add(self.file.by_id(shape.id))
# Elements with a lot of geometry benefit from instancing to save memory
if len(shape.geometry.faces) > 1000: # 333 tris
self.process_occurrence(shape)
if not iterator.next():
if not chunked_verts:
@@ -1363,17 +1480,25 @@ class LoadLinkedProject(bpy.types.Operator):
ms = np.vstack([default_mat, np.frombuffer(shape.geometry.colors_buffer).reshape((-1, 4))])
mi = np.frombuffer(shape.geometry.material_ids_buffer, dtype=np.int32)
for geom_material_idx, geom_material in enumerate(shape.geometry.materials):
if not geom_material.has_diffuse:
if not geom_material.instance_id():
ms[geom_material_idx + 1] = (0.8, 0.8, 0.8, 1)
chunked_materials.append(ms)
chunked_material_ids.append(mi + material_offset + 1)
material_offset += len(ms)
M4 = np.frombuffer(shape.transformation_buffer).reshape((4, 3))
M4 = np.concatenate((M4.T, r4))
matrix = np.frombuffer(shape.transformation_buffer).reshape((4, 4), order="F")
if gprops.has_blender_offset:
matrix = ifcopenshell.util.geolocation.global2local(
matrix,
float(gprops.blender_offset_x) * self.unit_scale,
float(gprops.blender_offset_y) * self.unit_scale,
float(gprops.blender_offset_z) * self.unit_scale,
float(gprops.blender_x_axis_abscissa),
float(gprops.blender_x_axis_ordinate),
)
vs = np.frombuffer(shape.geometry.verts_buffer).reshape((-1, 3))
vs = np.hstack((vs, np.ones((len(vs), 1))))
vs = (np.asmatrix(M4) * np.asmatrix(vs).T).T.A
vs = (np.asmatrix(matrix) * np.asmatrix(vs).T).T.A
vs = vs[:, :3].flatten()
fs = np.frombuffer(shape.geometry.faces_buffer, dtype=np.int32)
chunked_verts.append(vs)
@@ -1419,7 +1544,6 @@ class LoadLinkedProject(bpy.types.Operator):
chunked_materials = []
chunked_material_ids = []
material_offset = 0
max_slot_index = 0
offset = 0
if not iterator.next():
@@ -1449,20 +1573,12 @@ class LoadLinkedProject(bpy.types.Operator):
chunked_guid_ids,
)
break
self.elements -= results
bpy.context.scene.collection.children.link(self.collection)
print("Finished", time.time() - start)
break
bpy.context.scene.collection.children.link(self.collection)
print("Finished", time.time() - start)
return {"FINISHED"}
def is_local(self, shape: ShapeElementType) -> bool:
m = shape.transformation.matrix
if max([abs(co) for co in (m[9], m[10], m[11])]) > 1000:
return False
elif max([abs(co) for co in shape.geometry.verts[0:3]]) > 1000:
return False
return True
def process_occurrence(self, shape: ShapeElementType) -> None:
element = self.file.by_id(shape.id)
faces = shape.geometry.faces
@@ -1476,6 +1592,27 @@ class LoadLinkedProject(bpy.types.Operator):
if not mesh:
mesh = bpy.data.meshes.new("Mesh")
geometry = shape.geometry
gprops = bpy.context.scene.BIMGeoreferenceProperties
if (
gprops.has_blender_offset
and geometry.verts
and tool.Loader.is_point_far_away(
(geometry.verts[0], geometry.verts[1], geometry.verts[2]), is_meters=True
)
):
# Shift geometry close to the origin based off that first vert it found
verts_array = np.array(geometry.verts)
offset = np.array([-geometry.verts[0], -geometry.verts[1], -geometry.verts[2]])
offset_verts = verts_array + np.tile(offset, len(verts_array) // 3)
verts = offset_verts.tolist()
mesh["has_cartesian_point_offset"] = True
mesh["cartesian_point_offset"] = f"{geometry.verts[0]},{geometry.verts[1]},{geometry.verts[2]}"
else:
verts = geometry.verts
mesh["has_cartesian_point_offset"] = False
material_to_slot = {}
max_slot_index = 0
@@ -1483,7 +1620,7 @@ class LoadLinkedProject(bpy.types.Operator):
alpha = 1.0
if material.has_transparency and material.transparency > 0:
alpha = 1.0 - material.transparency
if material.has_diffuse:
if material.instance_id():
diffuse = material.diffuse + (alpha,)
else:
diffuse = (0.8, 0.8, 0.8, 1) # Blender's default material
@@ -1523,7 +1660,7 @@ class LoadLinkedProject(bpy.types.Operator):
self.meshes[shape.geometry.id] = mesh
obj = bpy.data.objects.new(tool.Loader.get_name(element), mesh)
obj.matrix_world = Matrix(mat.tolist())
obj.matrix_world = tool.Loader.apply_blender_offset_to_matrix_world(obj, mat)
obj["guids"] = [shape.guid]
obj["guid_ids"] = [len(mesh.polygons)]
@@ -143,34 +143,32 @@ class BIMProjectProperties(PropertyGroup):
filter_query: StringProperty(name="Filter Query")
should_filter_spatial_elements: BoolProperty(name="Filter Spatial Elements", default=False)
should_use_cpu_multiprocessing: BoolProperty(name="CPU Multiprocessing", default=True)
merge_mode: bpy.props.EnumProperty(
items=[
("NONE", "None", "No objects are merged"),
("IFC_CLASS", "IFC Class", "One object per IFC class"),
("IFC_TYPE", "IFC Type", "One object per IFC construction type"),
("MATERIAL", "Material", "One object per material"),
],
name="Merge Mode",
default="NONE",
)
should_merge_materials_by_colour: BoolProperty(name="Merge Materials by Colour", default=False)
should_stream: BoolProperty(name="Stream Data From IFC-SPF (Only for advanced users)", default=False)
should_load_geometry: BoolProperty(name="Load Geometry", default=True)
should_use_native_meshes: BoolProperty(name="Native Meshes", default=False)
should_clean_mesh: BoolProperty(name="Clean Meshes", default=False)
should_cache: BoolProperty(name="Cache", default=False)
is_coordinating: BoolProperty(name="For Coordination Only", default=False)
deflection_tolerance: FloatProperty(name="Deflection Tolerance", default=0.001)
angular_tolerance: FloatProperty(name="Angular Tolerance", default=0.5)
void_limit: IntProperty(name="Void Limit", default=30)
distance_limit: FloatProperty(name="Distance Limit", default=1000, subtype="DISTANCE")
false_origin_mode: bpy.props.EnumProperty(
items=[
(
"AUTOMATIC",
"Automatic",
"An automatic false origin will be detected from geometry with large coordinates",
),
("MANUAL", "Manual", "You can specify the false origin coordinates"),
("DISABLED", "Disabled", "The model in original local coordinates will be shown as is"),
],
name="False Origin Mode",
default="AUTOMATIC",
)
false_origin: StringProperty(
name="False Origin",
description=(
"False origin that will be used to offset the entire model.\n"
"(0,0,0) value is interpreted as an unset false origin - false origin will be guessed based on Distance Limit.\n"
"False origin is defined in project units"
),
description="False origin in project units that the Blender origin will correlate to",
default="0,0,0",
)
element_offset: IntProperty(name="Element Offset", default=0)
@@ -18,6 +18,7 @@
import os
import blenderbim.bim
import blenderbim.tool as tool
from blenderbim.bim.helper import prop_with_search
from bpy.types import Panel, Menu, UIList
from blenderbim.bim.ifc import IfcStore
@@ -36,6 +37,28 @@ class BIM_MT_project(Menu):
self.layout.operator("bim.new_project", text="New Project Wizard").preset = "wizard"
class BIM_MT_recent_projects(Menu):
bl_idname = "BIM_MT_recent_projects"
bl_label = "Open Recent IFC Project"
def draw(self, context):
paths = tool.Blender.get_recent_ifc_projects()
if not paths:
self.layout.label(text="No Recent IFC Projects")
return
for path in paths:
op = self.layout.operator(
"bim.load_project", text=path.name, icon_value=blenderbim.bim.icons["IFC"].icon_id
)
op.filepath = str(path)
op.should_start_fresh_session = True
op.use_detailed_tooltip = True
self.layout.separator()
self.layout.operator("bim.clear_recent_ifc_projects", icon="TRASH")
class BIM_MT_new_project(Menu):
bl_idname = "BIM_MT_new_project"
bl_label = "New Project"
@@ -44,6 +67,7 @@ class BIM_MT_new_project(Menu):
self.layout.operator_context = "INVOKE_DEFAULT"
op = self.layout.operator("bim.load_project", text="Open IFC Project", icon="FILEBROWSER")
op.should_start_fresh_session = True
self.layout.menu("BIM_MT_recent_projects", icon="NONE")
# Do we need to set it back to exec default?
# self.layout.operator_context = "EXEC_DEFAULT"
self.layout.separator()
@@ -67,6 +91,7 @@ def file_menu(self, context):
self.layout.menu("BIM_MT_project", icon="COLLECTION_NEW")
op = self.layout.operator("bim.load_project", text="Open IFC Project", icon="FILEBROWSER")
op.should_start_fresh_session = True
self.layout.menu("BIM_MT_recent_projects", icon="NONE")
self.layout.separator()
op = self.layout.operator("bim.save_project", icon="FILE_TICK", text="Save IFC Project")
op.should_save_as = False
@@ -96,13 +121,14 @@ class BIM_PT_project(Panel):
pprops = context.scene.BIMProjectProperties
self.file = IfcStore.get_file()
if pprops.is_loading:
self.draw_load_ui(context)
self.draw_advanced_loading_ui(context)
elif self.file or props.ifc_file:
self.draw_project_ui(context)
else:
self.draw_create_project_ui(context)
if props.ifc_file:
self.draw_loaded_project_ui(context)
else:
self.draw_unsaved_project_ui(context)
def draw_load_ui(self, context):
def draw_advanced_loading_ui(self, context):
pprops = context.scene.BIMProjectProperties
prop_with_search(self.layout, pprops, "filter_mode")
if pprops.filter_mode in ["DECOMPOSITION", "IFC_CLASS", "IFC_TYPE"]:
@@ -137,10 +163,6 @@ class BIM_PT_project(Panel):
row = self.layout.row()
row.prop(pprops, "should_merge_materials_by_colour")
row = self.layout.row()
row.prop(pprops, "is_coordinating")
if pprops.is_coordinating:
prop_with_search(self.layout, pprops, "merge_mode")
row = self.layout.row()
row.prop(pprops, "deflection_tolerance")
row = self.layout.row()
row.prop(pprops, "angular_tolerance")
@@ -149,7 +171,10 @@ class BIM_PT_project(Panel):
row = self.layout.row()
row.prop(pprops, "distance_limit")
row = self.layout.row()
row.prop(pprops, "false_origin")
row.prop(pprops, "false_origin_mode")
if pprops.false_origin_mode == "MANUAL":
row = self.layout.row()
row.prop(pprops, "false_origin")
row = self.layout.row()
row.label(text="Element Range")
@@ -159,14 +184,9 @@ class BIM_PT_project(Panel):
row = self.layout.row(align=True)
row.operator("bim.load_project_elements")
row.operator("bim.unload_project", text="", icon="CANCEL")
def draw_project_ui(self, context):
props = context.scene.BIMProperties
def draw_editing_buttons(self, context, row):
pprops = context.scene.BIMProjectProperties
row = self.layout.row(align=True)
row.label(text=os.path.basename(props.ifc_file) or "No File Found", icon="FILE")
if IfcStore.get_file():
if pprops.is_editing:
row.operator("bim.edit_header", icon="CHECKMARK", text="")
@@ -174,6 +194,10 @@ class BIM_PT_project(Panel):
else:
row.operator("bim.enable_editing_header", icon="GREASEPENCIL", text="")
def draw_editable_file_info(self, context):
pprops = context.scene.BIMProjectProperties
if IfcStore.get_file():
row = self.layout.row(align=True)
row.label(text="IFC Schema", icon="FILE_CACHE")
row.label(text=IfcStore.get_file().schema)
@@ -201,27 +225,61 @@ class BIM_PT_project(Panel):
if "[" in mvd:
mvd = mvd.split("[")[1][0:-1]
row.label(text=mvd)
else:
row = self.layout.row(align=True)
row.label(text="File Not Loaded", icon="ERROR")
if props.ifc_file:
row = self.layout.row(align=True)
if context.scene.BIMProperties.is_dirty:
row.label(text="Saved*", icon="EXPORT")
else:
row.label(text="Saved", icon="EXPORT")
row.label(text=ProjectData.data["last_saved"])
def draw_unsaved_project_ui(self, context):
# file name row
file_name_row = self.layout.row(align=True)
file_name_row.label(text="No File Found", icon="FILE")
self.draw_editing_buttons(context, file_name_row)
row = self.layout.row(align=True)
row.prop(props, "ifc_file", text="")
row.operator("bim.select_ifc_file", icon="FILE_FOLDER", text="")
row.operator("bim.unload_project", text="", icon="CANCEL")
# main section
self.draw_editable_file_info(context)
# file path row and actions section
row = self.layout.row()
row.label(text="File Not Saved", icon="ERROR")
def draw_loaded_project_ui(self, context):
# file name row
props = context.scene.BIMProperties
file_name_row = self.layout.row(align=True)
file_name_row.label(text=os.path.basename(props.ifc_file), icon="FILE")
self.draw_editing_buttons(context, file_name_row)
# main section
self.draw_editable_file_info(context)
# file path row and actions section
row = self.layout.row(align=True)
if context.scene.BIMProperties.is_dirty:
row.label(text="Saved*", icon="EXPORT")
else:
row = self.layout.row()
row.label(text="File Not Saved", icon="ERROR")
row.label(text="Saved", icon="EXPORT")
row.label(text=ProjectData.data["last_saved"])
row = self.layout.row(align=True)
row.prop(props, "ifc_file", text="")
row.operator("bim.select_ifc_file", icon="FILE_FOLDER", text="")
row.operator("bim.unload_project", text="", icon="CANCEL")
class BIM_PT_new_project_wizard(Panel):
bl_label = "New Project Wizard"
bl_idname = "BIM_PT_new_project_wizard"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_options = {"HIDE_HEADER"}
bl_parent_id = "BIM_PT_tab_new_project_wizard"
def draw(self, context):
self.layout.use_property_decorate = False
self.layout.use_property_split = True
def draw_create_project_ui(self, context):
props = context.scene.BIMProperties
pprops = context.scene.BIMProjectProperties
prop_with_search(self.layout, pprops, "export_schema")
@@ -79,7 +79,7 @@ def draw_enumerated_property(prop, layout, copy_operator=None):
def get_active_pset_obj_name(context, obj_type):
if obj_type in ("Object", "Material", "MaterialSet", "MaterialSetItem"):
if obj_type in ("Object", "MaterialSet", "MaterialSetItem"):
return context.active_object.name
return ""
@@ -138,7 +138,7 @@ def draw_psetqto_ui(context, pset_id, pset, props, layout, obj_type, allow_remov
else:
has_props_displayed = False
for prop in pset["Properties"]:
if context.preferences.addons["blenderbim"].preferences.should_hide_empty_props and (
if tool.Blender.get_addon_preferences().should_hide_empty_props and (
prop["NominalValue"] is None or prop["NominalValue"] == ""
):
continue
@@ -644,11 +644,8 @@ def get_opening_area(
bl_opening_obj = bpy.data.objects.new("MyObject", mesh)
opening_type = (
ifc_opening_element.PredefinedType
if ifc_opening_element.PredefinedType is not None
else get_opening_type(bl_opening_obj, obj)
)
if not (opening_type := ifcopenshell.util.element.get_predefined_type(ifc_opening_element)):
opening_type = get_opening_type(bl_opening_obj, obj)
if ignore_recesses and opening_type == "RECESS":
continue
@@ -19,9 +19,11 @@
from collections import defaultdict
import bpy
import ifcopenshell.util.element
import ifcopenshell.util.schema
from ifcopenshell.util.doc import get_entity_doc, get_predefined_type_doc, get_class_suggestions
import blenderbim.tool as tool
from blenderbim.bim.ifc import IfcStore
from typing import Union
def refresh():
@@ -29,6 +31,7 @@ def refresh():
class IfcClassData:
element: Union[ifcopenshell.entity_instance, None] = None
data = {}
is_loaded = False
@@ -40,6 +43,7 @@ class IfcClassData:
cls.data["ifc_classes"] = cls.ifc_classes()
cls.data["ifc_classes_suggestions"] = cls.ifc_classes_suggestions() # Call AFTER cls.ifc_classes()
cls.data["contexts"] = cls.contexts()
cls.data["has_entity"] = cls.has_entity()
cls.data["name"] = cls.name()
cls.data["has_inherited_predefined_type"] = cls.has_inherited_predefined_type()
@@ -146,15 +150,27 @@ class IfcClassData:
return results
@classmethod
def has_entity(cls):
try:
return tool.Ifc.get_entity(bpy.context.active_object)
except:
pass
def has_entity(cls) -> Union[ifcopenshell.entity_instance, None]:
obj = bpy.context.active_object
cls.element = None
if not obj:
return
element = tool.Ifc.get_entity(obj)
if not element:
return
# Double check
linked_obj = tool.Ifc.get_object(element)
if linked_obj != obj:
return
cls.element = element
return cls.element
@classmethod
def name(cls):
element = tool.Ifc.get_entity(bpy.context.view_layer.objects.active)
def name(cls) -> Union[str, None]:
element = cls.element
if not element:
return
name = element.is_a()
@@ -164,24 +180,24 @@ class IfcClassData:
return name
@classmethod
def has_inherited_predefined_type(cls):
element = tool.Ifc.get_entity(bpy.context.view_layer.objects.active)
def has_inherited_predefined_type(cls) -> bool:
element = cls.element
if not element:
return
return False
if element_type := ifcopenshell.util.element.get_type(element):
# Allow for None due to https://github.com/buildingSMART/IFC4.3.x-development/issues/818
return ifcopenshell.util.element.get_predefined_type(element_type) not in ("NOTDEFINED", None)
return False
@classmethod
def ifc_class(cls):
element = tool.Ifc.get_entity(bpy.context.view_layer.objects.active)
def ifc_class(cls) -> Union[str, None]:
element = cls.element
if element:
return element.is_a()
@classmethod
def can_reassign_class(cls):
element = tool.Ifc.get_entity(bpy.context.view_layer.objects.active)
def can_reassign_class(cls) -> bool:
element = cls.element
if element:
if element.is_a("IfcOpeningElement") or element.is_a("IfcOpeningStandardCase"):
return False
@@ -190,3 +206,4 @@ class IfcClassData:
for product in cls.ifc_products():
if element.is_a(product[0]):
return True
return False
@@ -214,9 +214,16 @@ class AssignClass(bpy.types.Operator, Operator):
class UnlinkObject(bpy.types.Operator):
bl_idname = "bim.unlink_object"
bl_label = "Unlink Object"
bl_description = (
"Unlink Blender object from it's linked IFC element.\n\n"
"You can either remove element the blender object is linked to from IFC or keep it. "
"Note that keeping the unlinked element in IFC might lead to unpredictable issues "
"and should be used only by advanced users"
)
bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty(name="Object Name")
should_delete: bpy.props.BoolProperty(name="Delete IFC Element", default=True)
skip_invoke: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"})
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
@@ -231,8 +238,7 @@ class UnlinkObject(bpy.types.Operator):
for obj in objects:
was_active_object = obj == context.active_object
if obj in IfcStore.edited_objs:
IfcStore.edited_objs.remove(obj)
tool.Ifc.finish_edit(obj)
element = tool.Ifc.get_entity(obj)
if element and self.should_delete:
@@ -268,6 +274,8 @@ class UnlinkObject(bpy.types.Operator):
obj = obj_copy
obj.name = object_name
elif element:
tool.Ifc.unlink(element)
tool.Root.unlink_object(obj)
for collection in obj.users_collection:
@@ -284,6 +292,8 @@ class UnlinkObject(bpy.types.Operator):
row.prop(self, "should_delete")
def invoke(self, context, event):
if self.skip_invoke:
return self.execute(context)
return context.window_manager.invoke_props_dialog(self)
@@ -47,7 +47,9 @@ class BIM_PT_class(Panel):
if not IfcClassData.data["has_entity"]:
row = self.layout.row(align=True)
row.label(text="IFC Element Not Found")
row.operator("bim.unlink_object", icon="UNLINKED", text="")
op = row.operator("bim.unlink_object", icon="UNLINKED", text="")
op.should_delete = False
op.skip_invoke = True
return
if props.is_reassigning_class:
row = self.layout.row(align=True)
@@ -52,6 +52,7 @@ classes = (
prop.BIMStyleProperties,
ui.BIM_PT_styles,
ui.BIM_UL_styles,
ui.BIM_PT_style,
)
@@ -20,10 +20,12 @@ import bpy
import ifcopenshell
import blenderbim.tool as tool
from ifcopenshell.util.doc import get_entity_doc
from typing import Union
def refresh():
StylesData.is_loaded = False
BlenderMaterialStyleData.is_loaded = False
class StylesData:
@@ -67,3 +69,55 @@ class StylesData:
@classmethod
def total_styles(cls):
return len(tool.Ifc.get().by_type("IfcPresentationStyle"))
class BlenderMaterialStyleData:
style: Union[ifcopenshell.entity_instance, None] = None
data = {}
is_loaded = False
@classmethod
def load(cls):
cls.data = {
"is_linked_to_style": cls.is_linked_to_style(),
# After is_linked_to_style.
"linked_style_name": cls.linked_style_name(),
}
cls.is_loaded = True
@classmethod
def is_linked_to_style(cls) -> bool:
ifc_file = tool.Ifc.get()
if not ifc_file:
return False
context = bpy.context
cls.linked_style = None
obj = context.active_object
if not obj:
return False
material = obj.active_material
if not material:
return False
props = material.BIMStyleProperties
style_id = props.ifc_definition_id
style = tool.Ifc.get_entity_by_id(style_id)
if not style:
return False
# Double check that it's the right material
# and not material from other Blender session.
linked_material = tool.Ifc.get_object(style)
if linked_material != material:
return False
cls.style = style
return True
@classmethod
def linked_style_name(cls) -> Union[str, None]:
"""Return str if Blender Material is linked to IFC style or None if it's not.
Method will return "Unnamed" if style name is None.
"""
if cls.style:
return cls.style.Name or "Unnamed"
return None
@@ -25,7 +25,6 @@ import blenderbim.core.style as core
import ifcopenshell.api
import ifcopenshell.api.style
import ifcopenshell.util.representation
from blenderbim.bim.module.style.prop import switch_shading
from pathlib import Path
from mathutils import Vector
@@ -80,7 +79,7 @@ class RemoveStyle(bpy.types.Operator, tool.Ifc.Operator):
style: bpy.props.IntProperty()
def _execute(self, context):
core.remove_style(tool.Ifc, tool.Material, tool.Style, style=tool.Ifc.get().by_id(self.style))
core.remove_style(tool.Ifc, tool.Style, style=tool.Ifc.get().by_id(self.style))
class AddStyle(bpy.types.Operator, tool.Ifc.Operator):
@@ -96,45 +95,87 @@ class AddStyle(bpy.types.Operator, tool.Ifc.Operator):
class UnlinkStyle(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.unlink_style"
bl_label = "Unlink Style"
bl_description = (
"Unlink Blender material from it's linked IFC style.\n\n"
"You can either remove style the material is linked to from IFC or keep it. "
"Note that keeping the unlinked style in IFC might lead to unpredictable issues "
"and should be used only by advanced users"
)
bl_options = {"REGISTER", "UNDO"}
style: bpy.props.IntProperty(default=0)
blender_material: bpy.props.StringProperty(name="Blender Material")
should_delete: bpy.props.BoolProperty(name="Delete IFC Style", default=True)
def draw(self, context):
row = self.layout.row()
row.prop(self, "should_delete")
def invoke(self, context, event):
return context.window_manager.invoke_props_dialog(self)
def _execute(self, context):
core.unlink_style(tool.Ifc, style=tool.Ifc.get().by_id(self.style))
# TODO: move to core.style
# Don't check blender_material and style_id as this operator is only called from UI.
assert isinstance(self.blender_material, str) # Type checker.
material = bpy.data.materials[self.blender_material]
style_id = material.BIMStyleProperties.ifc_definition_id
style = tool.Ifc.get_entity_by_id(style_id)
# Material is linked to a style from a different project.
if not style or tool.Ifc.get_object(style) != material:
tool.Ifc.unlink(obj=material)
return {"FINISHED"}
if self.should_delete:
# Create a copy that will be removed
# and leave user with unlinked original material.
# It's needed so we don't need to search everywhere original
# material was used and replace it with the unlinked version.
material_copy = material.copy()
tool.Ifc.unlink(element=style)
tool.Ifc.link(style, material_copy)
core.remove_style(tool.Ifc, tool.Style, style)
else:
tool.Ifc.unlink(element=style)
# Ensure there won't be any style sync on project save:
# bim.update_representation would create new IfcSurfaceStyle
# for unlinked blender material.
updated_meshes = set()
for obj in bpy.data.objects:
mesh = obj.data
if not isinstance(mesh, bpy.types.Mesh):
continue
if not mesh.BIMMeshProperties.ifc_definition_id:
continue
if mesh in updated_meshes:
continue
if not mesh.materials.get(material.name):
continue
tool.Geometry.record_object_materials(obj)
updated_meshes.add(mesh)
return {"FINISHED"}
class EnableEditingStyle(bpy.types.Operator, tool.Ifc.Operator):
class EnableEditingStyle(bpy.types.Operator):
bl_idname = "bim.enable_editing_style"
bl_label = "Enable Editing Style"
bl_options = {"REGISTER", "UNDO"}
style: bpy.props.IntProperty(default=0)
def _execute(self, context):
props = bpy.context.scene.BIMStylesProperties
style = tool.Ifc.get().by_id(self.style)
props.is_editing_style = style.id()
props.is_editing_class = "IfcSurfaceStyle"
attributes = props.attributes
attributes.clear()
blenderbim.bim.helper.import_attributes2(style, attributes)
def execute(self, context):
core.enable_editing_style(tool.Style, tool.Ifc.get().by_id(self.style))
return {"FINISHED"}
class DisableEditingStyle(bpy.types.Operator, tool.Ifc.Operator):
class DisableEditingStyle(bpy.types.Operator):
bl_idname = "bim.disable_editing_style"
bl_options = {"REGISTER", "UNDO"}
bl_label = "Disable Editing Style"
def _execute(self, context):
props = bpy.context.scene.BIMStylesProperties
style = tool.Ifc.get().by_id(props.is_editing_style)
material = tool.Ifc.get_object(style)
tool.Style.reload_material_from_ifc(material)
props.is_editing_style = 0
# restore selected style type
material = tool.Ifc.get_object(style)
material.BIMStyleProperties.active_style_type = material.BIMStyleProperties.active_style_type
def execute(self, context):
core.disable_editing_style(tool.Style)
return {"FINISHED"}
class EditStyle(bpy.types.Operator, tool.Ifc.Operator):
@@ -143,12 +184,7 @@ class EditStyle(bpy.types.Operator, tool.Ifc.Operator):
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
props = bpy.context.scene.BIMStylesProperties
style = tool.Ifc.get().by_id(props.is_editing_style)
attributes = blenderbim.bim.helper.export_attributes(props.attributes)
ifcopenshell.api.run("style.edit_presentation_style", tool.Ifc.get(), style=style, attributes=attributes)
props.is_editing_style = 0
core.load_styles(tool.Style, style_type=props.style_type)
core.edit_style(tool.Ifc, tool.Style)
class UpdateCurrentStyle(bpy.types.Operator):
@@ -180,7 +216,7 @@ class UpdateCurrentStyle(bpy.types.Operator):
for obj in context.selected_objects:
for mat in obj.data.materials:
if mat and mat.BIMMaterialProperties.ifc_style_id != 0:
if mat and mat.BIMStyleProperties.ifc_definition_id != 0:
mat.BIMStyleProperties.active_style_type = current_style_type
return {"FINISHED"}
@@ -628,12 +664,12 @@ class EnableEditingSurfaceStyle(bpy.types.Operator, tool.Ifc.Operator):
active_style_type = material.BIMStyleProperties.active_style_type
if self.ifc_class == "IfcExternallyDefinedSurfaceStyle" and active_style_type != "External":
if tool.Style.has_blender_external_style(style_elements):
switch_shading(material, "External")
tool.Style.switch_shading(material, "External")
elif (
self.ifc_class in ("IfcSurfaceStyleShading", "IfcSurfaceStyleRendering", "IfcSurfaceStyleWithTextures")
and active_style_type != "Shading"
):
switch_shading(material, "Shading")
tool.Style.switch_shading(material, "Shading")
class EditSurfaceStyle(bpy.types.Operator, tool.Ifc.Operator):
@@ -847,7 +883,7 @@ class SaveUVToStyle(bpy.types.Operator, tool.Ifc.Operator):
obj = context.active_object
# find active style item
style_id = material.BIMMaterialProperties.ifc_style_id
style_id = material.BIMStyleProperties.ifc_definition_id
style = ifc_file.by_id(style_id)
def get_active_representation_items():
@@ -74,7 +74,7 @@ STYLE_TYPES = [
def update_shading_styles(self, context):
for mat in bpy.data.materials:
if mat.BIMMaterialProperties.ifc_style_id == 0:
if mat.BIMStyleProperties.ifc_definition_id == 0:
continue
tool.Style.change_current_style_type(mat, self.active_style_type)
@@ -253,50 +253,18 @@ class BIMStylesProperties(PropertyGroup):
)
def switch_shading(blender_material: bpy.types.Material, style_type: Literal["External", "Shading"]) -> None:
if style_type == "External":
try:
bpy.ops.bim.activate_external_style(material_name=blender_material.name)
except RuntimeError as error:
if str(error).startswith("Error: Error loading external style for "):
return
raise error
elif style_type == "Shading":
style_elements = tool.Style.get_style_elements(blender_material)
rendering_style = None
texture_style = None
for surface_style in style_elements.values():
if surface_style.is_a() == "IfcSurfaceStyleShading":
tool.Loader.create_surface_style_shading(blender_material, surface_style)
elif surface_style.is_a("IfcSurfaceStyleRendering"):
rendering_style = surface_style
tool.Loader.create_surface_style_rendering(blender_material, surface_style)
elif surface_style.is_a("IfcSurfaceStyleWithTextures"):
texture_style = surface_style
if rendering_style and texture_style:
tool.Loader.create_surface_style_with_textures(blender_material, rendering_style, texture_style)
def update_shading_style(self, context):
blender_material = self.id_data
style_elements = tool.Style.get_style_elements(blender_material)
if self.active_style_type == "External":
if tool.Style.has_blender_external_style(style_elements):
switch_shading(blender_material, self.active_style_type)
tool.Style.switch_shading(blender_material, self.active_style_type)
elif self.active_style_type == "Shading":
switch_shading(blender_material, self.active_style_type)
tool.Style.record_shading(blender_material)
tool.Style.switch_shading(blender_material, self.active_style_type)
class BIMStyleProperties(PropertyGroup):
# TODO: remove, as attributes already moved to styles ui
attributes: CollectionProperty(name="Attributes", type=Attribute)
is_editing: BoolProperty(name="Is Editing")
external_style_attributes: CollectionProperty(name="External Style Attributes", type=Attribute)
is_editing_external_style: BoolProperty(name="Is Editing External Style")
ifc_definition_id: IntProperty(name="IFC Definition ID")
active_style_type: EnumProperty(
name="Active Style Type",
description="Update current blender material to match style type",
@@ -21,8 +21,7 @@ import blenderbim.bim.helper
import blenderbim.tool as tool
from bpy.types import Panel, UIList
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.module.style.data import StylesData
from bl_ui.properties_material import MaterialButtonsPanel
from blenderbim.bim.module.style.data import StylesData, BlenderMaterialStyleData
class BIM_PT_styles(Panel):
@@ -67,8 +66,6 @@ class BIM_PT_styles(Panel):
row.operator("bim.select_by_style", text="", icon="RESTRICT_SELECT_OFF").style = style.ifc_definition_id
op = row.operator("bim.assign_style_to_selected", text="", icon="BRUSH_DATA")
op.style_id = style.ifc_definition_id
op = row.operator("bim.unlink_style", text="", icon="UNLINKED")
op.style = style.ifc_definition_id
op = row.operator("bim.enable_editing_style", text="", icon="GREASEPENCIL")
op.style = style.ifc_definition_id
row.operator("bim.remove_style", text="", icon="X").style = style.ifc_definition_id
@@ -291,3 +288,43 @@ class BIM_UL_styles(UIList):
row2.label(text="", icon="LIGHT_POINT")
elif style.name == "IfcExternallyDefinedSurfaceStyle":
row2.label(text="", icon="APPEND_BLEND")
class BIM_PT_style(Panel):
bl_label = "Style"
bl_idname = "BIM_PT_style"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "material"
@classmethod
def poll(cls, context):
return bool(tool.Ifc.get() and (material := context.material) and material.BIMStyleProperties.ifc_definition_id)
def draw(self, context):
# NOTE: this UI is needed only to indicate whether blender material is linked to IFC
# and shouldn't be overloaded with any other features (they should be added to the general Styles UI).
if not BlenderMaterialStyleData.is_loaded:
BlenderMaterialStyleData.load()
material = context.material
style_id = material.BIMStyleProperties.ifc_definition_id
row = self.layout.row(align=True)
if style_id and not BlenderMaterialStyleData.data["is_linked_to_style"]:
row.label(text="Material has linked IFC from a different project.")
op = row.operator("bim.unlink_style", icon="UNLINKED", text="")
op.blender_material = material.name
return
else:
row.label(text="Material is linked to an IFC style.")
op = row.operator("bim.unlink_style", icon="UNLINKED", text="")
op.blender_material = material.name
row = self.layout.row(align=True)
row.label(text="IFC Style ID:")
row.label(text=str(style_id))
row = self.layout.row(align=True)
row.label(text="IFC Style Name:")
row.label(text=BlenderMaterialStyleData.data["linked_style_name"])
@@ -89,7 +89,7 @@ class SystemDecorator:
self.draw_batch("TRIS", vertices_coords, faces_color, face_indices)
def __call__(self, context, get_custom_bmesh=None, draw_faces=False, exit_edit_mode_callback=None):
self.addon_prefs = context.preferences.addons["blenderbim"].preferences
self.addon_prefs = tool.Blender.get_addon_preferences()
selected_elements_color = self.addon_prefs.decorator_color_selected
unselected_elements_color = self.addon_prefs.decorator_color_unselected
special_elements_color = self.addon_prefs.decorator_color_special
@@ -18,6 +18,7 @@
import bpy
import ifcopenshell.api
import ifcopenshell.util.element
import ifcopenshell.util.representation
import blenderbim.tool as tool
import blenderbim.core.geometry
@@ -74,6 +75,7 @@ class AddOpening(bpy.types.Operator, tool.Ifc.Operator):
obj1, obj2 = obj2, obj1
element1, element2 = element2, element1
# element1 - voided element, element2 - opening.
if element1.is_a("IfcOpeningElement"):
self.report({"INFO"}, "You can't add an opening to another opening.")
continue
@@ -127,7 +129,7 @@ class AddOpening(bpy.types.Operator, tool.Ifc.Operator):
)
if not has_visible_openings:
tool.Ifc.unlink(obj=obj2)
tool.Ifc.unlink(element=element2)
bpy.data.objects.remove(obj2)
context.view_layer.objects.active = obj1
@@ -148,7 +150,7 @@ class RemoveOpening(bpy.types.Operator, tool.Ifc.Operator):
if opening_obj:
opening_obj.name = "/".join(opening_obj.name.split("/")[1:])
tool.Ifc.unlink(obj=opening_obj)
tool.Ifc.unlink(element=opening)
ifcopenshell.api.run("void.remove_opening", tool.Ifc.get(), opening=opening)

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