Compare commits

..

160 Commits

Author SHA1 Message Date
Ryan Schultz c62ed5d3b6 shift+click, to quickly switch the drawing view without turning anything on or off in the scene.
as discussed: https://community.osarch.org/discussion/2196/quick-way-to-switch-between-drawing-views#latest
2024-08-04 10:00:22 -05:00
Ryan Schultz ffb6c82541 snapshot 2024-08-04 09:40:34 -05:00
Ryan Schultz a9e366b238 Assure text 'wraps' in CloseError 2024-08-03 14:30:06 -05:00
Ryan Schultz 1ca0dfb1e0 Make sure drawings are loaded when calling bim.activate_drawing from sheets UI.
related: c11ba23a3d
2024-08-03 14:09:15 -05:00
Ryan Schultz c11ba23a3d Fix #3728 - Be able to bim.activate_drawing a drawing listed in the sheets UI, as well. 2024-08-03 11:01:31 -05:00
Dion Moult 556c769b40 Sun position add-on is now detected in the current session instead of needing to restart Blender 2024-08-03 23:21:31 +10:00
Dion Moult 78173dd546 Add basic tests for new web module 2024-08-03 23:12:21 +10:00
Dion Moult 2196c16097 Fix failing georeference and spatial core/tool/bim tests and clean dead code 2024-08-03 23:08:35 +10:00
Dion Moult 7af5db7cd4 Collector now unassigns from old ifc collections 2024-08-03 22:04:38 +10:00
Dion Moult c58672cc25 Allow selecting a container object when changing spatial container 2024-08-03 19:29:06 +10:00
Dion Moult 85e022d76e IfcTester now groups results by types to make long failure lists more useful 2024-08-02 23:58:01 +10:00
Andrej730 872f40933f remove Python 3.9 from bbim makefile since a31051a93 2024-08-02 18:54:37 +05:00
Andrej730 4491018f5e fix tool surveyor test 2024-08-02 18:54:37 +05:00
Andrej730 a21c440b5b fix tool geometry tests 2024-08-02 18:54:37 +05:00
Andrej730 143e094db8 assign default container on assigning IfcAnnotation class
Currently if you create a IfcAnnotation it might just get lost as it's not assigned anywhere. Also noticed this since previously we allowed using bim.add_reference_image without any specific drawings.
2024-08-02 18:54:37 +05:00
Andrej730 94e9e945d7 fix system/qto tool tests after e6ac012 2024-08-02 18:54:36 +05:00
Andrej730 5e56115050 typing 2024-08-02 18:54:36 +05:00
Andrej730 1ad654aab3 Fix Ifc tool tests after #4843 and unlinking changes 2024-08-02 18:54:36 +05:00
Andrej730 596ffb093b dev environment script - keep it possible to run the script without any changes
by default it assumes it's executed from ifcopenshell repo directory
2024-08-02 18:54:36 +05:00
Andrej730 3576362fd8 blenderbim make test - run all tests but return error code at the end 2024-08-02 18:11:25 +05:00
Andrej730 2c949bb39a blenderbim "make test" - run tool and bim tests even if some core tests failed 2024-08-02 17:20:29 +05:00
Andrej730 34faa0617b fix running core tests on python 3.10 2024-08-02 17:05:28 +05:00
Dion Moult 15f6b3803f Fix #4986. IfcTester now determines whether a spec is relevant to the ifc version 2024-08-02 22:02:56 +10:00
Andrej730 8514a426bb fix potential blenderbim issues for python 3.10
tomllib was added to python 3.11, tomli can be used for the previous versions.

Though it is a bit questionable since internally Blender is using just tomllib and because of that I'm not sure anymore if Blender still supports Python 3.10.
2024-08-02 16:00:49 +05:00
Andrej730 2062324294 prevent issues importing blenderbim for core test
initialize_bbim_semver was possible breaking on running core tests if system python was missing tomllib
2024-08-02 16:00:49 +05:00
Dion Moult 90c6218210 Fix #5115. Members (e.g. stringers) can now have a stair modifier applied as they may be used to support stairs 2024-08-02 16:34:30 +10:00
Dion Moult 321bb7178d See #5110. Minor fix when appending linked elements. 2024-08-02 16:29:14 +10:00
Dion Moult 42dda0a07a Run black on light 2024-08-02 16:00:45 +10:00
Dion Moult adb17d91f9 Dedicated doc section for blend open/save issue 2024-08-02 15:57:17 +10:00
Dion Moult 646cc59687 Update live install script on linux/mac and merge system support docs into dev setup docs 2024-08-02 15:57:17 +10:00
Dion Moult 38759348cd Document new install location on 4.2 2024-08-02 15:57:17 +10:00
Dion Moult ff44f4f395 Show warning if a blend file is used alongside an ifc 2024-08-02 15:57:17 +10:00
Dion Moult a31051a937 Drop py39 from build matrix for BBIM as we now only support Blender >=4.2 2024-08-02 15:57:17 +10:00
Chirag Singh 5cfd69f337 Merge branch 'v0.8.0' of https://github.com/IfcOpenShell/IfcOpenShell into v0.8.0 2024-08-02 11:23:09 +05:30
Chirag Singh f4100f7951 Added HDR support 2024-08-02 11:22:40 +05:30
Thomas Krijnen 785d5118d0 installation.rst - minor enhancement 2024-08-02 05:02:20 +02:00
Thomas Krijnen fddd85817a installation.rst - minor doc enhancements 2024-08-02 04:59:09 +02:00
Thomas Krijnen a689fc6377 Consistency TopExp_Explorer::Current() 2024-08-02 04:50:06 +02:00
Augustin Trancart 8e5a7ed24e improve json lib discovery for cmake 2024-08-02 03:31:17 +02:00
Dion Moult 788217c73c Use loader to get name instead of hardcoding when loading IFCs. See #4517. 2024-08-02 09:31:15 +10:00
Blender Defender 1abc3a1422 feat: Update the patch UI of ResetAbsoluteCoordinates
Instead of specifying parameters `a`, `b`, `c` and `d`, more meaningful parameters have been introduced (`threshold`, `automatic_offset_point`, `x`, `y` and `z`), which allow to do the same operations in a more understandable way.
2024-08-02 08:26:14 +10:00
Richard Brice c31c34b7d2 reimplements piecewise_function using PIMPL idiom
PIMPL idiom allows implementaiton of piecewise_function to evolve without envoking systemwide recompile (unless the class definition changes)
2024-08-01 12:34:49 -07:00
Richard Brice b22ecba179 Implements mutually exclusive access to mapping cache with a mutex 2024-08-01 07:15:22 -07:00
Chirag Singh 22bc98b48b Merge branch 'v0.8.0' of https://github.com/IfcOpenShell/IfcOpenShell into v0.8.0 2024-08-01 19:29:48 +05:30
Chirag Singh cf70026570 Gensky corrected and output in .tiff format 2024-08-01 19:29:02 +05:30
Andrej730 3c4a865e0b ci.yml - also run tests for other ifcopenshell packages 2024-08-01 18:01:59 +05:00
Andrej730 8523bb317a bcf, bsdd - add "make test" 2024-08-01 18:01:59 +05:00
Andrej730 4b00b268c0 fix core geometry trests after c4ae957 and 698c648 2024-08-01 18:01:58 +05:00
Andrej730 f0ca34d107 fix core project tests after 0dc084d and e3cb811 2024-08-01 18:01:58 +05:00
Andrej730 3834ecb06a fix core root test after e3cb811 2024-08-01 18:01:58 +05:00
Andrej730 047c9e3ee9 fix core aggregate module test after 6eb3c48 2024-08-01 18:01:58 +05:00
Andrej730 786b796f37 fix breaking bim.activate_model for new collections structure 2024-08-01 18:01:58 +05:00
Andrej730 3fd8be72c2 refresh_ui_data note 2024-08-01 18:01:58 +05:00
Andrej730 cb80081473 fix covering test and fix bug adding coverings in non-meters projects
Noticed that adding a covering in millimeters project didn't worked - the reason was that shapely was using tolerance in ifc project units when get_obj_base_points was returning points in si (blender always operates in meters internally).
I've changed get_obj_base_points to return data in project units and get_bmesh_from_polygon to convert it back to meters for Blender.
It's probably would be better to always operate in si until we need to save data to ifc but I'm not that familiar with that module to just change it everywhere.

Ping @maxfb87 just in case.
2024-08-01 18:01:58 +05:00
Andrej730 5ac84e2be7 covering - more detailed poll messages 2024-08-01 18:01:58 +05:00
Andrej730 c8466e0606 typing 2024-08-01 18:01:58 +05:00
Thomas Krijnen db5167bbaf Prevent infinite recursion 2024-08-01 07:59:23 +02:00
Thomas Krijnen 7d6e26bfd5 Add revolve as top-level item in occt kernel 2024-08-01 07:48:42 +02:00
Thomas Krijnen 2fd339ac6b Hack/workaround for #5064 2024-08-01 07:48:28 +02:00
Dion Moult 6f6dc08696 Minor typo fixes 2024-08-01 14:58:23 +10:00
Andrej730 6cb063ebd2 fix bim drawing module test after 47704b1 2024-07-31 19:00:34 +05:00
Andrej730 fb95e3eb51 fix enabling sverchok as legacy addon on Blender 4.2
It was failing when BBIM is installed as an extension
2024-07-31 19:00:34 +05:00
Andrej730 17a061f917 fix bim test after e6ac012
when Types collection was renamed to IfcTypeProduct
2024-07-31 19:00:34 +05:00
Andrej730 51db65b868 fix resource bim tests after c399eb2
IfcLaborResource doesn't actually support IfcQuantityArea quantities
2024-07-31 19:00:34 +05:00
Andrej730 faab9e04a3 fix resource bim tests after 1a4e467
operator prop was deprecated a long time ago in e2d4a3a
2024-07-31 19:00:34 +05:00
Andrej730 bea92455dd clean up bim tests from trailing spaces 2024-07-31 19:00:34 +05:00
Andrej730 6be1b80a82 fix container operators after a9a4b0c + mention a limitation
If you would select container1 + obj1 with intention to copy obj1 to container1 - running bim.copy_to_container would also copy container1.
Similar issue for referencing/dereferncing structures in other structures in ifc4x3.
2024-07-31 19:00:34 +05:00
Andrej730 2927eadada Display referenced structured elements during container edit for more responsive ui
Before - https://imgur.com/a/m5qtDUn
After - https://imgur.com/a/TFlUNTa
2024-07-31 19:00:33 +05:00
Andrej730 4c5e90dc77 fix bim tests after collections no longer represent spatial hierarchy 2024-07-31 19:00:33 +05:00
Andrej730 f860973172 fix spatial module bim tests after a9a4b0c 2024-07-31 19:00:33 +05:00
Andrej730 94ed0cd59c fix bim.assign_container uses after a9a4b0c + add TODOs 2024-07-31 19:00:33 +05:00
Andrej730 93ea1f98ac fix qto tests after 8c3d10a 2024-07-31 19:00:33 +05:00
Andrej730 b68a92f251 remove bim.assign_objects_base_qto from tests (removed in 8c3d10a) 2024-07-31 19:00:33 +05:00
Andrej730 902da2e720 remove bim.pie_update_container
as we no longer rely on collections for container assignment
2024-07-31 19:00:33 +05:00
Andrej730 d384aaeb9c fix tests after a9a4b0c 2024-07-31 19:00:33 +05:00
Andrej730 133494af97 remove bimtester from bbim (related to 7157590) 2024-07-31 19:00:32 +05:00
Andrej730 64fc9462b6 fix bim attribute tests after 9122f00 2024-07-31 19:00:32 +05:00
Andrej730 3d86e5fff3 fix bim geometry tests after fccf56d 2024-07-31 19:00:32 +05:00
Andrej730 a24bb069aa fix bim tests after changes in quantities calculations 2024-07-31 19:00:32 +05:00
Andrej730 159355d63a typing 2024-07-31 19:00:32 +05:00
Andrej730 598cf4ba79 bim.perform_quantity_take_off description + info message 2024-07-31 19:00:32 +05:00
Andrej730 c86a4c88a9 fix broken search test after 410fff6 2024-07-31 19:00:32 +05:00
Jonathan Gan 6a9e76c2af Documentation: Added getting started documentation for ifcopenshell core (#4965)
* Added getting started documentation for ifcopenshell core

* Apply suggestions from code review

Co-authored-by: Thomas Krijnen <t.krijnen@gmail.com>

* Updated code snippet to use ->as instead of dynamic_cast based on feedback

* Missed out two dynamic casts

* Using preprocessor macros instead of chaining if-else

---------

Co-authored-by: Jonathan Gan <jonathan.gan@inspacion.com>
Co-authored-by: Thomas Krijnen <t.krijnen@gmail.com>
2024-07-31 07:28:25 +10:00
Andrej730 2070071c49 fix bbim builds... 2024-07-30 21:20:14 +05:00
Andrej730 9bbf3bcaba mention supported formats in export window
as currently there is no other ui to indicate that other formats besides .ifc are supported and I guess we just expect users to change extension in the saved filename

https://i.imgur.com/Qa6OlmR.png
2024-07-30 20:47:48 +05:00
Andrej730 0395c0c6c7 fix ifcjson export support #4832
Bumped the version, so Blender would recognize the updated wheel.

Related https://github.com/IFCJSON-Team/IFC2JSON_python/pull/3
2024-07-30 20:47:47 +05:00
Andrej730 a175ec4d28 fix bim tests after d4b055a 2024-07-30 20:46:16 +05:00
Andrej730 139627b6ec fix bim tests after b9f76042b 2024-07-30 20:46:16 +05:00
Andrej730 6f2eb0a116 fix bim tests after a156864 2024-07-30 20:46:16 +05:00
Andrej730 d26561acee fix for bim style module test 2024-07-30 20:46:16 +05:00
Andrej730 de0374bdbd fix error purging unused types in 75f8e75 2024-07-30 20:46:16 +05:00
Ziad-I 51463ab674 fix pid file not there on first run so exception was thrown and not caught
when the user ran the web ui for the first time. the running_pid file was not created yet by the server so it threw an exception due to with open not inside the try block
2024-07-30 18:18:35 +03:00
Dion Moult 6012695cfe Use a tolerance when detecting project norths heuristically 2024-07-30 23:57:54 +10:00
Dion Moult 081b3fcf33 Remove misleading load project button in new project wizard.
Previously there were subtle differences and two ways to load projects which is bad UX.
2024-07-30 23:57:54 +10:00
Dion Moult 27a2c94b4a Fix #5065. Restore "Import IFC" menu item for those wishing to purely work with IFC geometry instead of native ifc 2024-07-30 23:57:54 +10:00
Dion Moult b9f76042bb Remove deprecated export IFC operator ID 2024-07-30 23:57:54 +10:00
Andrej730 e7ef307984 remove redundant indent in bdc5a7a
Co-Authored-By: Adi Shavit <1814800+adishavit@users.noreply.github.com>
2024-07-30 18:56:02 +05:00
Andrej730 1f68e52ac2 black format - exclude 'mvd' and reformat pyproject.toml for readibility 2024-07-30 18:49:10 +05:00
Dion Moult 87dfb75c21 Merge pull request #5103 from IfcOpenShell/webui_drawings
Web UI drawings page
2024-07-30 23:12:04 +10:00
Blender Defender 8afb1b8b30 feat: Display float properties as string properties for more precision in the patch panel
This change affects the following recipes: `OffsetObjectPlacements`, `OffsetStoreyElevations`, `SetFalseOrigin`, `SetRefElevation` and `SetWorldCoordinateSystem`
2024-07-30 23:05:44 +10:00
Blender Defender 5572991a7c style: Run black on geom/occ_utils.py 2024-07-30 23:04:58 +10:00
Blender Defender 653b9288c0 feat: Allow merging multiple ifc projects at once with the MergeProject recipe 2024-07-30 23:04:58 +10:00
Chirag Singh 233b0856af Implemented gensky and fixed executable flag error as mentioned by Manu in #5033 2024-07-30 13:51:20 +05:30
Dion Moult ec29e3d60b Send sigkill not sigterm because aiohttp swallows sigterm and takes a long time
I'm not smart enough to know exactly why
2024-07-30 17:54:28 +10:00
Dion Moult 70bc32b3df Check for PID file prior to connecting to newly started web socket server 2024-07-30 17:28:35 +10:00
Ziad-I ca781655b0 add displaying of svgs in drawings page 2024-07-29 23:35:28 +03:00
Ziad-I 03e2cb70bb remove unnecessary padding 2024-07-29 22:45:35 +03:00
Chirag Singh c7c95e1f9d Implemented gensky (Values Hardcoded) 2024-07-29 23:15:31 +05:30
Chirag Singh b040c5e25b Pulled changes 2024-07-29 23:13:21 +05:30
Richard Brice 60b007d28a Fixes domain of vertical and cant pwf (Issue #5072) 2024-07-29 09:48:15 -07:00
Andrej730 52130a3eb6 fix materials / styles bim tests #4843 2024-07-29 18:53:12 +05:00
Andrej730 5e849d7b81 update core.tool.Material to fix tests 2024-07-29 18:53:12 +05:00
Andrej730 a7a28f613b Fix errors in tests and scripts removing styles
remove_style relies on style_type which is using StylesData to get the enum of it's values and StylesData will fail to load on `material = tool.Ifc.get_object(ifc_file.by_id(style.ifc_definition_id))` as ifc_definition_id was removed but not yet removed as an item from styles ui.

This issue doesn't occur when user is working from UI since StylesData precached when they load styles UI but if they run a script (as we do in bim tests) StylesData is not precached and will be loaded when get_active_style_type() is called leading to errors.
2024-07-29 18:53:12 +05:00
Andrej730 46b3bfecd1 fix issues assigning material sets and material lists 2024-07-29 18:53:12 +05:00
Andrej730 11b62a0fdc fix console errors adding new style and changing it's color
1) As adding new styles means it's not assigned anywhere, there is no need to keep graph up to date.
2) Removed Ifc Operator super class from couple operators that do not write ifc data
3) Moved common code to tool.Style
2024-07-29 18:53:11 +05:00
Andrej730 732bb93826 fix duplicated lines in 2575348 2024-07-29 18:53:11 +05:00
Andrej730 a0bda1afd3 typing 2024-07-29 18:53:11 +05:00
Dion Moult e0790601df Black 2024-07-29 22:38:55 +10:00
Dion Moult a9a4b0ccf0 Deprecate "duplicate" spatial tree browser in favour of regular object selection 2024-07-29 22:29:44 +10:00
Blender Defender f8848051ca style: Run black formatter on spatial/ui.py 2024-07-29 21:37:29 +10:00
Blender Defender b81031eecf fix: Remove the "animatable" option from the "engine" and "format" property in BIMFMProperties 2024-07-29 21:37:29 +10:00
Blender Defender 95a7ea8dce refactor: Create property group MultipleFileSelect
To easier create and display properties that allow one or more files as input, a `MultipleFileSelect` property group has been extracted, together with an operator for selecting the files and the code for displaying the property.
2024-07-29 21:37:29 +10:00
Dion Moult 98edb5a34c Display the default container in the UI when creating new types 2024-07-29 19:50:21 +10:00
Dion Moult 8ca2a55616 Implement filtered selection in new spatial decomposition panel 2024-07-29 18:09:08 +10:00
Dion Moult 74141bec73 Sort pset names and properties alphabetically
Can't believe it took this long to get around to doing this
2024-07-29 10:43:15 +10:00
Dion Moult f60c4fd3c5 Forgot to commit these files for the georeferencing updates 2024-07-29 10:42:37 +10:00
Ziad-I d9ee3522bc empty panels before adding names 2024-07-28 22:28:20 +03:00
Ziad-I 2538c47d01 fix display of list items 2024-07-28 22:01:13 +03:00
Ziad-I ec3dc71c0e add showing of drawings' and sheets' names 2024-07-28 21:04:16 +03:00
Ziad-I e5edd9a34d add retrieving drawings data from blender to web ui 2024-07-28 15:09:55 +03:00
Ziad-I f72359918d use blenderbim version instead of blender version 2024-07-27 22:12:14 +03:00
Kurt Battisti dfd505a794 Fix. Docs Georeferencing URL (#5096)
use users/advanced/georeferencing.html
2024-07-28 00:14:50 +10:00
Blender Defender 4a311fe4de fix: Remove the "ifc_patch_args" property (#5095)
* fix: Remove the "ifc_patch_args" property

This property has been removed, because patch recipes with arguments other than the boilerplate arguments automatically display these arguments as properties, and patch recipes that don't allow more arguments than the boilerplate arguments fail, if "ifc_patch_args" is not None or "[]". The test cases have been adjusted accordingly.

* style: run the black formatter to fix failing checks
2024-07-27 22:58:48 +10:00
Ziad-I 9d4493e1da add footer to index and gantt page 2024-07-27 15:52:49 +03:00
Andrej730 a1d8fbdbe9 Fix missing materials from sets #5093 #4832
Regression in ifcopenshell 0.8 - missing materials and styles from IfcMaterialProfileSets, IfcMaterialProfileSetUsages, IfcMaterialLayerSet.
2024-07-27 15:38:22 +05:00
Andrej730 952abde48e fix console errors related to material rename from ui
1) update_material_name was triggered for categories and failed since ifc id in that case = 0
2) update_material_name was triggered for materials when they were still incomplete and ifc id was still 0
3) moved setting material.name before settings ifc_id to avoid triggering unnecessary changes in ifc
2024-07-27 15:24:05 +05:00
Andrej730 168718063f fix regression #5092 after be7661a83 2024-07-27 15:24:01 +05:00
Dion Moult 4abcbbb4f9 Georeferencing calculator can now calculate blender offsets too 2024-07-27 19:54:12 +10:00
Dion Moult 1ac1b289c1 Map coords always visualise using map units, visualise IFC local origin, and don't show blender coords if no blender offset 2024-07-27 19:53:45 +10:00
Ziad-I c25173aa12 add event listeners and joining of room in drawings page 2024-07-26 22:43:10 +03:00
Andrej730 ea8d303883 fix some test-bim errors in Blender 4.2 2024-07-26 21:23:39 +05:00
Ziad-I 9d79337fa7 edit drawings html/css/js 2024-07-26 17:29:39 +03:00
Blender Defender 82a6ddb01c feat: Make sql_type an enum property in the Ifc2Sql recipe
Since there is a limited number of choices, a string property wouldn't be very helpful. Use a dynamic enum property (which is adjusted according to which types of SQL are available) instead.
2024-07-26 23:35:02 +10:00
Blender Defender a7152bdef0 feat: Convert argument names into prettier looking property names in the IFC Patch Panel
Inspired by #4721
2024-07-26 23:29:41 +10:00
Andrej730 6484a529b0 black format 2024-07-26 18:29:18 +05:00
Andrej730 6e1e653ceb fix tests to use release version of ifc4x3 (ci was failing) 2024-07-26 16:29:46 +05:00
Andrej730 0488e4cccb bcfp numpy 2.0+ support #5080
`np.float_` is deprecated, it was just an alias for `np.float64`
2024-07-26 16:29:26 +05:00
Andrej730 3cf92fd508 bsddclassification - remove description prop as unused 2024-07-26 16:29:26 +05:00
Andrej730 fb2bb7554e bsdd - add buttons to open URIs in browser
Examples:
- https://i.imgur.com/zSvU9vp.png
- https://imgur.com/a/WkbTiVa
2024-07-26 16:29:26 +05:00
Andrej730 be7661a83b remove duplicated operator bim.open_webbrowser
it did exactly the same as bim.open_uri
2024-07-26 16:29:25 +05:00
Andrej730 9684e21d93 bsdd - hide domain selection operator for the active domain
Example - https://imgur.com/a/SkpECro
2024-07-26 16:29:25 +05:00
Andrej730 ee5c401823 bsdd - expose bsdd props descriptions to ui
Example - https://imgur.com/a/xUUeuF5
2024-07-26 16:29:25 +05:00
Andrej730 4aa3ffb765 bsdd - check keyword length only in tool
just to make it more straightforward and remove unused output
2024-07-26 16:29:25 +05:00
Dion Moult c9affcc622 Update README.md 2024-07-26 20:09:26 +10:00
Ziad-I bddb8d1e56 add initial page layout 2024-07-26 02:19:55 +03:00
Ziad-I c4d3dad639 add page setup functions 2024-07-25 15:35:08 +03:00
Ziad-I adbf9093d3 add initial drawings page 2024-07-25 15:12:37 +03:00
Ziad-I c8defbb77f add connected list to gantt chart page 2024-07-25 14:59:56 +03:00
Ziad-I a14fc0a7f5 edit css rules 2024-07-25 14:25:44 +03:00
Ziad-I 8d2717d25b add connected blender list for index page 2024-07-24 23:26:43 +03:00
245 changed files with 4799 additions and 2500 deletions
+1 -1
View File
@@ -35,7 +35,7 @@ jobs:
strategy:
fail-fast: false
matrix:
pyver: [py39, py310, py311, py312]
pyver: [py310, py311, py312]
config:
- {
name: "Windows Build",
+1 -1
View File
@@ -24,7 +24,7 @@ jobs:
strategy:
fail-fast: false
matrix:
pyver: [py39, py310, py311, py312]
pyver: [py310, py311, py312]
config:
- {
name: "Windows Build",
+7 -1
View File
@@ -47,6 +47,7 @@ jobs:
run: |
python -m pip install --upgrade pip
pip install xmlschema xsdata numpy lxml pytest isodate lark networkx tabulate python-dateutil shapely
pip install mathutils
pip install src/bcf --no-deps
pip install https://github.com/Andrej730/aud/archive/refs/heads/master-reduced-size.zip
@@ -125,4 +126,9 @@ jobs:
python tests.py
cd ../src/ifcopenshell-python
mv ifcopenshell ifcopenshell-local # Force testing on installed module
make test-safe
make test
cd ../bcf && make test
pip install requests
cd ../bsdd && make test
cd ../ifcpatch && make test
cd ../ifctester && make test
+2 -2
View File
@@ -46,12 +46,12 @@ Contents
| ifcblender | Historic Blender IFC import add-on | LGPL-3.0-or-later\* |
| ifccityjson | Convert CityJSON to IFC | LGPL-3.0-or-later | [![PyPI](https://img.shields.io/pypi/v/ifccityjson?label=PyPI&color=006dad)](https://pypi.org/project/ifccityjson/) |
| ifcclash | Clash detection library and CLI app | LGPL-3.0-or-later | [![PyPI](https://img.shields.io/pypi/v/ifcclash?label=PyPI&color=006dad)](https://pypi.org/project/ifcclash/) |
| ifcconvert | CLI app to convert IFC to many other formats | LGPL-3.0-or-later\* | [![Official](https://img.shields.io/badge/IfcOpenShell.org-Download-70ba35)](https://docs.ifcopenshell.org/ifcconvert/installation.html)
| ifcconvert | CLI app to convert IFC to many other formats | LGPL-3.0-or-later\* | [![Official](https://img.shields.io/badge/IfcOpenShell.org-Download-70ba35)](https://docs.ifcopenshell.org/ifcconvert/installation.html) [![GitHub](https://img.shields.io/github/v/release/ifcopenshell/ifcopenshell?filter=ifcconvert-*&label=GitHub&color=f6f8fa)](https://github.com/IfcOpenShell/IfcOpenShell/releases?q=ifcconvert&expanded=true)
| ifccsv | Library and CLI app to export and import schedules from IFC | LGPL-3.0-or-later | [![PyPI](https://img.shields.io/pypi/v/ifccsv?label=PyPI&color=006dad)](https://pypi.org/project/ifccsv/) |
| ifcdiff | Compare changes between IFC models | LGPL-3.0-or-later | [![PyPI](https://img.shields.io/pypi/v/ifcdiff?label=PyPI&color=006dad)](https://pypi.org/project/ifcdiff/) |
| ifcfm | Extract IFC data for FM handover requirements | LGPL-3.0-or-later | [![PyPI](https://img.shields.io/pypi/v/ifcfm?label=PyPI&color=006dad)](https://pypi.org/project/ifcfm/) |
| ifcmax | Historic extension for IFC support in 3DS Max | LGPL-3.0-or-later\* | [![Official](https://img.shields.io/badge/IfcOpenShell.org-Download-70ba35)](https://docs.ifcopenshell.org/ifcmax.html)
| ifcopenshell-python | Python library for IFC manipulation | LGPL-3.0-or-later\* | [![GitHub](https://img.shields.io/github/v/release/ifcopenshell/ifcopenshell?filter=ifcopenshell-python-*&label=GitHub&color=f6f8fa)](https://github.com/IfcOpenShell/IfcOpenShell/releases?q=ifcopenshell-python&expanded=true) [![PyPI](https://img.shields.io/pypi/v/ifcopenshell?label=PyPI&color=006dad)](https://pypi.org/project/ifcopenshell/) [![Anaconda](https://img.shields.io/conda/vn/conda-forge/ifcopenshell?label=Anaconda&color=43b02a)](https://anaconda.org/conda-forge/ifcopenshell) [![Anaconda](https://img.shields.io/conda/vn/ifcopenshell/ifcopenshell?label=Anaconda-Unstable&color=43b02a)](https://anaconda.org/ifcopenshell/ifcopenshell) [![Docker](https://img.shields.io/docker/pulls/aecgeeks/ifcopenshell?label=Docker&color=1D63ED)](https://hub.docker.com/r/aecgeeks/ifcopenshell) [![AUR](https://img.shields.io/aur/version/ifcopenshell?label=AUR&color=1793d1)](https://aur.archlinux.org/packages/ifcopenshell) [![AUR Unstable](https://img.shields.io/aur/version/ifcopenshell-git?label=AUR-Unstable&color=1793d1)](https://aur.archlinux.org/packages/ifcopenshell-git) |
| ifcopenshell-python | Python library for IFC manipulation | LGPL-3.0-or-later\* | [![Official](https://img.shields.io/badge/IfcOpenShell.org-Download-70ba35)](https://docs.ifcopenshell.org/ifcopenshell-python/installation.html) [![GitHub](https://img.shields.io/github/v/release/ifcopenshell/ifcopenshell?filter=ifcopenshell-python-*&label=GitHub&color=f6f8fa)](https://github.com/IfcOpenShell/IfcOpenShell/releases?q=ifcopenshell-python&expanded=true) [![PyPI](https://img.shields.io/pypi/v/ifcopenshell?label=PyPI&color=006dad)](https://pypi.org/project/ifcopenshell/) [![Anaconda](https://img.shields.io/conda/vn/conda-forge/ifcopenshell?label=Anaconda&color=43b02a)](https://anaconda.org/conda-forge/ifcopenshell) [![Anaconda](https://img.shields.io/conda/vn/ifcopenshell/ifcopenshell?label=Anaconda-Unstable&color=43b02a)](https://anaconda.org/ifcopenshell/ifcopenshell) [![Docker](https://img.shields.io/docker/pulls/aecgeeks/ifcopenshell?label=Docker&color=1D63ED)](https://hub.docker.com/r/aecgeeks/ifcopenshell) [![AUR](https://img.shields.io/aur/version/ifcopenshell?label=AUR&color=1793d1)](https://aur.archlinux.org/packages/ifcopenshell) [![AUR Unstable](https://img.shields.io/aur/version/ifcopenshell-git?label=AUR-Unstable&color=1793d1)](https://aur.archlinux.org/packages/ifcopenshell-git) |
| ifcpatch | Utility to run pre-packaged scripts to manipulate IFCs | LGPL-3.0-or-later | [![PyPI](https://img.shields.io/pypi/v/ifcpatch?label=PyPI&color=006dad)](https://pypi.org/project/ifcpatch/) |
| ifcsverchok | Blender Add-on for visual node programming with IFC | GPL-3.0-or-later | [![GitHub Unstable](https://img.shields.io/github/v/release/ifcopenshell/ifcopenshell?filter=ifcsverchok-*.*.*.*&label=GitHub-Unstable&color=f6f8fa)](https://github.com/IfcOpenShell/IfcOpenShell/releases?q=ifcsverchok&expanded=true)
| ifctester | Library, CLI and webapp for IDS model auditing | LGPL-3.0-or-later | [![PyPI](https://img.shields.io/pypi/v/ifctester?label=PyPI&color=006dad)](https://pypi.org/project/ifctester/) |
+4 -3
View File
@@ -221,10 +221,11 @@ endif()
if(GLTF_SUPPORT OR CITYJSON_SUPPORT)
UNIFY_ENVVARS_AND_CACHE(JSON_INCLUDE_DIR)
find_file(json_hpp "json.hpp" ${JSON_INCLUDE_DIR}/nlohmann)
find_path(json_header_path "json.hpp" ${JSON_INCLUDE_DIR} PATH_SUFFIXES "nlohmann")
set(JSON_INCLUDE_DIR ${json_header_path})
if(json_hpp)
message(STATUS "JSON for Modern C++ header file found")
if(json_header_path)
message(STATUS "JSON for Modern C++ header file found in ${JSON_INCLUDE_DIR}")
else()
message(FATAL_ERROR "Unable to find JSON for Modern C++ header file, aborting")
endif()
+29 -2
View File
@@ -1,7 +1,34 @@
[tool.black]
line-length = 120
include = 'src/(bcf|bcfserver|blenderbim|bsdd|foundationserver|ifc2ca|ifc4d|ifc5d|ifcbimtester|ifcblender|ifccityjson|ifcclash|ifccsv|ifcdiff|ifcfm|ifcpatch|ifctester|ifcopenshell-python|ifcsverchok|opencdeserver)/.*.py$'
extend-exclude = '(src/ifcopenshell-python/ifcopenshell/express/*|src/ifc2ca/templates/*)'
include = '''
src/(
bcf
|bcfserver
|blenderbim
|bsdd
|foundationserver
|ifc2ca
|ifc4d
|ifc5d
|ifcbimtester
|ifcblender
|ifccityjson
|ifcclash
|ifccsv
|ifcdiff
|ifcfm
|ifcpatch
|ifctester
|ifcopenshell-python
|ifcsverchok
|opencdeserver
)/.*.py$
'''
extend-exclude = '''
src/ifcopenshell-python/ifcopenshell/express/*
|src/ifcopenshell-python/ifcopenshell/mvd/*
|src/ifc2ca/templates/*
'''
[tool.pyright]
reportInvalidTypeForm = false
+4
View File
@@ -33,6 +33,10 @@ models:
cd src && xsdata generate -p bcf.v2.model --unnest-classes --kw-only --slots -ds Google bcf/v2/xsd
cd src && xsdata generate -p bcf.v3.model --unnest-classes --kw-only --slots -ds Google bcf/v3/xsd
.PHONY: test
test:
pytest -p no:pytest-blender tests
# .PHONY
# api:
# openapi-python-client generate --url https://api.swaggerhub.com/apis/buildingSMART/BCF/3.0
+5 -5
View File
@@ -5,8 +5,8 @@ from numpy.typing import NDArray
def camera_vectors_from_element_placement(
elem_placement: NDArray[np.float_],
) -> tuple[NDArray[np.float_], NDArray[np.float_], NDArray[np.float_]]:
elem_placement: NDArray[np.float64],
) -> tuple[NDArray[np.float64], NDArray[np.float64], NDArray[np.float64]]:
"""
Calculate the vectors of a camera pointing to an element.
@@ -21,8 +21,8 @@ def camera_vectors_from_element_placement(
def camera_vectors_from_target_position(
target_position: NDArray[np.float_], offset: Optional[NDArray[np.float_]] = None
) -> tuple[NDArray[np.float_], NDArray[np.float_], NDArray[np.float_]]:
target_position: NDArray[np.float64], offset: Optional[NDArray[np.float64]] = None
) -> tuple[NDArray[np.float64], NDArray[np.float64], NDArray[np.float64]]:
"""
Calculate the vectors of a camera pointing to a target point.
@@ -50,7 +50,7 @@ def camera_vectors_from_target_position(
# return camera_position, -mat[:3, 2], mat[:3, 1]
def unit_vector(v: NDArray[np.float_]) -> NDArray[np.float_]:
def unit_vector(v: NDArray[np.float64]) -> NDArray[np.float64]:
"""
Return the unit vector of a vector.
+1 -1
View File
@@ -278,7 +278,7 @@ class TopicHandler:
self.add_visinfo_handler(new_viewpoint)
return new_viewpoint
def add_viewpoint_from_point_and_guids(self, position: NDArray[np.float_], *guids: str) -> None:
def add_viewpoint_from_point_and_guids(self, position: NDArray[np.float64], *guids: str) -> None:
"""Add a viewpoint pointing at an XYZ point in space
Args:
+4 -4
View File
@@ -176,7 +176,7 @@ class VisualizationInfoHandler:
@classmethod
def create_from_point_and_guids(
cls,
position: NDArray[np.float_],
position: NDArray[np.float64],
*guids: str,
xml_handler: Optional[AbstractXmlParserSerializer] = None,
) -> "VisualizationInfoHandler":
@@ -224,7 +224,7 @@ def build_viewpoint(element: entity_instance) -> mdl.VisualizationInfo:
)
def build_viewpoint_from_position_and_guids(position: NDArray[np.float_], *guids: str) -> mdl.VisualizationInfo:
def build_viewpoint_from_position_and_guids(position: NDArray[np.float64], *guids: str) -> mdl.VisualizationInfo:
"""
Return a BCF viewpoint of an IFC element.
@@ -261,7 +261,7 @@ def build_components(*guids: str) -> mdl.Components:
)
def build_camera(elem_placement: NDArray[np.float_]) -> mdl.PerspectiveCamera:
def build_camera(elem_placement: NDArray[np.float64]) -> mdl.PerspectiveCamera:
"""
Return a BCF camera for an IFC element placement matrix.
@@ -275,7 +275,7 @@ def build_camera(elem_placement: NDArray[np.float_]) -> mdl.PerspectiveCamera:
def build_camera_from_vectors(
camera_position: NDArray[np.float_], camera_dir: NDArray[np.float_], camera_up: NDArray[np.float_]
camera_position: NDArray[np.float64], camera_dir: NDArray[np.float64], camera_up: NDArray[np.float64]
) -> mdl.PerspectiveCamera:
"""
Return a BCF camera for an IFC element placement matrix.
+1 -1
View File
@@ -175,7 +175,7 @@ class TopicHandler:
new_viewpoint = VisualizationInfoHandler.create_new(element, self._xml_handler)
self.add_visinfo_handler(new_viewpoint)
def add_viewpoint_from_point_and_guids(self, position: NDArray[np.float_], *guids: str) -> None:
def add_viewpoint_from_point_and_guids(self, position: NDArray[np.float64], *guids: str) -> None:
"""
Add a viewpoint tergeting an IFC element to the topic.
+4 -4
View File
@@ -175,7 +175,7 @@ class VisualizationInfoHandler:
@classmethod
def create_from_point_and_guids(
cls,
position: NDArray[np.float_],
position: NDArray[np.float64],
*guids: str,
xml_handler: Optional[AbstractXmlParserSerializer] = None,
) -> "VisualizationInfoHandler":
@@ -218,7 +218,7 @@ def build_viewpoint(element: entity_instance) -> mdl.VisualizationInfo:
)
def build_viewpoint_from_position_and_guids(position: NDArray[np.float_], *guids: str) -> mdl.VisualizationInfo:
def build_viewpoint_from_position_and_guids(position: NDArray[np.float64], *guids: str) -> mdl.VisualizationInfo:
"""
Return a BCF viewpoint of an IFC element.
@@ -255,7 +255,7 @@ def build_components(*guids: str) -> mdl.Components:
)
def build_camera(elem_placement: NDArray[np.float_]) -> mdl.PerspectiveCamera:
def build_camera(elem_placement: NDArray[np.float64]) -> mdl.PerspectiveCamera:
"""
Return a BCF camera for an IFC element placement matrix.
@@ -269,7 +269,7 @@ def build_camera(elem_placement: NDArray[np.float_]) -> mdl.PerspectiveCamera:
def build_camera_from_vectors(
camera_position: NDArray[np.float_], camera_dir: NDArray[np.float_], camera_up: NDArray[np.float_]
camera_position: NDArray[np.float64], camera_dir: NDArray[np.float64], camera_up: NDArray[np.float64]
) -> mdl.PerspectiveCamera:
"""
Return a BCF camera for an IFC element placement matrix.
+19 -13
View File
@@ -49,11 +49,6 @@ LAST_COMMIT_HASH:=$(shell git rev-parse HEAD)
PYVERSION:=py310
PYPI_IMP:=cp
ifeq ($(PYVERSION), py39)
PYLIBDIR:=python3.9
PYNUMBER:=39
PYPI_VERSION:=3.9
endif
ifeq ($(PYVERSION), py310)
PYLIBDIR:=python3.10
PYNUMBER:=310
@@ -189,25 +184,30 @@ else ifeq ($(PLATFORM), macos)
else
cd build && . env/$(VENV_ACTIVATE) && $(PIP) download pyradiance $(PYPI_PLATFORM) --python-version $(PYPI_VERSION) --implementation $(PYPI_IMP) --only-binary=:all: --dest=./wheels
endif
# tomllib replacement for < Python 3.11, used to parse blender_manifest
ifeq ($(PYVERSION), py310)
cd build && . env/$(VENV_ACTIVATE) && $(PIP) download tomli --dest=./wheels
endif
# Provides jsgantt-improved supports for web-based construction sequencing gantt charts
cd build/blenderbim/bim/data/gantt/ && wget https://raw.githubusercontent.com/jsGanttImproved/jsgantt-improved/master/dist/jsgantt.js
cd build/blenderbim/bim/data/gantt/ && wget https://raw.githubusercontent.com/jsGanttImproved/jsgantt-improved/master/dist/jsgantt.css
# Provides IFCJSON functionality
cd build && wget https://github.com/IFCJSON-Team/IFC2JSON_python/archive/master.zip
cd build && unzip master.zip && rm master.zip
# TODO: replace with main repo if https://github.com/IFCJSON-Team/IFC2JSON_python/pull/3 is merged.
cd build && wget -O ifc2json.zip https://github.com/Moult/IFC2JSON_python/archive/refs/heads/feature-ios-v0.8.0.zip
cd build && unzip ifc2json.zip && rm ifc2json.zip
# IFCJSON doesn't have pyproject.toml, so we use python command.
cd build && . env/$(VENV_ACTIVATE) && cd IFC2JSON_python-master/file_converters && \
cd build && . env/$(VENV_ACTIVATE) && cd IFC2JSON_python-*/file_converters && \
$(PYTHON) -c "from setuptools import setup; \
setup( \
name='ifcjson', \
version='0.0.0', \
version='0.0.1', \
author='Jan Brouwer', \
author_email='jan@brewsky.nl', \
packages=['ifcjson'], \
)" bdist_wheel
cp -r build/IFC2JSON_python-master/file_converters/dist/*.whl build/wheels/
cp -r build/IFC2JSON_python-*/file_converters/dist/*.whl build/wheels/
# Brickschema requires pkg_resources which is provided by Blender.
# Provides Brickschema functionality
@@ -346,9 +346,15 @@ endif
.PHONY: test
test:
make test-core
make test-tool
make test-bim
@echo "Running all tests..."
@ERROR=0; \
make test-core || ERROR=1; \
make test-tool || ERROR=1; \
make test-bim || ERROR=1; \
if [ $$ERROR -ne 0 ]; then \
echo "One or more tests failed"; \
exit 1; \
fi
.PHONY: test-core
test-core:
+6 -5
View File
@@ -68,11 +68,14 @@ def initialize_bbim_semver():
in `addon_utils.modules()->bl_info['version']`,
therefore we just parse it from .toml.
"""
import tomllib
if sys.version_info >= (3, 11):
import tomllib as toml
else:
import tomli as toml
toml_path = Path(__file__).parent / "blender_manifest.toml"
with open(toml_path, "rb") as f:
manifest = tomllib.load(f)
manifest = toml.load(f)
semver_pattern = r"^(?P<major>0|[1-9]\d*)\.(?P<minor>0|[1-9]\d*)\.(?P<patch>0|[1-9]\d*)(?:-(?P<prerelease>(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P<buildmetadata>[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$"
version_str = manifest["version"]
re_version = re.match(semver_pattern, version_str)
@@ -82,9 +85,6 @@ def initialize_bbim_semver():
bbim_semver["version"] = version_str
initialize_bbim_semver()
def get_debug_info():
bbim_version = bbim_semver["version"]
@@ -177,6 +177,7 @@ def clean_up_dlls_safe_links() -> None:
if IN_BLENDER:
initialize_bbim_semver()
def get_binary_info() -> dict[str, Any]:
info = {}
+3 -1
View File
@@ -98,11 +98,12 @@ classes = [
operator.AddIfcFile,
operator.BIM_OT_add_section_plane,
operator.BIM_OT_delete_object,
operator.BIM_OT_open_webbrowser,
operator.BIM_OT_remove_section_plane,
operator.BIM_OT_select_object,
operator.BIM_OT_show_description,
operator.BIM_OT_multiple_file_selector,
operator.ClippingPlaneCutWithCappings,
operator.CloseBlendWarning,
operator.CloseError,
operator.EditBlenderCollection,
operator.FileAssociate,
@@ -121,6 +122,7 @@ classes = [
prop.StrProperty,
operator.BIM_OT_enum_property_search, # /!\ Register AFTER prop.StrProperty
prop.ObjProperty,
prop.MultipleFileSelect,
prop.Attribute,
prop.BIMAreaProperties,
prop.BIMTabProperties,
@@ -2,7 +2,9 @@ import sys
import os
import webbrowser
blenderbim_lib_path = os.environ.get("blenderbim_lib_path")
blenderbim_lib_path = os.environ.get("BLENDERBIM_LIB_PATH")
blenderbim_version = os.environ.get("BLENDERBIM_VERSION")
if blenderbim_lib_path:
sys.path.insert(0, blenderbim_lib_path)
@@ -52,9 +54,16 @@ class WebNamespace(socketio.AsyncNamespace):
"web_operator",
data,
namespace="/blender",
room=data["blenderId"],
room=data.get("blenderId", None),
)
async def on_get_svg(self, sid, data):
print("hello world!")
file_path = data["path"]
with open(file_path, "r") as file:
svg_data = file.read()
await sio.emit("svg_data", svg_data, room=sid, namespace="/web")
async def send_cached_messages(self, sid):
# Send cached messages to the connected web client
for blenderId, messages in blender_messages.items():
@@ -98,6 +107,12 @@ class BlenderNamespace(socketio.AsyncNamespace):
blender_messages[sid]["gantt_data"] = data
await sio.emit("gantt_data", {"blenderId": sid, "data": data}, namespace="/web")
async def on_drawings_data(self, sid, data):
print(f"Drawings directory from Blender client {sid}")
print(data)
blender_messages[sid]["drwings_data"] = data
await sio.emit("drawings_data", {"blenderId": sid, "data": data}, namespace="/web")
# Attach namespaces
sio.register_namespace(WebNamespace("/web"))
@@ -108,26 +123,45 @@ sio.register_namespace(BlenderNamespace("/blender"))
async def index(request):
with open("templates/index.html", "r") as f:
template = f.read()
html_content = pystache.render(template, {"port": sio_port})
html_content = pystache.render(template, {"port": sio_port, "version": blenderbim_version})
return web.Response(text=html_content, content_type="text/html")
async def gantt(request):
with open("templates/gantt.html", "r") as f:
template = f.read()
html_content = pystache.render(template, {"port": sio_port, "version": blenderbim_version})
return web.Response(text=html_content, content_type="text/html")
async def drawings(request):
with open("templates/drawings.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")
async def open_web_browser(app):
webbrowser.open(f"http://127.0.0.1:{sio_port}/")
async def on_startup(app):
pid_file = "running_pid.json"
if os.path.exists(pid_file):
with open(pid_file, "r") as f:
pids = json.load(f)
else:
pids = {}
pids[str(os.getpid())] = sio_port
with open(pid_file, "w") as f:
json.dump(pids, f, indent=4)
app.router.add_get("/", index)
app.router.add_get("/drawings", drawings)
app.router.add_get("/gantt", gantt)
app.router.add_static("/jsgantt/", path="../gantt", name="jsgantt")
app.router.add_static("/static/", path="./static", name="static")
app.on_startup.append(open_web_browser)
app.on_startup.append(on_startup)
def main():
@@ -0,0 +1,249 @@
:root {
--font-family: Arial, sans-serif;
--base-font-size: 16px;
--margin-tiny: 0.125rem;
--margin-small: 0.625rem;
--margin-medium: 1.25rem;
--margin-large: 2.5rem;
--padding-tiny: 0.125rem;
--padding-small: 0.625rem;
--padding-medium: 1rem;
--font-size-large: 1.2rem;
--logo-height: 2.5rem;
--nav-height: 2.5rem;
}
:root.dark {
color-scheme: dark;
--bg-color: #252525;
--text-color: #e0e0e0;
--nav-bg-color: #121212;
--nav-border-color: #25682a;
--nav-link-color: #fff;
--nav-link-hover-color: #3fb449;
--warning-color: #FFDB8F;
--border-color: #464444;
}
:root.light {
color-scheme: light;
--bg-color: #ffffff;
--text-color: #000000;
--nav-bg-color: #f8f8f8;
--nav-border-color: #cccccc;
--nav-link-color: #000000;
--nav-link-hover-color: #38a63d;
--warning-color: #FF4500;
--border-color: #222;
}
html {
font-size: var(--base-font-size);
color: var(--text-color);
}
body {
background-color: var(--bg-color);
color: var(--text-color);
margin: 0;
font-family: var(--font-family);
}
#container {
margin-top: var(--margin-medium);
margin-left: var(--margin-small);
margin-right: var(--margin-small);
margin-bottom: var(--margin-medium);
height: calc(100vh - var(--nav-height));
box-sizing: border-box;
display: flex;
}
h3 {
margin-top: 0;
}
nav {
background-color: var(--nav-bg-color);
height: var(--nav-height);
padding: var(--padding-medium) 0;
display: flex;
align-items: center;
position: relative;
border-bottom: 2px solid var(--nav-border-color);
}
nav .logo {
margin-left: var(--margin-large);
height: var(--logo-height);
}
nav ul {
list-style-type: none;
margin: 0;
margin-left: 0.0625rem;
display: flex;
flex-grow: 1;
justify-content: center;
}
nav ul li {
margin-right: 3.125rem;
}
nav ul li a {
text-decoration: none;
color: var(--nav-link-color);
font-size: var(--font-size-large);
}
nav ul li a:hover,
nav ul li a.active {
color: var(--nav-link-hover-color);
}
.warning {
color: var(--warning-color);
margin-bottom: var(--margin-small);
padding: var(--padding-tiny);
display: none;
}
#toggle-theme {
border: none;
background: none;
cursor: pointer;
font-size: var(--font-size-large);
margin-right: var(--margin-medium);
}
#toggle-theme:focus {
outline: none;
}
#client-list {
position: absolute;
top: calc(0 + var(--nav-height));
overflow-y: auto;
overflow: hidden;
transition: opacity 0.3s ease-out, visibility 0.3s ease-out;
border: 1px solid var(--table-border-color);
background-color: var(--nav-bg-color);
z-index: 1;
opacity: 0;
visibility: hidden;
width: auto;
}
#client-list.show {
opacity: 1;
visibility: visible;
}
#connected-list-div {
display: inline-block;
}
.client {
padding: var(--margin-small);
border-bottom: 1px solid var(--table-border-color);
cursor: pointer;
}
.client-details {
max-height: 0;
overflow: hidden;
padding-left: var(--padding-small);
transition: max-height 0.2s ease-out, padding 0.2s ease-out, opacity 0.1s ease-out;
background-color: var(--nav-bg-color);
margin-top: var(--margin-tiny);
opacity: 0;
}
.client-details.show {
max-height: none;
opacity: 1;
}
.client-detail {
border-bottom: 1px solid var(--table-border-color);
padding: var(--padding-small);
}
#show-connected-button {
width: auto;
background-color: var(--bg-color);
border: none;
font-size: var(--base-font-size);
}
#show-connected-button:hover,
.scroll-button:hover {
cursor: pointer;
}
.scroll-button {
font-size: var(--base-font-size);
margin-left: var(--margin-tiny);
margin-top: var(--margin-small);
}
.left {
flex: 1;
background-color: var(--bg-color);
display: flex;
flex-direction: column;
padding: 20px;
}
.right {
flex: 4;
background-color: var(--bg-color);
border: 1px solid var(--border-color);
}
#svg-container {
height: 100%;
}
.panel {
background-color: var(--nav-bg-color);
border: 1px solid var(--border-color);
padding: 10px;
flex-grow: 1;
display: flex;
flex-direction: column;
}
.card-header,
.card-body {
background-color: var(--bg-color);
border: 1px solid var(--border-color);
overflow: auto;
}
.card {
background-color: var(--nav-bg-color);
overflow: auto;
}
li.svg-name {
list-style-type: none;
}
#dropdown-menu:focus {
outline: none;
}
/* remove bootstrap css confilcts */
*,
::after,
::before {
box-sizing: unset;
}
.btn,
.btn:hover {
color: var(--nav-link-hover-color);
}
@@ -1,13 +1,17 @@
:root {
--font-family: Arial, sans-serif;
--base-font-size: 16px;
--margin-tiny: 0.125rem;
--margin-small: 0.625rem;
--margin-medium: 1.25rem;
--margin-large: 2.5rem;
--padding-small: 0.125rem;
--padding-tiny: 0.125rem;
--padding-small: 0.625rem;
--padding-medium: 1rem;
--font-size-small: 1rem;
--font-size-large: 1.2rem;
--logo-height: 2.5rem;
--nav-height: 2.5rem;
--folder-collapse-font-size: 0.75rem;
--folder-collapse-font-family: Courier, "Courier New", monospace;
--box-shadow: 0 0 0.7rem #5f5f5f66;
@@ -31,6 +35,7 @@
--group-item-bg-color: #4b4b4b;
--input-bg-color: #3b3b3b;
--input-border-color: #000;
--details-border-color: #464444;
}
:root.light {
@@ -42,6 +47,7 @@
--nav-link-color: #000000;
--nav-link-hover-color: #38a63d;
--warning-color: #FF4500;
--details-border-color: #222;
}
html {
@@ -53,9 +59,13 @@ body {
color: var(--primary-text-color);
margin: 0;
font-family: var(--font-family);
display: flex;
flex-direction: column;
min-height: 100vh;
}
#container {
flex: 1;
margin-top: var(--margin-medium);
margin-left: var(--margin-small);
margin-right: var(--margin-small);
@@ -107,7 +117,7 @@ nav ul li a.active {
.warning {
color: var(--warning-color);
margin-bottom: var(--margin-small);
padding: var(--padding-small);
padding: var(--padding-tiny);
display: none;
}
@@ -141,6 +151,87 @@ nav ul li a.active {
outline: none;
}
#client-list {
position: absolute;
top: calc(0 + var(--nav-height));
overflow-y: auto;
overflow: hidden;
transition: opacity 0.3s ease-out, visibility 0.3s ease-out;
border: 1px solid var(--table-border-color);
background-color: var(--nav-bg-color);
z-index: 1;
opacity: 0;
visibility: hidden;
width: auto;
}
#client-list.show {
opacity: 1;
visibility: visible;
}
#connected-list-div {
display: inline-block;
}
.client {
padding: var(--margin-small);
border-bottom: 1px solid var(--table-border-color);
cursor: pointer;
}
.client-details {
max-height: 0;
overflow: hidden;
padding-left: var(--padding-small);
transition: max-height 0.2s ease-out, padding 0.2s ease-out, opacity 0.1s ease-out;
background-color: var(--nav-bg-color);
margin-top: var(--margin-tiny);
opacity: 0;
}
.client-details.show {
max-height: none;
opacity: 1;
}
.client-detail {
border-bottom: 1px solid var(--table-border-color);
padding: var(--padding-small);
}
#show-connected-button {
width: auto;
background-color: var(--bg-color);
border: none;
font-size: var(--base-font-size);
}
#show-connected-button:hover,
.scroll-button:hover {
cursor: pointer;
}
.scroll-button {
font-size: var(--base-font-size);
margin-left: var(--margin-tiny);
margin-top: var(--margin-small);
}
footer {
background-color: var(--nav-bg-color);
text-align: right;
padding: var(--padding-tiny);
border-top: 1px solid var(--nav-border-color);
}
footer p {
margin: 0;
color: var(--text-color);
margin-right: var(--margin-small);
font-size: 0.8rem;
}
.gantt-info {
margin: 0.5rem;
font-size: var(--font-size-small);
@@ -1,12 +1,16 @@
:root {
--font-family: Arial, sans-serif;
--base-font-size: 16px;
--margin-tiny: 0.125rem;
--margin-small: 0.625rem;
--margin-medium: 1.25rem;
--margin-large: 2.5rem;
--padding-small: 0.125rem;
--padding-tiny: 0.125rem;
--padding-small: 0.625rem;
--padding-medium: 1rem;
--font-size-large: 1.2rem;
--logo-height: 2.5rem;
--nav-height: 2.5rem;
}
:root.dark {
@@ -18,6 +22,7 @@
--nav-link-color: #fff;
--nav-link-hover-color: #3fb449;
--warning-color: #FFDB8F;
--table-border-color: #464444;
}
:root.light {
@@ -29,6 +34,7 @@
--nav-link-color: #000000;
--nav-link-hover-color: #38a63d;
--warning-color: #FF4500;
--table-border-color: #222;
}
html {
@@ -40,29 +46,26 @@ body {
color: var(--text-color);
margin: 0;
font-family: var(--font-family);
display: flex;
flex-direction: column;
min-height: 100vh;
}
#container {
flex: 1;
margin-top: var(--margin-medium);
margin-left: var(--margin-small);
margin-right: var(--margin-small);
margin-bottom: var(--margin-medium);
}
.table-container {
margin-bottom: var(--margin-large);
}
.csv-table {
margin-top: var(--margin-small);
}
h3 {
margin-top: 0;
}
nav {
background-color: var(--nav-bg-color);
height: var(--nav-height);
padding: var(--padding-medium) 0;
display: flex;
align-items: center;
@@ -102,7 +105,7 @@ nav ul li a.active {
.warning {
color: var(--warning-color);
margin-bottom: var(--margin-small);
padding: var(--padding-small);
padding: var(--padding-tiny);
display: none;
}
@@ -118,6 +121,95 @@ nav ul li a.active {
outline: none;
}
#client-list {
position: absolute;
top: calc(0 + var(--nav-height));
overflow-y: auto;
overflow: hidden;
transition: opacity 0.3s ease-out, visibility 0.3s ease-out;
border: 1px solid var(--table-border-color);
background-color: var(--nav-bg-color);
z-index: 1;
opacity: 0;
visibility: hidden;
width: auto;
}
#client-list.show {
opacity: 1;
visibility: visible;
}
#connected-list-div {
display: inline-block;
}
.client {
padding: var(--margin-small);
border-bottom: 1px solid var(--table-border-color);
cursor: pointer;
}
.client-details {
max-height: 0;
overflow: hidden;
padding-left: var(--padding-small);
transition: max-height 0.2s ease-out, padding 0.2s ease-out, opacity 0.1s ease-out;
background-color: var(--nav-bg-color);
margin-top: var(--margin-tiny);
opacity: 0;
}
.client-details.show {
max-height: none;
opacity: 1;
}
.client-detail {
border-bottom: 1px solid var(--table-border-color);
padding: var(--padding-small);
}
#show-connected-button {
width: auto;
background-color: var(--bg-color);
border: none;
font-size: var(--base-font-size);
}
#show-connected-button:hover,
.scroll-button:hover {
cursor: pointer;
}
.scroll-button {
font-size: var(--base-font-size);
margin-left: var(--margin-tiny);
margin-top: var(--margin-small);
}
footer {
background-color: var(--nav-bg-color);
text-align: right;
padding: var(--padding-tiny);
border-top: 1px solid var(--nav-border-color);
}
footer p {
margin: 0;
color: var(--text-color);
margin-right: var(--margin-small);
font-size: 0.8rem;
}
.table-container {
margin-bottom: var(--margin-large);
}
.csv-table {
margin-top: var(--margin-small);
}
/* ------------ Overwriting Tabulator CSS rules ------------ */
:root.light .tabulator-header,
@@ -127,6 +219,6 @@ nav ul li a.active {
}
:root.light .tabulator {
border-top: 1px solid #222;
border-bottom: 2px solid #222;
border-top: 1px solid var(--table-border-color);
border-bottom: 2px solid var(--table-border-color);
}
@@ -0,0 +1,263 @@
// keeps track of blenders connected
const connectedClients = {};
const allDrawings = {};
let socket;
// Document ready function
$(document).ready(function () {
var systemTheme = window.matchMedia("(prefers-color-scheme: light)").matches
? "light"
: "dark";
var theme = localStorage.getItem("theme") || systemTheme;
setTheme(theme);
$("#dropdown-menu").change(function () {
const blenderId = $(this).attr("id");
const ifcFile = $(this).val();
displayDrawingsNames(blenderId, ifcFile);
});
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("connect", handleWebConnect);
socket.on("drawings_data", handleDrawingsData);
socket.on("svg_data", handleSvgData);
// socket.on("default_data", handleDefaultData);
}
// function used to get drawings data from blenderbim
function handleWebConnect() {
const msg = {
sourcePage: "drawings",
operator: {
type: "getDrawings",
},
};
socket.emit("web_operator", msg);
}
// 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];
// remove(blenderId);
}
$("#blender-count").text(function (i, text) {
return parseInt(text, 10) - 1;
});
}
function handleDrawingsData(data) {
const blenderId = data["blenderId"];
console.log(data);
const filename = data["data"]["ifc_file"];
const drawings = data["data"]["drawings_data"]["drawings"];
const sheets = data["data"]["drawings_data"]["sheets"];
allDrawings[filename] = { drawings: drawings, sheets: sheets };
console.log(connectedClients);
if (connectedClients.hasOwnProperty(blenderId)) {
if (!connectedClients[blenderId].shown) {
connectedClients[blenderId] = {
shown: true,
ifc_file: filename,
};
addSelectOption(blenderId, filename);
} else {
// update(blenderId, data, filename);
}
} else {
connectedClients[blenderId] = {
shown: true,
ifc_file: filename,
};
addSelectOption(blenderId, filename);
}
}
function handleSvgData(data) {
const svgContainer = document.getElementById("svg-container");
svgContainer.innerHTML = "";
svgContainer.style.backgroundColor = "white";
const dom = SVG(svgContainer).svg(data);
const svgElement = dom.node.children[0];
// to make the svg not grow outside the svg container
svgElement.setAttribute("width", "100%");
svgElement.setAttribute("height", "100%");
svgElement.style.maxWidth = "100%";
svgElement.style.maxHeight = "100%";
svgElement.style.width = "100%";
svgElement.style.height = "100%";
svgElement.style.boxSizing = "border-box"; // Ensure padding and borders are included in the element's total width and height
const panZoomControls = svgPanZoom(svgElement, {
zoomEnabled: true,
controlIconsEnabled: true,
fit: true,
center: true,
});
}
function addSelectOption(blenderId, filename) {
const filenameOption = $("<option></option>")
.attr("id", "blender-" + blenderId)
.val(filename)
.text(filename);
$("#dropdown-menu").append(filenameOption);
}
function displayDrawingsNames(blenderId, ifcFile) {
drawings = allDrawings[ifcFile].drawings;
sheets = allDrawings[ifcFile].sheets;
function createSvgNames(text, index, type) {
var label = $("<li></lio>")
.text(text)
.attr("id", type + "-" + index)
.addClass("svg-name")
.click(function () {
const id = $(this).attr("id").split("-");
const index = parseInt(id[1]);
const type = id[0];
const ifcFile = $("#dropdown-menu").val();
var path = "";
if (type === "drawing") {
path = allDrawings[ifcFile].drawings[index].path;
} else if (type === "sheet") {
path = allDrawings[ifcFile].sheets[index].path;
}
const msg = {
path: path,
};
console.log(msg);
socket.emit("get_svg", msg);
});
if (type === "drawing") $("#drawings-sub-panel").append(label);
else if (type === "sheet") $("#sheets-sub-panel").append(label);
}
$("#drawings-sub-panel").empty();
drawings.forEach(function (drawing, index) {
createSvgNames(drawing.name, index, "drawing");
});
$("#sheets-sub-panel").empty();
sheets.forEach(function (sheet, index) {
createSvgNames(sheet.name, index, "sheet");
});
}
function setTheme(theme) {
if (theme === "light") {
$("html").removeClass("dark").addClass("light");
$("#toggle-theme").html('<i class="fas fa-sun"></i>');
} else {
$("html").removeClass("light").addClass("dark");
$("#toggle-theme").html('<i class="fas fa-moon"></i>');
}
localStorage.setItem("theme", theme);
}
function toggleTheme() {
if ($("html").hasClass("dark")) {
setTheme("light");
} else {
setTheme("dark");
}
}
function toggleClientList() {
var clientList = $("#client-list");
if (clientList.hasClass("show")) {
clientList.removeClass("show");
return;
}
clientList.empty();
$.each(connectedClients, function (id, client) {
if (!client.shown) return;
const dropdownIcon = $("<i>")
.addClass("fas fa-chevron-down")
.css("margin-left", "0.5rem");
const clientDiv = $("<div>").addClass("client").text(client.ifc_file);
clientDiv.append(dropdownIcon);
const clientDetailsDiv = $("<div>").addClass("client-details");
if (id) {
const clientId = $("<div>")
.addClass("client-detail")
.text(`Blender ID: ${id}`);
clientDetailsDiv.append(clientId);
}
if (client.headers && client.headers.length) {
const clientHeaders = $("<div>")
.addClass("client-detail")
.text(`Table Headers: ${client.headers.join(", ")}`);
const scrollButton = $("<button>")
.addClass("scroll-button")
.text("Scroll to Table")
.on("click", function () {
$("html, body").animate(
{
scrollTop: $("#table-" + id).offset().top,
},
600
);
clientList.removeClass("show");
});
clientDetailsDiv.append(clientHeaders);
clientDetailsDiv.append(scrollButton);
}
clientDiv.append(clientDetailsDiv);
clientDiv.on("click", function () {
clientDetailsDiv.toggleClass("show");
});
clientList.append(clientDiv);
});
clientList.addClass("show");
}
@@ -60,6 +60,10 @@ function handleBlenderDisconnect(blenderId) {
delete connectedClients[blenderId];
removeGanttElement(blenderId);
}
$("#blender-count").text(function (i, text) {
return parseInt(text, 10) - 1;
});
}
// Function to handle 'gantt_data' event
@@ -77,6 +81,7 @@ function handleGanttData(data) {
if (!connectedClients[blenderId].shown) {
connectedClients[blenderId] = {
shown: true,
ifc_file: filename,
ganttTasks: ganttTasks,
workSchedule: ganttWorkSched,
};
@@ -89,6 +94,7 @@ function handleGanttData(data) {
} else {
connectedClients[blenderId] = {
shown: true,
ifc_file: filename,
ganttTasks: ganttTasks,
workSchedule: ganttWorkSched,
};
@@ -105,6 +111,10 @@ function handleDefaultData(data) {
// Function to add a new gantt with data and filename
function addGanttElement(blenderId, tasks, workSched, filename) {
$("#blender-count").text(function (i, text) {
return parseInt(text, 10) + 1;
});
const ganttContainer = $("<div></div>")
.addClass("gantt-container")
.attr("id", "container-" + blenderId);
@@ -382,3 +392,75 @@ function toggleTheme() {
setTheme("dark");
}
}
function toggleClientList() {
var clientList = $("#client-list");
if (clientList.hasClass("show")) {
clientList.removeClass("show");
return;
}
clientList.empty();
$.each(connectedClients, function (id, client) {
if (!client.shown) return;
const dropdownIcon = $("<i>")
.addClass("fas fa-chevron-down")
.css("margin-left", "0.5rem");
const clientDiv = $("<div>").addClass("client").text(client.ifc_file);
clientDiv.append(dropdownIcon);
const clientDetailsDiv = $("<div>").addClass("client-details");
if (id) {
const clientId = $("<div>")
.addClass("client-detail")
.text(`Blender ID: ${id}`);
clientDetailsDiv.append(clientId);
}
if (client.workSchedule && client.gantt) {
const clientScheduleName = $("<div>")
.addClass("client-detail")
.text(`Schedule Name: ${client.workSchedule.Name}`);
const clientScheduleDate = $("<div>")
.addClass("client-detail")
.text(
`Schedule Date: ${new Date(
client.workSchedule.CreationDate
).toLocaleDateString()}`
);
const scrollButton = $("<button>")
.addClass("scroll-button")
.text("Scroll to Gantt Chart")
.on("click", function () {
$("html, body").animate(
{
scrollTop: $("#gantt-" + id).offset().top,
},
600
);
clientList.removeClass("show");
});
clientDetailsDiv.append(clientScheduleName);
clientDetailsDiv.append(clientScheduleDate);
clientDetailsDiv.append(scrollButton);
}
clientDiv.append(clientDetailsDiv);
clientDiv.on("click", function () {
clientDetailsDiv.toggleClass("show");
});
clientList.append(clientDiv);
});
clientList.addClass("show");
}
@@ -42,6 +42,10 @@ function handleBlenderDisconnect(blenderId) {
delete connectedClients[blenderId];
removeTableElement(blenderId);
}
$("#blender-count").text(function (i, text) {
return parseInt(text, 10) - 1;
});
}
// Function to handle 'csv_data' event
@@ -81,6 +85,10 @@ function handleDefaultData(data) {
// Function to add a new table with data and filename
function addTableElement(blenderId, csvData, filename) {
$("#blender-count").text(function (i, text) {
return parseInt(text, 10) + 1;
});
// store headers of the csv data
const firstLine = csvData.indexOf("\n");
const csvHeaders = csvData.substring(0, firstLine).split(",");
@@ -274,3 +282,66 @@ function toggleTheme() {
setTheme("dark");
}
}
function toggleClientList() {
var clientList = $("#client-list");
if (clientList.hasClass("show")) {
clientList.removeClass("show");
return;
}
clientList.empty();
$.each(connectedClients, function (id, client) {
if (!client.shown) return;
const dropdownIcon = $("<i>")
.addClass("fas fa-chevron-down")
.css("margin-left", "0.5rem");
const clientDiv = $("<div>").addClass("client").text(client.ifc_file);
clientDiv.append(dropdownIcon);
const clientDetailsDiv = $("<div>").addClass("client-details");
if (id) {
const clientId = $("<div>")
.addClass("client-detail")
.text(`Blender ID: ${id}`);
clientDetailsDiv.append(clientId);
}
if (client.headers && client.headers.length) {
const clientHeaders = $("<div>")
.addClass("client-detail")
.text(`Table Headers: ${client.headers.join(", ")}`);
const scrollButton = $("<button>")
.addClass("scroll-button")
.text("Scroll to Table")
.on("click", function () {
$("html, body").animate(
{
scrollTop: $("#table-" + id).offset().top,
},
600
);
clientList.removeClass("show");
});
clientDetailsDiv.append(clientHeaders);
clientDetailsDiv.append(scrollButton);
}
clientDiv.append(clientDetailsDiv);
clientDiv.on("click", function () {
clientDetailsDiv.toggleClass("show");
});
clientList.append(clientDiv);
});
clientList.addClass("show");
}
@@ -0,0 +1,123 @@
<!DOCTYPE html>
<html lang="en" class="dark">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Web Client</title>
<link
rel="stylesheet"
href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css"
/>
<link rel="stylesheet" href="/static/css/drawings.css" />
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.6.0/css/all.min.css"
/>
<script
type="text/javascript"
src="https://code.jquery.com/jquery-3.6.0.min.js"
></script>
<script
type="text/javascript"
src="https://cdnjs.cloudflare.com/ajax/libs/svg.js/3.1.1/svg.min.js"
></script>
<script
type="text/javascript"
src="https://cdn.jsdelivr.net/npm/svg-pan-zoom@3.6.1/dist/svg-pan-zoom.min.js"
></script>
<script
type="text/javascript"
src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.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/drawings.js"></script>
</head>
<body>
<nav>
<img
src="https://blenderbim.org/assets/images/blender/blender-logo.png"
alt="Logo"
class="logo"
/>
<ul>
<li><a href="/">IFC Data</a></li>
<li><a href="/gantt">Gantt Chart</a></li>
<li><a href="/drawings" class="active">Drawings</a></li>
</ul>
<button id="toggle-theme" onclick="toggleTheme()">
<i class="fas fa-moon"></i>
</button>
</nav>
<div id="connected-list-div">
<button id="show-connected-button" onclick="toggleClientList()">
Connected Blenders:
<span id="blender-count">0</span>
<i class="fas fa-chevron-down"></i>
</button>
<div id="client-list"></div>
</div>
<div class="container-fluid d-flex" id="container">
<div class="col-2 left">
<!-- Dropdown menu -->
<div class="dropdown mb-3">
<label for="dropdown-menu">IFC File:</label>
<select class="p-0 pl-2" id="dropdown-menu">
<!-- IFC files will be dynamically populated here -->
<option value="" selected disabled hidden>IFC File</option>
</select>
</div>
<!-- Panel with sub-panels -->
<div class="panel">
<div id="accordion">
<div class="card pb-2">
<div class="card-header overflow-hidden" id="headingOne">
<button
class="btn p-0 w-100 text-left"
data-toggle="collapse"
data-target="#collapseOne"
>
Drawings
</button>
</div>
<div id="collapseOne" class="collapse show">
<div class="card-body">
<ul class="sub-panel pl-2" id="drawings-sub-panel">
<!-- Drawings will be dynamically populated here -->
</ul>
</div>
</div>
</div>
<div class="card pb-2">
<div class="card-header overflow-hidden" id="headingTwo">
<button
class="btn p-0 w-100 text-left"
data-toggle="collapse"
data-target="#collapseTwo"
>
Sheets
</button>
</div>
<div id="collapseTwo" class="collapse show">
<div class="card-body">
<ul class="sub-panel pl-2" id="sheets-sub-panel">
<!-- Sheets will be dynamically populated here -->
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-10 right pl-3">
<div id="svg-container"></div>
</div>
</div>
</body>
</html>
@@ -34,11 +34,23 @@
<ul>
<li><a href="/">IFC Data</a></li>
<li><a href="/gantt" class="active">Gantt Chart</a></li>
<li><a href="/drawings">Drawings</a></li>
</ul>
<button id="toggle-theme" onclick="toggleTheme()">
<i class="fas fa-moon"></i>
</button>
</nav>
<div id="connected-list-div">
<button id="show-connected-button" onclick="toggleClientList()">
Connected Blenders:
<span id="blender-count">0</span>
<i class="fas fa-chevron-down"></i>
</button>
<div id="client-list"></div>
</div>
<div id="container"></div>
<footer>
<p>BlenderBIM Version: {{version}}</p>
</footer>
</body>
</html>
@@ -41,11 +41,23 @@
<ul>
<li><a href="/" class="active">IFC Data</a></li>
<li><a href="/gantt">Gantt Chart</a></li>
<li><a href="/drawings">Drawings</a></li>
</ul>
<button id="toggle-theme" onclick="toggleTheme()">
<i class="fas fa-moon"></i>
</button>
</nav>
<div id="connected-list-div">
<button id="show-connected-button" onclick="toggleClientList()">
Connected Blenders:
<span id="blender-count">0</span>
<i class="fas fa-chevron-down"></i>
</button>
<div id="client-list"></div>
</div>
<div id="container"></div>
<footer>
<p>BlenderBIM Version: {{version}}</p>
</footer>
</body>
</html>
@@ -24,7 +24,6 @@ import numpy as np
import datetime
import zipfile
import tempfile
import addon_utils
import ifcopenshell
import ifcopenshell.api
import ifcopenshell.util.placement
+9 -2
View File
@@ -18,10 +18,9 @@
import os
import bpy
import json
import addon_utils
import ifcopenshell.util.element
import ifcopenshell.util.representation
import ifcopenshell.util.unit
import ifcopenshell.api.owner.settings
import blenderbim.bim
import blenderbim.tool as tool
@@ -170,6 +169,11 @@ def subscribe_to(obj, data_path, callback):
def refresh_ui_data():
"""Refresh cached UI data.
Note that calling non-ifc-operators by itself doesn't refresh the UI data
and it need to be refreshed manually if needed.
"""
from blenderbim.bim import modules
for name, value in modules.items():
@@ -299,3 +303,6 @@ def load_post(scene):
continue
tool.Blender.override_scene_panel(panel)
tool.Blender.setup_tabs()
if tool.Ifc.get() and bpy.data.is_saved:
bpy.context.scene.BIMProperties.has_blend_warning = True
+3
View File
@@ -23,6 +23,7 @@ import math
import ifcopenshell
import ifcopenshell.util.attribute
import ifcopenshell.util.element
import ifcopenshell.util.unit
from ifcopenshell.util.doc import get_attribute_doc, get_predefined_type_doc, get_property_doc
from mathutils import geometry
from mathutils import Vector
@@ -50,6 +51,8 @@ def draw_attribute(attribute, layout, copy_operator=None):
return
if value_name == "enum_value":
prop_with_search(layout, attribute, "enum_value", text=attribute.name)
elif value_name == "filepath_value":
attribute.filepath_value.layout_file_select(layout, filter_glob=attribute.filter_glob, text=attribute.name)
elif attribute.name in ["ScheduleDuration", "ActualDuration", "FreeFloat", "TotalFloat"]:
propis = bpy.context.scene.BIMWorkScheduleProperties
for item in propis.durations_attributes:
+4 -4
View File
@@ -551,7 +551,7 @@ class IfcImporter:
for axis in axes:
shape = tool.Loader.create_generic_shape(axis.AxisCurve)
mesh = self.create_mesh(axis, shape)
obj = bpy.data.objects.new(f"IfcGridAxis/{axis.AxisTag}", mesh)
obj = bpy.data.objects.new(tool.Loader.get_name(axis), mesh)
if tool.Blender.get_addon_preferences().lock_grids_on_import:
obj.lock_location = (True, True, True)
obj.lock_rotation = (True, True, True)
@@ -781,7 +781,7 @@ class IfcImporter:
mesh = bpy.data.meshes.new(mesh_name)
mesh.from_pydata([mathutils.Vector(vertex) * self.unit_scale], [], [])
obj = bpy.data.objects.new("{}/{}".format(product.is_a(), product.Name), mesh)
obj = bpy.data.objects.new(tool.Loader.get_name(product), mesh)
self.set_matrix_world(obj, tool.Loader.apply_blender_offset_to_matrix_world(obj, placement_matrix))
self.link_element(product, obj)
@@ -847,7 +847,7 @@ class IfcImporter:
mesh.from_pydata(vertex_list, [], [])
tool.Ifc.link(representation, mesh)
obj = bpy.data.objects.new("{}/{}".format(product.is_a(), product.Name), mesh)
obj = bpy.data.objects.new(tool.Loader.get_name(product), mesh)
self.set_matrix_world(obj, tool.Loader.apply_blender_offset_to_matrix_world(obj, placement_matrix))
self.link_element(product, obj)
return product
@@ -1261,7 +1261,7 @@ class IfcImporter:
def place_objects_in_collections(self) -> None:
for ifc_definition_id, obj in self.added_data.items():
if isinstance(obj, bpy.types.Object):
tool.Collector.assign(obj)
tool.Collector.assign(obj, should_clean_users_collection=False)
def is_curve_annotation(self, element: ifcopenshell.entity_instance) -> bool:
object_type = element.ObjectType
@@ -18,6 +18,7 @@
import bpy
import ifcopenshell
import ifcopenshell.api
import ifcopenshell.util.element
import blenderbim.tool as tool
import blenderbim.core.aggregate as core
@@ -167,7 +168,7 @@ class BIM_OT_add_aggregate(bpy.types.Operator, tool.Ifc.Operator):
tool.Ifc,
tool.Collector,
tool.Spatial,
structure_obj=tool.Ifc.get_object(current_container),
container=current_container,
element_obj=aggregate,
)
core.assign_object(tool.Ifc, tool.Aggregate, tool.Collector, relating_obj=aggregate, related_obj=obj)
@@ -21,6 +21,7 @@ import json
import ifcopenshell
import ifcopenshell.api
import ifcopenshell.guid
import ifcopenshell.util.element
import blenderbim.bim.helper
import blenderbim.bim.handler
import blenderbim.tool as tool
@@ -36,7 +37,7 @@ class EnableEditingAttributes(bpy.types.Operator):
def execute(self, context):
self.file = IfcStore.get_file()
obj = bpy.data.objects.get(self.obj)
obj = bpy.data.objects[self.obj]
props = obj.BIMAttributeProperties
props.attributes.clear()
@@ -18,14 +18,12 @@
import os
import bpy
import json
import string
import random
import shutil
import requests
import tempfile
import datetime
import addon_utils
import blenderbim.tool as tool
@@ -26,6 +26,7 @@ import numpy as np
import tempfile
import webbrowser
import ifcopenshell
import ifcopenshell.util.geolocation
import ifcopenshell.util.unit
import blenderbim.bim.module.bcf.prop as bcf_prop
from . import bcfstore
@@ -60,10 +60,14 @@ class SearchBSDDClass(bpy.types.Operator):
class GetBSDDClassificationProperties(bpy.types.Operator):
bl_idname = "bim.get_bsdd_classification_properties"
bl_label = "Search bSDD Class Properties"
bl_description = "Search for bSDD class properties for the currently selected class"
bl_description = (
"Search for bSDD class properties for the currently selected class. All non-IFC properties are skipped"
)
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
properties_found = core.get_class_properties(bsdd.Client(), tool.Bsdd)
self.report({"INFO"}, f"{properties_found} properties found for the active classification.")
pset_data = core.get_class_properties(bsdd.Client(), tool.Bsdd)
psets_found = len(pset_data)
props_found = sum(len(pset) for pset in pset_data.values())
self.report({"INFO"}, f"{psets_found} psets found ({props_found} props) for the active classification.")
return {"FINISHED"}
@@ -43,7 +43,6 @@ class BSDDDomain(PropertyGroup):
class BSDDClassification(PropertyGroup):
name: StringProperty(name="Name")
reference_code: StringProperty(name="Reference Code")
description: StringProperty(name="Description")
uri: StringProperty(name="Namespace URI")
domain_name: StringProperty(name="Domain Name")
domain_namespace_uri: StringProperty(name="Domain Namespace URI")
@@ -68,8 +68,7 @@ class BIM_PT_bsdd(Panel):
row = box.row(align=True)
row.label(text="Version")
row.label(text=selected_domain.version)
box.label(text="URI:")
box.label(text=selected_domain.uri)
box.operator("bim.open_uri", text="Open bSDD In Browser", icon="URL").uri = selected_domain.uri
else:
row = self.layout.row()
@@ -79,14 +78,18 @@ class BIM_PT_bsdd(Panel):
class BIM_UL_bsdd_domains(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
if item:
props = context.scene.BIMBSDDProperties
row = layout.row(align=True)
if item.status != "Active":
row.label(text=f"{item.name} ({item.organization_name_owner}) - {item.status}", icon="ERROR")
else:
row.label(text=f"{item.name} ({item.organization_name_owner})")
op = row.operator("bim.set_active_bsdd_domain", text="", icon="RESTRICT_SELECT_OFF")
op.name = item.name
op.uri = item.uri
if item.uri == props.active_uri:
row.label(text="", icon="URL")
else:
op = row.operator("bim.set_active_bsdd_domain", text="", icon="RESTRICT_SELECT_OFF")
op.name = item.name
op.uri = item.uri
class BIM_UL_bsdd_classifications(UIList):
@@ -95,3 +98,4 @@ class BIM_UL_bsdd_classifications(UIList):
row = layout.row(align=True)
row.label(text=item.reference_code)
row.label(text=item.name)
row.operator("bim.open_uri", text="", icon="URL").uri = item.uri
@@ -22,7 +22,6 @@ import bpy
import time
import random
import logging
import platform
import subprocess
import ifcopenshell
import ifcopenshell.api
@@ -30,6 +29,7 @@ import ifcopenshell.geom
import ifcopenshell.util.element
import ifcopenshell.util.placement
import ifcopenshell.util.representation
import ifcopenshell.util.unit
import blenderbim.tool as tool
import blenderbim.core.debug as core
import blenderbim.bim.handler
@@ -21,6 +21,7 @@ import bpy
import json
import ifcopenshell.util.element
import ifcopenshell.util.representation
import ifcopenshell.util.unit
import blenderbim.tool as tool
from pathlib import Path
from typing import Any, Union
@@ -25,6 +25,7 @@ import bmesh
import shapely
import ifcopenshell
import ifcopenshell.util.element
import ifcopenshell.util.unit
import blenderbim.tool as tool
import blenderbim.bim.module.drawing.helper as helper
from math import pi, sin, cos, tan, acos, atan, degrees, radians, ceil
@@ -1472,12 +1472,9 @@ class ActivateModel(bpy.types.Operator):
CutDecorator.uninstall()
# save current visibility statuses for Views and Types collections
# save current visibility statuses
visibility_status: dict[bpy.types.Object, bool] = {}
for col in bpy.data.collections["Views"].children:
for obj in col.objects:
visibility_status[obj] = obj.hide_get()
for obj in bpy.data.collections["Types"].objects:
for obj in bpy.data.objects:
visibility_status[obj] = obj.hide_get()
if not bpy.app.background:
@@ -1508,6 +1505,7 @@ class ActivateModel(bpy.types.Operator):
obj.hide_set(hide_status)
tool.Blender.update_viewport()
blenderbim.bim.handler.refresh_ui_data()
return {"FINISHED"}
@@ -1515,46 +1513,56 @@ class ActivateDrawing(bpy.types.Operator):
bl_idname = "bim.activate_drawing"
bl_label = "Activate Drawing"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Activates the selected drawing view.\n\n" + "ALT+CLICK to keep the viewport position"
bl_description = "Activates the selected drawing view.\n\n" + "ALT+CLICK to keep the viewport position.\n\n" + "SHIFT+CLICK activiate drawing without turning objects on/off."
drawing: bpy.props.IntProperty()
camera_view_point: bpy.props.BoolProperty(name="Camera View Point", default=True, options={"SKIP_SAVE"})
switch_camera_only: bpy.props.BoolProperty(name="Only Changes Camera View", default=False, options={"SKIP_SAVE"})
def invoke(self, context, event):
# keep the viewport position on alt+click
# make sure to use SKIP_SAVE on property, otherwise it might get stuck
if event.type == "LEFTMOUSE" and event.alt:
self.camera_view_point = False
# Only activates the camera view on shift+click. Does not turn on/off objects in scene
if event.type == "LEFTMOUSE" and event.shift:
self.switch_camera_only = True
return self.execute(context)
def execute(self, context):
if bpy.context.scene.DocProperties.is_editing_drawings == False:
bpy.ops.bim.load_drawings()
drawing = tool.Ifc.get().by_id(self.drawing)
dprops = bpy.context.scene.DocProperties
if not self.camera_view_point:
viewport_position = tool.Blender.get_viewport_position()
if self.switch_camera_only:
camera = tool.Drawing.import_drawing(drawing)
tool.Blender.activate_camera(camera)
else:
if not self.camera_view_point:
viewport_position = tool.Blender.get_viewport_position()
core.activate_drawing_view(tool.Ifc, tool.Blender, tool.Drawing, drawing=drawing)
core.activate_drawing_view(tool.Ifc, tool.Blender, tool.Drawing, drawing=drawing)
if not self.camera_view_point:
tool.Blender.set_viewport_position(viewport_position)
if not self.camera_view_point:
tool.Blender.set_viewport_position(viewport_position)
dprops.active_drawing_id = self.drawing
# reset DrawingsData to reload_drawing_styles work correctly
DrawingsData.is_loaded = False
dprops.drawing_styles.clear()
if ifcopenshell.util.element.get_pset(drawing, "EPset_Drawing", "HasUnderlay"):
bpy.ops.bim.reload_drawing_styles()
bpy.ops.bim.activate_drawing_style()
core.sync_references(tool.Ifc, tool.Collector, tool.Drawing, drawing=tool.Ifc.get().by_id(self.drawing))
CutDecorator.install(context)
tool.Drawing.show_decorations()
dprops.active_drawing_id = self.drawing
# reset DrawingsData to reload_drawing_styles work correctly
DrawingsData.is_loaded = False
dprops.drawing_styles.clear()
if ifcopenshell.util.element.get_pset(drawing, "EPset_Drawing", "HasUnderlay"):
bpy.ops.bim.reload_drawing_styles()
bpy.ops.bim.activate_drawing_style()
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="")
# 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"}
@@ -21,6 +21,8 @@ import bpy
import json
import enum
import ifcopenshell
import ifcopenshell.api
import ifcopenshell.util.element
import blenderbim.tool as tool
import blenderbim.core.drawing as core
import blenderbim.bim.module.drawing.annotation as annotation
@@ -29,6 +29,7 @@ import ifcopenshell
import ifcopenshell.util.element
import ifcopenshell.util.representation
import ifcopenshell.util.selector
import ifcopenshell.util.unit
import blenderbim.tool as tool
import blenderbim.bim.module.drawing.helper as helper
import blenderbim.bim.module.drawing.annotation as annotation
@@ -430,6 +430,19 @@ class BIM_PT_sheets(Panel):
active_sheet = self.props.sheets[self.props.active_sheet_index]
row = self.layout.row(align=True)
row.alignment = "RIGHT"
if active_sheet.reference_type == "DRAWING":
drawingnamesvg = active_sheet.name
drawingname = drawingnamesvg.split('.')[0]
ifc_file = tool.Ifc.get()
ifc_annotations = ifc_file.by_type('IfcAnnotation')
for annotation in ifc_annotations:
if annotation.Name == drawingname:
drawingid = annotation.id()
drawing_button = row.row(align=True)
op = drawing_button.operator("bim.activate_drawing", icon="OUTLINER_OB_CAMERA", text="")
op.drawing = drawingid
row.operator("bim.edit_sheet", icon="GREASEPENCIL", text="")
row.operator("bim.open_sheet", icon="URL", text="")
row.operator("bim.add_drawing_to_sheet", icon="IMAGE_PLANE", text="")
@@ -22,7 +22,6 @@ from . import ui, prop, operator
classes = (
operator.ExecuteIfcFM,
operator.ExecuteIfcFMFederate,
operator.SelectFMIfcFile,
operator.SelectFMSpreadsheetFiles,
prop.BIMFMProperties,
ui.BIM_PT_fm,
@@ -24,20 +24,6 @@ import logging
import tempfile
import ifcopenshell
import blenderbim.tool as tool
from blenderbim.bim.operator import MultipleFileSelector
class SelectFMIfcFile(MultipleFileSelector):
bl_idname = "bim.select_fm_ifc_file"
bl_label = "Select FM IFC File"
filename_ext = ".ifc"
filter_glob: bpy.props.StringProperty(default="*.ifc;*.ifczip;*.ifcxml", options={"HIDDEN"})
def execute(self, context):
props = context.scene.BIMFMProperties
self.update_props(props, "ifc_file", props.ifc_files)
return {"FINISHED"}
class ExecuteIfcFM(bpy.types.Operator):
@@ -50,7 +36,7 @@ class ExecuteIfcFM(bpy.types.Operator):
@classmethod
def poll(cls, context):
props = context.scene.BIMFMProperties
return props.should_load_from_memory or props.ifc_file
return props.should_load_from_memory or props.ifc_files.single_file
def invoke(self, context, event):
props = context.scene.BIMFMProperties
@@ -63,14 +49,13 @@ class ExecuteIfcFM(bpy.types.Operator):
props = context.scene.BIMFMProperties
ifc_file = tool.Ifc.get()
filepaths = []
if not (ifc_file and props.should_load_from_memory):
if len(props.ifc_files):
ifc_files = [ifcopenshell.open(f.name) for f in props.ifc_files]
filepaths = [f.name for f in props.ifc_files]
else:
ifc_files = [ifcopenshell.open(props.ifc_file)]
else:
if ifc_file and props.should_load_from_memory:
ifc_files = [ifc_file]
else:
ifc_files = [ifcopenshell.open(f.name) for f in props.ifc_files.file_list]
if len(ifc_files) > 1:
filepaths = [f.name for f in props.ifc_files.file_list]
for i, ifc_file in enumerate(ifc_files):
if filepaths:
@@ -18,7 +18,7 @@
import bpy
from blenderbim.bim.module.fm.data import FMData
from blenderbim.bim.prop import StrProperty
from blenderbim.bim.prop import MultipleFileSelect, StrProperty
from bpy.types import PropertyGroup
from bpy.props import (
PointerProperty,
@@ -39,11 +39,10 @@ def get_engine(self, context):
class BIMFMProperties(PropertyGroup):
ifc_file: StringProperty(default="", name="IFC File")
ifc_files: CollectionProperty(name="IFC Files", type=StrProperty)
ifc_files: PointerProperty(type=MultipleFileSelect)
spreadsheet_files: CollectionProperty(name="Spreadsheets", type=StrProperty)
should_load_from_memory: BoolProperty(default=False, name="Load from Memory")
engine: EnumProperty(items=get_engine, name="Engine")
engine: EnumProperty(items=get_engine, name="Engine", options=set())
format: EnumProperty(
items=[
("csv", "csv", ""),
@@ -52,4 +51,5 @@ class BIMFMProperties(PropertyGroup):
],
name="Format",
default="ods",
options=set(),
)
@@ -45,12 +45,8 @@ class BIM_PT_fm(Panel):
row.prop(props, "should_load_from_memory")
if not IfcStore.get_file() or not props.should_load_from_memory:
row = layout.row(align=True)
if len(props.ifc_files) > 1:
row.label(text=f"{len(props.ifc_files)} Files Selected")
else:
row.prop(props, "ifc_file")
row.operator("bim.select_fm_ifc_file", icon="FILE_FOLDER", text="")
row = layout.row()
props.ifc_files.layout_file_select(row, "*.ifc;*.ifczip;*.ifcxml", "IFC File(s)")
row = layout.row()
row.prop(props, "engine")
@@ -18,8 +18,11 @@
import bpy
import ifcopenshell.util.element
import blenderbim.tool as tool
import ifcopenshell.util.geolocation
import ifcopenshell.util.placement
import ifcopenshell.util.schema
import ifcopenshell.util.unit
import blenderbim.tool as tool
from mathutils import Vector
@@ -21,9 +21,6 @@ from . import ui, prop, operator
classes = (
operator.AddGeoreferencing,
operator.ConvertAngleToCoordinates,
operator.ConvertGlobalToLocal,
operator.ConvertLocalToGlobal,
operator.DisableEditingGeoreferencing,
operator.DisableEditingTrueNorth,
operator.DisableEditingWCS,
@@ -20,7 +20,10 @@
import bpy
import numpy as np
import blenderbim.tool as tool
import ifcopenshell.util.element
import ifcopenshell.util.geolocation
import ifcopenshell.util.schema
import ifcopenshell.util.unit
from mathutils import Matrix, Vector
from ifcopenshell.util.doc import get_entity_doc
@@ -44,6 +47,7 @@ class GeoreferenceData:
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.data["local_origin"] = cls.local_origin()
cls.is_loaded = True
@classmethod
@@ -165,7 +169,7 @@ class GeoreferenceData:
props = bpy.context.scene.BIMGeoreferenceProperties
if props.has_blender_offset:
blender_xyz = ifcopenshell.util.geolocation.xyz2enh(
blender_xyz = ifcopenshell.util.geolocation.enh2xyz(
result["x"],
result["y"],
result["z"],
@@ -186,3 +190,16 @@ class GeoreferenceData:
wcs[2][3] *= unit_scale
result["matrix"] = Matrix(wcs)
return result
@classmethod
def local_origin(cls):
props = bpy.context.scene.BIMGeoreferenceProperties
if not props.has_blender_offset:
return
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
x = float(props.blender_offset_x) * -1
y = float(props.blender_offset_y) * -1
z = float(props.blender_offset_z) * -1
enh = ifcopenshell.util.geolocation.auto_xyz2enh(tool.Ifc.get(), 0, 0, 0)
xyz_si = Vector([x * unit_scale, y * unit_scale, z * unit_scale])
return {"x": x, "y": y, "z": z, "enh": enh, "location": xyz_si}
@@ -16,6 +16,7 @@
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
import blf
import gpu
import bmesh
@@ -61,6 +62,10 @@ class GeoreferenceDecorator:
self.calculate_angles(context)
props = context.scene.BIMGeoreferenceProperties
e, n, h = [round(float(o), 7) for o in props.model_origin.split(",")]
if (operation := GeoreferenceData.data["coordinate_operation"]) and (scale := operation.get("Scale", None)):
e *= scale
n *= scale
h *= scale
self.addon_prefs = tool.Blender.get_addon_preferences()
@@ -71,10 +76,14 @@ class GeoreferenceDecorator:
blf.enable(self.font_id, blf.SHADOW)
blf.shadow(self.font_id, 6, 0, 0, 0, 1)
text = f"Blender Coordinates ({GeoreferenceData.data['local_unit_symbol']}) X: 0, Y: 0, Z: 0"
if props.has_blender_offset:
text = "Blender Origin"
text += f"\nBlender Coordinates ({GeoreferenceData.data['local_unit_symbol']}) X: 0, Y: 0, Z: 0"
else:
text = "IFC Local Origin"
text += f"\nLocal Coordinates ({GeoreferenceData.data['local_unit_symbol']}) X: {props.blender_offset_x}, Y: {props.blender_offset_y}, Z: {props.blender_offset_z}"
if GeoreferenceData.data["projected_crs"]:
text += f"\nMap Coordinates ({GeoreferenceData.data['local_unit_symbol']}) E: {e}, N: {n}, H: {h}"
text += f"\nMap Coordinates ({GeoreferenceData.data['map_unit_symbol']}) E: {e}, N: {n}, H: {h}"
self.draw_text_at_position(context, text, Vector((0, -0.1, 0)))
angle = Matrix.Rotation(radians(self.pn_angle), 4, "Z")
@@ -107,9 +116,10 @@ class GeoreferenceDecorator:
arc_mid = angle_half @ arc_start
self.draw_text_at_position(context, f"{self.tn_angle}deg", arc_mid)
if (wcs := GeoreferenceData.data["world_coordinate_system"]) and ["has_transformation"]:
if (wcs := GeoreferenceData.data["world_coordinate_system"]) and wcs["has_transformation"]:
text = "WCS"
text += f"\nBlender Coordinates ({GeoreferenceData.data['local_unit_symbol']}) X: {wcs['blender_x']}, Y: {wcs['blender_y']}, Z: {wcs['blender_z']}"
if props.has_blender_offset:
text += f"\nBlender Coordinates ({GeoreferenceData.data['local_unit_symbol']}) X: {wcs['blender_x']}, Y: {wcs['blender_y']}, Z: {wcs['blender_z']}"
text += f"\nLocal Coordinates ({GeoreferenceData.data['local_unit_symbol']}) X: {wcs['x']}, Y: {wcs['y']}, Z: {wcs['z']}"
if operation := GeoreferenceData.data["coordinate_operation"]:
e = operation.get("Eastings")
@@ -126,6 +136,22 @@ class GeoreferenceDecorator:
self.draw_text_at_position(context, text, position)
if props.has_blender_offset:
text = "IFC Local Origin"
x = GeoreferenceData.data["local_origin"]["x"]
y = GeoreferenceData.data["local_origin"]["y"]
z = GeoreferenceData.data["local_origin"]["z"]
location = GeoreferenceData.data["local_origin"]["location"]
text += f"\nBlender Coordinates ({GeoreferenceData.data['local_unit_symbol']}) X: {x}, Y: {y}, Z: {z}"
text += f"\nLocal Coordinates ({GeoreferenceData.data['local_unit_symbol']}) X: 0, Y: 0, Z: 0"
if GeoreferenceData.data["projected_crs"]:
e, n, h = GeoreferenceData.data["local_origin"]["enh"]
text += f"\nMap Coordinates ({GeoreferenceData.data['map_unit_symbol']}) E: {e}, N: {n}, H: {h}"
if location.length > 1000:
location = location.normalized() * 3
text += "\n(Warning: Actual XYZ Not Shown)"
self.draw_text_at_position(context, text, location)
def draw_text_at_position(self, context, text, position):
coords_2d = location_3d_to_region_2d(context.region, context.region_data, position)
if not coords_2d:
@@ -255,25 +281,50 @@ class GeoreferenceDecorator:
verts, edges = arc_segments
self.draw_batch("LINES", verts, decorator_color_special, edges)
if (wcs := GeoreferenceData.data["world_coordinate_system"]) and ["has_transformation"]:
if (wcs := GeoreferenceData.data["world_coordinate_system"]) and wcs["has_transformation"]:
if wcs["blender_location"].length < 1000:
verts = [Vector((0, 0, 0)), wcs["blender_location"]]
edges = [[0, 1]]
self.draw_batch("LINES", verts, decorator_color_special, edges)
self.draw_batch("POINTS", verts[1:], decorator_color_special)
else:
location = wcs["blender_location"].normalized()
edges = [[0, 1]]
verts = [Vector((0, 0, 0)), wcs["blender_location"].normalized() * 3]
verts = [Vector((0, 0, 0)), location * 3]
self.draw_batch("LINES", verts, decorator_color_special, edges)
verts = [wcs["blender_location"].normalized() * 3, wcs["blender_location"].normalized() * 3.1]
self.draw_batch("LINES", verts, decorator_color_error, edges)
verts = [wcs["blender_location"].normalized() * 3.12, wcs["blender_location"].normalized() * 3.2]
self.draw_batch("LINES", verts, decorator_color_error, edges)
verts = [wcs["blender_location"].normalized() * 3.22, wcs["blender_location"].normalized() * 3.3]
self.draw_batch("LINES", verts, decorator_color_error, edges)
verts = [wcs["blender_location"].normalized() * 3.32, wcs["blender_location"].normalized() * 3.4]
self.draw_batch("LINES", verts, decorator_color_error, edges)
self.draw_dashed_line(location * 3, location * 6, decorator_color_error)
props = context.scene.BIMGeoreferenceProperties
if props.has_blender_offset:
location = GeoreferenceData.data["local_origin"]["location"]
if location.length < 1000:
verts = [Vector((0, 0, 0)), location]
edges = [[0, 1]]
self.draw_batch("LINES", verts, decorator_color_special, edges)
self.draw_batch("POINTS", verts[1:], decorator_color_special)
else:
location = location.normalized()
edges = [[0, 1]]
verts = [Vector((0, 0, 0)), location * 3]
self.draw_batch("LINES", verts, decorator_color_special, edges)
self.draw_dashed_line(location * 3, location * 6, decorator_color_error)
def draw_dashed_line(self, start, end, colour):
direction = (end - start).normalized()
distance = (end - start).length
current_distance = Vector((0, 0, 0))
gap = 0.05 * direction
dash = 0.1 * direction
points = [start]
while current_distance.length < distance:
points.append(start + current_distance + dash)
points.append(start + current_distance + dash + gap)
current_distance += gap + dash
points.pop()
edges = [[i, i + 1] for i in range(0, len(points), 2)]
verts = points
self.draw_batch("LINES", verts, colour, edges)
def calculate_angles(self, context):
self.pn_angle = 0.0
@@ -87,55 +87,6 @@ class GetCursorLocation(bpy.types.Operator, tool.Ifc.Operator):
core.get_cursor_location(tool.Georeference)
class ConvertLocalToGlobal(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.convert_local_to_global"
bl_label = "Convert Local To Global"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Convert local coordinate to global coordinate"
@classmethod
def poll(cls, context):
file = tool.Ifc.get()
props = context.scene.BIMGeoreferenceProperties
return file and props.local_coordinates.count(",") == 2
def _execute(self, context):
core.convert_local_to_global(tool.Georeference)
class ConvertGlobalToLocal(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.convert_global_to_local"
bl_label = "Convert Global To Local"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Convert global coordinate to local coordinate"
@classmethod
def poll(cls, context):
file = tool.Ifc.get()
props = context.scene.BIMGeoreferenceProperties
return file and file.by_type("IfcUnitAssignment") and props.local_coordinates.count(",") == 2
def _execute(self, context):
core.convert_global_to_local(tool.Georeference)
class ConvertAngleToCoordinates(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.convert_angle_to_coord"
bl_label = "Convert Angle To Y Axis"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Convert angle to Y axis"
type: bpy.props.StringProperty()
@classmethod
def poll(cls, context):
file = tool.Ifc.get()
props = context.scene.BIMGeoreferenceProperties
return file and (props.angle_degree_input_x or props.angle_degree_input_y)
def _execute(self, context):
core.convert_angle_to_coord(tool.Georeference, type=self.type)
class ImportPlot(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.import_plot"
bl_label = "Import Plot"
@@ -170,7 +121,7 @@ class EditWCS(bpy.types.Operator, tool.Ifc.Operator):
bl_description = "Edit the WCS"
def _execute(self, context):
core.edit_wcs(tool.Ifc, tool.Georeference)
core.edit_wcs(tool.Georeference)
class DisableEditingWCS(bpy.types.Operator, tool.Ifc.Operator):
@@ -18,6 +18,7 @@
import bpy
import ifcopenshell.util.geolocation
import blenderbim.tool as tool
from blenderbim.bim.prop import Attribute
from bpy.types import PropertyGroup
from bpy.props import (
@@ -101,6 +102,74 @@ def update_should_visualise(self, context):
GeoreferenceDecorator.uninstall()
def update_blender_coordinates(self, context):
props = bpy.context.scene.BIMGeoreferenceProperties
if props.is_updating_coordinates:
return
props.is_updating_coordinates = True
blender_coordinates = tool.Georeference.get_coordinates("blender")
local_coordinates = ifcopenshell.util.geolocation.xyz2enh(
*blender_coordinates,
float(props.blender_offset_x),
float(props.blender_offset_y),
float(props.blender_offset_z),
float(props.blender_x_axis_abscissa),
float(props.blender_x_axis_ordinate),
)
tool.Georeference.set_coordinates("local", local_coordinates)
tool.Georeference.set_coordinates(
"map", ifcopenshell.util.geolocation.auto_xyz2enh(tool.Ifc.get(), *local_coordinates)
)
props.is_updating_coordinates = False
def update_local_coordinates(self, context):
props = bpy.context.scene.BIMGeoreferenceProperties
if props.is_updating_coordinates:
return
props.is_updating_coordinates = True
local_coordinates = tool.Georeference.get_coordinates("local")
tool.Georeference.set_coordinates(
"map", ifcopenshell.util.geolocation.auto_xyz2enh(tool.Ifc.get(), *local_coordinates)
)
if props.has_blender_offset:
tool.Georeference.set_coordinates(
"blender",
ifcopenshell.util.geolocation.enh2xyz(
*local_coordinates,
float(props.blender_offset_x),
float(props.blender_offset_y),
float(props.blender_offset_z),
float(props.blender_x_axis_abscissa),
float(props.blender_x_axis_ordinate),
),
)
props.is_updating_coordinates = False
def update_map_coordinates(self, context):
props = bpy.context.scene.BIMGeoreferenceProperties
if props.is_updating_coordinates:
return
props.is_updating_coordinates = True
map_coordinates = tool.Georeference.get_coordinates("map")
local_coordinates = ifcopenshell.util.geolocation.auto_enh2xyz(tool.Ifc.get(), *map_coordinates)
tool.Georeference.set_coordinates("local", local_coordinates)
if props.has_blender_offset:
tool.Georeference.set_coordinates(
"blender",
ifcopenshell.util.geolocation.enh2xyz(
*local_coordinates,
float(props.blender_offset_x),
float(props.blender_offset_y),
float(props.blender_offset_z),
float(props.blender_x_axis_abscissa),
float(props.blender_x_axis_ordinate),
),
)
props.is_updating_coordinates = False
class BIMGeoreferenceProperties(PropertyGroup):
coordinate_operation_class: bpy.props.EnumProperty(
items=get_coordinate_operation_class, name="Coordinate Operation Class"
@@ -111,11 +180,24 @@ class BIMGeoreferenceProperties(PropertyGroup):
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)
is_updating_coordinates: BoolProperty(name="Is Updating Coordinates", default=False)
blender_coordinates: StringProperty(
name="Blender Coordinates",
description='Formatted "x,y,z" (without quotes)',
default="0,0,0",
update=update_blender_coordinates,
)
local_coordinates: StringProperty(
name="Local Coordinates", description='Formatted "x,y,z" (without quotes)', default="0,0,0"
name="Local Coordinates",
description='Formatted "x,y,z" (without quotes)',
default="0,0,0",
update=update_local_coordinates,
)
map_coordinates: StringProperty(
name="Map Coordinates", description='Formatted "x,y,z" (without quotes)', default="0,0,0"
name="Map Coordinates",
description='Formatted "x,y,z" (without quotes)',
default="0,0,0",
update=update_map_coordinates,
)
should_visualise: BoolProperty(
name="Should Visualise",
@@ -288,12 +288,15 @@ class BIM_PT_gis_calculator(Panel):
props = context.scene.BIMGeoreferenceProperties
row = self.layout.row(align=True)
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()
row.prop(props, "map_coordinates", text=f"Map ({GeoreferenceData.data['map_unit_symbol']})")
if props.has_blender_offset:
row = self.layout.row(align=True)
row.prop(props, "blender_coordinates", text=f"Blender ({GeoreferenceData.data['local_unit_symbol']})")
row.operator("bim.get_cursor_location", text="", icon="TRACKER")
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.prop(props, "local_coordinates", text=f"Local ({GeoreferenceData.data['local_unit_symbol']})")
if not props.has_blender_offset:
row.operator("bim.get_cursor_location", text="", icon="TRACKER")
row = self.layout.row()
row.prop(props, "map_coordinates", text=f"Map ({GeoreferenceData.data['map_unit_symbol']})")
@@ -0,0 +1,37 @@
{
skymap.cal - Map a pre swewd sky picture onto a hemisphere
###########################################
#
# SKY PICTURE MAP
#
# Copyright (C) 1992 by Curtin University
# School of Architecture
#
# Curtin University of Technology
# Department of Architecture
# GPO Box U 1987
# Perth, Western Australia
#
# Version 1.1 October 1993.
# Fixed NaN errors.
#
# Simon Crone
# email: crones@puffin.curtin.edu.au
#
###########################################
A1 = aspect ratio ( height / width )
}
u_scale = if( A1 -1, 1, A1);
v_scale = if( A1 -1, A1, 1);
hang = if( abs( Dx) - FTINY,
atan2( Dy, Dx),
if( Dy, PI/2, -PI/2 )
);
map_u = ( (hang + PI ) / (PI*2) ) / u_scale;
map_v = ( (asin( Dz) + (PI/2) ) / PI ) / v_scale;
@@ -19,6 +19,16 @@
import bpy
from . import ui, prop, operator
import stat
from pathlib import Path
import importlib
import pyradiance
def get_pyradiance_path():
return importlib.util.find_spec("pyradiance").submodule_search_locations[0]
classes = (
operator.ExportOBJ,
@@ -38,6 +48,12 @@ classes = (
def register():
bpy.types.Scene.radiance_exporter_properties = bpy.props.PointerProperty(type=prop.RadianceExporterProperties)
bpy.types.Scene.BIMSolarProperties = bpy.props.PointerProperty(type=prop.BIMSolarProperties)
pyradiance_path = Path(get_pyradiance_path())
bin_path = pyradiance_path / "bin"
if bin_path.exists():
for file in bin_path.iterdir():
if file.is_file():
file.chmod(file.stat().st_mode | stat.S_IEXEC)
def unregister():
@@ -34,10 +34,15 @@ class SolarData:
def load(cls):
cls.is_loaded = True
cls.data = {
"sun_position": cls.sun_position(),
"sites": cls.sites(),
"true_north": cls.true_north(),
}
@classmethod
def sun_position(cls):
return tool.Blender.get_sun_position_addon()
@classmethod
def sites(cls):
results = []
@@ -28,8 +28,6 @@ from gpu_extras.batch import batch_for_shader
from bpy_extras.view3d_utils import location_3d_to_region_2d
from blenderbim.bim.module.light.data import SolarData
sun_position = tool.Blender.get_sun_position_addon()
class SolarDecorator:
is_installed = False
@@ -149,7 +147,7 @@ class SolarDecorator:
indices = []
coord_offset = 0
for h in range(24):
analemma_verts = sun_position.sun_calc.calc_analemma(context, h)
analemma_verts = SolarData.data["sun_position"].sun_calc.calc_analemma(context, h)
coords.extend([origin @ v for v in analemma_verts])
for i in range(len(analemma_verts) - 1):
indices.append((coord_offset + i, coord_offset + i + 1))
@@ -0,0 +1,27 @@
[
{
"ceiling": {
"Black Ceiling 1": "void plastic black_ceiling\n0\n0\n5 0.0460 0.0456 0.0463 0.0052 0.2000",
"Black Ceiling 2": "void plastic black_ceiling\n0\n0\n5 0.1116 0.1085 0.0938 0.0000 0.0000",
"Plastic Ceiling Vent E14 526": "void plastic plastic_ceiling_vent_e14_526\n0\n0\n5 0.7384 0.7195 0.6540 0.0110 0.0500",
"Specular Reflective Ceiling Panels": "void plastic specular_reflective_ceiling_panels\n0\n0\n5 0.7708 0.7673 0.6819 0.0741 0.0500"
},
"Door": {
"Wooden Door": "void plastic wooden_door\n0\n0\n5 0.6164 0.4257 0.2156 0.0185 0.2000",
"Wooden Door 2": "void plastic wooden_door_2\n0\n0\n5 0.0403 0.0388 0.0406 0.0121 0.2000",
"White Wooden Door": "void plastic white_wooden_door\n0\n0\n5 0.7893 0.7502 0.6413 0.0080 0.2000",
"Off White Door": "void plastic off_white_door\n0\n0\n5 0.8607 0.8611 0.7852 0.0258 0.2000",
"Bedroom Door": "void plastic bedroom_door\n0\n0\n5 0.2508 0.0782 0.0111 0.0279 0.2000"
},
"Floor": {
"Light Blue Ceramic": "void plastic light_blue_ceramic\n0\n0\n5 0.7477 0.7776 0.7807 0.0323 0.1000",
"Light Grey Ceramic": "void plastic light_grey_ceramic\n0\n0\n5 0.6386 0.5941 0.4982 0.0106 0.2000",
"Purple Carpet": "void plastic purple_carpet\n0\n0\n5 0.0425 0.0179 0.0371 0.0001 0.3000"
},
"Wall": {
"Dirty Dark Pink Painted Wall": "void plastic dirty_dark_pink_painted_wall\n0\n0\n5 0.3533 0.1751 0.1690 0.0000 0.2000",
"Blue Green Painted Walls": "void plastic blue_green_painted_walls\n0\n0\n5 -0.0013 0.2092 0.2601 0.0140 0.2000",
"Red Painted Wall": "void plastic red_painted_wall\n0\n0\n5 0.4621 0.0156 0.0091 0.0396 0.2000"
}
}
]
@@ -24,6 +24,7 @@ except ImportError:
print("PyRadiance is not available. Rendering functionality will be disabled.")
pr = None
from datetime import datetime
import bpy
import blenderbim.tool as tool
from pathlib import Path
@@ -70,9 +71,6 @@ class ExportOBJ(bpy.types.Operator):
ifc_file_path = context.scene.radiance_exporter_properties.ifc_file
ifc_file = ifcopenshell.open(ifc_file_path)
# for material in ifc_file.by_type("IfcMaterial"):
# self.report({'INFO'}, f"Material: {material.Name}, ID: {material.id()}")
obj_file_path = os.path.join(output_dir, "model.obj")
mtl_file_path = os.path.join(output_dir, "model.mtl")
@@ -86,6 +84,7 @@ class ExportOBJ(bpy.types.Operator):
else:
elements = ifc_file.by_type("IfcElement")
elements += ifc_file.by_type("IfcSite")
elements = [e for e in elements if not e.is_a("IfcFeatureElement") or e.is_a("IfcSurfaceFeature")]
iterator = ifcopenshell.geom.iterator(settings, ifc_file, multiprocessing.cpu_count(), include=elements)
@@ -128,6 +127,7 @@ class RadianceRender(bpy.types.Operator):
return {"CANCELLED"}
# Get the resolution from the user input
props = context.scene.radiance_exporter_properties
resolution_x, resolution_y = props.radiance_resolution_x, props.radiance_resolution_y
quality = props.radiance_quality.upper()
@@ -136,9 +136,29 @@ class RadianceRender(bpy.types.Operator):
output_dir = props.output_dir
output_file_name = props.output_file_name
output_file_format = props.output_file_format
use_hdr = props.use_hdr
choose_hdr_image = props.choose_hdr_image
if use_hdr:
hdr_image = "noon_grass_2k.hdr"
hdr_mask = "noon_grass_2k_mask.hdr"
sky_map_cal = "skymap.cal"
hdr_image_path = os.path.join(os.path.dirname(__file__), "HDRs", hdr_image)
hdr_mask_path = os.path.join(os.path.dirname(__file__), "HDRs", hdr_mask)
sky_map_cal_path = os.path.join(os.path.dirname(__file__), "HDRs", sky_map_cal)
# os.chdir(output_dir)
obj_file_path = os.path.join(output_dir, "model.obj")
sun_props = context.scene.BIMSolarProperties
sky_file_path = os.path.join(output_dir, "sky.rad")
latitude = sun_props.latitude
longitude = sun_props.longitude
timezone = sun_props.timezone
month = sun_props.month
day = sun_props.day
hour = sun_props.hour
minute = sun_props.minute
camera = self.get_active_camera(context)
if camera is None:
self.report({"ERROR"}, "No active camera found in the scene. Please add a camera and set it as active.")
@@ -147,29 +167,124 @@ class RadianceRender(bpy.types.Operator):
# Get camera position and direction
camera_position, camera_direction = self.get_camera_data(camera)
# Material processing
# style = []
dt = datetime(2024, month, day, hour, minute)
# with open(obj_file_path, "r") as obj_file:
# for line in obj_file:
# if line.startswith("usemtl"):
# l = line.strip().split(" ")
# style.append(l[1])
sky_description = pr.gensky(
dt=dt,
latitude=latitude,
longitude=longitude,
timezone=timezone,
sunny_with_sun=True,
sunny_without_sun=False,
cloudy=True,
ground_reflectance=0.2,
turbidity=3.0,
)
sky_description_str = sky_description.decode("utf-8")
# Write all this to file
# skyfunc glow sky_glow
# 0
# 0
# 4 .9 .9 1.15 0
# sky_glow source sky
# 0
# 0
# 4 0 0 1 180
# skyfunc glow ground_glow
# 0
# 0
# 4 1.4 .9 .6 0
# ground_glow source ground
# 0
# 0
# 4 0 0 -1 180
if choose_hdr_image == "Noon":
with open(sky_file_path, "w") as f:
f.write(sky_description_str)
f.write("\n")
# f.write("skyfunc glow sky_glow\n0\n0\n4 .9 .9 1.15 0\n")
# f.write("sky_glow source sky\n0\n0\n4 0 0 1 180\n")
# f.write("skyfunc glow ground_glow\n0\n0\n4 1.4 .9 .6 0\n")
# f.write("ground_glow source ground\n0\n0\n4 0 0 -1 180\n")
f.write(
'''void colorpict env_map
7 red green blue "'''
+ hdr_image_path
+ '''" "'''
+ sky_map_cal_path
+ '''" map_u map_v
0
1 0.5
# This is a multiplier to colour balance the env map
# In this case, it provides a rough ground luminance from 3k-5k
env_map colorfunc env_colour
4 100 100 100 .
0
0
# .37 .57 1.5 is measured from a HDRI image
# It is multiplied by a factor such that grey(r,g,b) = 1
skyfunc colorfunc sky_colour
4 .64 .99 2.6 .
0
0
void mixpict composite
7 env_colour sky_colour grey "'''
+ hdr_mask_path
+ '''" "'''
+ sky_map_cal_path
+ """" map_u map_v
0
2 0.5 1
composite glow env_map_glow
0
0
4 1 1 1 0
env_map_glow source sky
0
0
4 0 0 1 180
env_colour glow ground_glow
0
0
4 1 1 1 0
ground_glow source ground
0
0
4 0 0 -1 180"""
)
else:
with open(sky_file_path, "w") as f:
f.write(sky_description_str)
f.write("\n")
f.write("skyfunc glow sky_glow\n0\n0\n4 .9 .9 1.15 0\n")
f.write("sky_glow source sky\n0\n0\n4 0 0 1 180\n")
f.write("skyfunc glow ground_glow\n0\n0\n4 1.4 .9 .6 0\n")
f.write("ground_glow source ground\n0\n0\n4 0 0 -1 180\n")
# Check if json file is empty or not
props = context.scene.radiance_exporter_properties
if props.use_json_file:
# Load data from JSON file
with open(props.json_file, "r") as file:
data = json.load(file)
else:
# Use in-UI material mappings
data = props.get_mappings_dict()
print(data)
# Create materials.rad file
materials_file = os.path.join(output_dir, "materials.rad")
with open(materials_file, "w") as file:
file.write("void plastic white\n0\n0\n5 0.8 0.8 0.8 0 0\n")
@@ -181,12 +296,8 @@ class RadianceRender(bpy.types.Operator):
file.write("void trans olive_trans\n0\n0\n7 0.6 0.7 0.4 0.05 0.05 0.7 0.2\n")
for style_id, radiance_material in data.items():
print(style_id, radiance_material)
# print(style_id, radiance_material)
file.write("inherit alias " + style_id + " " + data.get(style_id, "white") + "\n")
# file.write(f"inherit alias {style_id} {radiance_material}\n")
# for i in set(ifc_materials):
# print(i)
# file.write("inherit alias " + i + " " + data.get(i, "white") + "\n")
self.report({"INFO"}, "Exported Materials Rad file to: {}".format(materials_file))
@@ -197,7 +308,6 @@ class RadianceRender(bpy.types.Operator):
self.report({"INFO"}, "obj2mesh output: {}".format(mesh_file_path))
scene_file = os.path.join(output_dir, "scene.rad")
with open(scene_file, "w") as file:
# file.write("void mesh model\n1 " + rtm_file_path + "\n0\n0\n")
file.write('void mesh model\n1 "' + rtm_file_path + '"\n0\n0\n')
self.report({"INFO"}, "Exported Scene file to: {}".format(scene_file))
@@ -210,6 +320,7 @@ class RadianceRender(bpy.types.Operator):
scene.add_material(material_path)
scene.add_surface(scene_path)
scene.add_source(sky_file_path)
aview = pr.View(position=camera_position, direction=camera_direction)
scene.add_view(aview)
@@ -223,15 +334,21 @@ class RadianceRender(bpy.types.Operator):
variability=variability,
)
output_path = os.path.join(output_dir, f"{output_file_name}.{output_file_format.lower()}")
output_hdr_path = os.path.join(output_dir, f"render123.{output_file_format.lower()}")
if output_file_format == "HDR":
with open(output_path, "wb") as wtr:
with open(output_hdr_path, "wb") as wtr:
wtr.write(image)
else:
pass
self.report({"INFO"}, "Radiance rendering completed. Output: {}".format(output_path))
pcond_image = pr.pcond(hdr=output_hdr_path, human=True)
tiff_path = os.path.join(output_dir, f"{output_file_name}.tiff")
pr.ra_tiff(inp=pcond_image, out=tiff_path, lzw=True)
self.report({"INFO"}, "Radiance rendering completed. Output: {}".format(tiff_path))
return {"FINISHED"}
def get_active_camera(self, context):
@@ -23,13 +23,19 @@ import datetime
import blenderbim.tool as tool
from math import radians, pi
from mathutils import Euler, Vector, Matrix, Quaternion
from bpy.props import IntProperty, StringProperty, EnumProperty, FloatProperty, FloatVectorProperty, BoolProperty
from bpy.props import (
IntProperty,
StringProperty,
EnumProperty,
FloatProperty,
FloatVectorProperty,
BoolProperty,
CollectionProperty,
)
from bpy.types import PropertyGroup
from blenderbim.bim.module.light.data import SolarData
from blenderbim.bim.module.light.decorator import SolarDecorator
sun_position = tool.Blender.get_sun_position_addon()
def get_sites(self, context):
if not SolarData.is_loaded:
@@ -95,6 +101,12 @@ def update_display_sun_path(self, context):
def update_sun_path():
if not SolarData.is_loaded:
SolarData.load()
if (sun_position := SolarData.data["sun_position"]) is None:
return
props = bpy.context.scene.BIMSolarProperties
sun_props = bpy.context.scene.sun_pos_properties
props.timezone = tzfpy.get_tz(props.longitude, props.latitude)
@@ -182,6 +194,7 @@ class RadianceExporterProperties(PropertyGroup):
)
material_mappings: bpy.props.CollectionProperty(type=bpy.types.PropertyGroup, name="Material Mappings")
# material_mappings: CollectionProperty(type=MaterialMapping)
should_load_from_memory: BoolProperty(
name="Load from Memory",
@@ -246,11 +259,26 @@ class RadianceExporterProperties(PropertyGroup):
name="Output File Format",
description="Format of the output image file",
items=[
("HDR", "HDR", "High Dynamic Range"),
("HDR", "HDR + Tiff", "High Dynamic Range"),
],
default="HDR",
)
use_hdr: BoolProperty(
name="Use HDR",
description="Use HDR image format",
default=True,
)
choose_hdr_image: EnumProperty(
name="HDR Image",
description="Choose an HDR image to use",
items=[
("Noon", "Noon", "Noon"),
],
default="Noon",
)
class BIMSolarProperties(PropertyGroup):
sites: EnumProperty(items=get_sites, name="Sites")
@@ -21,9 +21,6 @@ import blenderbim.tool as tool
from blenderbim.bim.module.light.data import SolarData
sun_position = tool.Blender.get_sun_position_addon()
class BIM_PT_radiance_exporter(bpy.types.Panel):
"""Creates a Panel in the render properties window"""
@@ -48,7 +45,6 @@ class BIM_PT_radiance_exporter(bpy.types.Panel):
if not tool.Ifc.get() or not props.should_load_from_memory:
row = self.layout.row(align=True)
row.prop(props, "ifc_file")
# row.operator("bim.select_ifctester_ifc_file", icon="FILE_FOLDER", text="")
row = layout.row()
row.prop(props, "output_dir")
@@ -96,6 +92,12 @@ class BIM_PT_radiance_exporter(bpy.types.Panel):
row.prop(props, "output_file_format")
layout.separator()
row = layout.row()
row.prop(props, "use_hdr")
row = layout.row()
row.prop(props, "choose_hdr_image")
row = layout.row()
row.operator("export_scene.radiance", text="Export Geometry for Simulation")
@@ -116,13 +118,13 @@ class BIM_PT_solar(bpy.types.Panel):
bl_options = {"HIDE_HEADER"}
def draw(self, context):
if sun_position is None:
self.layout.label(text="Enable 'Sun Position' addon to continue.")
return
if not SolarData.is_loaded:
SolarData.load()
if (sun_position := SolarData.data["sun_position"]) is None:
self.layout.label(text="Enable 'Sun Position' Add-on To Continue", icon="ERROR")
return
props = context.scene.BIMSolarProperties
if SolarData.data["sites"]:
@@ -22,6 +22,7 @@ import ifcopenshell.api
import ifcopenshell.util.element
import ifcopenshell.util.attribute
import ifcopenshell.util.representation
import ifcopenshell.util.unit
import blenderbim.bim.helper
import blenderbim.tool as tool
import blenderbim.core.style
@@ -120,6 +120,11 @@ def get_contexts(self, context):
def update_material_name(self: "Material", context: bpy.types.Context) -> None:
# No need to update anything if it's either category
# or ifc_definition_id is not yet set
# (which occurs when Material still in the process of creation).
if self.is_category or not self.ifc_definition_id:
return
ifc_file = tool.Ifc.get()
name = self.name
material = ifc_file.by_id(self.ifc_definition_id)
@@ -143,7 +143,6 @@ classes = (
stair.EnableEditingStair,
stair.RemoveStair,
pie.OpenPieClass,
pie.PieUpdateContainer,
pie.PieAddOpening,
pie.VIEW3D_MT_PIE_bim,
pie.VIEW3D_MT_PIE_bim_class,
@@ -21,6 +21,7 @@ import json
import ifcopenshell
import ifcopenshell.api
import ifcopenshell.util.element
import ifcopenshell.util.unit
import blenderbim.tool as tool
from mathutils import Vector, Matrix
@@ -19,6 +19,7 @@
import bpy
import ifcopenshell
import ifcopenshell.util.element
import blenderbim.tool as tool
import blenderbim.core.covering as core
@@ -31,9 +32,7 @@ class AddInstanceFlooringCoveringFromCursor(bpy.types.Operator, tool.Ifc.Operato
@classmethod
def poll(cls, context):
relating_type_id = int(bpy.data.scenes["Scene"].BIMModelProperties.relating_type_id)
relating_type = ifcopenshell.util.element.get_predefined_type(tool.Ifc.get().by_id(relating_type_id))
return relating_type == "FLOORING"
return tool.Covering.covering_poll_relating_type_check(cls, context, "FLOORING")
def _execute(self, context):
try:
@@ -50,9 +49,7 @@ class AddInstanceCeilingCoveringFromCursor(bpy.types.Operator, tool.Ifc.Operator
@classmethod
def poll(cls, context):
relating_type_id = int(bpy.data.scenes["Scene"].BIMModelProperties.relating_type_id)
relating_type = ifcopenshell.util.element.get_predefined_type(tool.Ifc.get().by_id(relating_type_id))
return relating_type == "CEILING"
return tool.Covering.covering_poll_relating_type_check(cls, context, "CEILING")
def _execute(self, context):
try:
@@ -69,8 +66,10 @@ class RegenSelectedCoveringObject(bpy.types.Operator, tool.Ifc.Operator):
@classmethod
def poll(cls, context):
element = tool.Ifc.get_entity(bpy.context.active_object)
return element and element.is_a("IfcCovering")
if (obj := context.active_object) and (element := tool.Ifc.get_entity(obj)) and element.is_a("IfcCovering"):
return True
cls.poll_message_set("IfcCovering must be selected.")
return False
def _execute(self, context):
try:
@@ -89,11 +88,7 @@ class AddInstanceFlooringCoveringsFromWalls(bpy.types.Operator, tool.Ifc.Operato
@classmethod
def poll(cls, context):
element = tool.Ifc.get_entity(bpy.context.active_object)
relating_type_id = int(bpy.data.scenes["Scene"].BIMModelProperties.relating_type_id)
relating_type = ifcopenshell.util.element.get_predefined_type(tool.Ifc.get().by_id(relating_type_id))
if element and element.is_a("IfcWall") and tool.Model.get_usage_type(element) == "LAYER2":
return context.selected_objects and relating_type == "FLOORING"
return tool.Covering.covering_poll_wall_selected(cls, context, "FLOORING")
def _execute(self, context):
# This only works based on a 2D plan only considering the standard
@@ -117,11 +112,7 @@ class AddInstanceCeilingCoveringsFromWalls(bpy.types.Operator, tool.Ifc.Operator
@classmethod
def poll(cls, context):
element = tool.Ifc.get_entity(bpy.context.active_object)
relating_type_id = int(bpy.data.scenes["Scene"].BIMModelProperties.relating_type_id)
relating_type = ifcopenshell.util.element.get_predefined_type(tool.Ifc.get().by_id(relating_type_id))
if element and element.is_a("IfcWall") and tool.Model.get_usage_type(element) == "LAYER2":
return context.selected_objects and relating_type == "CEILING"
return tool.Covering.covering_poll_wall_selected(cls, context, "CEILING")
def _execute(self, context):
# This only works based on a 2D plan only considering the standard
@@ -21,6 +21,7 @@ import math
import json
import ifcopenshell
import ifcopenshell.util.element
import ifcopenshell.util.schema
from ifcopenshell.util.doc import get_entity_doc, get_predefined_type_doc
import blenderbim.tool as tool
from math import degrees
@@ -50,6 +51,7 @@ class AuthoringData:
cls.props = bpy.context.scene.BIMModelProperties
if ifc_element_type:
cls.ifc_element_type = None if ifc_element_type == "all" else ifc_element_type
cls.data["default_container"] = cls.default_container()
cls.data["ifc_element_type"] = cls.ifc_element_type
cls.data["ifc_classes"] = cls.ifc_classes()
cls.data["relating_type_id"] = cls.relating_type_id() # only after .ifc_classes()
@@ -74,6 +76,15 @@ class AuthoringData:
cls.data["boundary_class"] = cls.boundary_class()
cls.data["selected_material_usages"] = cls.selected_material_usages()
@classmethod
def default_container(cls) -> str | None:
props = bpy.context.scene.BIMSpatialDecompositionProperties
if props.default_container:
try:
return tool.Ifc.get().by_id(props.default_container).Name
except:
pass
@classmethod
def boundary_class(cls):
declaration = tool.Ifc.schema().declaration_by_name("IfcRelSpaceBoundary")
@@ -25,6 +25,8 @@ import ifcopenshell
import ifcopenshell.api
import ifcopenshell.util.element
import ifcopenshell.util.representation
import ifcopenshell.util.schema
import ifcopenshell.util.unit
from ifcopenshell.util.shape_builder import V
import blenderbim.tool as tool
import blenderbim.core.geometry
@@ -63,28 +63,6 @@ class PieAddOpening(bpy.types.Operator):
return {"FINISHED"}
class PieUpdateContainer(bpy.types.Operator):
bl_idname = "bim.pie_update_container"
bl_label = "Update Spatial Container"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
for obj in context.selected_objects:
if not obj.BIMObjectProperties.ifc_definition_id:
continue
for collection in obj.users_collection:
spatial_obj = collection.BIMCollectionProperties.obj
if spatial_obj and spatial_obj.BIMObjectProperties.ifc_definition_id:
blenderbim.core.spatial.assign_container(
tool.Ifc, tool.Collector, tool.Spatial, structure_obj=spatial_obj, element_obj=obj
)
break
return {"FINISHED"}
class VIEW3D_MT_PIE_bim(bpy.types.Menu):
bl_label = "Geometry"
@@ -93,7 +71,6 @@ class VIEW3D_MT_PIE_bim(bpy.types.Menu):
pie.operator("bim.edit_object_placement")
pie.operator("bim.update_representation").ifc_representation_class = ""
pie.operator("bim.pie_add_opening")
pie.operator("bim.pie_update_container")
pie.operator("bim.open_pie_class", text="Assign IFC Class")
pie.operator("bim.aggregate_assign_object", text="Assign Aggregation")
pie.operator("bim.aggregate_unassign_object", text="Unassign Aggregation")
@@ -225,9 +225,8 @@ class AddConstrTypeInstance(bpy.types.Operator):
else:
parent = ifcopenshell.util.element.get_container(building_element)
if parent:
parent_obj = tool.Ifc.get_object(parent)
blenderbim.core.spatial.assign_container(
tool.Ifc, tool.Collector, tool.Spatial, structure_obj=parent_obj, element_obj=obj
tool.Ifc, tool.Collector, tool.Spatial, container=parent, element_obj=obj
)
# set occurrences properties for the types defined with modifiers
@@ -21,6 +21,7 @@ import json
import bmesh
import ifcopenshell
import ifcopenshell.api
import ifcopenshell.util.element
import ifcopenshell.util.representation
import ifcopenshell.util.unit
import blenderbim.core.root
@@ -503,6 +503,7 @@ class BimToolUI:
def draw_type_selection_interface(cls):
# shared by both sidebar and header
row = cls.layout.row(align=True)
row.label(text=f"Container: {AuthoringData.data['default_container']}", icon="OUTLINER_COLLECTION")
if AuthoringData.data["ifc_classes"]:
if not AuthoringData.data["ifc_element_type"]:
row.label(text="", icon="FILE_VOLUME")
@@ -67,7 +67,6 @@ class ExecuteIfcPatch(bpy.types.Operator):
bl_idname = "bim.execute_ifc_patch"
bl_label = "Execute IFCPatch"
file_format: bpy.props.StringProperty()
use_json_for_args: bpy.props.BoolProperty()
@classmethod
def poll(cls, context):
@@ -76,9 +75,8 @@ class ExecuteIfcPatch(bpy.types.Operator):
def execute(self, context):
props = context.scene.BIMPatchProperties
if self.use_json_for_args or not props.ifc_patch_args_attr:
arguments = json.loads(props.ifc_patch_args or "[]")
else:
arguments = []
if props.ifc_patch_args_attr:
arguments = [arg.get_value() for arg in props.ifc_patch_args_attr]
if props.should_load_from_memory and tool.Ifc.get():
@@ -124,23 +122,48 @@ class UpdateIfcPatchArguments(bpy.types.Operator):
new_attr = patch_args.add()
data_type = arg_info.get("type", "str")
if isinstance(data_type, list):
if "file" in data_type:
data_type = ["file"]
data_type = [dt for dt in data_type if dt != "NoneType"][0]
new_attr.data_type = {
"Literal": "enum",
"file": "file",
"str": "string",
"float": "float",
"int": "integer",
"bool": "boolean",
}[data_type]
new_attr.name = arg_name
new_attr.name = self.pretty_arg_name(arg_name)
if new_attr.data_type == "enum":
new_attr.enum_items = json.dumps(arg_info.get("enum_items", []))
new_attr.enum_value = arg_info.get("default", new_attr.get_value_default())
continue
if new_attr.data_type == "file":
new_attr.filepath_value.single_file = arg_info.get("default", new_attr.get_value_default())
new_attr.filter_glob = arg_info.get("filter_glob", "*.ifc;*.ifczip;*.ifcxml")
continue
new_attr.set_value(arg_info.get("default", new_attr.get_value_default()))
return {"FINISHED"}
def pretty_arg_name(self, arg_name: str):
words = []
for word in arg_name.split("_"):
word = word.strip().capitalize()
replacements = [("Dir", "Directory"), ("Sql", "SQL")]
for prev, new in replacements:
word = word.replace(prev, new)
words.append(word)
return " ".join(words)
class RunMigratePatch(bpy.types.Operator):
bl_idname = "bim.run_migrate_patch"
@@ -65,6 +65,5 @@ class BIMPatchProperties(PropertyGroup):
ifc_patch_recipes: EnumProperty(items=get_ifcpatch_recipes, name="Recipes", update=update_ifc_patch_recipe)
ifc_patch_input: StringProperty(default="", name="IFC Patch Input IFC")
ifc_patch_output: StringProperty(default="", name="IFC Patch Output IFC")
ifc_patch_args: StringProperty(default="", name="Arguments")
ifc_patch_args_attr: CollectionProperty(type=Attribute, name="Arguments")
should_load_from_memory: BoolProperty(default=False, name="Load from Memory")
@@ -56,7 +56,4 @@ class BIM_PT_patch(bpy.types.Panel):
if props.ifc_patch_args_attr:
draw_attributes(props.ifc_patch_args_attr, layout)
else:
layout.row().prop(props, "ifc_patch_args")
op = layout.operator("bim.execute_ifc_patch")
op.use_json_for_args = len(props.ifc_patch_args_attr) == 0
@@ -28,16 +28,15 @@ classes = (
operator.BIM_OT_load_clipping_planes,
operator.BIM_OT_save_clipping_planes,
operator.ChangeLibraryElement,
operator.ClearRecentIFCProjects,
operator.CreateClippingPlane,
operator.CreateProject,
operator.ClearRecentIFCProjects,
operator.DisableCulling,
operator.DisableEditingHeader,
operator.EditHeader,
operator.EnableCulling,
operator.EnableEditingHeader,
operator.ExportIFC,
operator.ExportIFCDeprecated,
operator.FlipClippingPlane,
operator.LinkIfc,
operator.LoadLink,
@@ -92,6 +91,7 @@ def register():
bpy.utils.register_tool(workspace.ExploreTool, after={"builtin.transform"}, separator=True, group=False)
bpy.types.Scene.BIMProjectProperties = bpy.props.PointerProperty(type=prop.BIMProjectProperties)
bpy.app.handlers.load_post.append(decorator.toggle_decorations_on_load)
bpy.types.TOPBAR_MT_file_import.append(ui.file_import_menu)
bpy.types.TOPBAR_MT_file.prepend(ui.file_menu)
bpy.types.TOPBAR_MT_file_context_menu.prepend(ui.file_menu)
wm = bpy.context.window_manager
@@ -730,12 +730,12 @@ class LoadProject(bpy.types.Operator, IFCFileSelector):
if not self.is_existing_ifc_file():
return {"FINISHED"}
if tool.Blender.is_default_scene():
if self.should_start_fresh_session and tool.Blender.is_default_scene():
for obj in bpy.data.objects:
bpy.data.objects.remove(obj)
filepath = Path(self.get_filepath())
context.scene.BIMProperties.ifc_file = str(filepath)
context.scene.BIMProperties.ifc_file = filepath.as_posix()
context.scene.BIMProjectProperties.is_loading = True
context.scene.BIMProjectProperties.total_elements = len(tool.Ifc.get().by_type("IfcElement"))
tool.Blender.register_toolbar()
@@ -743,6 +743,8 @@ class LoadProject(bpy.types.Operator, IFCFileSelector):
if not self.is_advanced:
bpy.ops.bim.load_project_elements()
if not self.should_start_fresh_session:
bpy.ops.bim.convert_to_blender()
except:
blenderbim.last_error = traceback.format_exc()
raise
@@ -1235,6 +1237,10 @@ class ExportIFCBase:
if bpy.data.is_saved:
layout.prop(self, "use_relative_path")
layout.separator()
layout.label(text="Supported formats for export:")
layout.label(text=".ifc, .ifczip, .ifcjson")
def invoke(self, context, event):
if not tool.Ifc.get():
bpy.ops.wm.save_mainfile("INVOKE_DEFAULT")
@@ -1333,18 +1339,6 @@ class ExportIFC(ExportIFCBase, bpy.types.Operator):
pass
# TODO: remove as deprecated, better wait couple releases since
# this operator is used for saving IFC files in user scripts.
class ExportIFCDeprecated(ExportIFCBase, bpy.types.Operator):
bl_idname = "export_ifc.bim"
def execute(self, context):
msg = f"'{ExportIFCDeprecated.bl_idname}' operator name is deprecated, use '{ExportIFC.bl_idname}'."
self.report({"WARNING"}, msg)
print(msg)
return super().execute(context)
class LoadLinkedProject(bpy.types.Operator):
bl_idname = "bim.load_linked_project"
bl_label = "Load a project for viewing only."
@@ -1880,8 +1874,7 @@ class AppendInspectedLinkedElement(AppendLibraryElement):
)
self.import_product_from_ifc(element, context)
element_type = ifcopenshell.util.element.get_type(element)
obj = tool.Ifc.get_object(element_type)
if obj is None:
if element_type and tool.Ifc.get_object(element_type) is None:
self.import_type_from_ifc(element_type, context)
return {"FINISHED"}
@@ -18,6 +18,7 @@
import os
import bpy
import ifcopenshell.util.element
import ifcopenshell.util.placement
from blenderbim.bim.module.project.data import ProjectData
from blenderbim.bim.ifc import IfcStore
@@ -25,6 +25,11 @@ from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.module.project.data import ProjectData, LinksData
def file_import_menu(self, context):
op = self.layout.operator("bim.load_project", text="IFC (Geometry Only) (.ifc/.ifczip/.ifcxml)")
op.should_start_fresh_session = False
class BIM_MT_project(Menu):
bl_idname = "BIM_MT_project"
bl_label = "New IFC Project"
@@ -123,6 +128,15 @@ class BIM_PT_project(Panel):
if pprops.is_loading:
self.draw_advanced_loading_ui(context)
elif self.file or props.ifc_file:
if props.has_blend_warning:
box = self.layout.box()
box.alert = True
row = box.row(align=True)
row.label(text="Your Model May Be Outdated", icon="ERROR")
op = row.operator("bim.open_uri", text="", icon="QUESTION")
op.uri = "https://docs.blenderbim.org/users/troubleshooting.html#saving-and-loading-blend-files"
row.operator("bim.close_blend_warning", text="", icon="CANCEL")
if props.ifc_file:
self.draw_loaded_project_ui(context)
else:
@@ -294,9 +308,8 @@ class BIM_PT_new_project_wizard(Panel):
row.prop(props, "volume_unit", text="Volume Unit")
prop_with_search(self.layout, pprops, "template_file", text="Template")
row = self.layout.row(align=True)
row = self.layout.row()
row.operator("bim.create_project")
row.operator("bim.load_project").should_start_fresh_session = False
class BIM_PT_project_library(Panel):
@@ -51,13 +51,13 @@ class Data:
psetqtos = ifcopenshell.util.element.get_psets(
element, psets_only=psets_only, qtos_only=qtos_only, should_inherit=False
)
for name, data in psetqtos.items():
for name, data in sorted(psetqtos.items()):
results.append(
{
"id": data["id"],
"Name": name,
"is_expanded": is_expanded.get(data["id"], True),
"Properties": [{"Name": k, "NominalValue": v} for k, v in data.items() if k != "id"],
"Properties": [{"Name": k, "NominalValue": v} for k, v in sorted(data.items()) if k != "id"],
}
)
return sorted(results, key=lambda v: v["Name"])
@@ -19,9 +19,10 @@
import bpy
import json
import ifcopenshell.api
import ifcopenshell.util.unit
import ifcopenshell.util.pset
import ifcopenshell.util.attribute
import ifcopenshell.util.element
import ifcopenshell.util.pset
import ifcopenshell.util.unit
import blenderbim.bim.schema
import blenderbim.bim.helper
import blenderbim.bim.handler
@@ -16,6 +16,7 @@
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
import blenderbim.tool as tool
from bpy.types import Panel
from blenderbim.bim.ifc import IfcStore
@@ -34,16 +35,21 @@ from blenderbim.bim.module.pset.data import (
ProfilePsetsData,
WorkSchedulePsetsData,
)
from typing import Any, Optional
def draw_property(prop, layout, copy_operator=None):
def draw_property(
prop: bpy.types.PropertyGroup, layout: bpy.types.UILayout, copy_operator: Optional[str] = None
) -> None:
if prop.value_type == "IfcPropertySingleValue":
draw_single_property(prop, layout, copy_operator)
elif prop.value_type == "IfcPropertyEnumeratedValue":
draw_enumerated_property(prop, layout, copy_operator)
def draw_single_property(prop, layout, copy_operator=None):
def draw_single_property(
prop: bpy.types.PropertyGroup, layout: bpy.types.UILayout, copy_operator: Optional[str] = None
) -> None:
value_name = prop.metadata.get_value_name(display_only=True)
if not value_name:
layout.label(text=prop["Name"])
@@ -63,7 +69,9 @@ def draw_single_property(prop, layout, copy_operator=None):
op.name = prop.metadata.name
def draw_enumerated_property(prop, layout, copy_operator=None):
def draw_enumerated_property(
prop: bpy.types.PropertyGroup, layout: bpy.types.UILayout, copy_operator: Optional[str] = None
) -> None:
value_name = prop.metadata.get_value_name()
if not value_name:
layout.label(text=prop.metadata.name)
@@ -78,13 +86,21 @@ def draw_enumerated_property(prop, layout, copy_operator=None):
op.name = prop.metadata.name
def get_active_pset_obj_name(context, obj_type):
def get_active_pset_obj_name(context: bpy.types.Context, obj_type: tool.Ifc.OBJECT_TYPE) -> str:
if obj_type in ("Object", "MaterialSet", "MaterialSetItem"):
return context.active_object.name
return ""
def draw_psetqto_ui(context, pset_id, pset, props, layout, obj_type, allow_removing=True):
def draw_psetqto_ui(
context: bpy.types.Context,
pset_id: int,
pset: dict[str, Any],
props: bpy.types.PropertyGroup,
layout: bpy.types.UILayout,
obj_type: tool.Ifc.OBJECT_TYPE,
allow_removing: bool = True,
) -> None:
box = layout.box()
row = box.row(align=True)
if "is_expanded" not in pset:
@@ -155,7 +171,9 @@ def draw_psetqto_ui(context, pset_id, pset, props, layout, obj_type, allow_remov
row.label(text="No Properties Set")
def draw_psetqto_editable_ui(box, props, prop):
def draw_psetqto_editable_ui(
box: bpy.types.UILayout, props: bpy.types.PropertyGroup, prop: bpy.types.PropertyGroup
) -> None:
row = box.row(align=True)
draw_property(prop, row, copy_operator="bim.copy_property_to_selection")
@@ -25,6 +25,7 @@ classes = (
operator.CalculateFaceAreas,
operator.CalculateFormworkArea,
operator.CalculateObjectVolumes,
operator.CalculateSideFormworkArea,
operator.CalculateSingleQuantity,
operator.PerformQuantityTakeOff,
prop.BIMQtoProperties,
@@ -18,25 +18,28 @@
import bpy
import bmesh
from typing import Callable
def calculate_height(obj):
def calculate_height(obj: bpy.types.Object) -> float:
return obj.dimensions[2]
def calculate_edges_lengths(objs, context):
def calculate_edges_lengths(objs: list[bpy.types.Object], context: bpy.types.Context):
return calculate_mesh_quantity(objs, context, lambda bm: sum((e.calc_length() for e in bm.edges if e.select)))
def calculate_faces_areas(objs, context):
def calculate_faces_areas(objs: list[bpy.types.Object], context: bpy.types.Context) -> float:
return calculate_mesh_quantity(objs, context, lambda bm: sum((f.calc_area() for f in bm.faces if f.select)))
def calculate_volumes(objs, context):
def calculate_volumes(objs: list[bpy.types.Object], context: bpy.types.Context) -> float:
return calculate_mesh_quantity(objs, context, lambda bm: bm.calc_volume())
def calculate_mesh_quantity(objs: bpy.types.Object, context, operation):
def calculate_mesh_quantity(
objs: list[bpy.types.Object], context: bpy.types.Context, operation: Callable[[bmesh.types.BMesh], float]
) -> float:
"""Get the sum of the target quantity on all passed mesh objects
:param objs: iterable of mesh object
@@ -58,7 +61,7 @@ def calculate_mesh_quantity(objs: bpy.types.Object, context, operation):
return result
def calculate_formwork_area(objs, context):
def calculate_formwork_area(objs: list[bpy.types.Object], context: bpy.types.Context) -> float:
"""
Formwork is defined as the surface area required to cover all exposed
surfaces of one or more objects, excluding top surfaces (i.e. that have a
@@ -85,6 +88,7 @@ def calculate_formwork_area(objs, context):
copied_obj.name = "Formwork"
copied_obj.BIMObjectProperties.ifc_definition_id = 0
modifier = copied_obj.modifiers.new("Formwork", "REMESH")
assert isinstance(modifier, bpy.types.RemeshModifier)
modifier.mode = "SHARP"
# This hardcoded value may be optimised through a better understanding of the octree division.
# These values are based off some trial and error heuristics I've learned through experience.
@@ -108,7 +112,7 @@ def calculate_formwork_area(objs, context):
return result
def calculate_side_formwork_area(objs, context):
def calculate_side_formwork_area(objs: list[bpy.types.Object], context: bpy.types.Context) -> float:
"""
Side formwork is defined as the surface area required to cover all exposed
surfaces of one or more objects, excluding top and bottom surfaces (i.e.
@@ -99,6 +99,21 @@ class CalculateFormworkArea(bpy.types.Operator):
return {"FINISHED"}
class CalculateSideFormworkArea(bpy.types.Operator):
bl_idname = "bim.calculate_side_formwork_area"
bl_label = "Calculate Side Formwork Area"
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
return context.selected_objects and context.active_object
def execute(self, context):
result = helper.calculate_side_formwork_area([o for o in context.selected_objects if o.type == "MESH"], context)
context.scene.BIMQtoProperties.qto_result = str(round(result, 3))
return {"FINISHED"}
class CalculateSingleQuantity(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.calculate_single_quantity"
bl_label = "Calculate Single Quantity"
@@ -137,7 +152,10 @@ class PerformQuantityTakeOff(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.perform_quantity_take_off"
bl_label = "Perform Quantity Take-off"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Perform a quantity take off based of a QTO rule configuration"
bl_description = (
"Perform a quantity take off on selected objects based of a QTO rule configuration."
"If no objects are selected, quantities calculated for all available IfcElements."
)
@classmethod
def poll(cls, context):
@@ -162,4 +180,6 @@ class PerformQuantityTakeOff(bpy.types.Operator, tool.Ifc.Operator):
ifc_file = tool.Ifc.get()
results = ifc5d.qto.quantify(ifc_file, elements, rules)
ifc5d.qto.edit_qtos(ifc_file, results)
self.report({"INFO"}, f"Quantities are calculated for {len(elements)} elements.")
return {"FINISHED"}
@@ -19,6 +19,9 @@
import bpy
import blenderbim.tool as tool
import ifcopenshell
import ifcopenshell.util.constraint
import ifcopenshell.util.cost
import ifcopenshell.util.date
import ifcopenshell.util.resource
@@ -18,6 +18,7 @@
import bpy
import ifcopenshell
import ifcopenshell.util.element
import ifcopenshell.util.schema
import blenderbim.tool as tool
from blenderbim.bim.module.root.data import IfcClassData
@@ -175,6 +175,9 @@ class Search(Operator):
props = context.scene.CsvProperties
elif self.property_group == "BIMSearchProperties":
props = context.scene.BIMSearchProperties
else:
raise Exception(f"bim.search - unexpected property group name '{self.property_group}'.")
results = ifcopenshell.util.selector.filter_elements(
tool.Ifc.get(), tool.Search.export_filter_query(props.filter_groups)
)
@@ -18,7 +18,7 @@
import bpy
import blenderbim.tool as tool
from ifcopenshell import util
import ifcopenshell.util.schema
from blenderbim.bim.prop import ObjProperty, BIMFilterGroup
from blenderbim.bim.module.search.data import SearchData, ColourByPropertyData, SelectSimilarData
from bpy.types import PropertyGroup
@@ -134,6 +134,6 @@ class BIMSearchProperties(PropertyGroup):
def get_classes(self, ifc_product):
declaration = tool.Ifc.schema().declaration_by_name(ifc_product)
declarations = util.schema.get_subtypes(declaration)
declarations = ifcopenshell.util.schema.get_subtypes(declaration)
names = [d.name() for d in declarations]
return [(c, c, "") for c in sorted(names)]
@@ -20,6 +20,7 @@ import bpy
import isodate
import ifcopenshell.api
import ifcopenshell.util.attribute
import ifcopenshell.util.date
from ifcopenshell.util.doc import get_predefined_type_doc
import blenderbim.tool as tool
import blenderbim.core.sequence as core
@@ -21,7 +21,6 @@ from . import ui, prop, operator, workspace
classes = (
operator.AssignContainer,
operator.ChangeSpatialLevel,
operator.ContractContainer,
operator.CopyToContainer,
operator.DeleteContainer,
@@ -39,14 +38,11 @@ classes = (
operator.SelectSimilarContainer,
operator.SetContainerVisibility,
operator.SetDefaultContainer,
prop.SpatialElement,
prop.Element,
prop.BIMSpatialProperties,
prop.BIMObjectSpatialProperties,
prop.BIMContainer,
prop.BIMSpatialDecompositionProperties,
ui.BIM_PT_spatial,
ui.BIM_UL_containers,
ui.BIM_UL_containers_manager,
ui.BIM_UL_elements,
ui.BIM_PT_spatial_decomposition,
@@ -57,7 +53,6 @@ classes = (
def register():
if not bpy.app.background:
bpy.utils.register_tool(workspace.SpatialTool, after={"bim.annotation_tool"}, separator=False, group=False)
bpy.types.Scene.BIMSpatialProperties = bpy.props.PointerProperty(type=prop.BIMSpatialProperties)
bpy.types.Object.BIMObjectSpatialProperties = bpy.props.PointerProperty(type=prop.BIMObjectSpatialProperties)
bpy.types.Scene.BIMSpatialDecompositionProperties = bpy.props.PointerProperty(
type=prop.BIMSpatialDecompositionProperties
@@ -67,6 +62,5 @@ def register():
def unregister():
if not bpy.app.background:
bpy.utils.unregister_tool(workspace.SpatialTool)
del bpy.types.Scene.BIMSpatialProperties
del bpy.types.Object.BIMObjectSpatialProperties
del bpy.types.Scene.BIMSpatialDecompositionProperties
@@ -37,7 +37,8 @@ class SpatialData:
if cls.data["poll"]:
cls.data.update(
{
"parent_container_id": cls.parent_container_id(),
"default_container": cls.default_container(),
"selected_containers": cls.selected_containers(),
"is_directly_contained": cls.is_directly_contained(),
"label": cls.label(),
"references": cls.references(),
@@ -56,14 +57,17 @@ class SpatialData:
return False
@classmethod
def parent_container_id(cls):
container_id = bpy.context.scene.BIMSpatialProperties.active_container_id
if not container_id:
return
container = tool.Ifc.get_entity_by_id(container_id)
if not container or container.is_a("IfcProject"):
return
return container.Decomposes[0].RelatingObject.id()
def default_container(cls) -> str | None:
props = bpy.context.scene.BIMSpatialDecompositionProperties
if props.default_container:
try:
return tool.Ifc.get().by_id(props.default_container).Name
except:
pass
@classmethod
def selected_containers(cls):
return sorted([e.Name or "Unnamed" for e in tool.Spatial.get_selected_containers()])
@classmethod
def label(cls):
@@ -97,7 +101,7 @@ class SpatialDecompositionData:
}
@classmethod
def default_container(cls) -> str:
def default_container(cls) -> str | None:
props = bpy.context.scene.BIMSpatialDecompositionProperties
if props.default_container:
try:
@@ -29,13 +29,21 @@ import blenderbim.bim.handler
class ReferenceStructure(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.reference_structure"
bl_label = "Reference Structure"
bl_description = (
"Reference selected objects from all selected structures.\n\n"
"Currently we do not support referencing structures in other structures "
"though it is allowed in IFC4X3"
)
bl_options = {"REGISTER", "UNDO"}
structure: bpy.props.IntProperty()
def _execute(self, context):
sprops = context.scene.BIMSpatialProperties
containers = [tool.Ifc.get().by_id(c.ifc_definition_id) for c in sprops.containers if c.is_selected]
for obj in context.selected_objects:
objs = tool.Spatial.get_selected_objects_without_containers()
if not objs:
self.report({"INFO"}, "No non-spatial objects are selected.")
return
containers = tool.Spatial.get_selected_containers()
for obj in objs:
element = tool.Ifc.get_entity(obj)
if not element:
continue
@@ -46,13 +54,21 @@ class ReferenceStructure(bpy.types.Operator, tool.Ifc.Operator):
class DereferenceStructure(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.dereference_structure"
bl_label = "Dereference Structure"
bl_description = (
"Dereference selected objects from all selected structures.\n\n"
"Currently we do not support referencing structures in other structures "
"though it is allowed in IFC4X3"
)
bl_options = {"REGISTER", "UNDO"}
structure: bpy.props.IntProperty()
def _execute(self, context):
sprops = context.scene.BIMSpatialProperties
containers = [tool.Ifc.get().by_id(c.ifc_definition_id) for c in sprops.containers if c.is_selected]
for obj in context.selected_objects:
objs = tool.Spatial.get_selected_objects_without_containers()
if not objs:
self.report({"INFO"}, "No non-spatial objects are selected.")
return
containers = tool.Spatial.get_selected_containers()
for obj in objs:
element = tool.Ifc.get_entity(obj)
if not element:
continue
@@ -63,15 +79,16 @@ class DereferenceStructure(bpy.types.Operator, tool.Ifc.Operator):
class AssignContainer(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.assign_container"
bl_label = "Assign Container"
bl_description = "Assign current default container to the selected objects"
bl_options = {"REGISTER", "UNDO"}
structure: bpy.props.IntProperty()
def _execute(self, context):
structure_obj = tool.Ifc.get_object(tool.Ifc.get().by_id(self.structure))
for element_obj in context.selected_objects:
core.assign_container(
tool.Ifc, tool.Collector, tool.Spatial, structure_obj=structure_obj, element_obj=element_obj
)
props = context.active_object.BIMObjectSpatialProperties
if (container_obj := props.container_obj) and (container := tool.Ifc.get_entity(container_obj)):
for element_obj in context.selected_objects:
core.assign_container(
tool.Ifc, tool.Collector, tool.Spatial, container=container, element_obj=element_obj
)
class EnableEditingContainer(bpy.types.Operator, tool.Ifc.Operator):
@@ -83,16 +100,6 @@ class EnableEditingContainer(bpy.types.Operator, tool.Ifc.Operator):
core.enable_editing_container(tool.Spatial, obj=context.active_object)
class ChangeSpatialLevel(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.change_spatial_level"
bl_label = "Change Spatial Level"
bl_options = {"REGISTER", "UNDO"}
parent: bpy.props.IntProperty()
def _execute(self, context):
core.change_spatial_level(tool.Spatial, parent=tool.Ifc.get().by_id(self.parent))
class DisableEditingContainer(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.disable_editing_container"
bl_label = "Disable Editing Container"
@@ -114,30 +121,36 @@ class RemoveContainer(bpy.types.Operator, tool.Ifc.Operator):
class CopyToContainer(bpy.types.Operator, tool.Ifc.Operator):
"""
Copies selected 3D elements in the viewport to checkmarked spatial containers
Copies selected 3D elements in the viewport to the selected spatial containers
Example: bulk copy a wall to multiple storeys
1. Select one or more 3D elements in the viewport
2. Enable the checkmark next to one or more containers in the container list below to select it
2. Select one or more spatial containers in the viewport
3. Press this button
4. The copied elements will have a new position relative to the destination containers
"""
Copying containers to other containers currently is not supported."""
bl_idname = "bim.copy_to_container"
bl_label = "Copy To Container"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
sprops = context.scene.BIMSpatialProperties
objs = tool.Spatial.get_selected_objects_without_containers()
if not objs:
self.report({"INFO"}, "No non-spatial objects are selected.")
return
containers = tool.Spatial.get_selected_containers()
# Track decompositions so they can be recreated after the operation
relationships = tool.Root.get_decomposition_relationships(context.selected_objects)
relationships = tool.Root.get_decomposition_relationships(objs)
old_to_new = {}
containers = [tool.Ifc.get().by_id(c.ifc_definition_id) for c in sprops.containers if c.is_selected]
for obj in context.selected_objects:
for obj in objs:
result_objs = core.copy_to_container(tool.Ifc, tool.Collector, tool.Spatial, obj=obj, containers=containers)
if result_objs:
old_to_new[tool.Ifc.get_entity(obj)] = result_objs
# Recreate decompositions
tool.Root.recreate_decompositions(relationships, old_to_new)
blenderbim.bim.handler.refresh_ui_data()
@@ -237,15 +250,36 @@ class SelectDecomposedElements(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.select_decomposed_elements"
bl_label = "Select Children"
bl_options = {"REGISTER", "UNDO"}
should_filter: bpy.props.BoolProperty(name="Should Filter", default=True, options={"SKIP_SAVE"})
container: bpy.props.IntProperty()
ifc_class: bpy.props.StringProperty()
relating_type: bpy.props.IntProperty()
@classmethod
def description(cls, context, operator):
return f"Select all elements contained in this {operator.ifc_class}"
return "Select all contained elements filtered by this type" + "\nALT+CLICK to select all contained elements"
def invoke(self, context, event):
if event.type == "LEFTMOUSE" and event.alt:
self.should_filter = False
return self.execute(context)
def _execute(self, context):
core.select_decomposed_elements(tool.Spatial, container=tool.Ifc.get().by_id(self.container))
if self.should_filter:
ifc_class = self.ifc_class
relating_type = tool.Ifc.get().by_id(self.relating_type) if self.relating_type else None
is_untyped = self.relating_type == 0
else:
ifc_class = ""
relating_type = None
is_untyped = False
core.select_decomposed_elements(
tool.Spatial,
container=tool.Ifc.get().by_id(self.container),
ifc_class=ifc_class,
relating_type=relating_type,
is_untyped=is_untyped,
)
class SetDefaultContainer(bpy.types.Operator, tool.Ifc.Operator):
@@ -32,6 +32,7 @@ from bpy.props import (
)
import blenderbim.tool as tool
import ifcopenshell
import ifcopenshell.util.element
def get_subelement_class(self, context):
@@ -63,47 +64,32 @@ def update_should_include_children(self, context):
tool.Spatial.load_contained_elements()
def update_relating_container_from_object(self, context):
if self.relating_container_object is None or context.active_object is None:
def update_container_obj(self, context):
if self.container_obj is None or not (obj := context.active_object):
return
element = tool.Ifc.get_entity(self.relating_container_object)
if not element:
if not (element := tool.Ifc.get_entity(self.container_obj)):
self.container_obj = None
return
container = ifcopenshell.util.element.get_container(element)
if container:
bpy.ops.bim.assign_container(structure=container.id())
else:
bpy.ops.bim.disable_editing_container()
bpy.ops.bim.remove_container()
if tool.Spatial.can_contain(element, obj):
return
if (
(container := ifcopenshell.util.element.get_container(element))
and (container_obj := tool.Ifc.get_object(container))
and tool.Spatial.can_contain(container, obj)
):
self.container_obj = container_obj
return
self.container_obj = None
def is_object_applicable(self, obj):
element = tool.Ifc.get_entity(obj)
return bool(element)
class SpatialElement(PropertyGroup):
name: StringProperty(name="Name")
long_name: StringProperty(name="Long Name")
has_decomposition: BoolProperty(name="Has Decomposition")
ifc_definition_id: IntProperty(name="IFC Definition ID")
is_selected: BoolProperty(name="Is Selected")
class BIMSpatialProperties(PropertyGroup):
containers: CollectionProperty(name="Containers", type=SpatialElement)
active_container_index: IntProperty(name="Active Container Index")
active_container_id: IntProperty(name="Active Container Id")
def poll_container_obj(self, obj):
return obj is None or tool.Ifc.get_entity(obj)
class BIMObjectSpatialProperties(PropertyGroup):
is_editing: BoolProperty(name="Is Editing")
relating_container_object: PointerProperty(
type=bpy.types.Object,
name="Copy Container",
update=update_relating_container_from_object,
poll=is_object_applicable,
description="Copy the target object's container to the active object",
container_obj: PointerProperty(
type=bpy.types.Object, name="Container", update=update_container_obj, poll=poll_container_obj
)
@@ -123,6 +109,7 @@ class Element(PropertyGroup):
name: StringProperty(name="Name")
is_class: BoolProperty(name="Is Class", default=False)
is_type: BoolProperty(name="Is Type", default=False)
ifc_definition_id: IntProperty(name="IFC Definition ID")
total: IntProperty(name="Total")
@@ -144,3 +131,8 @@ class BIMSpatialDecompositionProperties(PropertyGroup):
def active_container(self):
if self.containers and self.active_container_index < len(self.containers):
return self.containers[self.active_container_index]
@property
def active_element(self):
if self.elements and self.active_element_index < len(self.elements):
return self.elements[self.active_element_index]
@@ -39,24 +39,32 @@ class BIM_PT_spatial(Panel):
if not SpatialData.is_loaded:
SpatialData.load()
props = context.scene.BIMSpatialProperties
osprops = context.active_object.BIMObjectSpatialProperties
if osprops.is_editing:
row = self.layout.row(align=True)
if SpatialData.data["parent_container_id"]:
op = row.operator("bim.change_spatial_level", text="", icon="FRAME_PREV")
op.parent = SpatialData.data["parent_container_id"]
if props.containers and props.active_container_index < len(props.containers):
op = row.operator("bim.assign_container", icon="CHECKMARK")
op.structure = props.containers[props.active_container_index].ifc_definition_id
row.operator("bim.reference_structure", icon="LINKED", text="")
row.operator("bim.dereference_structure", icon="UNLINKED", text="")
row.operator("bim.copy_to_container", icon="COPYDOWN", text="")
row.operator("bim.disable_editing_container", icon="CANCEL", text="")
if SpatialData.data["default_container"]:
row = self.layout.row(align=True)
row.prop(osprops, "container_obj", text="", icon="OUTLINER_COLLECTION")
row.operator("bim.assign_container", icon="CHECKMARK", text="")
row.operator("bim.disable_editing_container", icon="CANCEL", text="")
self.layout.template_list("BIM_UL_containers", "", props, "containers", props, "active_container_index")
self.layout.prop(osprops, "relating_container_object")
if SpatialData.data["selected_containers"]:
row = self.layout.row()
row.label(text=f"{len(SpatialData.data['selected_containers'])} Selected Containers")
for name in SpatialData.data["selected_containers"][:3]:
row = self.layout.row()
row.label(text=name, icon="OUTLINER_COLLECTION")
if len(SpatialData.data["selected_containers"]) > 3:
row = self.layout.row()
row.label(text=f"... {len(SpatialData.data['selected_containers']) - 3} More")
row = self.layout.row(align=True)
row.operator("bim.reference_structure", icon="LINKED", text="Reference Selected")
row.operator("bim.dereference_structure", icon="UNLINKED", text="")
row = self.layout.row()
row.operator("bim.copy_to_container", icon="COPYDOWN", text="Copy Object To Selected")
else:
row = self.layout.row()
row.label(text="No Selected Containers")
else:
row = self.layout.row(align=True)
if SpatialData.data["label"]:
@@ -69,28 +77,17 @@ class BIM_PT_spatial(Panel):
else:
row.label(text="No Spatial Container")
row.operator("bim.enable_editing_container", icon="GREASEPENCIL", text="")
for reference in SpatialData.data["references"]:
if references := SpatialData.data["references"]:
self.layout.label(text=f"{len(references)} References:")
else:
self.layout.label(text="No References Found")
for reference in references:
row = self.layout.row()
row.label(text=reference, icon="LINKED")
class BIM_UL_containers(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
if item:
if item.has_decomposition:
op = layout.operator("bim.change_spatial_level", text="", icon="DISCLOSURE_TRI_RIGHT", emboss=False)
op.parent = item.ifc_definition_id
layout.label(text=item.name)
layout.label(text=item.long_name)
layout.prop(
item,
"is_selected",
icon="CHECKBOX_HLT" if item.is_selected else "CHECKBOX_DEHLT",
text="",
emboss=False,
)
class BIM_PT_spatial_decomposition(Panel):
bl_label = "Spatial Decomposition"
bl_idname = "BIM_PT_spatial_decomposition"
@@ -161,15 +158,23 @@ class BIM_PT_spatial_decomposition(Panel):
if not self.props.total_elements:
row = self.layout.row(align=True)
row.label(text="No Contained Elements", icon="FILE_3D")
row.label(text=f"{self.props.active_container.ifc_class} > No Contained Elements", icon="FILE_3D")
row.prop(self.props, "should_include_children", text="", icon="OUTLINER")
return
row = self.layout.row(align=True)
row.label(text=f"{self.props.total_elements} Contained Elements", icon="FILE_3D")
row.label(
text=f"{self.props.active_container.ifc_class} > {self.props.total_elements} Contained Elements",
icon="FILE_3D",
)
row.prop(self.props, "should_include_children", text="", icon="OUTLINER")
op = row.operator("bim.select_decomposed_elements", icon="RESTRICT_SELECT_OFF", text="")
op.ifc_class = self.props.active_container.ifc_class
if self.props.active_element.is_class:
op.ifc_class = self.props.active_element.name
op.relating_type = 0
elif self.props.active_element.is_type:
op.ifc_class = ""
op.relating_type = self.props.active_element.ifc_definition_id
op.container = ifc_definition_id
self.layout.template_list(
@@ -187,7 +192,20 @@ class BIM_UL_containers_manager(UIList):
if item:
row = layout.row(align=True)
self.draw_hierarchy(row, item)
row.prop(item, "name", emboss=False, text="")
icon = {
"IfcProject": "FILE",
"IfcSite": "WORLD",
"IfcBuilding": "HOME",
"IfcBuildingStorey": "LINENUMBERS_OFF",
"IfcSpace": "ANTIALIASED",
"IfcFacilityPart": "MOD_FLUID",
"IfcBridgePart": "MOD_FLUID",
"IfcFacilityPartCommon": "MOD_FLUID",
"IfcMarinePart": "MOD_FLUID",
"IfcRailwayPart": "MOD_FLUID",
"IfcRoadPart": "MOD_FLUID",
}.get(item.ifc_class, "META_PLANE")
row.prop(item, "name", emboss=False, text="", icon=icon)
if item.long_name:
row.prop(item, "long_name", emboss=False, text="")
col = row.column()
@@ -208,24 +226,6 @@ class BIM_UL_containers_manager(UIList):
)
else:
row.label(text="", icon="BLANK1")
if item.ifc_class == "IfcProject":
row.label(text="", icon="FILE")
else:
icon = {
"IfcSite": "WORLD",
"IfcBuilding": "HOME",
"IfcBuildingStorey": "LINENUMBERS_OFF",
"IfcSpace": "ANTIALIASED",
"IfcFacilityPart": "MOD_FLUID",
"IfcBridgePart": "MOD_FLUID",
"IfcFacilityPartCommon": "MOD_FLUID",
"IfcMarinePart": "MOD_FLUID",
"IfcRailwayPart": "MOD_FLUID",
"IfcRoadPart": "MOD_FLUID",
}.get(item.ifc_class, "META_PLANE")
op = row.operator("bim.select_decomposed_elements", icon=icon, text="", emboss=False)
op.ifc_class = item.ifc_class
op.container = item.ifc_definition_id
class BIM_UL_elements(UIList):
@@ -240,7 +240,6 @@ class BIM_UL_elements(UIList):
col.label(text=str(item.total))
elif item.is_type:
row.label(text="", icon="BLANK1")
row.label(text="", icon="DISCLOSURE_TRI_DOWN")
row.label(text=item.name)
col = row.column()
col.alignment = "RIGHT"
@@ -18,6 +18,7 @@
import bpy
import ifcopenshell
import ifcopenshell.util.schema
import blenderbim.tool as tool
from ifcopenshell.util.doc import get_entity_doc
from typing import Union
@@ -25,6 +25,7 @@ import blenderbim.core.style as core
import ifcopenshell.api
import ifcopenshell.api.style
import ifcopenshell.util.representation
import ifcopenshell.util.unit
from pathlib import Path
from mathutils import Vector
@@ -535,22 +536,22 @@ class RemoveTextureMap(bpy.types.Operator):
return {"FINISHED"}
class EnableAddingPresentationStyle(bpy.types.Operator, tool.Ifc.Operator):
class EnableAddingPresentationStyle(bpy.types.Operator):
bl_idname = "bim.enable_adding_presentation_style"
bl_label = "Enable Add Presentation Style"
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
style_type = bpy.context.scene.BIMStylesProperties.style_type
style_type = tool.Style.get_active_style_type()
if style_type != "IfcSurfaceStyle":
cls.poll_message_set(f"Adding {style_type} is not currently supported.")
return False
return True
def _execute(self, context):
props = bpy.context.scene.BIMStylesProperties
props.is_adding = True
def execute(self, context):
tool.Style.enable_adding_presentation_style()
return {"FINISHED"}
class DuplicateStyle(bpy.types.Operator, tool.Ifc.Operator):
@@ -569,14 +570,14 @@ class DuplicateStyle(bpy.types.Operator, tool.Ifc.Operator):
bpy.ops.bim.load_styles(style_type=style_type)
class DisableAddingPresentationStyle(bpy.types.Operator, tool.Ifc.Operator):
class DisableAddingPresentationStyle(bpy.types.Operator):
bl_idname = "bim.disable_adding_presentation_style"
bl_label = "Disable Add Presentation Style"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
props = bpy.context.scene.BIMStylesProperties
props.is_adding = False
def execute(self, context):
tool.Style.disable_adding_presentation_style()
return {"FINISHED"}
class AddPresentationStyle(bpy.types.Operator, tool.Ifc.Operator):
@@ -629,7 +630,7 @@ class AddPresentationStyle(bpy.types.Operator, tool.Ifc.Operator):
else:
self.report({"INFO"}, f"Adding style of type {props.style_type} is not yet supported.")
props.is_adding = False
tool.Style.disable_adding_presentation_style()
core.load_styles(tool.Style, style_type=props.style_type)

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