Compare commits

...

99 Commits

Author SHA1 Message Date
Jesusbill cbe8165e08 add undo for "document" module #1475 2021-07-05 02:21:10 +02:00
Jesusbill 6a7a122de2 add undo for "csv" module #1475 2021-07-05 02:20:54 +02:00
Jesusbill e44db61fbd add undo for "classification" module #1475 2021-07-05 02:20:24 +02:00
Dion Moult 72b60e85d4 Minor fix 2021-07-05 09:01:10 +10:00
Dion Moult c48263ade9 Add installation instructions for IfcSverchok 2021-07-05 09:00:21 +10:00
Jesusbill 5ec081c6bc add undo for "contraint" module #1475 2021-07-04 18:33:35 +02:00
Jesusbill cc3f94097c add undo for "layer" module #1475 2021-07-04 18:32:29 +02:00
Dion Moult 253d478fdc Implement undo for style module operators. See #1475. 2021-07-04 20:28:21 +10:00
Dion Moult 549bc0e1a9 Minor fix. See #1475. 2021-07-04 20:22:20 +10:00
Dion Moult 9286de4813 Minor fix. See #1547. 2021-07-04 19:57:09 +10:00
Dion Moult b8015644e9 Implement undo for context and geometry operators. See #1475. 2021-07-04 19:45:34 +10:00
Dion Moult 8a917dc050 Fix #1529. Properly represent Blender materials as 1:1 mappings to surface styles, with support for material definition representation. 2021-07-04 12:38:41 +10:00
Dion Moult 68b6389794 Fix #1547. 2021-07-04 09:54:49 +10:00
Dion Moult 85bd616f6e Minor fix 2021-07-03 18:46:17 +10:00
Dion Moult 4ff60553cc Fix bug with inability to refresh token in bsdd api. Thanks TestPrab! 2021-07-03 17:45:29 +10:00
Dion Moult dcd6cace03 Fix bug in IfcSverchok nodes where refresh buttons didn't work 2021-07-03 14:34:15 +10:00
Dion Moult b7cfa53b4d IfcSverchok by_type node now updates to be schema dependent on the input file 2021-07-03 14:33:47 +10:00
Dion Moult e0048f7a3c Minor fix 2021-07-03 10:41:06 +10:00
Jesusbill a8dcb2f306 undo for structural - wip 2021-07-03 02:08:31 +02:00
Dion Moult a8f0a4807c Add undo support for group module operators 2021-07-03 10:03:27 +10:00
Dion Moult d5539399e8 New print IFC file debug operator 2021-07-03 10:02:26 +10:00
Dion Moult eb89308019 Minor fix 2021-07-02 19:16:55 +10:00
Dion Moult e7e7b22fd5 Implement undo support for all root operations. Clean up implementation, making passing transaction keys unnecessary. See #1475. 2021-07-02 19:10:51 +10:00
Dion Moult b4a422c2b8 New add application usecase, improved add_classification usecase and better defaults for owner settings. Thanks johltn 2021-07-02 16:30:57 +10:00
Carmen Fan 30f778e0a7 restore other schema versions 2021-07-01 20:42:45 +02:00
Carmen Fan 2032b361c8 fix spelling 2021-07-01 20:42:45 +02:00
Carmen Fan 529b3a9867 add necessary export definitions , add IFCParse as a dependency to IfcGeom build 2021-07-01 20:42:45 +02:00
Dion Moult cac8c64ac8 Document undo system 2021-07-01 21:05:26 +10:00
Dion Moult 3847c9bd7a Implement undo for aggregate operations, with automatic element-obj link undo/redo and improved undo syntax. See #1475. 2021-07-01 20:43:33 +10:00
Thomas Krijnen ffbaa6f0e0 #1537 implement schema enum cast 2021-07-01 11:13:50 +02:00
Thomas Krijnen b5c3812849 Missing change for 6ecedaa128 2021-07-01 11:09:15 +02:00
Dion Moult 30ad915649 Minor fix 2021-07-01 11:59:25 +10:00
Dion Moult c2cf3e8639 Minor fix 2021-07-01 11:53:56 +10:00
Dion Moult c1527b4199 Document module architecture 2021-07-01 11:53:00 +10:00
Dion Moult a1032a3bf0 Describe IfcOpenShell python architecture in the developer guide 2021-07-01 11:17:59 +10:00
Dion Moult 733b7c0e1e Add basic documentation for developer guide 2021-07-01 09:55:59 +10:00
Dion Moult 6b0a58db7d Experimental undo and redo Python prototype (#1539)
* Experimental undo and redo Python prototype

* Simplify history, use transaction jargon, use walk for more robust serialisation

* Black file, set history size, add file reference to entity_instance constructor

* Ensure that files are always passed when entities are wrapped

* Add support for undo/redo of all project module operations

* Minor fix

* Blender to IFC mappings are now managed by Blender RNA, so they don't break on undo operations. See #1475.

* Implement undo for all attribute operations. See #1475.

* Update documentation for installation of add-on

* Revert Blender RNA approach for Blender-IFC mappings, because it didn't scale, but still fix the undo/redo object memory corruption with new "reload_linked_elements" method.
2021-06-30 18:34:12 +10:00
Thomas Krijnen 10bf875e92 Remove wrong and unnecessary shape cast in derived profile 2021-06-27 20:29:47 +02:00
Thomas Krijnen 2fd2b49709 #1539 Option to specify id/instance_name in file::add() 2021-06-27 11:14:26 +02:00
Dion Moult 37a61c549f Accommmodate incorrect body representation contexts coming from Revit 2021-06-27 11:00:20 +10:00
Thomas Krijnen da10a1060e #1475 IfcFile::recalculate_id_counter() 2021-06-26 13:23:55 +02:00
Dion Moult ebfb29d6da Fix #1538. Bug when absolute coordinates are detected from a coordinate list. 2021-06-26 17:09:44 +10:00
Dion Moult 6e4e6a88a4 Add support for parametric updating of cardinal points 1-9 for profile set usages 2021-06-25 17:25:26 +10:00
Dion Moult 312de694b6 Task trees can now be sorted by any attribute independent of the WBS 2021-06-23 18:17:39 +10:00
Dion Moult 788a5a829e Tasks now auto generate identification numbers 2021-06-23 14:13:18 +10:00
Dion Moult a4391df9ca Wall tool now rebranded as BIM tool with a new icon thanks to s-leger! 2021-06-23 11:25:05 +10:00
Dion Moult 1071ac00d4 Fix validation errors in IFC2X3 with missing ownership and mandatory attributes 2021-06-23 10:35:25 +10:00
Thomas Krijnen 6ecedaa128 tree select(gp_Pnt) also with extend 2021-06-22 15:46:21 +02:00
blocovin 8f154c753c Fix xml double value is rounded #1533 2021-06-22 15:12:36 +02:00
Dion Moult e863015e85 You can now customise the data columns shown in the work task tree 2021-06-22 19:32:59 +10:00
Dion Moult 2c49b3bde8 MSProject XML to IFC conversion now supports blank IFCs or non-existent work plans 2021-06-22 13:22:48 +10:00
Dion Moult 650ebeddd6 Column profiles now update when switching type 2021-06-21 14:44:42 +10:00
Dion Moult 5d0d3aaa16 Minor fix 2021-06-21 14:13:25 +10:00
Dion Moult 54526960c8 Column profiles now auto update when you change the material profile 2021-06-21 12:21:55 +10:00
Jesusbill e0b93a70d3 black formatting 2021-06-20 20:58:36 +02:00
Jesusbill e569b97c56 Add general CRUD operations for boundary conditions (from scene properties) 2021-06-20 20:57:12 +02:00
Jesusbill a0b7fe2736 you can now filter structural loads based on name and inverse references 2021-06-20 20:53:45 +02:00
Dion Moult aadb28bb79 Fix #1456. Improve usability when a project library only has a single class type in it. 2021-06-18 16:05:58 +10:00
Dion Moult 2f1f332d31 Fix #1522. Appending from project libraries now supports material properties and styles. 2021-06-18 15:54:01 +10:00
Dion Moult 1ee2f420c8 New feature to see derived global and local elevations 2021-06-18 14:19:53 +10:00
Dion Moult 567bc7b667 Fix confusing transparency attribute in add style API usecase. See #1529. 2021-06-18 08:03:44 +10:00
Dion Moult 2a2f4d692a Fix bug where extrusion detection on voided profiles was not reliable 2021-06-17 19:24:16 +10:00
Dion Moult 1b49a10a15 Minor fix, bump readme 2021-06-17 18:30:25 +10:00
Dion Moult a08918fd22 Scheduled dates now cascade when dependencies, lag times, and calendars are modified 2021-06-17 14:59:28 +10:00
Dion Moult 50608d9459 Modifications to scheduled dates can now cascade through the task tree 2021-06-17 13:23:09 +10:00
Dion Moult 97d0286b57 Uncached schedule recalculation is now twice as fast 2021-06-17 11:05:58 +10:00
Cyril Waechter 2a7fbea9c3 Add recipe to extract only spaces (#1521)
`ExtractElements` was not allowing to extract only spaces.
It also helps to workaround revit-ifc failure in linking spaces.
See: https://github.com/Autodesk/revit-ifc/issues/15
2021-06-17 10:31:18 +10:00
Dion Moult 257997c2cb New feature to copy elements to multiple spatial containers 2021-06-16 09:21:44 +10:00
Dion Moult 2dfc35877c Generated construction animations now support animating input product consumptions, movement to/from, and multiple processes per product 2021-06-15 21:22:28 +10:00
Dion Moult 68233a8fb2 Add support for product inputs into tasks, such as for movements on site 2021-06-15 20:55:43 +10:00
Dion Moult 76e2f688e5 Level of Effort activities are now excluded from P6 import to IFC. 2021-06-15 18:28:43 +10:00
Dion Moult 4d4bedcf9a Fix #1526. Fix bug where custom pset template properties wouldn't show up. 2021-06-15 17:48:45 +10:00
Dion Moult 36a18ea0f9 You can now bulk copy task attributes to other tasks in the task tree 2021-06-15 17:29:04 +10:00
Dion Moult fa1984e89e You can now animate demolition, logistic, and renovation type processes during construction sequencing 2021-06-15 16:35:40 +10:00
Dion Moult 3be0a74316 Minor UI fix for unreadable "derived grid north" in geolocation panel 2021-06-15 15:15:49 +10:00
Dion Moult 90cb56220f Fix #1524. Fixed bug where unlinking copied Blender objects didn't work. 2021-06-15 13:05:24 +10:00
Dion Moult fc05bdf6d6 Support importing elements with no representations 2021-06-10 11:17:49 +10:00
Dion Moult 4c50b2d224 Support editing profile set usage attributes cardinal point and reference extent. Thanks Jesusbill! 2021-06-09 11:23:34 +10:00
Dion Moult b9472c9bae Implement free float calculation. Thanks SigmaDimensions! 2021-06-08 11:36:07 +10:00
Dion Moult 7d841c3f27 New dumb column tool. Creates a parametric solid column from profile sets. 2021-06-07 19:43:02 +10:00
Dion Moult ca0dac1353 The authoring UI now only shows available types so you don't waste your time checking what's available. 2021-06-07 16:09:56 +10:00
Dion Moult 1a962a7f60 Dumb slabs now auto calculate new quantities when edited 2021-06-07 15:58:03 +10:00
Dion Moult f3c1d1df31 Fix bug where you couldn't delete a quantity from a quantity set 2021-06-07 15:52:38 +10:00
Dion Moult 6a0562d00a Footprint and side areas are now generated for dumb wall quantities 2021-06-07 14:44:05 +10:00
Dion Moult 137a00225c Dumb slabs now auto generate footprint representations 2021-06-07 14:28:46 +10:00
Dion Moult 5a822d5e39 Slabs with voided profiles are now supported. 2021-06-07 11:20:05 +10:00
Dion Moult d844223367 Fix bug where P6 import module was not shipped 2021-06-07 10:32:26 +10:00
Dion Moult 6a29ab0d61 Fix bug where 2D or 1D objects generated an invalid box representation 2021-06-07 10:32:05 +10:00
Dion Moult e9c7b21bfc Fix bug where all meshes were cast to a solid by accident. Thanks s-leger! 2021-06-07 10:31:27 +10:00
Dion Moult ed316ba691 Fix #1382. You can now search property values agnostic of pset names. Thanks stefkeB! 2021-06-06 20:55:25 +10:00
Dion Moult cfd5742b5b Fix unnecessary error message when getting psets. 2021-06-06 20:54:40 +10:00
Dion Moult feac601340 Fix #1513. Materials now are included when importing types from a project library. 2021-06-06 10:05:24 +10:00
Dion Moult a8dc734351 Fix bugs where adding types without a material was broken. See #1513. 2021-06-06 09:57:50 +10:00
Dion Moult e0cbbf2eb7 Minor fixes to slab/wall syncing and stability in changing thicknesses. 2021-06-05 19:48:04 +10:00
Dion Moult 082ee3c95d Fix #1511. Prevent double registration of listener callbacks, and simplify listeners. 2021-06-05 19:48:04 +10:00
Thomas Krijnen ece6e6c8ca #1480 continued 2021-06-05 09:49:19 +02:00
Dion Moult 5c1d26ac91 Dumb walls and slabs now are always exported as parametric solids instead of meshes. 2021-06-04 17:30:37 +10:00
Dion Moult 153b6ee24d Total float calculation now takes into account calendar 2021-06-04 17:22:35 +10:00
Dion Moult 8b3b1eeb8e Fix #1510. Whoops :) 2021-06-04 15:29:14 +10:00
142 changed files with 9573 additions and 1003 deletions
+17 -17
View File
@@ -23,7 +23,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) # not necessary, but encouraged
project (IfcOpenShell VERSION 0.6.0)
# use extra version to make pre-release using eg semver
# use extra version to make pre-release using eg semver
set( EXTRA_VERSION "-alpha.3")
foreach(max_year RANGE 2014 2030)
@@ -441,7 +441,7 @@ IF(MSVC)
# Disable overeager and false positives causing C4458 ("declaration of 'indentifier' hides class member"), at least for now.
ADD_DEFINITIONS(-wd4458)
ENDIF()
# Enforce standards-conformance on VS > 2015, older Boost versions fail to compile with this
# Enforce standards-conformance on VS > 2015, older Boost versions fail to compile with this
if (MSVC_VERSION GREATER 1900 AND (Boost_MAJOR_VERSION GREATER 1 OR Boost_MINOR_VERSION GREATER 66))
add_definitions(-permissive-)
endif()
@@ -486,8 +486,8 @@ INCLUDE_DIRECTORIES(${INCLUDE_DIRECTORIES} ${OCC_INCLUDE_DIR} ${OPENCOLLADA_INCL
function(files_for_ifc_version IFC_VERSION RESULT_NAME)
set(IFC_PARSE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../src/ifcparse)
set(${RESULT_NAME}
${IFC_PARSE_DIR}/Ifc${IFC_VERSION}.h
set(${RESULT_NAME}
${IFC_PARSE_DIR}/Ifc${IFC_VERSION}.h
${IFC_PARSE_DIR}/Ifc${IFC_VERSION}enum.h
${IFC_PARSE_DIR}/Ifc${IFC_VERSION}.cpp
PARENT_SCOPE
@@ -504,13 +504,13 @@ if(COMPILE_SCHEMA)
# @todo, this appears to be untested at the moment
find_package(PythonInterp)
IF(NOT PYTHONINTERP_FOUND)
MESSAGE(FATAL_ERROR "A Python interpreter is necessary when COMPILE_SCHEMA is enabled. Disable COMPILE_SCHEMA or fix Python paths to proceed.")
ENDIF()
set(IFC_RELEASE_NOT_USED ${SCHEMA_VERSIONS})
# Install pyparsing if necessary
execute_process(COMMAND ${PYTHON_EXECUTABLE} -m pip freeze OUTPUT_VARIABLE PYTHON_PACKAGE_LIST)
if ("${PYTHON_PACKAGE_LIST}" STREQUAL "")
@@ -532,23 +532,23 @@ if(COMPILE_SCHEMA)
else()
message(STATUS "Python interpreter with pyparsing found")
endif()
# Bootstrap the parser
message(STATUS "Compiling schema, this will take a while...")
execute_process(COMMAND ${PYTHON_EXECUTABLE} bootstrap.py express.bnf
WORKING_DIRECTORY ../src/ifcexpressparser
execute_process(COMMAND ${PYTHON_EXECUTABLE} bootstrap.py express.bnf
WORKING_DIRECTORY ../src/ifcexpressparser
OUTPUT_FILE express_parser.py
RESULT_VARIABLE SUCCESS)
if (NOT "${SUCCESS}" STREQUAL "0")
MESSAGE(FATAL_ERROR "Failed to bootstrap parser. Make sure pyparsing is installed")
endif()
# Generate code
execute_process(COMMAND ${PYTHON_EXECUTABLE} ../ifcexpressparser/express_parser.py ../../${COMPILE_SCHEMA}
WORKING_DIRECTORY ../src/ifcparse
OUTPUT_VARIABLE COMPILED_SCHEMA_NAME)
# Prevent the schema that had just been compiled from being excluded
foreach(s ${SCHEMA_VERSIONS})
if("${COMPILED_SCHEMA_NAME}" STREQUAL "${s}")
@@ -638,7 +638,7 @@ if (UNIX)
find_package(Threads)
endif()
TARGET_LINK_LIBRARIES(IfcGeom ${IFCGEOM_SCHEMA_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT})
TARGET_LINK_LIBRARIES(IfcGeom IfcParse ${IFCGEOM_SCHEMA_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT})
endif(BUILD_IFCGEOM)
@@ -708,7 +708,7 @@ INSTALL(TARGETS IfcGeomServer
endif()
# Documentation
# Documentation
IF(BUILD_DOCUMENTATION)
set(CMAKE_MODULE_PATH "../docs/cmake")
ADD_SUBDIRECTORY(../docs docs)
@@ -727,7 +727,7 @@ IF(BUILD_IFCMAX)
ENDIF()
# CMake installation targets
INSTALL(FILES ${IFCPARSE_H_FILES}
INSTALL(FILES ${IFCPARSE_H_FILES}
DESTINATION ${INCLUDEDIR}/ifcparse
)
@@ -738,11 +738,11 @@ INSTALL(TARGETS IfcParse
)
if(BUILD_IFCGEOM)
INSTALL(FILES ${IFCGEOM_H_FILES}
INSTALL(FILES ${IFCGEOM_H_FILES}
DESTINATION ${INCLUDEDIR}/ifcgeom
)
INSTALL(FILES ${SCHEMA_AGNOSTIC_H_FILES}
INSTALL(FILES ${SCHEMA_AGNOSTIC_H_FILES}
DESTINATION ${INCLUDEDIR}/ifcgeom_schema_agnostic
)
+4 -1
View File
@@ -4,7 +4,10 @@ A simple Python implementation of BCF. The data model is described in `data.py`.
Manipulation of BCF-XML is available via `bcfxml.py` and manipulation of BCF-API
is available via `bcfapi.py`.
Currently supports BCF version 2.1.
- BCF-XML version 2.1: Fully supported
- BCF-API version 2.1: Not supported, will probably tackle this after BCF-API v3.0
- BCF-XML version 3.0: Almost fully supported, except for the documents module
- BCF-API version 3.0: Not supported, but work underway to support it
## bcfxml
+4 -2
View File
@@ -13,10 +13,10 @@ endif
# Provides IfcOpenShell Python functionality
ifeq ($(PYVERSION), py37)
cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcblender-python-37-v0.6.0-81ad689-$(PLATFORM)64.zip
cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcblender-python-37-v0.6.0-2fd2b49-$(PLATFORM)64.zip
endif
ifeq ($(PYVERSION), py39)
cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcblender-python-39-v0.6.0-81ad689-$(PLATFORM)64.zip
cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcblender-python-39-v0.6.0-2fd2b49-$(PLATFORM)64.zip
endif
cd dist/working && unzip ifcblender*
cp -r dist/working/io_import_scene_ifc/ifcopenshell dist/blenderbim/libs/site/packages/
@@ -62,6 +62,8 @@ endif
cp -r dist/working/IfcOpenShell-0.6.0/src/ifccsv/* dist/blenderbim/libs/site/packages/
# Provides IFCPatch functionality
cp -r dist/working/IfcOpenShell-0.6.0/src/ifcpatch/ifcpatch dist/blenderbim/libs/site/packages/
# Provides IFCP6 functionality
cp -r dist/working/IfcOpenShell-0.6.0/src/ifcp6/ifcp6 dist/blenderbim/libs/site/packages/
rm -rf dist/working
# Provides Mustache templating in construction documentation
@@ -110,6 +110,8 @@ if bpy is not None:
for cls in classes:
bpy.utils.register_class(cls)
bpy.app.handlers.depsgraph_update_post.append(on_register)
bpy.app.handlers.undo_post.append(handler.undo_post)
bpy.app.handlers.redo_post.append(handler.redo_post)
bpy.app.handlers.load_post.append(handler.setDefaultProperties)
bpy.app.handlers.load_post.append(handler.loadIfcStore)
bpy.app.handlers.save_pre.append(handler.ensureIfcExported)
+34 -56
View File
@@ -12,7 +12,10 @@ global_subscription_owner = object()
def mode_callback(obj, data):
for obj in bpy.context.selected_objects + [bpy.context.active_object]:
objects = bpy.context.selected_objects
if bpy.context.active_object:
objects += [bpy.context.active_object]
for obj in objects:
if (
obj.mode != "EDIT"
or not obj.data
@@ -30,6 +33,10 @@ def mode_callback(obj, data):
def name_callback(obj, data):
try:
oby.type
except:
return # In case the object RNA is gone during an undo / redo operation
# Blender material names are up to 63 UTF-8 bytes
if not obj.BIMObjectProperties.ifc_definition_id or "/" not in obj.name or len(bytes(obj.name, "utf-8")) >= 63:
return
@@ -40,7 +47,7 @@ def name_callback(obj, data):
collection = obj.users_collection[0]
collection.name = obj.name
if element.is_a("IfcGrid"):
axis_obj = IfcStore.id_map[element.UAxes[0].id()]
axis_obj = IfcStore.get_element(element.UAxes[0].id())
axis_collection = axis_obj.users_collection[0]
grid_collection = None
for collection in bpy.data.collections:
@@ -60,7 +67,8 @@ def active_object_callback():
for obj in bpy.context.selected_objects:
if not obj.BIMObjectProperties.ifc_definition_id:
continue
if IfcStore.id_map[obj.BIMObjectProperties.ifc_definition_id] != obj:
stored_obj = IfcStore.get_element(obj.BIMObjectProperties.ifc_definition_id)
if stored_obj and stored_obj != obj:
bpy.ops.bim.copy_class(obj=obj.name)
@@ -102,14 +110,26 @@ def loadIfcStore(scene):
if not ifc_file:
return
IfcStore.get_schema()
[
IfcStore.link_element(ifc_file.by_id(o.BIMObjectProperties.ifc_definition_id), o)
for o in bpy.data.objects
if o.BIMObjectProperties.ifc_definition_id
]
IfcStore.reload_linked_elements()
purge_module_data()
@persistent
def undo_post(scene):
if IfcStore.last_transaction != bpy.context.scene.BIMProperties.last_transaction:
IfcStore.last_transaction = bpy.context.scene.BIMProperties.last_transaction
IfcStore.undo()
IfcStore.reload_linked_elements(should_reload_selected=True)
@persistent
def redo_post(scene):
if IfcStore.last_transaction != bpy.context.scene.BIMProperties.last_transaction:
IfcStore.last_transaction = bpy.context.scene.BIMProperties.last_transaction
IfcStore.redo()
IfcStore.reload_linked_elements(should_reload_selected=True)
@persistent
def ensureIfcExported(scene):
if IfcStore.get_file() and not bpy.context.scene.BIMProperties.ifc_file:
@@ -121,14 +141,12 @@ def get_application(ifc):
for element in ifc.by_type("IfcApplication"):
if element.ApplicationIdentifier == "BlenderBIM" and element.Version == version:
return element
return ifc.create_entity(
"IfcApplication",
**{
"ApplicationDeveloper": create_application_organisation(ifc),
"Version": get_application_version(),
"ApplicationFullName": "BlenderBIM Add-on",
"ApplicationIdentifier": "BlenderBIM",
},
return ifcopenshell.api.run(
"owner.add_application",
ifc,
version=get_application_version(),
application_full_name="BlenderBIM Add-on",
application_identifier="BlenderBIM",
)
@@ -145,46 +163,6 @@ def get_application_version():
)
def create_application_organisation(ifc):
return ifc.create_entity(
"IfcOrganization",
**{
"Name": "IfcOpenShell",
"Description": "IfcOpenShell is an open source (LGPL) software library that helps users and software developers to work with the IFC file format.",
"Roles": [ifc.create_entity("IfcActorRole", **{"Role": "USERDEFINED", "UserDefinedRole": "CONTRIBUTOR"})],
"Addresses": [
ifc.create_entity(
"IfcTelecomAddress",
**{
"Purpose": "USERDEFINED",
"UserDefinedPurpose": "WEBPAGE",
"Description": "The main webpage of the software collection.",
"WWWHomePageURL": "https://ifcopenshell.org",
},
),
ifc.create_entity(
"IfcTelecomAddress",
**{
"Purpose": "USERDEFINED",
"UserDefinedPurpose": "WEBPAGE",
"Description": "The BlenderBIM Add-on webpage of the software collection.",
"WWWHomePageURL": "https://blenderbim.org",
},
),
ifc.create_entity(
"IfcTelecomAddress",
**{
"Purpose": "USERDEFINED",
"UserDefinedPurpose": "REPOSITORY",
"Description": "The source code repository of the software collection.",
"WWWHomePageURL": "https://github.com/IfcOpenShell/IfcOpenShell.git",
},
),
],
},
)
@persistent
def setDefaultProperties(scene):
global global_subscription_owner
+10 -1
View File
@@ -8,21 +8,30 @@ from mathutils import Vector
from blenderbim.bim.ifc import IfcStore
def draw_attributes(props, layout):
def draw_attributes(props, layout, copy_operator=None):
for attribute in props:
row = layout.row(align=True)
value = None
if attribute.data_type == "string":
row.prop(attribute, "string_value", text=attribute.name)
value = attribute.string_value
elif attribute.data_type == "boolean":
row.prop(attribute, "bool_value", text=attribute.name)
value = attribute.bool_value
elif attribute.data_type == "integer":
row.prop(attribute, "int_value", text=attribute.name)
value = attribute.int_value
elif attribute.data_type == "float":
row.prop(attribute, "float_value", text=attribute.name)
value = attribute.float_value
elif attribute.data_type == "enum":
row.prop(attribute, "enum_value", text=attribute.name)
value = attribute.enum_value
if attribute.is_optional:
row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
if copy_operator:
op = row.operator(f"{copy_operator}", text="", icon="COPYDOWN")
op.data = json.dumps({"name": attribute.name, "value": value, "is_null": attribute.is_null})
def import_attributes(ifc_class, props, data, callback=None):
+136 -6
View File
@@ -1,4 +1,5 @@
import bpy
import uuid
import ifcopenshell
import blenderbim.bim.handler
@@ -15,6 +16,10 @@ class IfcStore:
library_path = ""
library_file = None
element_listeners = set()
current_transaction = ""
last_transaction = ""
history = []
future = []
@staticmethod
def purge():
@@ -65,6 +70,27 @@ class IfcStore:
def add_element_listener(callback):
IfcStore.element_listeners.add(callback)
@staticmethod
def reload_linked_elements(should_reload_selected=False):
file = IfcStore.get_file()
if not file:
return
if should_reload_selected:
objects = bpy.context.selected_objects
if bpy.context.active_object:
objects += [bpy.context.active_object]
else:
objects = bpy.data.objects
for obj in objects:
if not obj.BIMObjectProperties.ifc_definition_id:
continue
element = file.by_id(obj.BIMObjectProperties.ifc_definition_id)
data = {"id": element.id(), "obj": obj.name}
if hasattr(element, "GlobalId"):
data["guid"] = element.GlobalId
IfcStore.commit_link_element(data)
@staticmethod
def link_element(element, obj):
IfcStore.id_map[element.id()] = obj
@@ -76,6 +102,27 @@ class IfcStore:
for listener in IfcStore.element_listeners:
listener(element, obj)
if IfcStore.history:
data = {"id": element.id(), "guid": getattr(element, "GlobalId", None), "obj": obj.name}
IfcStore.history[-1]["operations"].append(
{"rollback": IfcStore.rollback_link_element, "commit": IfcStore.commit_link_element, "data": data}
)
@staticmethod
def rollback_link_element(data):
del IfcStore.id_map[data["id"]]
if data["guid"]:
del IfcStore.guid_map[data["guid"]]
@staticmethod
def commit_link_element(data):
obj = bpy.data.objects.get(data["obj"])
IfcStore.id_map[data["id"]] = obj
if data["guid"]:
IfcStore.guid_map[data["guid"]] = obj
blenderbim.bim.handler.subscribe_to(obj, "mode", blenderbim.bim.handler.mode_callback)
blenderbim.bim.handler.subscribe_to(obj, "name", blenderbim.bim.handler.name_callback)
@staticmethod
def unlink_element(element=None, obj=None):
if element is None:
@@ -84,13 +131,96 @@ class IfcStore:
except:
pass
if element:
del IfcStore.id_map[element.id()]
else:
del IfcStore.id_map[obj.BIMObjectProperties.ifc_definition_id]
try:
if element:
del IfcStore.id_map[element.id()]
else:
del IfcStore.id_map[obj.BIMObjectProperties.ifc_definition_id]
except:
pass
if element and hasattr(element, "GlobalId"):
del IfcStore.guid_map[element.GlobalId]
try:
if element and hasattr(element, "GlobalId"):
del IfcStore.guid_map[element.GlobalId]
except:
pass
if obj:
obj.BIMObjectProperties.ifc_definition_id = 0
@staticmethod
def execute_ifc_operator(operator, context):
is_top_level_operator = not bool(IfcStore.current_transaction)
if is_top_level_operator:
IfcStore.begin_transaction(operator)
IfcStore.get_file().begin_transaction()
# This empty transaction ensures that each operator has at least one transaction
IfcStore.add_transaction_operation(operator, rollback=lambda data: True, commit=lambda data: True)
else:
operator.transaction_key = IfcStore.current_transaction
result = getattr(operator, "_execute")(context)
if is_top_level_operator:
IfcStore.get_file().end_transaction()
IfcStore.add_transaction_operation(
operator, rollback=IfcStore.rollback_ifc_operator, commit=IfcStore.commit_ifc_operator
)
IfcStore.end_transaction(operator)
return result
@staticmethod
def rollback_ifc_operator(data):
IfcStore.get_file().undo()
blenderbim.bim.handler.purge_module_data()
@staticmethod
def commit_ifc_operator(data):
IfcStore.get_file().redo()
blenderbim.bim.handler.purge_module_data()
@staticmethod
def begin_transaction(operator):
IfcStore.current_transaction = str(uuid.uuid4())
operator.transaction_key = IfcStore.current_transaction
@staticmethod
def end_transaction(operator):
IfcStore.current_transaction = ""
operator.transaction_key = ""
@staticmethod
def add_transaction_operation(operator, rollback=None, commit=None):
key = getattr(operator, "transaction_key", None)
data = getattr(operator, "transaction_data", None)
bpy.context.scene.BIMProperties.last_transaction = key
IfcStore.last_transaction = key
rollback = rollback or getattr(operator, "rollback", lambda data: True)
commit = commit or getattr(operator, "commit", lambda data: True)
if IfcStore.history and IfcStore.history[-1]["key"] == key:
IfcStore.history[-1]["operations"].append({"rollback": rollback, "commit": commit, "data": data})
else:
IfcStore.history.append(
{"key": key, "operations": [{"rollback": rollback, "commit": commit, "data": data}]}
)
IfcStore.future = []
@staticmethod
def undo():
if not IfcStore.history:
return
event = IfcStore.history.pop()
for transaction in event["operations"][::-1]:
transaction["rollback"](transaction["data"])
IfcStore.future.append(event)
@staticmethod
def redo():
if not IfcStore.future:
return
event = IfcStore.future.pop()
for transaction in event["operations"]:
transaction["commit"](transaction["data"])
IfcStore.history.append(event)
+87 -197
View File
@@ -39,14 +39,14 @@ class MaterialCreator:
def __init__(self, ifc_import_settings, ifc_importer):
self.mesh = None
self.materials = {}
self.styles = {}
self.parsed_meshes = set()
self.ifc_import_settings = ifc_import_settings
self.ifc_importer = ifc_importer
def create(self, element, obj, mesh):
self.obj = obj
self.mesh = mesh
self.parse_material(element)
self.obj = obj
if (hasattr(element, "Representation") and not element.Representation) or (
hasattr(element, "RepresentationMaps") and not element.RepresentationMaps
):
@@ -55,7 +55,7 @@ class MaterialCreator:
return
self.parsed_meshes.add(self.mesh.name)
if self.parse_representations(element):
self.assign_material_slots_to_faces(obj)
self.assign_material_slots_to_faces()
def parse_representations(self, element):
has_parsed = False
@@ -80,51 +80,23 @@ class MaterialCreator:
def parse_representation_item(self, item):
if not item.StyledByItem:
return
item_id = self.mesh.BIMMeshProperties.ifc_item_ids.add()
item_id.name = str(item.id())
styled_item = item.StyledByItem[0] # Cardinality is S[0:1]
style_name = self.get_surface_style_name(styled_item)
if not style_name:
style_ids = [e.id() for e in self.ifc_importer.file.traverse(item.StyledByItem[0]) if e.is_a("IfcSurfaceStyle")]
if not style_ids:
return
if self.mesh.materials.get(style_name):
item_id.slot_index = self.mesh.materials.find(style_name)
return True
style = bpy.data.materials.get(style_name)
if not style:
style = bpy.data.materials.new(style_name)
self.parse_styled_item(styled_item, style)
self.assign_style_to_mesh(style)
item_id.slot_index = len(self.mesh.materials) - 1
for style_id in style_ids:
material = self.styles[style_id]
if self.mesh.materials.find(material.name) == -1:
self.mesh.materials.append(material)
return True
def assign_material_slots_to_faces(self, obj):
def assign_material_slots_to_faces(self):
if "ios_materials" not in self.mesh or not self.mesh["ios_materials"]:
return
if len(obj.material_slots) == 1:
if len(self.obj.material_slots) == 1:
return
material_to_slot = {}
for i, material in enumerate(self.mesh["ios_materials"]):
if material == "NULLMAT":
continue
elif "surface-style-" in material:
material = material.split("-")[2]
if len(bytes(material, "utf-8")) > 63: # Blender material names are up to 63 UTF-8 bytes
material = bytes(material, "utf-8")[0:63].decode("utf-8")
slot_index = obj.material_slots.find(material)
if slot_index == -1:
# If we can't find the material, it is possible that the
# material name is duplicated, and so a '.001' is added.
# The maximum characters for the material name is 59 in this
# scenario.
material = bytes(material, "utf-8")[0:59].decode("utf-8")
slot_index = [self.canonicalise_material_name(s.name) for s in obj.material_slots].index(material)
slot_index = self.obj.material_slots.find(self.styles[material].name)
material_to_slot[i] = slot_index
if len(self.mesh.polygons) == len(self.mesh["ios_material_ids"]):
@@ -133,130 +105,6 @@ class MaterialCreator:
]
self.mesh.polygons.foreach_set("material_index", material_index)
def canonicalise_material_name(self, name):
return re.sub(r"\.[0-9]{3}$", "", name)
def parse_material(self, element):
for association in element.HasAssociations:
if association.is_a("IfcRelAssociatesMaterial"):
material_select = association.RelatingMaterial
if material_select.is_a("IfcMaterialDefinition"):
self.create_definition(material_select)
elif material_select.is_a("IfcMaterialUsageDefinition"):
self.create_usage_definition(material_select)
elif material_select.is_a("IfcMaterialList"):
# Note that lists are deprecated
self.create_material_list(material_select)
# To support IFC2X3 equivalent of IfcMaterialDefinition
elif material_select.is_a("IfcMaterial") or material_select.is_a("IfcMaterialLayerSet"):
self.create_definition(material_select)
# To support IFC2X3 equivalent of IfcMaterialUsageDefinition
elif material_select.is_a("IfcMaterialLayerSetUsage"):
self.create_usage_definition(material_select)
# IFC2X3 supports assigning a material layer directly. This is silly.
elif material_select.is_a("IfcMaterialLayer"):
pass
def create_layer_set_usage(self, usage):
# TODO import rest of the layer set usage data
self.create_definition(usage.ForLayerSet)
def create_definition(self, material):
if material.is_a("IfcMaterial"):
self.create_single(material)
elif material.is_a("IfcMaterialConstituentSet"):
self.create_constituent_set(material)
elif material.is_a("IfcMaterialLayerSet"):
self.create_layer_set(material)
elif material.is_a("IfcMaterialProfileSet"):
self.create_profile_set(material)
def create_usage_definition(self, material):
if material.is_a("IfcMaterialLayerSetUsage"):
self.create_layer_set_usage(material)
elif material.is_a("IfcMaterialProfileSetUsage"):
pass # TODO
def create_single(self, material):
if material.Name not in self.materials:
self.create_new_single(material)
def create_layer_set(self, layer_set):
for layer in layer_set.MaterialLayers:
if layer.Material:
if layer.Material.Name not in self.materials:
self.create_new_single(layer.Material)
def create_constituent_set(self, constituent_set):
for constituent in constituent_set.MaterialConstituents:
if constituent.Material.Name not in self.materials:
self.create_new_single(constituent.Material)
def create_profile_set(self, profile_set):
for profile in profile_set.MaterialProfiles:
if profile.Material.Name not in self.materials:
self.create_new_single(profile.Material)
def create_material_list(self, material_list):
for material in material_list.Materials:
if material.Name not in self.materials:
self.create_new_single(material)
def create_new_single(self, material):
self.materials[material.Name] = obj = bpy.data.materials.new(material.Name)
obj.BIMObjectProperties.ifc_definition_id = int(material.id())
if not material.HasRepresentation or not material.HasRepresentation[0].Representations:
return
for representation in material.HasRepresentation[0].Representations:
if not representation.Items:
continue
for item in representation.Items:
if not item.is_a("IfcStyledItem"):
continue
self.parse_styled_item(item, obj)
def get_surface_style_name(self, styled_item):
if styled_item.Name:
return styled_item.Name
styles = self.get_styled_item_styles(styled_item)
for style in styles:
if not style.is_a("IfcSurfaceStyle"):
continue
if style.Name:
return style.Name
return str(style.id())
return None # We only support surface styles right now
def parse_styled_item(self, styled_item, material):
styles = self.get_styled_item_styles(styled_item)
for style in styles:
if not style.is_a("IfcSurfaceStyle"):
continue
material.BIMMaterialProperties.ifc_style_id = int(style.id())
for surface_style in style.Styles:
if surface_style.is_a("IfcSurfaceStyleShading"):
alpha = 1.0
# Transparency was added in IFC4
if hasattr(surface_style, "Transparency") and surface_style.Transparency:
alpha = 1 - surface_style.Transparency
material.diffuse_color = (
surface_style.SurfaceColour.Red,
surface_style.SurfaceColour.Green,
surface_style.SurfaceColour.Blue,
alpha,
)
# IfcPresentationStyleAssignment is deprecated as of IFC4
# However it is still widely used thanks to Revit :(
def get_styled_item_styles(self, styled_item):
styles = []
for style in styled_item.Styles:
if style.is_a("IfcPresentationStyleAssignment"):
styles.extend(self.get_styled_item_styles(style))
else:
styles.append(style)
return styles
def resolve_mapped_representation_items(self, representation):
items = []
for item in representation.Items:
@@ -266,11 +114,6 @@ class MaterialCreator:
items.append(item)
return items
def assign_style_to_mesh(self, material):
if not self.mesh:
return
self.mesh.materials.append(material)
class IfcImporter:
def __init__(self, ifc_import_settings):
@@ -336,6 +179,10 @@ class IfcImporter:
self.profile_code("Create aggregate tree")
self.create_openings_collection()
self.profile_code("Create opening collection")
self.create_materials()
self.profile_code("Create materials")
self.create_styles()
self.profile_code("Create styles")
self.process_element_filter()
self.profile_code("Process element filter")
self.parse_native_elements()
@@ -346,6 +193,8 @@ class IfcImporter:
self.profile_code("Create native products")
self.create_products()
self.profile_code("Create products")
self.create_empty_products()
self.profile_code("Create empty products")
self.create_type_products()
self.profile_code("Create type products")
self.create_annotation()
@@ -381,13 +230,10 @@ class IfcImporter:
def is_point_far_away(self, point):
# Arbitrary threshold based on experience
coords = point
if hasattr(point, "Coordinates"):
return (
abs(point.Coordinates[0]) > 1000000
or abs(point.Coordinates[1]) > 1000000
or abs(point.Coordinates[2]) > 1000000
)
return abs(point[0]) > 1000000 or abs(point[1]) > 1000000 or abs(point[2]) > 1000000
coords = point.Coordinates
return abs(coords[0]) > 1000000 or abs(coords[1]) > 1000000 or abs(coords[2]) > 1000000
def process_element_filter(self):
if not self.ifc_import_settings.ifc_selector:
@@ -507,18 +353,18 @@ class IfcImporter:
elements_checked = 0
# If more than these points aren't far away, the file probably isn't absolutely positioned
element_checking_threshold = 100
try:
point_lists = self.file.by_type("IfcCartesianPointList3D")
except:
if self.file.schema == "IFC2X3":
# IFC2X3 does not have IfcCartesianPointList3D
point_lists = []
else:
point_lists = self.file.by_type("IfcCartesianPointList3D")
for point_list in point_lists:
elements_checked += 1
if elements_checked > element_checking_threshold:
return
for i, point in enumerate(point_list.CoordList):
if len(point) == 3 and self.is_point_far_away(point):
return point[0]
return point
for point in self.file.by_type("IfcCartesianPoint"):
is_used_in_placement = False
@@ -532,7 +378,7 @@ class IfcImporter:
if elements_checked > element_checking_threshold:
return
if len(point.Coordinates) == 3 and self.is_point_far_away(point):
return point[0]
return point.Coordinates
def apply_blender_offset_to_matrix(self, matrix):
props = bpy.context.scene.BIMGeoreferenceProperties
@@ -703,7 +549,9 @@ class IfcImporter:
shape = iterator.get()
if shape:
product = self.file.by_id(shape.guid)
if shape.context != "Body" and shape.guid in IfcStore.guid_map:
# Facetation is to accommodate broken Revit files
# See https://forums.buildingsmart.org/t/suggestions-on-how-to-improve-clarity-of-representation-context-usage-in-documentation/3663/6?u=moult
if shape.context not in ["Body", "Facetation"] and IfcStore.get_element(shape.guid):
# We only load a single context, and we prioritise the Body context. See #1290.
pass
elif product.is_a("IfcAnnotation") and product.ObjectType == "DRAWING":
@@ -715,6 +563,15 @@ class IfcImporter:
break
print("Done creating geometry")
def create_empty_products(self):
for element in self.file.by_type("IfcProduct"):
if element.id() in self.added_data:
continue
if element.is_a("IfcPort"):
continue
if not element.Representation:
self.create_product(element)
def create_annotation(self):
self.create_curve_products(self.file.by_type("IfcAnnotation"))
@@ -829,8 +686,8 @@ class IfcImporter:
def get_representation_item_material_name(self, item):
if not item.StyledByItem:
return
styled_item = item.StyledByItem[0]
return self.material_creator.get_surface_style_name(styled_item)
style_ids = [e.id() for e in self.ifc_importer.file.traverse(item.StyledByItem[0]) if e.is_a("IfcSurfaceStyle")]
return style_ids[0] if style_ids else None
def create_native_faceted_brep(self, element, mesh_name):
# TODO: georeferencing?
@@ -851,6 +708,7 @@ class IfcImporter:
for representation in self.native_data[element.GlobalId]["representations"]:
for item in representation["raw"].Items:
# TODO: if I reimplement native faceted breps, recheck this material implementation
materials.append(self.get_representation_item_material_name(item) or "NULLMAT")
mesh = item.get_info_2(recursive=True) # See bug #841
total_item_polygons = 0
@@ -1137,6 +995,47 @@ class IfcImporter:
self.opening_collection = bpy.data.collections.new("IfcOpeningElements")
self.project["blender"].children.link(self.opening_collection)
def create_materials(self):
for material in self.file.by_type("IfcMaterial"):
blender_material = bpy.data.materials.new(material.Name)
blender_material.BIMObjectProperties.ifc_definition_id = material.id()
self.material_creator.materials[material.id()] = blender_material
blender_material.use_fake_user = True
def create_styles(self):
parsed_styles = set()
for material_definition_representation in self.file.by_type("IfcMaterialDefinitionRepresentation"):
material = material_definition_representation.RepresentedMaterial
for representation in material_definition_representation.Representations:
for style in [e for e in self.file.traverse(representation) if e.is_a("IfcSurfaceStyle")]:
blender_material = self.material_creator.materials[material.id()]
self.create_style(style, blender_material)
parsed_styles.add(style.id())
for style in self.file.by_type("IfcSurfaceStyle"):
if style.id() in parsed_styles:
continue
name = style.Name or str(style.id())
blender_material = bpy.data.materials.new(name)
self.create_style(style, blender_material)
def create_style(self, style, blender_material):
blender_material.BIMMaterialProperties.ifc_style_id = style.id()
self.material_creator.styles[style.id()] = blender_material
for surface_style in style.Styles:
if surface_style.is_a("IfcSurfaceStyleShading"):
alpha = 1.0
# Transparency was added in IFC4
if hasattr(surface_style, "Transparency") and surface_style.Transparency:
alpha = 1 - surface_style.Transparency
blender_material.diffuse_color = (
surface_style.SurfaceColour.Red,
surface_style.SurfaceColour.Green,
surface_style.SurfaceColour.Blue,
alpha,
)
def get_name(self, element):
return "{}/{}".format(element.is_a(), element.Name)
@@ -1153,8 +1052,8 @@ class IfcImporter:
bpy.ops.object.delete({"selected_objects": objects_to_purge})
def place_objects_in_spatial_tree(self):
for global_id, obj in self.added_data.items():
self.place_object_in_spatial_tree(self.file.by_guid(global_id), obj)
for ifc_definition_id, obj in self.added_data.items():
self.place_object_in_spatial_tree(self.file.by_id(ifc_definition_id), obj)
def place_object_in_spatial_tree(self, element, obj):
if element.is_a("IfcProject"):
@@ -1341,9 +1240,7 @@ class IfcImporter:
if "TargetView" in pset:
camera.BIMCameraProperties.target_view = pset["TargetView"]
if "Scale" in pset:
valid_scales = [
i[0] for i in getDiagramScales(None, None) if pset["Scale"] == i[0].split("|")[-1]
]
valid_scales = [i[0] for i in getDiagramScales(None, None) if pset["Scale"] == i[0].split("|")[-1]]
if valid_scales:
camera.BIMCameraProperties.diagram_scale = valid_scales[0]
else:
@@ -1406,14 +1303,7 @@ class IfcImporter:
edges = [[e[i], e[i + 1]] for i in range(0, len(e), 2)]
mesh.from_pydata(vertices, edges, [])
ios_materials = []
for mat in geometry.materials:
# See bug #866
if mat.original_name():
ios_materials.append(mat.original_name())
else:
ios_materials.append(mat.name)
mesh["ios_materials"] = ios_materials
mesh["ios_materials"] = [int(m.name.split("-")[2]) for m in geometry.materials]
mesh["ios_material_ids"] = geometry.material_ids
return mesh
except:
@@ -1465,7 +1355,7 @@ class IfcImporter:
break
def link_element(self, element, obj):
self.added_data[element.GlobalId] = obj
self.added_data[element.id()] = obj
IfcStore.link_element(element, obj)
@@ -7,10 +7,14 @@ from ifcopenshell.api.aggregate.data import Data
class AssignObject(bpy.types.Operator):
bl_idname = "bim.assign_object"
bl_label = "Assign Object"
bl_options = {"REGISTER", "UNDO"}
relating_object: bpy.props.StringProperty()
related_object: bpy.props.StringProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
self.file = IfcStore.get_file()
related_objects = (
[bpy.data.objects.get(self.related_object)] if self.related_object else bpy.context.selected_objects
@@ -60,6 +64,7 @@ class AssignObject(bpy.types.Operator):
class EnableEditingAggregate(bpy.types.Operator):
bl_idname = "bim.enable_editing_aggregate"
bl_label = "Enable Editing Aggregate"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
bpy.context.active_object.BIMObjectProperties.relating_object = None
@@ -71,6 +76,7 @@ class DisableEditingAggregate(bpy.types.Operator):
bl_idname = "bim.disable_editing_aggregate"
bl_label = "Disable Editing Aggregate"
obj: bpy.props.StringProperty()
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
@@ -81,9 +87,13 @@ class DisableEditingAggregate(bpy.types.Operator):
class AddAggregate(bpy.types.Operator):
bl_idname = "bim.add_aggregate"
bl_label = "Add Aggregate"
bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
aggregate_collection = bpy.data.collections.new("IfcElementAssembly/Assembly")
bpy.context.scene.collection.children.link(aggregate_collection)
@@ -16,6 +16,8 @@ class BIM_PT_aggregate(Panel):
props = context.active_object.BIMObjectProperties
if not props.ifc_definition_id:
return False
if not IfcStore.get_element(props.ifc_definition_id):
return False
if not IfcStore.get_file().by_id(props.ifc_definition_id).is_a("IfcObjectDefinition"):
return False
if props.ifc_definition_id not in Data.products:
@@ -9,6 +9,7 @@ from ifcopenshell.api.attribute.data import Data
class EnableEditingAttributes(bpy.types.Operator):
bl_idname = "bim.enable_editing_attributes"
bl_label = "Enable Editing Attributes"
bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
obj_type: bpy.props.StringProperty()
@@ -45,6 +46,7 @@ class EnableEditingAttributes(bpy.types.Operator):
class DisableEditingAttributes(bpy.types.Operator):
bl_idname = "bim.disable_editing_attributes"
bl_label = "Disable Editing Attributes"
bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
obj_type: bpy.props.StringProperty()
@@ -61,10 +63,14 @@ class DisableEditingAttributes(bpy.types.Operator):
class EditAttributes(bpy.types.Operator):
bl_idname = "bim.edit_attributes"
bl_label = "Edit Attributes"
bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
obj_type: bpy.props.StringProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
self.file = IfcStore.get_file()
if self.obj_type == "Object":
obj = bpy.data.objects.get(self.obj)
@@ -112,6 +118,7 @@ class EditAttributes(bpy.types.Operator):
class GenerateGlobalId(bpy.types.Operator):
bl_idname = "bim.generate_global_id"
bl_label = "Regenerate GlobalId"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
index = bpy.context.active_object.BIMAttributeProperties.attributes.find("GlobalId")
@@ -78,6 +78,8 @@ class BIM_PT_object_attributes(Panel):
@classmethod
def poll(cls, context):
if not IfcStore.get_element(context.active_object.BIMObjectProperties.ifc_definition_id):
return False
return bool(context.active_object.BIMObjectProperties.ifc_definition_id)
def draw(self, context):
@@ -20,6 +20,8 @@ class BIM_PT_boundary(Panel):
props = context.active_object.BIMObjectProperties
if not props.ifc_definition_id:
return False
if not IfcStore.get_element(props.ifc_definition_id):
return False
if IfcStore.get_file().by_id(props.ifc_definition_id).is_a() not in ["IfcSpace", "IfcExternalSpatialElement"]:
return False
return True
@@ -26,8 +26,12 @@ class LoadClassificationLibrary(bpy.types.Operator):
class AddClassification(bpy.types.Operator):
bl_idname = "bim.add_classification"
bl_label = "Add Classification"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
props = context.scene.BIMClassificationProperties
ifcopenshell.api.run(
"classification.add_classification",
@@ -41,6 +45,7 @@ class AddClassification(bpy.types.Operator):
class EnableEditingClassification(bpy.types.Operator):
bl_idname = "bim.enable_editing_classification"
bl_label = "Enable Editing Classification"
bl_options = {"REGISTER", "UNDO"}
classification: bpy.props.IntProperty()
def execute(self, context):
@@ -64,6 +69,7 @@ class EnableEditingClassification(bpy.types.Operator):
class DisableEditingClassification(bpy.types.Operator):
bl_idname = "bim.disable_editing_classification"
bl_label = "Disable Editing Classification"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
context.scene.BIMClassificationProperties.active_classification_id = 0
@@ -73,9 +79,13 @@ class DisableEditingClassification(bpy.types.Operator):
class RemoveClassification(bpy.types.Operator):
bl_idname = "bim.remove_classification"
bl_label = "Remove Classification"
bl_options = {"REGISTER", "UNDO"}
classification: bpy.props.IntProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"classification.remove_classification",
@@ -89,8 +99,12 @@ class RemoveClassification(bpy.types.Operator):
class EditClassification(bpy.types.Operator):
bl_idname = "bim.edit_classification"
bl_label = "Edit Classification"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
props = context.scene.BIMClassificationProperties
attributes = {}
for attribute in props.classification_attributes:
@@ -114,6 +128,7 @@ class EditClassification(bpy.types.Operator):
class EnableEditingClassificationReference(bpy.types.Operator):
bl_idname = "bim.enable_editing_classification_reference"
bl_label = "Enable Editing Classification Reference"
bl_options = {"REGISTER", "UNDO"}
reference: bpy.props.IntProperty()
obj: bpy.props.StringProperty()
@@ -138,6 +153,7 @@ class EnableEditingClassificationReference(bpy.types.Operator):
class DisableEditingClassificationReference(bpy.types.Operator):
bl_idname = "bim.disable_editing_classification_reference"
bl_label = "Disable Editing Classification Reference"
bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
def execute(self, context):
@@ -149,10 +165,14 @@ class DisableEditingClassificationReference(bpy.types.Operator):
class RemoveClassificationReference(bpy.types.Operator):
bl_idname = "bim.remove_classification_reference"
bl_label = "Remove Classification Reference"
bl_options = {"REGISTER", "UNDO"}
reference: bpy.props.IntProperty()
obj: bpy.props.StringProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
self.file = IfcStore.get_file()
ifcopenshell.api.run(
@@ -171,9 +191,13 @@ class RemoveClassificationReference(bpy.types.Operator):
class EditClassificationReference(bpy.types.Operator):
bl_idname = "bim.edit_classification_reference"
bl_label = "Edit Classification Reference"
bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
props = obj.BIMClassificationReferenceProperties
attributes = {}
@@ -196,10 +220,14 @@ class EditClassificationReference(bpy.types.Operator):
class AddClassificationReference(bpy.types.Operator):
bl_idname = "bim.add_classification_reference"
bl_label = "Add Classification Reference"
bl_options = {"REGISTER", "UNDO"}
reference: bpy.props.IntProperty()
obj: bpy.props.StringProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
self.file = IfcStore.get_file()
@@ -229,6 +257,7 @@ class AddClassificationReference(bpy.types.Operator):
class ChangeClassificationLevel(bpy.types.Operator):
bl_idname = "bim.change_classification_level"
bl_label = "Change Classification Level"
bl_options = {"REGISTER", "UNDO"}
parent_id: bpy.props.IntProperty()
def execute(self, context):
@@ -70,6 +70,8 @@ class BIM_PT_classification_references(Panel):
@classmethod
def poll(cls, context):
if not IfcStore.get_element(context.active_object.BIMObjectProperties.ifc_definition_id):
return False
return bool(context.active_object.BIMObjectProperties.ifc_definition_id)
def draw(self, context):
@@ -9,6 +9,7 @@ from ifcopenshell.api.constraint.data import Data
class LoadObjectives(bpy.types.Operator):
bl_idname = "bim.load_objectives"
bl_label = "Load Objectives"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
props = context.scene.BIMConstraintProperties
@@ -26,6 +27,7 @@ class LoadObjectives(bpy.types.Operator):
class DisableConstraintEditingUI(bpy.types.Operator):
bl_idname = "bim.disable_constraint_editing_ui"
bl_label = "Disable Constraint Editing UI"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
context.scene.BIMConstraintProperties.is_editing = ""
@@ -36,6 +38,7 @@ class DisableConstraintEditingUI(bpy.types.Operator):
class EnableEditingConstraint(bpy.types.Operator):
bl_idname = "bim.enable_editing_constraint"
bl_label = "Enable Editing Constraint"
bl_options = {"REGISTER", "UNDO"}
constraint: bpy.props.IntProperty()
def execute(self, context):
@@ -68,6 +71,7 @@ class EnableEditingConstraint(bpy.types.Operator):
class DisableEditingConstraint(bpy.types.Operator):
bl_idname = "bim.disable_editing_constraint"
bl_label = "Disable Editing Constraint"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
context.scene.BIMConstraintProperties.active_constraint_id = 0
@@ -77,8 +81,12 @@ class DisableEditingConstraint(bpy.types.Operator):
class AddObjective(bpy.types.Operator):
bl_idname = "bim.add_objective"
bl_label = "Add Objective"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
result = ifcopenshell.api.run("constraint.add_objective", IfcStore.get_file())
Data.load(IfcStore.get_file())
bpy.ops.bim.load_objectives()
@@ -89,8 +97,12 @@ class AddObjective(bpy.types.Operator):
class EditObjective(bpy.types.Operator):
bl_idname = "bim.edit_objective"
bl_label = "Edit Objective"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
props = context.scene.BIMConstraintProperties
attributes = {}
for attribute in props.constraint_attributes:
@@ -114,9 +126,13 @@ class EditObjective(bpy.types.Operator):
class RemoveConstraint(bpy.types.Operator):
bl_idname = "bim.remove_constraint"
bl_label = "Remove Constraint"
bl_options = {"REGISTER", "UNDO"}
constraint: bpy.props.IntProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
props = context.scene.BIMConstraintProperties
self.file = IfcStore.get_file()
ifcopenshell.api.run(
@@ -131,6 +147,7 @@ class RemoveConstraint(bpy.types.Operator):
class EnableAssigningConstraint(bpy.types.Operator):
bl_idname = "bim.enable_assigning_constraint"
bl_label = "Enable Assigning Constraint"
bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
def execute(self, context):
@@ -145,6 +162,7 @@ class EnableAssigningConstraint(bpy.types.Operator):
class DisableAssigningConstraint(bpy.types.Operator):
bl_idname = "bim.disable_assigning_constraint"
bl_label = "Disable Assigning Constraint"
bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
def execute(self, context):
@@ -157,10 +175,14 @@ class DisableAssigningConstraint(bpy.types.Operator):
class AssignConstraint(bpy.types.Operator):
bl_idname = "bim.assign_constraint"
bl_label = "Assign Constraint"
bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
constraint: bpy.props.IntProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
self.file = IfcStore.get_file()
ifcopenshell.api.run(
@@ -178,10 +200,14 @@ class AssignConstraint(bpy.types.Operator):
class UnassignConstraint(bpy.types.Operator):
bl_idname = "bim.unassign_constraint"
bl_label = "Unassign Constraint"
bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
constraint: bpy.props.IntProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
self.file = IfcStore.get_file()
ifcopenshell.api.run(
@@ -64,6 +64,8 @@ class BIM_PT_object_constraints(Panel):
@classmethod
def poll(cls, context):
if not IfcStore.get_element(context.active_object.BIMObjectProperties.ifc_definition_id):
return False
return bool(context.active_object.BIMObjectProperties.ifc_definition_id)
def draw(self, context):
@@ -7,11 +7,15 @@ from ifcopenshell.api.context.data import Data
class AddSubcontext(bpy.types.Operator):
bl_idname = "bim.add_subcontext"
bl_label = "Add Subcontext"
bl_options = {"REGISTER", "UNDO"}
context: bpy.props.StringProperty()
subcontext: bpy.props.StringProperty()
target_view: bpy.props.StringProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"context.add_context",
@@ -29,9 +33,13 @@ class AddSubcontext(bpy.types.Operator):
class RemoveSubcontext(bpy.types.Operator):
bl_idname = "bim.remove_subcontext"
bl_label = "Remove Context"
bl_options = {"REGISTER", "UNDO"}
ifc_definition_id: bpy.props.IntProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"context.remove_context", self.file, **{"context": self.file.by_id(self.ifc_definition_id)}
@@ -13,6 +13,7 @@ from blenderbim.bim.handler import purge_module_data
class AddCsvAttribute(bpy.types.Operator):
bl_idname = "bim.add_csv_attribute"
bl_label = "Add CSV Attribute"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
attribute = context.scene.CsvProperties.csv_attributes.add()
@@ -22,6 +23,7 @@ class AddCsvAttribute(bpy.types.Operator):
class RemoveCsvAttribute(bpy.types.Operator):
bl_idname = "bim.remove_csv_attribute"
bl_label = "Remove CSV Attribute"
bl_options = {"REGISTER", "UNDO"}
index: bpy.props.IntProperty()
def execute(self, context):
@@ -98,6 +100,7 @@ class ImportIfcCsv(bpy.types.Operator):
class EyedropIfcCsv(bpy.types.Operator):
bl_idname = "bim.eyedrop_ifccsv"
bl_label = "Query Selected Items"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
global_ids = []
@@ -112,6 +115,7 @@ class EyedropIfcCsv(bpy.types.Operator):
class SelectCsvIfcFile(bpy.types.Operator):
bl_idname = "bim.select_csv_ifc_file"
bl_label = "Select CSV IFC File"
bl_options = {"REGISTER", "UNDO"}
filename_ext = ".ifc"
filter_glob: bpy.props.StringProperty(default="*.ifc;*.ifczip;*.ifcxml", options={"HIDDEN"})
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
@@ -2,6 +2,8 @@ import bpy
from . import ui, prop, operator
classes = (
operator.PrintIfcFile,
operator.ValidateIfcFile,
operator.ProfileImportIFC,
operator.CreateAllShapes,
operator.CreateShapeFromStepId,
@@ -5,6 +5,28 @@ import blenderbim.bim.import_ifc as import_ifc
from blenderbim.bim.ifc import IfcStore
class PrintIfcFile(bpy.types.Operator):
bl_idname = "bim.print_ifc_file"
bl_label = "Print IFC File"
def execute(self, context):
print(IfcStore.get_file().wrapped_data.to_string())
return {"FINISHED"}
class ValidateIfcFile(bpy.types.Operator):
bl_idname = "bim.validate_ifc_file"
bl_label = "Validate IFC File"
def execute(self, context):
import ifcopenshell.validate
logger = logging.getLogger("validate")
logger.setLevel(logging.DEBUG)
ifcopenshell.validate.validate(IfcStore.get_file(), logger)
return {"FINISHED"}
class ProfileImportIFC(bpy.types.Operator):
bl_idname = "bim.profile_import_ifc"
bl_label = "Profile Import IFC"
@@ -20,6 +20,9 @@ class BIM_PT_debug(Panel):
row.operator("bim.validate_ifc_file", icon="CHECKMARK", text="")
row.operator("bim.select_ifc_file", icon="FILE_FOLDER", text="")
row = layout.row()
row.operator("bim.print_ifc_file")
row = layout.row()
row.operator("bim.create_all_shapes")
@@ -9,6 +9,7 @@ from ifcopenshell.api.document.data import Data
class LoadInformation(bpy.types.Operator):
bl_idname = "bim.load_information"
bl_label = "Load Information"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
self.file = IfcStore.get_file()
@@ -31,6 +32,7 @@ class LoadInformation(bpy.types.Operator):
class LoadDocumentReferences(bpy.types.Operator):
bl_idname = "bim.load_document_references"
bl_label = "Load Document References"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
self.file = IfcStore.get_file()
@@ -53,6 +55,7 @@ class LoadDocumentReferences(bpy.types.Operator):
class DisableDocumentEditingUI(bpy.types.Operator):
bl_idname = "bim.disable_document_editing_ui"
bl_label = "Disable Document Editing UI"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
context.scene.BIMDocumentProperties.is_editing = ""
@@ -63,6 +66,7 @@ class DisableDocumentEditingUI(bpy.types.Operator):
class EnableEditingDocument(bpy.types.Operator):
bl_idname = "bim.enable_editing_document"
bl_label = "Enable Editing Document"
bl_options = {"REGISTER", "UNDO"}
document: bpy.props.IntProperty()
def execute(self, context):
@@ -99,6 +103,7 @@ class EnableEditingDocument(bpy.types.Operator):
class DisableEditingDocument(bpy.types.Operator):
bl_idname = "bim.disable_editing_document"
bl_label = "Disable Editing Document"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
context.scene.BIMDocumentProperties.active_document_id = 0
@@ -108,8 +113,12 @@ class DisableEditingDocument(bpy.types.Operator):
class AddInformation(bpy.types.Operator):
bl_idname = "bim.add_information"
bl_label = "Add Information"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
result = ifcopenshell.api.run("document.add_information", IfcStore.get_file())
Data.load(IfcStore.get_file())
bpy.ops.bim.load_information()
@@ -120,8 +129,12 @@ class AddInformation(bpy.types.Operator):
class AddDocumentReference(bpy.types.Operator):
bl_idname = "bim.add_document_reference"
bl_label = "Add Document Reference"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
result = ifcopenshell.api.run("document.add_reference", IfcStore.get_file())
Data.load(IfcStore.get_file())
bpy.ops.bim.load_document_references()
@@ -132,8 +145,12 @@ class AddDocumentReference(bpy.types.Operator):
class EditInformation(bpy.types.Operator):
bl_idname = "bim.edit_information"
bl_label = "Edit Information"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
props = context.scene.BIMDocumentProperties
attributes = {}
for attribute in props.document_attributes:
@@ -157,8 +174,12 @@ class EditInformation(bpy.types.Operator):
class EditDocumentReference(bpy.types.Operator):
bl_idname = "bim.edit_document_reference"
bl_label = "Edit Document Reference"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
props = context.scene.BIMDocumentProperties
attributes = {}
for attribute in props.document_attributes:
@@ -180,9 +201,13 @@ class EditDocumentReference(bpy.types.Operator):
class RemoveDocument(bpy.types.Operator):
bl_idname = "bim.remove_document"
bl_label = "Remove Document"
bl_options = {"REGISTER", "UNDO"}
document: bpy.props.IntProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
props = context.scene.BIMDocumentProperties
self.file = IfcStore.get_file()
ifcopenshell.api.run("document.remove_document", self.file, **{"document": self.file.by_id(self.document)})
@@ -197,6 +222,7 @@ class RemoveDocument(bpy.types.Operator):
class EnableAssigningDocument(bpy.types.Operator):
bl_idname = "bim.enable_assigning_document"
bl_label = "Enable Assigning Document"
bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
def execute(self, context):
@@ -213,6 +239,7 @@ class EnableAssigningDocument(bpy.types.Operator):
class DisableAssigningDocument(bpy.types.Operator):
bl_idname = "bim.disable_assigning_document"
bl_label = "Disable Assigning Document"
bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
def execute(self, context):
@@ -225,10 +252,14 @@ class DisableAssigningDocument(bpy.types.Operator):
class AssignDocument(bpy.types.Operator):
bl_idname = "bim.assign_document"
bl_label = "Assign Document"
bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
document: bpy.props.IntProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
self.file = IfcStore.get_file()
ifcopenshell.api.run(
@@ -246,10 +277,14 @@ class AssignDocument(bpy.types.Operator):
class UnassignDocument(bpy.types.Operator):
bl_idname = "bim.unassign_document"
bl_label = "Unassign Document"
bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
document: bpy.props.IntProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
self.file = IfcStore.get_file()
ifcopenshell.api.run(
@@ -68,6 +68,8 @@ class BIM_PT_object_documents(Panel):
@classmethod
def poll(cls, context):
if not IfcStore.get_element(context.active_object.BIMObjectProperties.ifc_definition_id):
return False
return bool(context.active_object.BIMObjectProperties.ifc_definition_id)
def draw(self, context):
@@ -396,7 +396,7 @@ class CreateDrawing(bpy.types.Operator):
def get_classes(self, element, position, svg_writer):
classes = [position, element.is_a()]
material = ifcopenshell.util.element.get_material(element)
material = ifcopenshell.util.element.get_material(element, should_skip_usage=True)
if material:
classes.append("material-{}".format(re.sub("[^0-9a-zA-Z]+", "", self.get_material_name(material))))
classes.append("globalid-{}".format(element.GlobalId))
@@ -10,6 +10,7 @@ classes = (
operator.UpdateParametricRepresentation,
operator.GetRepresentationIfcParameters,
prop.BIMGeometryProperties,
ui.BIM_PT_derived_placements,
ui.BIM_PT_representations,
ui.BIM_PT_mesh,
ui.BIM_PT_workarounds,
@@ -1,5 +1,6 @@
import bpy
import bmesh
import mathutils
import ifcopenshell
import ifcopenshell.util.unit
from math import pi
@@ -197,19 +198,26 @@ class Helper:
return {"profile": outer_loop, "inner_curves": inner_loops, "extrusion": extrusion}
# An extrusion edge is an edge that shares a single vertex with a profile
# face and is not parallel to the face.
# face and is not on the plane of the face.
def detect_extrusion_edge(self, bm, profile_face):
bm.edges.ensure_lookup_table()
extrusion = None
face_verts_set = set(profile_face.verts)
for edge in bm.edges:
edge_vector = edge.verts[1].co - edge.verts[0].co
unshared_verts = set(edge.verts) - face_verts_set
angle_to_normal = edge_vector.angle(profile_face.normal)
if len(unshared_verts) == 1 and (angle_to_normal < 0.001 or angle_to_normal - pi < 0.001):
if unshared_verts.pop() == edge.verts[1]:
return [edge.verts[0].index, edge.verts[1].index]
return [edge.verts[1].index, edge.verts[0].index]
if len(unshared_verts) == 1:
unshared_vert = unshared_verts.pop()
if (
abs(
mathutils.geometry.distance_point_to_plane(
unshared_vert.co, profile_face.verts[0].co, profile_face.normal
)
)
> 0.001
):
if unshared_vert == edge.verts[1]:
return [edge.verts[0].index, edge.verts[1].index]
return [edge.verts[1].index, edge.verts[0].index]
def create_extruded_area_solid(self, mesh, extrusion_indices, profile_def):
position = self.create_ifc_axis_2_placement_3d(
@@ -17,9 +17,13 @@ from mathutils import Vector
class EditObjectPlacement(bpy.types.Operator):
bl_idname = "bim.edit_object_placement"
bl_label = "Edit Object Placement"
bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
objs = [bpy.data.objects.get(self.obj)] if self.obj else bpy.context.selected_objects
self.file = IfcStore.get_file()
# TODO: determine how to deal with this module dependency
@@ -55,10 +59,16 @@ class EditObjectPlacement(bpy.types.Operator):
class AddRepresentation(bpy.types.Operator):
bl_idname = "bim.add_representation"
bl_label = "Add Representation"
bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
context_id: bpy.props.IntProperty()
ifc_representation_class: bpy.props.StringProperty()
profile_set_usage: bpy.props.IntProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
self.file = IfcStore.get_file()
@@ -91,6 +101,8 @@ class AddRepresentation(bpy.types.Operator):
"total_items": max(1, len(obj.material_slots)),
"should_force_faceted_brep": context.scene.BIMGeometryProperties.should_force_faceted_brep,
"should_force_triangulation": context.scene.BIMGeometryProperties.should_force_triangulation,
"ifc_representation_class": self.ifc_representation_class,
"profile_set_usage": self.file.by_id(self.profile_set_usage) if self.profile_set_usage else None
}
result = ifcopenshell.api.run("geometry.add_representation", self.file, **representation_data)
@@ -143,6 +155,7 @@ class AddRepresentation(bpy.types.Operator):
class SwitchRepresentation(bpy.types.Operator):
bl_idname = "bim.switch_representation"
bl_label = "Switch Representation"
bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
ifc_definition_id: bpy.props.IntProperty()
should_reload: bpy.props.BoolProperty()
@@ -215,10 +228,14 @@ class SwitchRepresentation(bpy.types.Operator):
class RemoveRepresentation(bpy.types.Operator):
bl_idname = "bim.remove_representation"
bl_label = "Remove Representation"
bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
representation_id: bpy.props.IntProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
self.file = IfcStore.get_file()
representation = self.file.by_id(self.representation_id)
obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
@@ -251,10 +268,14 @@ class RemoveRepresentation(bpy.types.Operator):
class UpdateRepresentation(bpy.types.Operator):
bl_idname = "bim.update_representation"
bl_label = "Update Representation"
bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
ifc_representation_class: bpy.props.StringProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
if not ContextData.is_loaded:
ContextData.load(IfcStore.get_file())
@@ -329,6 +350,7 @@ class UpdateRepresentation(bpy.types.Operator):
class UpdateParametricRepresentation(bpy.types.Operator):
bl_idname = "bim.update_parametric_representation"
bl_label = "Update Parametric Representation"
bl_options = {"REGISTER", "UNDO"}
index: bpy.props.IntProperty()
def execute(self, context):
@@ -344,6 +366,7 @@ class UpdateParametricRepresentation(bpy.types.Operator):
class GetRepresentationIfcParameters(bpy.types.Operator):
bl_idname = "bim.get_representation_ifc_parameters"
bl_label = "Get Representation IFC Parameters"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
self.file = IfcStore.get_file()
@@ -14,6 +14,8 @@ class BIM_PT_representations(Panel):
@classmethod
def poll(cls, context):
if not IfcStore.get_element(context.active_object.BIMObjectProperties.ifc_definition_id):
return False
return IfcStore.get_file()
def draw(self, context):
@@ -111,6 +113,36 @@ def BIM_PT_transform(self, context):
row.operator("bim.edit_object_placement")
class BIM_PT_derived_placements(Panel):
bl_label = "IFC Derived Placements"
bl_idname = "BIM_PT_derived_placements"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
bl_parent_id = "OBJECT_PT_transform"
def draw(self, context):
z = context.active_object.matrix_world.translation.z
z_values = [co[2] for co in context.active_object.bound_box]
row = self.layout.row(align=True)
row.label(text="Min Global Z")
row.label(text="{0:.3f}".format(min(z_values) + z))
row = self.layout.row(align=True)
row.label(text="Max Global Z")
row.label(text="{0:.3f}".format(max(z_values) + z))
collection = bpy.data.objects.get(context.active_object.users_collection[0].name)
if collection:
collection_z = collection.matrix_world.translation.z
row = self.layout.row(align=True)
row.label(text="Min Local Z")
row.label(text="{0:.3f}".format(min(z_values) + z - collection_z))
row = self.layout.row(align=True)
row.label(text="Max Local Z")
row.label(text="{0:.3f}".format(max(z_values) + z - collection_z))
class BIM_PT_workarounds(Panel):
bl_label = "IFC Vendor Workarounds"
bl_idname = "BIM_PT_workarounds"
@@ -117,6 +117,7 @@ class BIM_PT_gis(Panel):
row = self.layout.row(align=True)
row.label(text="XAxisOrdinate")
row.label(text=props.blender_x_axis_ordinate)
row = self.layout.row(align=True)
row.label(text="Derived Grid North")
row.label(
text=str(
@@ -8,6 +8,7 @@ from ifcopenshell.api.group.data import Data
class LoadGroups(bpy.types.Operator):
bl_idname = "bim.load_groups"
bl_label = "Load Groups"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
props = context.scene.BIMGroupProperties
@@ -25,6 +26,7 @@ class LoadGroups(bpy.types.Operator):
class DisableGroupEditingUI(bpy.types.Operator):
bl_idname = "bim.disable_group_editing_ui"
bl_label = "Disable Group Editing UI"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
context.scene.BIMGroupProperties.is_editing = False
@@ -34,8 +36,12 @@ class DisableGroupEditingUI(bpy.types.Operator):
class AddGroup(bpy.types.Operator):
bl_idname = "bim.add_group"
bl_label = "Add Group"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
result = ifcopenshell.api.run("group.add_group", IfcStore.get_file())
Data.load(IfcStore.get_file())
bpy.ops.bim.load_groups()
@@ -46,8 +52,12 @@ class AddGroup(bpy.types.Operator):
class EditGroup(bpy.types.Operator):
bl_idname = "bim.edit_group"
bl_label = "Edit Group"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
props = context.scene.BIMGroupProperties
attributes = {}
for attribute in props.group_attributes:
@@ -67,9 +77,13 @@ class EditGroup(bpy.types.Operator):
class RemoveGroup(bpy.types.Operator):
bl_idname = "bim.remove_group"
bl_label = "Remove Group"
bl_options = {"REGISTER", "UNDO"}
group: bpy.props.IntProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
props = context.scene.BIMGroupProperties
self.file = IfcStore.get_file()
ifcopenshell.api.run("group.remove_group", self.file, **{"group": self.file.by_id(self.group)})
@@ -81,6 +95,7 @@ class RemoveGroup(bpy.types.Operator):
class EnableEditingGroup(bpy.types.Operator):
bl_idname = "bim.enable_editing_group"
bl_label = "Enable Editing Group"
bl_options = {"REGISTER", "UNDO"}
group: bpy.props.IntProperty()
def execute(self, context):
@@ -106,6 +121,7 @@ class EnableEditingGroup(bpy.types.Operator):
class DisableEditingGroup(bpy.types.Operator):
bl_idname = "bim.disable_editing_group"
bl_label = "Disable Editing Group"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
context.scene.BIMGroupProperties.active_group_id = 0
@@ -115,10 +131,14 @@ class DisableEditingGroup(bpy.types.Operator):
class AssignGroup(bpy.types.Operator):
bl_idname = "bim.assign_group"
bl_label = "Assign Group"
bl_options = {"REGISTER", "UNDO"}
product: bpy.props.StringProperty()
group: bpy.props.IntProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
product = bpy.data.objects.get(self.product) if self.product else context.active_object
self.file = IfcStore.get_file()
ifcopenshell.api.run(
@@ -136,10 +156,14 @@ class AssignGroup(bpy.types.Operator):
class UnassignGroup(bpy.types.Operator):
bl_idname = "bim.unassign_group"
bl_label = "Unassign Group"
bl_options = {"REGISTER", "UNDO"}
product: bpy.props.StringProperty()
group: bpy.props.IntProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
product = bpy.data.objects.get(self.product) if self.product else context.active_object
self.file = IfcStore.get_file()
ifcopenshell.api.run(
@@ -157,6 +181,7 @@ class UnassignGroup(bpy.types.Operator):
class SelectGroupProducts(bpy.types.Operator):
bl_idname = "bim.select_group_products"
bl_label = "Select Group Products"
bl_options = {"REGISTER", "UNDO"}
group: bpy.props.IntProperty()
def execute(self, context):
@@ -9,6 +9,7 @@ from ifcopenshell.api.layer.data import Data
class LoadLayers(bpy.types.Operator):
bl_idname = "bim.load_layers"
bl_label = "Load Layers"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
self.file = IfcStore.get_file()
@@ -27,6 +28,7 @@ class LoadLayers(bpy.types.Operator):
class DisableLayerEditingUI(bpy.types.Operator):
bl_idname = "bim.disable_layer_editing_ui"
bl_label = "Disable Layer Editing UI"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
context.scene.BIMLayerProperties.is_editing = False
@@ -36,6 +38,7 @@ class DisableLayerEditingUI(bpy.types.Operator):
class EnableEditingLayer(bpy.types.Operator):
bl_idname = "bim.enable_editing_layer"
bl_label = "Enable Editing Layer"
bl_options = {"REGISTER", "UNDO"}
layer: bpy.props.IntProperty()
def execute(self, context):
@@ -61,6 +64,7 @@ class EnableEditingLayer(bpy.types.Operator):
class DisableEditingLayer(bpy.types.Operator):
bl_idname = "bim.disable_editing_layer"
bl_label = "Disable Editing Layer"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
context.scene.BIMLayerProperties.active_layer_id = 0
@@ -70,8 +74,12 @@ class DisableEditingLayer(bpy.types.Operator):
class AddPresentationLayer(bpy.types.Operator):
bl_idname = "bim.add_presentation_layer"
bl_label = "Add Layer"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
result = ifcopenshell.api.run("layer.add_layer", IfcStore.get_file())
Data.load(IfcStore.get_file())
bpy.ops.bim.load_layers()
@@ -82,8 +90,12 @@ class AddPresentationLayer(bpy.types.Operator):
class EditPresentationLayer(bpy.types.Operator):
bl_idname = "bim.edit_presentation_layer"
bl_label = "Edit Layer"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
props = context.scene.BIMLayerProperties
attributes = {}
for attribute in props.layer_attributes:
@@ -103,9 +115,13 @@ class EditPresentationLayer(bpy.types.Operator):
class RemovePresentationLayer(bpy.types.Operator):
bl_idname = "bim.remove_presentation_layer"
bl_label = "Remove Presentation Layer"
bl_options = {"REGISTER", "UNDO"}
layer: bpy.props.IntProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
props = context.scene.BIMLayerProperties
self.file = IfcStore.get_file()
ifcopenshell.api.run("layer.remove_layer", self.file, **{"layer": self.file.by_id(self.layer)})
@@ -117,10 +133,14 @@ class RemovePresentationLayer(bpy.types.Operator):
class AssignPresentationLayer(bpy.types.Operator):
bl_idname = "bim.assign_presentation_layer"
bl_label = "Assign Presentation Layer"
bl_options = {"REGISTER", "UNDO"}
item: bpy.props.StringProperty()
layer: bpy.props.IntProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
item = bpy.data.meshes.get(self.item) if self.item else context.active_object.data
self.file = IfcStore.get_file()
ifcopenshell.api.run(
@@ -135,10 +155,14 @@ class AssignPresentationLayer(bpy.types.Operator):
class UnassignPresentationLayer(bpy.types.Operator):
bl_idname = "bim.unassign_presentation_layer"
bl_label = "Unassign Presentation Layer"
bl_options = {"REGISTER", "UNDO"}
item: bpy.props.StringProperty()
layer: bpy.props.IntProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
item = bpy.data.meshes.get(self.item) if self.item else context.active_object.data
self.file = IfcStore.get_file()
ifcopenshell.api.run(
@@ -2,6 +2,7 @@ import bpy
import json
import ifcopenshell.api
import ifcopenshell.util.attribute
import blenderbim.bim.helper
from blenderbim.bim.module.material.prop import purge as material_prop_purge
from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.material.data import Data
@@ -26,7 +27,7 @@ class AssignParameterizedProfile(bpy.types.Operator):
ifcopenshell.api.run(
"material.assign_profile",
self.file,
**{"material_profile": self.file.by_id(self.material_profile), "profile": profile}
**{"material_profile": self.file.by_id(self.material_profile), "profile": profile},
)
Data.load_profiles()
ProfileData.load(self.file)
@@ -175,9 +176,7 @@ class RemoveProfile(bpy.types.Operator):
def execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"material.remove_profile", self.file, **{"profile": self.file.by_id(self.profile)}
)
ifcopenshell.api.run("material.remove_profile", self.file, **{"profile": self.file.by_id(self.profile)})
Data.load_profiles()
return {"FINISHED"}
@@ -313,20 +312,28 @@ class EnableEditingAssignedMaterial(bpy.types.Operator):
elif product_data["type"] == "IfcMaterialLayerSet":
material_set_data = Data.layer_sets[product_data["id"]]
elif product_data["type"] == "IfcMaterialLayerSetUsage":
layer_set_usage = Data.layer_set_usages[product_data["id"]]
material_set_data = Data.layer_sets[layer_set_usage["ForLayerSet"]]
material_set_usage = Data.layer_set_usages[product_data["id"]]
material_set_data = Data.layer_sets[material_set_usage["ForLayerSet"]]
material_set_class = "IfcMaterialLayerSet"
elif product_data["type"] == "IfcMaterialProfileSet":
material_set_data = Data.profile_sets[product_data["id"]]
elif product_data["type"] == "IfcMaterialProfileSetUsage":
profile_set_usage = Data.profile_set_usages[product_data["id"]]
material_set_data = Data.profile_sets[profile_set_usage["ForProfileSet"]]
material_set_usage = Data.profile_set_usages[product_data["id"]]
material_set_data = Data.profile_sets[material_set_usage["ForProfileSet"]]
material_set_class = "IfcMaterialProfileSet"
elif product_data["type"] == "IfcMaterialList":
material_set_data = Data.lists[product_data["id"]]
else:
material_set_data = {}
while len(props.material_set_usage_attributes) > 0:
props.material_set_usage_attributes.remove(0)
if "Usage" in product_data["type"]:
blenderbim.bim.helper.import_attributes(
product_data["type"], props.material_set_usage_attributes, material_set_usage, self.import_attributes
)
while len(props.material_set_attributes) > 0:
props.material_set_attributes.remove(0)
@@ -340,6 +347,36 @@ class EnableEditingAssignedMaterial(bpy.types.Operator):
new.string_value = "" if new.is_null else material_set_data[attribute.name()]
return {"FINISHED"}
def import_attributes(self, name, prop, data):
if name == "CardinalPoint":
# TODO: complain to buildingSMART
cardinal_point_map = {
1: "bottom left",
2: "bottom centre",
3: "bottom right",
4: "mid-depth left",
5: "mid-depth centre",
6: "mid-depth right",
7: "top left",
8: "top centre",
9: "top right",
10: "geometric centroid",
11: "bottom in line with the geometric centroid",
12: "left in line with the geometric centroid",
13: "right in line with the geometric centroid",
14: "top in line with the geometric centroid",
15: "shear centre",
16: "bottom in line with the shear centre",
17: "left in line with the shear centre",
18: "right in line with the shear centre",
19: "top in line with the shear centre",
}
prop.data_type = "enum"
prop.enum_items = json.dumps(cardinal_point_map)
if data[name]:
prop.enum_value = str(data[name])
return True
class DisableEditingAssignedMaterial(bpy.types.Operator):
bl_idname = "bim.disable_editing_assigned_material"
@@ -358,6 +395,7 @@ class EditAssignedMaterial(bpy.types.Operator):
bl_label = "Edit Assigned Material"
obj: bpy.props.StringProperty()
material_set: bpy.props.IntProperty()
material_set_usage: bpy.props.IntProperty()
def execute(self, context):
self.file = IfcStore.get_file()
@@ -379,12 +417,24 @@ class EditAssignedMaterial(bpy.types.Operator):
ifcopenshell.api.run(
"material.edit_assigned_material",
self.file,
**{
"element": material_set,
"attributes": attributes,
},
**{"element": material_set, "attributes": attributes},
)
Data.load(IfcStore.get_file(), obj.BIMObjectProperties.ifc_definition_id)
if self.material_set_usage:
material_set_usage = self.file.by_id(self.material_set_usage)
attributes = blenderbim.bim.helper.export_attributes(props.material_set_usage_attributes)
attributes["CardinalPoint"] = int(attributes["CardinalPoint"]) if attributes["CardinalPoint"] else None
ifcopenshell.api.run(
"material.edit_profile_usage",
self.file,
**{"usage": material_set_usage, "attributes": attributes},
)
if material_set_usage.is_a("IfcMaterialLayerSetUsage"):
Data.load_layer_usages()
elif material_set_usage.is_a("IfcMaterialProfileSetUsage"):
Data.load_profile_usages()
if material_set.is_a("IfcMaterialConstituentSet"):
Data.load_constituents()
elif material_set.is_a("IfcMaterialLayerSet"):
@@ -83,6 +83,7 @@ class BIMObjectMaterialProperties(PropertyGroup):
material_type: EnumProperty(items=getMaterialTypes, name="Material Type")
material: EnumProperty(items=getMaterials, name="Material")
is_editing: BoolProperty(name="Is Editing", default=False)
material_set_usage_attributes: CollectionProperty(name="Material Set Usage Attributes", type=Attribute)
material_set_attributes: CollectionProperty(name="Material Set Attributes", type=Attribute)
active_material_set_item_id: IntProperty(name="Active Material Set ID")
material_set_item_attributes: CollectionProperty(name="Material Set Item Attributes", type=Attribute)
@@ -1,3 +1,4 @@
import blenderbim.bim.helper
from bpy.types import Panel
from ifcopenshell.api.material.data import Data
from ifcopenshell.api.profile.data import Data as ProfileData
@@ -35,6 +36,8 @@ class BIM_PT_object_material(Panel):
props = context.active_object.BIMObjectProperties
if not props.ifc_definition_id:
return False
if not IfcStore.get_element(props.ifc_definition_id):
return False
if not hasattr(IfcStore.get_file().by_id(props.ifc_definition_id), "HasAssociations"):
return False
return True
@@ -109,6 +112,8 @@ class BIM_PT_object_material(Panel):
if self.props.is_editing:
op = row.operator("bim.edit_assigned_material", icon="CHECKMARK", text="")
op.material_set = self.material_set_id
if "Usage" in self.product_data["type"]:
op.material_set_usage = self.product_data["id"]
row.operator("bim.disable_editing_assigned_material", icon="CANCEL", text="")
else:
row.operator("bim.enable_editing_assigned_material", icon="GREASEPENCIL", text="")
@@ -140,6 +145,8 @@ class BIM_PT_object_material(Panel):
self.draw_read_only_set_ui()
def draw_editable_set_ui(self):
blenderbim.bim.helper.draw_attributes(self.props.material_set_usage_attributes, self.layout)
for attribute in self.props.material_set_attributes:
row = self.layout.row(align=True)
row.prop(attribute, "string_value", text=attribute.name)
@@ -265,6 +272,38 @@ class BIM_PT_object_material(Panel):
row.label(text="Description")
row.label(text=str(self.material_set_data["Description"]))
if self.product_data["type"] == "IfcMaterialProfileSetUsage":
# TODO: complain to buildingSMART
cardinal_point_map = {
1: "bottom left",
2: "bottom centre",
3: "bottom right",
4: "mid-depth left",
5: "mid-depth centre",
6: "mid-depth right",
7: "top left",
8: "top centre",
9: "top right",
10: "geometric centroid",
11: "bottom in line with the geometric centroid",
12: "left in line with the geometric centroid",
13: "right in line with the geometric centroid",
14: "top in line with the geometric centroid",
15: "shear centre",
16: "bottom in line with the shear centre",
17: "left in line with the shear centre",
18: "right in line with the shear centre",
19: "top in line with the shear centre",
}
if self.material_set_usage["CardinalPoint"]:
row = self.layout.row(align=True)
row.label(text="CardinalPoint")
row.label(text=cardinal_point_map[self.material_set_usage["CardinalPoint"]])
if self.material_set_usage["ReferenceExtent"]:
row = self.layout.row(align=True)
row.label(text="ReferenceExtent")
row.label(text=str(self.material_set_usage["ReferenceExtent"]))
total_thickness = 0
for item_id in self.set_items:
if self.product_data["type"] == "IfcMaterialList":
@@ -35,7 +35,7 @@ addon_keymaps = []
def register():
bpy.utils.register_tool(workspace.WallTool, after={"builtin.scale_cage"}, separator=True, group=True)
bpy.utils.register_tool(workspace.BimTool, after={"builtin.scale_cage"}, separator=True, group=True)
bpy.types.Scene.BIMModelProperties = bpy.props.PointerProperty(type=prop.BIMModelProperties)
bpy.types.VIEW3D_MT_mesh_add.append(grid.add_object_button)
bpy.types.VIEW3D_MT_mesh_add.append(stair.add_object_button)
@@ -52,7 +52,7 @@ def register():
def unregister():
bpy.utils.unregister_tool(workspace.WallTool)
bpy.utils.unregister_tool(workspace.BimTool)
del bpy.types.Scene.BIMModelProperties
bpy.app.handlers.load_post.remove(handler.load_post)
bpy.types.VIEW3D_MT_mesh_add.remove(grid.add_object_button)
@@ -0,0 +1,168 @@
import bpy
import bmesh
import math
import ifcopenshell
import ifcopenshell.util.type
import ifcopenshell.util.unit
import ifcopenshell.util.element
import mathutils.geometry
import blenderbim.bim.handler
from blenderbim.bim.ifc import IfcStore
from math import pi, degrees
from mathutils import Vector, Matrix
from ifcopenshell.api.pset.data import Data as PsetData
from ifcopenshell.api.material.data import Data as MaterialData
from blenderbim.bim.module.geometry.helper import Helper
def element_listener(element, obj):
blenderbim.bim.handler.subscribe_to(obj, "mode", mode_callback)
def mode_callback(obj, data):
for obj in bpy.context.selected_objects + [bpy.context.active_object]:
if (
obj.mode != "EDIT"
or not obj.data
or not isinstance(obj.data, (bpy.types.Mesh, bpy.types.Curve, bpy.types.TextCurve))
or not obj.BIMObjectProperties.ifc_definition_id
or not bpy.context.scene.BIMProjectProperties.is_authoring
):
return
product = IfcStore.get_file().by_id(obj.BIMObjectProperties.ifc_definition_id)
parametric = ifcopenshell.util.element.get_psets(product).get("EPset_Parametric")
if not parametric or parametric["Engine"] != "BlenderBIM.DumbColumn":
return
IfcStore.edited_objs.add(obj)
bm = bmesh.from_edit_mesh(obj.data)
bmesh.ops.dissolve_limit(bm, angle_limit=pi / 180 * 1, verts=bm.verts, edges=bm.edges)
bmesh.update_edit_mesh(obj.data)
bm.free()
def ensure_solid(usecase_path, ifc_file, settings):
product = ifc_file.by_id(settings["blender_object"].BIMObjectProperties.ifc_definition_id)
parametric = ifcopenshell.util.element.get_psets(product).get("EPset_Parametric")
if not parametric or parametric["Engine"] != "BlenderBIM.DumbColumn":
return
material = ifcopenshell.util.element.get_material(product)
if material and material.is_a("IfcMaterialProfileSetUsage"):
settings["profile_set_usage"] = material
else:
return
settings["ifc_representation_class"] = "IfcExtrudedAreaSolid/IfcMaterialProfileSetUsage"
class DumbColumnGenerator:
def __init__(self, relating_type):
self.relating_type = relating_type
def generate(self):
self.file = IfcStore.get_file()
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(IfcStore.get_file())
material = ifcopenshell.util.element.get_material(self.relating_type)
if material and material.is_a("IfcMaterialProfileSet"):
self.profile_set = material
else:
return
self.collection = bpy.context.view_layer.active_layer_collection.collection
self.collection_obj = bpy.data.objects.get(self.collection.name)
self.length = 3
self.rotation = 0
self.location = Vector((0, 0, 0))
return self.derive_from_cursor()
def derive_from_cursor(self):
self.location = bpy.context.scene.cursor.location
return self.create_column()
def create_column(self):
# A cube
verts = [
Vector((-1, -1, -1)),
Vector((-1, -1, 1)),
Vector((-1, 1, -1)),
Vector((-1, 1, 1)),
Vector((1, -1, -1)),
Vector((1, -1, 1)),
Vector((1, 1, -1)),
Vector((1, 1, 1)),
]
edges = []
faces = [
[0, 2, 3, 1],
[2, 3, 7, 6],
[4, 5, 7, 6],
[0, 1, 5, 4],
[1, 3, 7, 5],
[0, 2, 6, 4],
]
mesh = bpy.data.meshes.new(name="Dumb Column")
mesh.from_pydata(verts, edges, faces)
obj = bpy.data.objects.new("Column", mesh)
obj.name = "Column"
obj.location = self.location
if self.collection_obj and self.collection_obj.BIMObjectProperties.ifc_definition_id:
obj.location[2] = self.collection_obj.location[2]
self.collection.objects.link(obj)
bpy.ops.bim.assign_class(
obj=obj.name, ifc_class="IfcColumn", predefined_type="COLUMN", should_add_representation=False
)
element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
bpy.ops.bim.assign_type(relating_type=self.relating_type.id(), related_object=obj.name)
profile_set_usage = ifcopenshell.util.element.get_material(element)
bpy.ops.bim.add_representation(
obj=obj.name,
context_id=ifcopenshell.util.representation.get_context(self.file, "Model", "Body", "MODEL_VIEW").id(),
ifc_representation_class="IfcExtrudedAreaSolid/IfcMaterialProfileSetUsage",
profile_set_usage=profile_set_usage.id(),
)
representation = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
bpy.ops.bim.switch_representation(obj=obj.name, ifc_definition_id=representation.id(), should_reload=True)
pset = ifcopenshell.api.run("pset.add_pset", self.file, product=element, name="EPset_Parametric")
ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"Engine": "BlenderBIM.DumbColumn"})
MaterialData.load(self.file)
obj.select_set(True)
return obj
class DumbColumnRegenerator:
def regenerate_from_profile(self, usecase_path, ifc_file, settings):
self.file = IfcStore.get_file()
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(ifc_file)
profile = settings["profile"].Profile
if not profile:
return
for profile_set in [
mp.ToMaterialProfileSet[0] for mp in self.file.get_inverse(profile) if mp.is_a("IfcMaterialProfile")
]:
for inverse in ifc_file.get_inverse(profile_set):
if not inverse.is_a("IfcMaterialProfileSetUsage"):
continue
if ifc_file.schema == "IFC2X3":
for rel in ifc_file.get_inverse(inverse):
if not rel.is_a("IfcRelAssociatesMaterial"):
continue
for element in rel.RelatedObjects:
self.change_profile(element)
else:
for rel in inverse.AssociatedTo:
for element in rel.RelatedObjects:
self.change_profile(element)
def regenerate_from_type(self, usecase_path, ifc_file, settings):
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(ifc_file)
new_material = ifcopenshell.util.element.get_material(settings["relating_type"])
if not new_material or not new_material.is_a("IfcMaterialProfileSet"):
return
self.change_profile(settings["related_object"])
def change_profile(self, element):
obj = IfcStore.get_element(element.id())
if not obj:
return
representation = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
if representation:
bpy.ops.bim.switch_representation(obj=obj.name, ifc_definition_id=representation.id(), should_reload=True)
@@ -1,20 +1,67 @@
import bpy
import ifcopenshell
import ifcopenshell.api
from blenderbim.bim.module.model import product, wall, slab
from blenderbim.bim.module.model import product, wall, slab, column
from blenderbim.bim.ifc import IfcStore
from bpy.app.handlers import persistent
@persistent
def load_post(*args):
ifcopenshell.api.add_post_listener("geometry.add_representation", None, product.generate_box)
ifcopenshell.api.add_post_listener(
"geometry.add_representation", "BlenderBIM.Product.GenerateBox", product.generate_box
)
ifcopenshell.api.add_post_listener(
"material.edit_profile_usage",
"BlenderBIM.Product.RegenerateProfileUsage",
product.regenerate_profile_usage,
)
ifcopenshell.api.add_post_listener("geometry.add_representation", None, wall.generate_axis)
ifcopenshell.api.add_post_listener("geometry.add_representation", None, wall.calculate_quantities)
ifcopenshell.api.add_pre_listener("material.edit_layer", None, wall.DumbWallPlaner().regenerate_from_layer)
ifcopenshell.api.add_pre_listener("type.assign_type", None, wall.DumbWallPlaner().regenerate_from_type)
IfcStore.add_element_listener(wall.element_listener)
ifcopenshell.api.add_pre_listener(
"geometry.add_representation", "BlenderBIM.DumbWall.EnsureSolid", wall.ensure_solid
)
ifcopenshell.api.add_post_listener(
"geometry.add_representation", "BlenderBIM.DumbWall.GenerateAxis", wall.generate_axis
)
ifcopenshell.api.add_post_listener(
"geometry.add_representation", "BlenderBIM.DumbWall.CalculateQuantities", wall.calculate_quantities
)
ifcopenshell.api.add_pre_listener(
"material.edit_layer", "BlenderBIM.DumbWall.RegenerateFromLayer", wall.DumbWallPlaner().regenerate_from_layer
)
ifcopenshell.api.add_pre_listener(
"type.assign_type", "BlenderBIM.DumbWall.RegenerateFromType", wall.DumbWallPlaner().regenerate_from_type
)
IfcStore.add_element_listener(slab.element_listener)
ifcopenshell.api.add_pre_listener("material.edit_layer", None, slab.DumbSlabPlaner().regenerate_from_layer)
ifcopenshell.api.add_pre_listener("type.assign_type", None, slab.DumbSlabPlaner().regenerate_from_type)
ifcopenshell.api.add_pre_listener(
"geometry.add_representation", "BlenderBIM.DumbSlab.EnsureSolid", slab.ensure_solid
)
ifcopenshell.api.add_post_listener(
"geometry.add_representation", "BlenderBIM.DumbSlab.GenerateFootprint", slab.generate_footprint
)
ifcopenshell.api.add_post_listener(
"geometry.add_representation", "BlenderBIM.DumbSlab.CalculateQuantities", slab.calculate_quantities
)
ifcopenshell.api.add_pre_listener(
"material.edit_layer", "BlenderBIM.DumbSlab.RegenerateFromLayer", slab.DumbSlabPlaner().regenerate_from_layer
)
ifcopenshell.api.add_pre_listener(
"type.assign_type", "BlenderBIM.DumbSlab.RegenerateFromType", slab.DumbSlabPlaner().regenerate_from_type
)
IfcStore.add_element_listener(column.element_listener)
ifcopenshell.api.add_pre_listener(
"geometry.add_representation", "BlenderBIM.DumbColumn.EnsureSolid", column.ensure_solid
)
ifcopenshell.api.add_post_listener(
"material.edit_profile",
"BlenderBIM.DumbColumn.RegenerateFromProfile",
column.DumbColumnRegenerator().regenerate_from_profile,
)
ifcopenshell.api.add_post_listener(
"type.assign_type",
"BlenderBIM.DumbColumn.RegenerateFromType",
column.DumbColumnRegenerator().regenerate_from_type,
)
@@ -3,9 +3,10 @@ import ifcopenshell
import ifcopenshell.api
import ifcopenshell.util.element
import ifcopenshell.util.representation
from . import wall, slab
from . import wall, slab, column
from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.pset.data import Data as PsetData
from mathutils import Vector
class AddTypeInstance(bpy.types.Operator):
@@ -31,6 +32,10 @@ class AddTypeInstance(bpy.types.Operator):
obj = slab.DumbSlabGenerator(self.file.by_id(int(relating_type))).generate()
if obj:
return {"FINISHED"}
elif ifc_class == "IfcColumnType":
obj = column.DumbColumnGenerator(self.file.by_id(int(relating_type))).generate()
if obj:
return {"FINISHED"}
# A cube
verts = [
Vector((-1, -1, -1)),
@@ -65,11 +70,13 @@ class AddTypeInstance(bpy.types.Operator):
return {"FINISHED"}
def generate_box(usecase_path, ifc_file, **settings):
def generate_box(usecase_path, ifc_file, settings):
box_context = ifcopenshell.util.representation.get_context(ifc_file, "Model", "Box", "MODEL_VIEW")
if not box_context:
return
obj = settings["blender_object"]
if 0 in list(obj.dimensions):
return
product = ifc_file.by_id(obj.BIMObjectProperties.ifc_definition_id)
old_box = ifcopenshell.util.representation.get_representation(product, "Model", "Box", "MODEL_VIEW")
if settings["context"].ContextType == "Model" and getattr(settings["context"], "ContextIdentifier") == "Body":
@@ -87,3 +94,24 @@ def generate_box(usecase_path, ifc_file, **settings):
should_run_listeners=False,
**{"product": product, "representation": new_box}
)
def regenerate_profile_usage(usecase_path, ifc_file, settings):
elements = []
if ifc_file.schema == "IFC2X3":
for rel in ifc_file.get_inverse(settings["usage"]):
if not rel.is_a("IfcRelAssociatesMaterial"):
continue
for element in rel.RelatedObjects:
elements.append(element)
else:
for rel in settings["usage"].AssociatedTo:
for element in rel.RelatedObjects:
elements.append(element)
for element in elements:
obj = IfcStore.get_element(element.id())
if not obj:
continue
representation = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
if representation:
bpy.ops.bim.switch_representation(obj=obj.name, ifc_definition_id=representation.id(), should_reload=True)
@@ -4,12 +4,15 @@ import math
import ifcopenshell
import ifcopenshell.util.type
import ifcopenshell.util.unit
import ifcopenshell.util.element
import mathutils.geometry
import blenderbim.bim.handler
from blenderbim.bim.ifc import IfcStore
from math import pi, degrees
from mathutils import Vector, Matrix
from ifcopenshell.api.pset.data import Data as PsetData
from ifcopenshell.api.material.data import Data as MaterialData
from blenderbim.bim.module.geometry.helper import Helper
def element_listener(element, obj):
@@ -28,8 +31,9 @@ def mode_callback(obj, data):
return
product = IfcStore.get_file().by_id(obj.BIMObjectProperties.ifc_definition_id)
parametric = ifcopenshell.util.element.get_psets(product).get("EPset_Parametric")
if parametric and parametric["Engine"] != "BlenderBIM.DumbSlab":
if not parametric or parametric["Engine"] != "BlenderBIM.DumbSlab":
return
IfcStore.edited_objs.add(obj)
modifier = [m for m in obj.modifiers if m.type == "SOLIDIFY"]
if modifier:
return
@@ -52,6 +56,145 @@ def mode_callback(obj, data):
modifier.thickness = depth
def ensure_solid(usecase_path, ifc_file, settings):
product = ifc_file.by_id(settings["blender_object"].BIMObjectProperties.ifc_definition_id)
parametric = ifcopenshell.util.element.get_psets(product).get("EPset_Parametric")
if not parametric or parametric["Engine"] != "BlenderBIM.DumbSlab":
return
settings["ifc_representation_class"] = "IfcExtrudedAreaSolid/IfcArbitraryProfileDefWithVoids"
def generate_footprint(usecase_path, ifc_file, settings):
footprint_context = ifcopenshell.util.representation.get_context(ifc_file, "Plan", "FootPrint", "SKETCH_VIEW")
if not footprint_context:
return
obj = settings["blender_object"]
product = ifc_file.by_id(obj.BIMObjectProperties.ifc_definition_id)
parametric = ifcopenshell.util.element.get_psets(product).get("EPset_Parametric")
if not parametric or parametric["Engine"] != "BlenderBIM.DumbSlab":
return
old_footprint = ifcopenshell.util.representation.get_representation(product, "Plan", "FootPrint", "SKETCH_VIEW")
if settings["context"].ContextType == "Model" and getattr(settings["context"], "ContextIdentifier") == "Body":
if old_footprint:
bpy.ops.bim.remove_representation(representation_id=old_footprint.id(), obj=obj.name)
helper = Helper(ifc_file)
indices = helper.auto_detect_arbitrary_profile_with_voids_extruded_area_solid(settings["geometry"])
bm = bmesh.new()
bm.from_mesh(settings["geometry"])
bm.verts.ensure_lookup_table()
bm.edges.ensure_lookup_table()
profile_edges = []
def append_profile_edges(profile_edges, indices):
indices.append(indices[0]) # Close the loop
edge_vert_pairs = list(zip(indices, indices[1:]))
for p in edge_vert_pairs:
profile_edges.append(
[e for e in bm.verts[p[0]].link_edges if e.other_vert(bm.verts[p[0]]).index == p[1]][0]
)
append_profile_edges(profile_edges, indices["profile"])
for inner_indices in indices["inner_curves"]:
append_profile_edges(profile_edges, inner_indices)
irrelevant_edges = [e for e in bm.edges if e not in profile_edges]
bmesh.ops.delete(bm, geom=irrelevant_edges, context="EDGES")
mesh = bpy.data.meshes.new("Temporary Footprint")
bm.to_mesh(mesh)
bm.free()
new_settings = settings.copy()
new_settings["context"] = footprint_context
new_settings["geometry"] = mesh
new_footprint = ifcopenshell.api.run(
"geometry.add_representation", ifc_file, should_run_listeners=False, **new_settings
)
ifcopenshell.api.run(
"geometry.assign_representation",
ifc_file,
should_run_listeners=False,
**{"product": product, "representation": new_footprint}
)
bpy.data.meshes.remove(mesh)
def calculate_quantities(usecase_path, ifc_file, settings):
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(ifc_file)
obj = settings["blender_object"]
product = ifc_file.by_id(obj.BIMObjectProperties.ifc_definition_id)
parametric = ifcopenshell.util.element.get_psets(product).get("EPset_Parametric")
if not parametric or parametric["Engine"] != "BlenderBIM.DumbSlab":
return
qto = ifcopenshell.api.run(
"pset.add_qto", ifc_file, should_run_listeners=False, product=product, name="Qto_SlabBaseQuantities"
)
length = obj.dimensions[0] / unit_scale
width = obj.dimensions[1] / unit_scale
depth = obj.dimensions[2] / unit_scale
perimeter = 0
helper = Helper(ifc_file)
indices = helper.auto_detect_arbitrary_profile_with_voids_extruded_area_solid(settings["geometry"])
bm = bmesh.new()
bm.from_mesh(settings["geometry"])
bm.verts.ensure_lookup_table()
def calculate_profile_length(indices):
indices.append(indices[0]) # Close the loop
edge_vert_pairs = list(zip(indices, indices[1:]))
return sum([(bm.verts[p[1]].co - bm.verts[p[0]].co).length for p in edge_vert_pairs])
perimeter += calculate_profile_length(indices["profile"])
for inner_indices in indices["inner_curves"]:
perimeter += calculate_profile_length(inner_indices)
bm.free()
if product.HasOpenings:
# TODO: calculate gross / net
gross_area = 0
net_area = 0
gross_volume = 0
net_volume = 0
else:
bm = bmesh.new()
bm.from_object(obj, bpy.context.evaluated_depsgraph_get())
bm.faces.ensure_lookup_table()
gross_area = sum([f.calc_area() for f in bm.faces if f.normal.z > 0.9])
net_area = gross_area
gross_volume = bm.calc_volume()
net_volume = gross_volume
bm.free()
properties={
"Depth": round(depth, 2),
"Perimeter": round(perimeter, 2),
"GrossArea": round(gross_area, 2),
"NetArea": round(net_area, 2),
"GrossVolume": round(gross_volume, 2),
"NetVolume": round(net_volume, 2),
}
if round(obj.dimensions[0] * obj.dimensions[1] * obj.dimensions[2], 2) == round(gross_volume, 2):
properties.update({
"Length": round(length, 2),
"Width": round(width, 2),
})
else:
properties.update({
"Length": None,
"Width": None,
})
ifcopenshell.api.run( "pset.edit_qto", ifc_file, should_run_listeners=False, qto=qto, properties=properties)
PsetData.load(ifc_file, obj.BIMObjectProperties.ifc_definition_id)
class DumbSlabGenerator:
def __init__(self, relating_type):
self.relating_type = relating_type
@@ -106,7 +249,12 @@ class DumbSlabGenerator:
else:
obj.location[2] -= self.depth
self.collection.objects.link(obj)
bpy.ops.bim.assign_class(obj=obj.name, ifc_class="IfcSlab", predefined_type="FLOOR")
bpy.ops.bim.assign_class(
obj=obj.name,
ifc_class="IfcSlab",
predefined_type="FLOOR",
ifc_representation_class="IfcExtrudedAreaSolid/IfcArbitraryProfileDefWithVoids",
)
bpy.ops.bim.assign_type(relating_type=self.relating_type.id(), related_object=obj.name)
element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
pset = ifcopenshell.api.run("pset.add_pset", self.file, product=element, name="EPset_Parametric")
@@ -117,13 +265,12 @@ class DumbSlabGenerator:
class DumbSlabPlaner:
def regenerate_from_layer(self, usecase_path, ifc_file, **settings):
def regenerate_from_layer(self, usecase_path, ifc_file, settings):
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(ifc_file)
layer = settings["layer"]
thickness = settings["attributes"].get("LayerThickness")
if thickness is None or layer.LayerThickness == thickness:
if thickness is None:
return
delta_thickness = thickness - layer.LayerThickness
for layer_set in layer.ToMaterialLayerSet:
total_thickness = sum([l.LayerThickness for l in layer_set.MaterialLayers])
if not total_thickness:
@@ -136,27 +283,21 @@ class DumbSlabPlaner:
if not rel.is_a("IfcRelAssociatesMaterial"):
continue
for element in rel.RelatedObjects:
self.change_thickness(element, delta_thickness)
self.change_thickness(element, thickness)
else:
for rel in inverse.AssociatedTo:
for element in rel.RelatedObjects:
self.change_thickness(element, delta_thickness)
self.change_thickness(element, thickness)
def regenerate_from_type(self, usecase_path, ifc_file, **settings):
def regenerate_from_type(self, usecase_path, ifc_file, settings):
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(ifc_file)
new_material = ifcopenshell.util.element.get_material(settings["relating_type"])
if not new_material.is_a("IfcMaterialLayerSet"):
if not new_material or not new_material.is_a("IfcMaterialLayerSet"):
return
obj = IfcStore.get_element(settings["related_object"].id())
if not obj:
return
current_thickness = obj.dimensions.z / self.unit_scale
new_thickness = sum([l.LayerThickness for l in new_material.MaterialLayers])
if current_thickness == new_thickness:
return
self.change_thickness(settings["related_object"], new_thickness - current_thickness)
self.change_thickness(settings["related_object"], new_thickness)
def change_thickness(self, element, delta_thickness):
def change_thickness(self, element, thickness):
parametric = ifcopenshell.util.element.get_psets(element).get("EPset_Parametric")
if not parametric or parametric["Engine"] != "BlenderBIM.DumbSlab":
return
@@ -165,10 +306,14 @@ class DumbSlabPlaner:
if not obj:
return
delta_thickness = (thickness * self.unit_scale) - obj.dimensions.z
if round(delta_thickness, 2) == 0:
return
modifier = [m for m in obj.modifiers if m.type == "SOLIDIFY"]
if modifier:
modifier = modifier[0]
else:
pass
modifier.thickness += delta_thickness * self.unit_scale
obj.location[2] -= delta_thickness * self.unit_scale
modifier.thickness += delta_thickness
obj.location[2] -= delta_thickness
@@ -7,6 +7,7 @@ import ifcopenshell.util.unit
import ifcopenshell.util.element
import ifcopenshell.util.representation
import mathutils.geometry
import blenderbim.bim.handler
from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.pset.data import Data as PsetData
from ifcopenshell.api.material.data import Data as MaterialData
@@ -14,6 +15,27 @@ from math import pi, degrees
from mathutils import Vector, Matrix
def element_listener(element, obj):
blenderbim.bim.handler.subscribe_to(obj, "mode", mode_callback)
def mode_callback(obj, data):
for obj in bpy.context.selected_objects + [bpy.context.active_object]:
if (
obj.mode != "EDIT"
or not obj.data
or not isinstance(obj.data, (bpy.types.Mesh, bpy.types.Curve, bpy.types.TextCurve))
or not obj.BIMObjectProperties.ifc_definition_id
or not bpy.context.scene.BIMProjectProperties.is_authoring
):
return
product = IfcStore.get_file().by_id(obj.BIMObjectProperties.ifc_definition_id)
parametric = ifcopenshell.util.element.get_psets(product).get("EPset_Parametric")
if not parametric or parametric["Engine"] != "BlenderBIM.DumbWall":
return
IfcStore.edited_objs.add(obj)
class AddWall(bpy.types.Operator):
bl_idname = "bim.add_wall"
bl_label = "Add Wall"
@@ -78,6 +100,7 @@ class AlignWall(bpy.types.Operator):
aligner.align_first_layer()
elif self.align_type == "INTERIOR":
aligner.align_last_layer()
IfcStore.edited_objs.add(obj)
return {"FINISHED"}
@@ -92,6 +115,7 @@ class FlipWall(bpy.types.Operator):
return {"FINISHED"}
for obj in selected_objs:
DumbWallFlipper(obj).flip()
IfcStore.edited_objs.add(obj)
return {"FINISHED"}
@@ -106,6 +130,7 @@ class SplitWall(bpy.types.Operator):
return {"FINISHED"}
for obj in selected_objs:
DumbWallSplitter(obj, bpy.context.scene.cursor.location).split()
IfcStore.edited_objs.add(obj)
return {"FINISHED"}
@@ -676,7 +701,11 @@ class DumbWallGenerator:
if self.collection_obj and self.collection_obj.BIMObjectProperties.ifc_definition_id:
obj.location[2] = self.collection_obj.location[2]
self.collection.objects.link(obj)
bpy.ops.bim.assign_class(obj=obj.name, ifc_class="IfcWall")
bpy.ops.bim.assign_class(
obj=obj.name,
ifc_class="IfcWall",
ifc_representation_class="IfcExtrudedAreaSolid/IfcArbitraryClosedProfileDef",
)
bpy.ops.bim.assign_type(relating_type=self.relating_type.id(), related_object=obj.name)
element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
pset = ifcopenshell.api.run("pset.add_pset", self.file, product=element, name="EPset_Parametric")
@@ -686,7 +715,15 @@ class DumbWallGenerator:
return obj
def generate_axis(usecase_path, ifc_file, **settings):
def ensure_solid(usecase_path, ifc_file, settings):
product = ifc_file.by_id(settings["blender_object"].BIMObjectProperties.ifc_definition_id)
parametric = ifcopenshell.util.element.get_psets(product).get("EPset_Parametric")
if not parametric or parametric["Engine"] != "BlenderBIM.DumbWall":
return
settings["ifc_representation_class"] = "IfcExtrudedAreaSolid/IfcArbitraryClosedProfileDef"
def generate_axis(usecase_path, ifc_file, settings):
axis_context = ifcopenshell.util.representation.get_context(ifc_file, "Model", "Axis", "GRAPH_VIEW")
if not axis_context:
return
@@ -721,7 +758,7 @@ def generate_axis(usecase_path, ifc_file, **settings):
bpy.data.meshes.remove(mesh)
def calculate_quantities(usecase_path, ifc_file, **settings):
def calculate_quantities(usecase_path, ifc_file, settings):
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(ifc_file)
obj = settings["blender_object"]
product = ifc_file.by_id(obj.BIMObjectProperties.ifc_definition_id)
@@ -737,11 +774,20 @@ def calculate_quantities(usecase_path, ifc_file, **settings):
if product.HasOpenings:
# TODO: calculate gross / net
gross_footprint_area = 0
net_footprint_area = 0
gross_side_area = 0
net_side_area = 0
gross_volume = 0
net_volume = 0
else:
bm = bmesh.new()
bm.from_mesh(obj.data)
bm.faces.ensure_lookup_table()
gross_footprint_area = sum([f.calc_area() for f in bm.faces if f.normal.z < -0.9])
net_footprint_area = gross_footprint_area
gross_side_area = sum([f.calc_area() for f in bm.faces if f.normal.y > 0.9])
net_side_area = gross_side_area
gross_volume = bm.calc_volume()
net_volume = gross_volume
bm.free()
@@ -755,6 +801,10 @@ def calculate_quantities(usecase_path, ifc_file, **settings):
"Length": round(length, 2),
"Width": round(width, 2),
"Height": round(height, 2),
"GrossFootprintArea": round(gross_footprint_area, 2),
"NetFootprintArea": round(net_footprint_area, 2),
"GrossSideArea": round(gross_side_area, 2),
"NetSideArea": round(net_side_area, 2),
"GrossVolume": round(gross_volume, 2),
"NetVolume": round(net_volume, 2),
},
@@ -763,13 +813,12 @@ def calculate_quantities(usecase_path, ifc_file, **settings):
class DumbWallPlaner:
def regenerate_from_layer(self, usecase_path, ifc_file, **settings):
def regenerate_from_layer(self, usecase_path, ifc_file, settings):
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(ifc_file)
layer = settings["layer"]
thickness = settings["attributes"].get("LayerThickness")
if thickness is None or layer.LayerThickness == thickness:
if thickness is None:
return
delta_thickness = thickness - layer.LayerThickness
for layer_set in layer.ToMaterialLayerSet:
total_thickness = sum([l.LayerThickness for l in layer_set.MaterialLayers])
if not total_thickness:
@@ -782,27 +831,21 @@ class DumbWallPlaner:
if not rel.is_a("IfcRelAssociatesMaterial"):
continue
for element in rel.RelatedObjects:
self.change_thickness(element, delta_thickness)
self.change_thickness(element, thickness)
else:
for rel in inverse.AssociatedTo:
for element in rel.RelatedObjects:
self.change_thickness(element, delta_thickness)
self.change_thickness(element, thickness)
def regenerate_from_type(self, usecase_path, ifc_file, **settings):
def regenerate_from_type(self, usecase_path, ifc_file, settings):
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(ifc_file)
new_material = ifcopenshell.util.element.get_material(settings["relating_type"])
if not new_material.is_a("IfcMaterialLayerSet"):
if not new_material or not new_material.is_a("IfcMaterialLayerSet"):
return
obj = IfcStore.get_element(settings["related_object"].id())
if not obj:
return
current_thickness = obj.dimensions.y / self.unit_scale
new_thickness = sum([l.LayerThickness for l in new_material.MaterialLayers])
if current_thickness == new_thickness:
return
self.change_thickness(settings["related_object"], new_thickness - current_thickness)
self.change_thickness(settings["related_object"], new_thickness)
def change_thickness(self, element, delta_thickness):
def change_thickness(self, element, thickness):
parametric = ifcopenshell.util.element.get_psets(element).get("EPset_Parametric")
if not parametric or parametric["Engine"] != "BlenderBIM.DumbWall":
return
@@ -811,6 +854,10 @@ class DumbWallPlaner:
if not obj:
return
delta_thickness = (thickness * self.unit_scale) - obj.dimensions.y
if round(delta_thickness, 2) == 0:
return
bm = bmesh.new()
bm.from_mesh(obj.data)
bmesh.ops.dissolve_limit(bm, angle_limit=pi / 180 * 1, verts=bm.verts, edges=bm.edges)
@@ -823,9 +870,10 @@ class DumbWallPlaner:
bm.to_mesh(obj.data)
obj.data.update()
bm.free()
IfcStore.edited_objs.add(obj)
def thicken_face(self, face, delta_thickness):
slide_magnitude = abs(delta_thickness) / 2 * self.unit_scale
slide_magnitude = abs(delta_thickness) / 2
for vert in face.verts:
slide_vector = None
for edge in vert.link_edges:
@@ -1,15 +1,16 @@
import os
import bpy
from bpy.types import WorkSpaceTool
class WallTool(WorkSpaceTool):
class BimTool(WorkSpaceTool):
bl_space_type = "VIEW_3D"
bl_context_mode = "OBJECT"
bl_idname = "bim.wall_tool"
bl_label = "Wall Tool"
bl_description = "Gives you wall related superpowers"
bl_icon = "ops.generic.select_circle"
bl_idname = "bim.bim_tool"
bl_label = "BIM Tool"
bl_description = "Gives you BIM authoring related superpowers"
bl_icon = os.path.join(os.path.dirname(__file__), "ops.authoring.bim")
bl_widget = None
# https://docs.blender.org/api/current/bpy.types.KeyMapItems.html
bl_keymap = (
@@ -4,7 +4,6 @@ from . import ui, prop, operator
classes = (
operator.CreateProject,
operator.CreateProjectLibrary,
operator.ValidateIfcFile,
operator.SelectLibraryFile,
operator.ChangeLibraryElement,
operator.RefreshLibrary,
@@ -2,7 +2,9 @@ import bpy
import logging
import ifcopenshell
import ifcopenshell.api
import ifcopenshell.util.representation
import bpy
import blenderbim.bim.handler
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim import import_ifc
@@ -10,8 +12,18 @@ from blenderbim.bim import import_ifc
class CreateProject(bpy.types.Operator):
bl_idname = "bim.create_project"
bl_label = "Create Project"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
IfcStore.begin_transaction(self)
IfcStore.add_transaction_operation(self, rollback=self.rollback, commit=lambda data: True)
result = self._execute(context)
self.transaction_data = {"file": self.file}
IfcStore.add_transaction_operation(self, rollback=lambda data: True, commit=self.commit)
IfcStore.end_transaction(self)
return result
def _execute(self, context):
self.file = IfcStore.get_file()
if self.file:
return {"FINISHED"}
@@ -37,10 +49,9 @@ class CreateProject(bpy.types.Operator):
bpy.ops.bim.add_subcontext(context="Plan")
bpy.ops.bim.add_subcontext(context="Plan", subcontext="Annotation", target_view="PLAN_VIEW")
for subcontext in self.file.by_type("IfcGeometricRepresentationSubContext"):
if subcontext.ContextIdentifier == "Body":
bpy.context.scene.BIMProperties.contexts = str(subcontext.id())
break
bpy.context.scene.BIMProperties.contexts = str(
ifcopenshell.util.representation.get_context(self.file, "Model", "Body", "MODEL_VIEW").id()
)
bpy.ops.bim.assign_class(obj=site.name, ifc_class="IfcSite")
bpy.ops.bim.assign_class(obj=building.name, ifc_class="IfcBuilding")
@@ -48,15 +59,33 @@ class CreateProject(bpy.types.Operator):
bpy.ops.bim.assign_object(related_object=site.name, relating_object=project.name)
bpy.ops.bim.assign_object(related_object=building.name, relating_object=site.name)
bpy.ops.bim.assign_object(related_object=building_storey.name, relating_object=building.name)
# Data.load()
return {"FINISHED"}
def rollback(self, data):
IfcStore.file = None
blenderbim.bim.handler.purge_module_data()
def commit(self, data):
blenderbim.bim.handler.purge_module_data()
IfcStore.file = data["file"]
class CreateProjectLibrary(bpy.types.Operator):
bl_idname = "bim.create_project_library"
bl_label = "Create Project Library"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
IfcStore.begin_transaction(self)
IfcStore.add_transaction_operation(self, rollback=self.rollback, commit=lambda data: True)
result = self._execute(context)
self.transaction_data = {"file": self.file}
IfcStore.add_transaction_operation(self, rollback=lambda data: True, commit=self.commit)
IfcStore.end_transaction(self)
return result
def _execute(self, context):
self.file = IfcStore.get_file()
if self.file:
return {"FINISHED"}
@@ -75,27 +104,30 @@ class CreateProjectLibrary(bpy.types.Operator):
bpy.ops.bim.assign_unit()
return {"FINISHED"}
def rollback(self, data):
IfcStore.file = None
blenderbim.bim.handler.purge_module_data()
class ValidateIfcFile(bpy.types.Operator):
bl_idname = "bim.validate_ifc_file"
bl_label = "Validate IFC File"
def execute(self, context):
import ifcopenshell.validate
logger = logging.getLogger("validate")
logger.setLevel(logging.DEBUG)
ifcopenshell.validate.validate(IfcStore.get_file(), logger)
return {"FINISHED"}
def commit(self, data):
blenderbim.bim.handler.purge_module_data()
IfcStore.file = data["file"]
class SelectLibraryFile(bpy.types.Operator):
bl_idname = "bim.select_library_file"
bl_label = "Select Library File"
bl_options = {"REGISTER", "UNDO"}
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
filter_glob: bpy.props.StringProperty(default="*.ifc;*.ifczip;*.ifcxml", options={"HIDDEN"})
def execute(self, context):
old_filepath = IfcStore.library_path
result = self._execute(context)
self.transaction_data = {"old_filepath": old_filepath, "filepath": self.filepath}
IfcStore.add_transaction_operation(self)
return result
def _execute(self, context):
IfcStore.library_path = self.filepath
IfcStore.library_file = ifcopenshell.open(self.filepath)
bpy.ops.bim.refresh_library()
@@ -105,6 +137,18 @@ class SelectLibraryFile(bpy.types.Operator):
context.window_manager.fileselect_add(self)
return {"RUNNING_MODAL"}
def rollback(self, data):
if data["old_filepath"]:
IfcStore.library_path = data["old_filepath"]
IfcStore.library_file = ifcopenshell.open(data["old_filepath"])
else:
IfcStore.library_path = ""
IfcStore.library_file = None
def commit(self, data):
IfcStore.library_path = data["filepath"]
IfcStore.library_file = ifcopenshell.open(data["filepath"])
class RefreshLibrary(bpy.types.Operator):
bl_idname = "bim.refresh_library"
@@ -131,6 +175,7 @@ class RefreshLibrary(bpy.types.Operator):
class ChangeLibraryElement(bpy.types.Operator):
bl_idname = "bim.change_library_element"
bl_label = "Change Library Element"
bl_options = {"REGISTER", "UNDO"}
element_name: bpy.props.StringProperty()
def execute(self, context):
@@ -143,7 +188,7 @@ class ChangeLibraryElement(bpy.types.Operator):
[ifc_classes.add(e.is_a()) for e in elements]
while len(self.props.library_elements) > 0:
self.props.library_elements.remove(0)
if len(ifc_classes) == 1:
if len(ifc_classes) == 1 and list(ifc_classes)[0] == self.element_name:
for element in elements:
new = self.props.library_elements.add()
new.name = element.Name or "Unnamed"
@@ -162,6 +207,7 @@ class ChangeLibraryElement(bpy.types.Operator):
class RewindLibrary(bpy.types.Operator):
bl_idname = "bim.rewind_library"
bl_label = "Rewind Library"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
self.props = context.scene.BIMProjectProperties
@@ -179,40 +225,70 @@ class RewindLibrary(bpy.types.Operator):
class AssignLibraryDeclaration(bpy.types.Operator):
bl_idname = "bim.assign_library_declaration"
bl_label = "Assign Library Declaration"
bl_options = {"REGISTER", "UNDO"}
definition: bpy.props.IntProperty()
def execute(self, context):
IfcStore.library_file.begin_transaction()
result = self._execute(context)
IfcStore.library_file.end_transaction()
IfcStore.add_transaction_operation(self)
return result
def _execute(self, context):
self.props = context.scene.BIMProjectProperties
self.file = IfcStore.library_file
ifcopenshell.api.run(
"project.assign_declaration",
IfcStore.library_file,
definition=IfcStore.library_file.by_id(self.definition),
relating_context=IfcStore.library_file.by_type("IfcProjectLibrary")[0],
self.file,
definition=self.file.by_id(self.definition),
relating_context=self.file.by_type("IfcProjectLibrary")[0],
)
element_name = self.props.active_library_element
bpy.ops.bim.rewind_library()
bpy.ops.bim.change_library_element(element_name = element_name)
bpy.ops.bim.change_library_element(element_name=element_name)
return {"FINISHED"}
def rollback(self, data):
IfcStore.library_file.undo()
def commit(self, data):
IfcStore.library_file.redo()
class UnassignLibraryDeclaration(bpy.types.Operator):
bl_idname = "bim.unassign_library_declaration"
bl_label = "Unassign Library Declaration"
bl_options = {"REGISTER", "UNDO"}
definition: bpy.props.IntProperty()
def execute(self, context):
IfcStore.library_file.begin_transaction()
result = self._execute(context)
IfcStore.library_file.end_transaction()
IfcStore.add_transaction_operation(self)
return result
def _execute(self, context):
self.props = context.scene.BIMProjectProperties
self.file = IfcStore.library_file
ifcopenshell.api.run(
"project.unassign_declaration",
IfcStore.library_file,
definition=IfcStore.library_file.by_id(self.definition),
relating_context=IfcStore.library_file.by_type("IfcProjectLibrary")[0],
self.file,
definition=self.file.by_id(self.definition),
relating_context=self.file.by_type("IfcProjectLibrary")[0],
)
element_name = self.props.active_library_element
bpy.ops.bim.rewind_library()
bpy.ops.bim.change_library_element(element_name = element_name)
bpy.ops.bim.change_library_element(element_name=element_name)
return {"FINISHED"}
def rollback(self, data):
IfcStore.library_file.undo()
def commit(self, data):
IfcStore.library_file.redo()
class SaveLibraryFile(bpy.types.Operator):
bl_idname = "bim.save_library_file"
@@ -226,15 +302,22 @@ class SaveLibraryFile(bpy.types.Operator):
class AppendLibraryElement(bpy.types.Operator):
bl_idname = "bim.append_library_element"
bl_label = "Append Library Element"
bl_options = {"REGISTER", "UNDO"}
definition: bpy.props.IntProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
self.file = IfcStore.get_file()
element = ifcopenshell.api.run(
"project.append_asset",
IfcStore.get_file(),
self.file,
library=IfcStore.library_file,
element=IfcStore.library_file.by_id(self.definition),
)
self.import_type_from_ifc(element)
blenderbim.bim.handler.purge_module_data()
return {"FINISHED"}
def import_type_from_ifc(self, element):
@@ -260,6 +343,7 @@ class AppendLibraryElement(bpy.types.Operator):
class EnableEditingHeader(bpy.types.Operator):
bl_idname = "bim.enable_editing_header"
bl_label = "Enable Editing Header"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
self.file = IfcStore.get_file()
@@ -291,23 +375,56 @@ class EnableEditingHeader(bpy.types.Operator):
class EditHeader(bpy.types.Operator):
bl_idname = "bim.edit_header"
bl_label = "Edit Header"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
self.transaction_data = {}
self.transaction_data["old"] = self.record_state()
result = self._execute(context)
self.transaction_data["new"] = self.record_state()
IfcStore.add_transaction_operation(self)
return result
def _execute(self, context):
self.file = IfcStore.get_file()
props = context.scene.BIMProjectProperties
props.is_editing = True
self.file.wrapped_data.header.file_description.description = (f'ViewDefinition[{props.mvd}]',)
self.file.wrapped_data.header.file_description.description = (f"ViewDefinition[{props.mvd}]",)
self.file.wrapped_data.header.file_name.author = (props.author_name, props.author_email)
self.file.wrapped_data.header.file_name.organization = (props.organisation_name, props.organisation_email)
self.file.wrapped_data.header.file_name.authorization = props.authorisation
bpy.ops.bim.disable_editing_header()
return {"FINISHED"}
def record_state(self):
self.file = IfcStore.get_file()
return {
"description": self.file.wrapped_data.header.file_description.description,
"author": self.file.wrapped_data.header.file_name.author,
"organisation": self.file.wrapped_data.header.file_name.organization,
"authorisation": self.file.wrapped_data.header.file_name.authorization,
}
def rollback(self, data):
file = IfcStore.get_file()
file.wrapped_data.header.file_description.description = data["old"]["description"]
file.wrapped_data.header.file_name.author = data["old"]["author"]
file.wrapped_data.header.file_name.organization = data["old"]["organisation"]
file.wrapped_data.header.file_name.authorization = data["old"]["authorisation"]
def commit(self, data):
file = IfcStore.get_file()
file.wrapped_data.header.file_description.description = data["new"]["description"]
file.wrapped_data.header.file_name.author = data["new"]["author"]
file.wrapped_data.header.file_name.organization = data["new"]["organisation"]
file.wrapped_data.header.file_name.authorization = data["new"]["authorisation"]
class DisableEditingHeader(bpy.types.Operator):
bl_idname = "bim.disable_editing_header"
bl_label = "Disable Editing Header"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
context.scene.BIMProjectProperties.is_editing = False
@@ -4,6 +4,7 @@ import ifcopenshell.api
import ifcopenshell.util.unit
import ifcopenshell.util.pset
import ifcopenshell.util.attribute
import blenderbim.bim.schema
from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.pset.data import Data
from blenderbim.bim.module.pset.qto_calculator import QtoCalculator
@@ -42,8 +43,7 @@ class EnablePsetEditing(bpy.types.Operator):
pset_data = data[self.pset_id]
self.props.active_pset_name = pset_data["Name"]
templates = ifcopenshell.util.pset.get_template("IFC4")
pset_template = templates.get_by_name(pset_data["Name"])
pset_template = blenderbim.bim.schema.ifc.psetqto.get_by_name(pset_data["Name"])
if pset_template:
self.load_from_pset_template(pset_template, pset_data)
@@ -192,6 +192,9 @@ class EditPset(bpy.types.Operator):
},
)
else:
for key, value in properties.items():
if isinstance(value, float):
properties[key] = round(value, 4)
ifcopenshell.api.run(
"pset.edit_qto",
self.file,
@@ -101,6 +101,8 @@ class BIM_PT_object_psets(Panel):
props = context.active_object.BIMObjectProperties
if not props.ifc_definition_id:
return False
if not IfcStore.get_element(props.ifc_definition_id):
return False
if props.ifc_definition_id not in Data.products:
Data.load(IfcStore.get_file(), props.ifc_definition_id)
if not Data.products[props.ifc_definition_id]:
@@ -144,6 +146,8 @@ class BIM_PT_object_qtos(Panel):
props = context.active_object.BIMObjectProperties
if not props.ifc_definition_id:
return False
if not IfcStore.get_element(props.ifc_definition_id):
return False
if props.ifc_definition_id not in Data.products:
Data.load(IfcStore.get_file(), props.ifc_definition_id)
if not Data.products[props.ifc_definition_id]:
@@ -12,6 +12,7 @@ from blenderbim.bim.ifc import IfcStore
class EnableReassignClass(bpy.types.Operator):
bl_idname = "bim.enable_reassign_class"
bl_label = "Enable Reassign IFC Class"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
obj = bpy.context.active_object
@@ -41,6 +42,7 @@ class EnableReassignClass(bpy.types.Operator):
class DisableReassignClass(bpy.types.Operator):
bl_idname = "bim.disable_reassign_class"
bl_label = "Disable Reassign IFC Class"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
bpy.context.active_object.BIMObjectProperties.is_reassigning_class = False
@@ -50,9 +52,13 @@ class DisableReassignClass(bpy.types.Operator):
class ReassignClass(bpy.types.Operator):
bl_idname = "bim.reassign_class"
bl_label = "Reassign IFC Class"
bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
objects = [bpy.data.objects.get(self.obj)] if self.obj else bpy.context.selected_objects
self.file = IfcStore.get_file()
predefined_type = bpy.context.scene.BIMRootProperties.ifc_predefined_type
@@ -83,8 +89,13 @@ class AssignClass(bpy.types.Operator):
predefined_type: bpy.props.StringProperty()
userdefined_type: bpy.props.StringProperty()
context_id: bpy.props.IntProperty()
should_add_representation: bpy.props.BoolProperty(default=True)
ifc_representation_class: bpy.props.StringProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
objects = [bpy.data.objects.get(self.obj)] if self.obj else bpy.context.selected_objects
self.file = IfcStore.get_file()
self.declaration = IfcStore.get_schema().declaration_by_name(self.ifc_class)
@@ -111,7 +122,10 @@ class AssignClass(bpy.types.Operator):
obj.name = "{}/{}".format(product.is_a(), obj.name)
IfcStore.link_element(product, obj)
bpy.ops.bim.add_representation(obj=obj.name, context_id=self.context_id)
if self.should_add_representation:
bpy.ops.bim.add_representation(
obj=obj.name, context_id=self.context_id, ifc_representation_class=self.ifc_representation_class
)
if product.is_a("IfcElementType"):
self.place_in_types_collection(obj)
@@ -170,9 +184,13 @@ class AssignClass(bpy.types.Operator):
class UnassignClass(bpy.types.Operator):
bl_idname = "bim.unassign_class"
bl_label = "Unassign IFC Class"
bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
self.file = IfcStore.get_file()
if self.obj:
objects = [bpy.data.objects.get(self.obj)]
@@ -209,9 +227,13 @@ class UnassignClass(bpy.types.Operator):
class UnlinkObject(bpy.types.Operator):
bl_idname = "bim.unlink_object"
bl_label = "Unlink Object"
bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
self.file = IfcStore.get_file()
if self.obj:
objects = [bpy.data.objects.get(self.obj)]
@@ -228,9 +250,13 @@ class UnlinkObject(bpy.types.Operator):
class CopyClass(bpy.types.Operator):
bl_idname = "bim.copy_class"
bl_label = "Copy Class"
bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
self.file = IfcStore.get_file()
if self.obj:
objects = [bpy.data.objects.get(self.obj)]
@@ -25,6 +25,7 @@ class BIM_PT_class(Panel):
row = self.layout.row(align=True)
row.label(text="IFC Element Not Found")
row.operator("bim.unlink_object", icon="UNLINKED", text="")
return
if props.is_reassigning_class:
row = self.layout.row(align=True)
row.operator("bim.reassign_class", icon="CHECKMARK")
@@ -111,11 +111,16 @@ class SelectPset(bpy.types.Operator):
continue
element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
psets = ifcopenshell.util.element.get_psets(element)
if search_pset_name == "":
props = {}
[props.update(p) for p in psets.values()]
else:
props = None
if context.scene.BIMSearchProperties.should_ignorecase:
props = next((v for k, v in psets.items() if k.lower() == search_pset_name.lower()), {})
props = props or next((v for k, v in psets.items() if k.lower() == search_pset_name.lower()), {})
value = str(next((v for k, v in props.items() if k.lower() == search_prop_name.lower()), None))
else:
props = psets.get(search_pset_name, {})
props = props or psets.get(search_pset_name, {})
value = props.get(search_prop_name, None)
if does_keyword_exist(pattern, value):
obj.select_set(True)
@@ -163,11 +168,16 @@ class ColourByPset(bpy.types.Operator):
continue
element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
psets = ifcopenshell.util.element.get_psets(element)
if search_pset_name == "":
props = {}
[props.update(p) for p in psets.values()]
else:
props = None
if context.scene.BIMSearchProperties.should_ignorecase:
props = next((v for k, v in psets.items() if k.lower() == search_pset_name.lower()), {})
props = props or next((v for k, v in psets.items() if k.lower() == search_pset_name.lower()), {})
value = str(next((v for k, v in props.items() if k.lower() == search_prop_name.lower()), None))
else:
props = psets.get(search_pset_name, {})
props = props or psets.get(search_pset_name, {})
value = str(props.get(search_prop_name, None))
if value not in values:
values[value] = next(colours)
@@ -47,6 +47,7 @@ classes = (
operator.DisableEditingTask,
operator.DisableEditingTaskTime,
operator.EditTask,
operator.CopyTaskAttribute,
operator.AssignPredecessor,
operator.AssignSuccessor,
operator.UnassignPredecessor,
@@ -59,6 +60,8 @@ classes = (
operator.RemoveTaskCalendar,
operator.AssignProduct,
operator.UnassignProduct,
operator.AssignProcess,
operator.UnassignProcess,
operator.GenerateGanttChart,
operator.ImportP6,
operator.ImportMSP,
@@ -70,6 +73,9 @@ classes = (
operator.BlenderBIM_DatePicker,
operator.BlenderBIM_DatePickerSetDate,
operator.BlenderBIM_RedrawDatePicker,
operator.AddTaskColumn,
operator.RemoveTaskColumn,
operator.SetTaskSortColumn,
prop.WorkPlan,
prop.BIMWorkPlanProperties,
prop.Task,
@@ -82,6 +88,7 @@ classes = (
ui.BIM_PT_work_plans,
ui.BIM_PT_work_schedules,
ui.BIM_PT_work_calendars,
ui.BIM_UL_task_columns,
ui.BIM_UL_tasks,
)
@@ -244,10 +244,14 @@ class EnableEditingTasks(bpy.types.Operator):
self.contracted_tasks = json.loads(self.props.contracted_tasks)
self.sort_keys = {
i: Data.tasks[i]["Identification"] or "" for i in Data.work_schedules[self.work_schedule]["RelatedObjects"]
i: self.get_sort_key(Data.tasks[i]) for i in Data.work_schedules[self.work_schedule]["RelatedObjects"]
}
for related_object_id in sorted(self.sort_keys, key=self.natural_sort_key):
related_object_ids = sorted(self.sort_keys, key=self.natural_sort_key)
if self.props.is_sort_reversed:
related_object_ids.reverse()
for related_object_id in related_object_ids:
self.create_new_task_li(related_object_id, 0)
bpy.ops.bim.load_task_properties()
self.props.editing_type = "TASKS"
@@ -262,10 +266,27 @@ class EnableEditingTasks(bpy.types.Operator):
if task["RelatedObjects"]:
new.has_children = True
if new.is_expanded:
self.sort_keys = {i: Data.tasks[i]["Identification"] or "" for i in task["RelatedObjects"]}
for related_object_id in sorted(self.sort_keys, key=self.natural_sort_key):
self.sort_keys = {i: self.get_sort_key(Data.tasks[i]) for i in task["RelatedObjects"]}
related_object_ids = sorted(self.sort_keys, key=self.natural_sort_key)
if self.props.is_sort_reversed:
related_object_ids.reverse()
for related_object_id in related_object_ids:
self.create_new_task_li(related_object_id, level_index + 1)
def get_sort_key(self, task):
# Sorting only applies to actual tasks, not the WBS
if task["RelatedObjects"]:
# Sorry for the hack
return "0000000000" + (task["Identification"] or "")
if not self.props.sort_column:
return task["Identification"] or ""
column_type, name = self.props.sort_column.split(".")
if column_type == "IfcTask":
return task.get(name)
elif column_type == "IfcTaskTime" and task.get("TaskTime"):
task_time = Data.task_times[task.get("TaskTime")].get(name)
return task["Identification"] or ""
def natural_sort_key(self, i, _nsre=re.compile("([0-9]+)")):
s = self.sort_keys[i]
return [int(text) if text.isdigit() else text.lower() for text in _nsre.split(s)]
@@ -533,6 +554,30 @@ class EditTask(bpy.types.Operator):
return {"FINISHED"}
class CopyTaskAttribute(bpy.types.Operator):
bl_idname = "bim.copy_task_attribute"
bl_label = "Copy Task Attribute"
data: bpy.props.StringProperty()
def execute(self, context):
data = json.loads(self.data)
self.file = IfcStore.get_file()
props = context.scene.BIMTaskTreeProperties
for task in props.tasks:
if task.is_selected:
ifcopenshell.api.run(
"sequence.edit_task",
self.file,
**{
"task": self.file.by_id(task.ifc_definition_id),
"attributes": {data["name"]: None if data["is_null"] else data["value"]},
},
)
Data.load(IfcStore.get_file())
bpy.ops.bim.load_task_properties()
return {"FINISHED"}
class AssignPredecessor(bpy.types.Operator):
bl_idname = "bim.assign_predecessor"
bl_label = "Assign Predecessor"
@@ -541,14 +586,14 @@ class AssignPredecessor(bpy.types.Operator):
def execute(self, context):
props = context.scene.BIMWorkScheduleProperties
self.file = IfcStore.get_file()
ifcopenshell.api.run(
rel = ifcopenshell.api.run(
"sequence.assign_sequence",
self.file,
relating_process=IfcStore.get_file().by_id(self.task),
related_process=IfcStore.get_file().by_id(props.active_task_id),
)
Data.load(self.file)
bpy.ops.bim.load_task_properties(task=self.task)
bpy.ops.bim.load_task_properties()
return {"FINISHED"}
@@ -560,14 +605,14 @@ class AssignSuccessor(bpy.types.Operator):
def execute(self, context):
props = context.scene.BIMWorkScheduleProperties
self.file = IfcStore.get_file()
ifcopenshell.api.run(
rel = ifcopenshell.api.run(
"sequence.assign_sequence",
self.file,
relating_process=IfcStore.get_file().by_id(props.active_task_id),
related_process=IfcStore.get_file().by_id(self.task),
)
Data.load(self.file)
bpy.ops.bim.load_task_properties(task=self.task)
bpy.ops.bim.load_task_properties()
return {"FINISHED"}
@@ -586,7 +631,7 @@ class UnassignPredecessor(bpy.types.Operator):
related_process=IfcStore.get_file().by_id(props.active_task_id),
)
Data.load(self.file)
bpy.ops.bim.load_task_properties(task=self.task)
bpy.ops.bim.load_task_properties()
return {"FINISHED"}
@@ -605,7 +650,7 @@ class UnassignSuccessor(bpy.types.Operator):
related_process=self.file.by_id(self.task),
)
Data.load(self.file)
bpy.ops.bim.load_task_properties(task=self.task)
bpy.ops.bim.load_task_properties()
return {"FINISHED"}
@@ -613,18 +658,18 @@ class AssignProduct(bpy.types.Operator):
bl_idname = "bim.assign_product"
bl_label = "Assign Product"
task: bpy.props.IntProperty()
related_product: bpy.props.StringProperty()
relating_product: bpy.props.StringProperty()
def execute(self, context):
related_products = (
[bpy.data.objects.get(self.related_product)] if self.related_product else bpy.context.selected_objects
relating_products = (
[bpy.data.objects.get(self.relating_product)] if self.relating_product else bpy.context.selected_objects
)
for related_product in related_products:
for relating_product in relating_products:
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"sequence.assign_product",
self.file,
relating_product=self.file.by_id(related_product.BIMObjectProperties.ifc_definition_id),
relating_product=self.file.by_id(relating_product.BIMObjectProperties.ifc_definition_id),
related_object=self.file.by_id(self.task),
)
Data.load(self.file)
@@ -635,24 +680,68 @@ class UnassignProduct(bpy.types.Operator):
bl_idname = "bim.unassign_product"
bl_label = "Unassign Product"
task: bpy.props.IntProperty()
related_product: bpy.props.StringProperty()
relating_product: bpy.props.StringProperty()
def execute(self, context):
related_products = (
[bpy.data.objects.get(self.related_product)] if self.related_product else bpy.context.selected_objects
relating_products = (
[bpy.data.objects.get(self.relating_product)] if self.relating_product else bpy.context.selected_objects
)
for related_product in related_products:
for relating_product in relating_products:
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"sequence.unassign_product",
self.file,
relating_product=self.file.by_id(related_product.BIMObjectProperties.ifc_definition_id),
relating_product=self.file.by_id(relating_product.BIMObjectProperties.ifc_definition_id),
related_object=self.file.by_id(self.task),
)
Data.load(self.file)
return {"FINISHED"}
class AssignProcess(bpy.types.Operator):
bl_idname = "bim.assign_process"
bl_label = "Assign Process"
task: bpy.props.IntProperty()
related_object: bpy.props.StringProperty()
def execute(self, context):
related_objects = (
[bpy.data.objects.get(self.related_object)] if self.related_object else bpy.context.selected_objects
)
for related_object in related_objects:
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"sequence.assign_process",
self.file,
related_object=self.file.by_id(related_object.BIMObjectProperties.ifc_definition_id),
relating_process=self.file.by_id(self.task),
)
Data.load(self.file)
return {"FINISHED"}
class UnassignProcess(bpy.types.Operator):
bl_idname = "bim.unassign_process"
bl_label = "Unassign Process"
task: bpy.props.IntProperty()
related_object: bpy.props.StringProperty()
def execute(self, context):
related_objects = (
[bpy.data.objects.get(self.related_object)] if self.related_object else bpy.context.selected_objects
)
for related_object in related_objects:
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"sequence.unassign_process",
self.file,
related_object=self.file.by_id(related_object.BIMObjectProperties.ifc_definition_id),
relating_process=self.file.by_id(self.task),
)
Data.load(self.file)
return {"FINISHED"}
class GenerateGanttChart(bpy.types.Operator):
bl_idname = "bim.generate_gantt_chart"
bl_label = "Generate Gantt Chart"
@@ -1092,19 +1181,18 @@ class EditTaskCalendar(bpy.types.Operator):
def execute(self, context):
self.file = IfcStore.get_file()
task = self.file.by_id(self.task)
ifcopenshell.api.run(
"control.assign_control",
self.file,
**{
"relating_control": self.file.by_id(self.work_calendar),
"related_object": self.file.by_id(self.task),
"related_object": task,
},
)
ifcopenshell.api.run("sequence.cascade_schedule", self.file, task=task)
Data.load(IfcStore.get_file())
if Data.tasks[self.task]["RelatedObjects"]:
bpy.ops.bim.load_task_properties()
else:
bpy.ops.bim.load_task_properties(task=self.task)
bpy.ops.bim.load_task_properties()
return {"FINISHED"}
@@ -1116,19 +1204,18 @@ class RemoveTaskCalendar(bpy.types.Operator):
def execute(self, context):
self.file = IfcStore.get_file()
task = self.file.by_id(self.task)
ifcopenshell.api.run(
"control.unassign_control",
self.file,
**{
"relating_control": self.file.by_id(self.work_calendar),
"related_object": self.file.by_id(self.task),
"related_object": task,
},
)
ifcopenshell.api.run("sequence.cascade_schedule", self.file, task=task)
Data.load(IfcStore.get_file())
if Data.tasks[self.task]["RelatedObjects"]:
bpy.ops.bim.load_task_properties()
else:
bpy.ops.bim.load_task_properties(task=self.task)
bpy.ops.bim.load_task_properties()
return {"FINISHED"}
@@ -1220,6 +1307,7 @@ class UnassignLagTime(bpy.types.Operator):
"sequence.unassign_lag_time", self.file, **{"rel_sequence": self.file.by_id(self.sequence)}
)
Data.load(IfcStore.get_file())
bpy.ops.bim.load_task_properties()
return {"FINISHED"}
@@ -1254,6 +1342,7 @@ class EditSequenceAttributes(bpy.types.Operator):
)
Data.load(self.file)
bpy.ops.bim.disable_editing_sequence()
bpy.ops.bim.load_task_properties()
return {"FINISHED"}
@@ -1273,6 +1362,7 @@ class EditSequenceTimeLag(bpy.types.Operator):
)
Data.load(self.file)
bpy.ops.bim.disable_editing_sequence()
bpy.ops.bim.load_task_properties()
return {"FINISHED"}
@@ -1374,23 +1464,99 @@ class VisualiseWorkScheduleDateRange(bpy.types.Operator):
for obj in bpy.data.objects:
if not obj.BIMObjectProperties.ifc_definition_id:
continue
product_frames = self.product_frames.get(obj.BIMObjectProperties.ifc_definition_id)
if not product_frames:
continue
obj.hide_viewport = True
obj.keyframe_insert(data_path="hide_viewport", frame=self.start_frame)
obj.hide_viewport = False
obj.color = (0.0, 1.0, 0.0, 1)
obj.keyframe_insert(data_path="hide_viewport", frame=product_frames["STARTED"])
obj.keyframe_insert(data_path="color", frame=product_frames["STARTED"])
obj.color = (1.0, 1.0, 1.0, 1)
obj.keyframe_insert(data_path="color", frame=product_frames["COMPLETED"])
product_frames = self.product_frames.get(obj.BIMObjectProperties.ifc_definition_id, [])
for product_frame in product_frames:
if product_frame["relationship"] == "input":
self.animate_input(obj, product_frame)
elif product_frame["relationship"] == "output":
self.animate_output(obj, product_frame)
area = next(area for area in context.screen.areas if area.type == "VIEW_3D")
area.spaces[0].shading.color_type = "OBJECT"
context.scene.frame_start = self.start_frame
context.scene.frame_end = self.start_frame + self.total_frames
return {"FINISHED"}
def animate_input(self, obj, product_frame):
if product_frame["type"] in ["LOGISTIC", "MOVE", "DISPOSAL"]:
self.animate_movement_from(obj, product_frame)
else:
self.animate_consumption(obj, product_frame)
def animate_output(self, obj, product_frame):
if product_frame["type"] in ["CONSTRUCTION", "INSTALLATION"]:
self.animate_creation(obj, product_frame)
elif product_frame["type"] in ["DEMOLITION", "DISMANTLE", "DISPOSAL", "REMOVAL"]:
self.animate_destruction(obj, product_frame)
elif product_frame["type"] in ["ATTENDANCE", "MAINTENANCE", "OPERATION", "RENOVATION"]:
self.animate_operation(obj, product_frame)
elif product_frame["type"] in ["LOGISTIC", "MOVE", "DISPOSAL"]:
self.animate_movement_to(obj, product_frame)
else:
self.animate_operation(obj, product_frame)
def animate_creation(self, obj, product_frame):
obj.hide_viewport = True
obj.keyframe_insert(data_path="hide_viewport", frame=self.start_frame)
obj.hide_viewport = False
obj.color = (0.0, 1.0, 0.0, 1)
obj.keyframe_insert(data_path="hide_viewport", frame=product_frame["STARTED"])
obj.keyframe_insert(data_path="color", frame=product_frame["STARTED"])
obj.color = (1.0, 1.0, 1.0, 1)
obj.keyframe_insert(data_path="color", frame=product_frame["COMPLETED"])
def animate_destruction(self, obj, product_frame):
obj.color = (1.0, 1.0, 1.0, 1)
obj.keyframe_insert(data_path="color", frame=self.start_frame)
obj.keyframe_insert(data_path="color", frame=product_frame["STARTED"] - 1)
obj.color = (1.0, 0.0, 0.0, 1)
obj.keyframe_insert(data_path="color", frame=product_frame["STARTED"])
obj.hide_viewport = True
obj.color = (0.0, 0.0, 0.0, 1)
obj.keyframe_insert(data_path="color", frame=product_frame["COMPLETED"])
obj.keyframe_insert(data_path="hide_viewport", frame=product_frame["COMPLETED"])
def animate_operation(self, obj, product_frame):
obj.color = (1.0, 1.0, 1.0, 1)
obj.keyframe_insert(data_path="color", frame=self.start_frame)
obj.keyframe_insert(data_path="color", frame=product_frame["STARTED"] - 1)
obj.color = (0.0, 0.0, 1.0, 1)
obj.keyframe_insert(data_path="color", frame=product_frame["STARTED"])
obj.color = (1.0, 1.0, 1.0, 1)
obj.keyframe_insert(data_path="color", frame=product_frame["COMPLETED"])
def animate_movement_to(self, obj, product_frame):
obj.hide_viewport = True
obj.keyframe_insert(data_path="hide_viewport", frame=self.start_frame)
obj.hide_viewport = False
obj.color = (1.0, 1.0, 0.0, 1)
obj.keyframe_insert(data_path="hide_viewport", frame=product_frame["STARTED"])
obj.keyframe_insert(data_path="color", frame=product_frame["STARTED"])
obj.color = (1.0, 1.0, 1.0, 1)
obj.keyframe_insert(data_path="color", frame=product_frame["COMPLETED"])
def animate_movement_from(self, obj, product_frame):
obj.color = (1.0, 1.0, 1.0, 1)
obj.keyframe_insert(data_path="color", frame=self.start_frame)
obj.keyframe_insert(data_path="color", frame=product_frame["STARTED"] - 1)
obj.color = (1.0, 0.5, 0.0, 1)
obj.keyframe_insert(data_path="color", frame=product_frame["STARTED"])
obj.hide_viewport = True
obj.color = (0.0, 0.0, 0.0, 1)
obj.keyframe_insert(data_path="color", frame=product_frame["COMPLETED"])
obj.keyframe_insert(data_path="hide_viewport", frame=product_frame["COMPLETED"])
def animate_consumption(self, obj, product_frame):
obj.color = (1.0, 1.0, 1.0, 1)
obj.keyframe_insert(data_path="color", frame=self.start_frame)
obj.keyframe_insert(data_path="color", frame=product_frame["STARTED"] - 1)
obj.color = (0.0, 1.0, 1.0, 1)
obj.keyframe_insert(data_path="color", frame=product_frame["STARTED"])
obj.hide_viewport = True
obj.color = (0.0, 0.0, 0.0, 1)
obj.keyframe_insert(data_path="color", frame=product_frame["COMPLETED"])
obj.keyframe_insert(data_path="hide_viewport", frame=product_frame["COMPLETED"])
def calculate_total_frames(self):
if self.props.speed_types == "FRAME_SPEED":
return self.calculate_using_frames(
@@ -1440,12 +1606,24 @@ class VisualiseWorkScheduleDateRange(bpy.types.Operator):
finish = helper.derive_date(task.id(), "ScheduleFinish", is_latest=True)
if not start or not finish:
return
product_ids = [r.RelatingProduct.id() for r in task.HasAssignments or [] if r.is_a("IfcRelAssignsToProduct")]
for product_id in product_ids:
self.product_frames[product_id] = {
output_ids = [r.RelatingProduct.id() for r in task.HasAssignments or [] if r.is_a("IfcRelAssignsToProduct")]
for output_id in output_ids:
self.add_product_frame(output_id, task, start, finish, "output")
input_ids = []
[input_ids.extend([o.id() for o in r.RelatedObjects]) for r in task.OperatesOn or []]
for input_id in input_ids:
self.add_product_frame(input_id, task, start, finish, "input")
def add_product_frame(self, product_id, task, start, finish, relationship):
self.product_frames.setdefault(product_id, []).append(
{
"type": task.PredefinedType,
"relationship": relationship,
"STARTED": round(self.start_frame + (((start - self.start) / self.duration) * self.total_frames)),
"COMPLETED": round(self.start_frame + (((finish - self.start) / self.duration) * self.total_frames)),
}
)
class BlenderBIM_DatePicker(bpy.types.Operator):
@@ -1548,3 +1726,40 @@ class RecalculateSchedule(bpy.types.Operator):
)
Data.load(self.file)
return {"FINISHED"}
class AddTaskColumn(bpy.types.Operator):
bl_idname = "bim.add_task_column"
bl_label = "Add Task Column"
column_type: bpy.props.StringProperty()
name: bpy.props.StringProperty()
data_type: bpy.props.StringProperty()
def execute(self, context):
self.props = context.scene.BIMWorkScheduleProperties
new = self.props.columns.add()
new.name = f"{self.column_type}.{self.name}"
new.data_type = self.data_type
return {"FINISHED"}
class RemoveTaskColumn(bpy.types.Operator):
bl_idname = "bim.remove_task_column"
bl_label = "Remove Task Column"
def execute(self, context):
self.props = context.scene.BIMWorkScheduleProperties
self.props.columns.remove(self.props.active_column_index)
return {"FINISHED"}
class SetTaskSortColumn(bpy.types.Operator):
bl_idname = "bim.set_task_sort_column"
bl_label = "Set Task Sort Column"
column: bpy.props.StringProperty()
def execute(self, context):
self.props = context.scene.BIMWorkScheduleProperties
self.props.sort_column = self.column
bpy.ops.bim.enable_editing_tasks(work_schedule=self.props.active_work_schedule_id)
return {"FINISHED"}
@@ -1,5 +1,6 @@
import bpy
import ifcopenshell.api
import ifcopenshell.util.attribute
from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.sequence.data import Data
from blenderbim.bim.prop import StrProperty, Attribute
@@ -17,6 +18,53 @@ from bpy.props import (
)
taskcolumns_enum = []
tasktimecolumns_enum = []
def purge():
global taskcolumns_enum
global tasktimecolumns_enum
taskcolumns_enum = []
tasktimecolumns_enum = []
def getTaskColumns(self, context):
global taskcolumns_enum
if not len(taskcolumns_enum) and IfcStore.get_schema():
taskcolumns_enum.extend(
[
(a.name() + "/" + ifcopenshell.util.attribute.get_primitive_type(a), a.name(), "")
for a in IfcStore.get_schema().declaration_by_name("IfcTask").all_attributes()
if ifcopenshell.util.attribute.get_primitive_type(a)
in ["string", "float", "integer", "boolean", "enum"]
]
)
return taskcolumns_enum
def getTaskTimeColumns(self, context):
global tasktimecolumns_enum
if not len(tasktimecolumns_enum) and IfcStore.get_schema():
tasktimecolumns_enum.extend(
[
(a.name() + "/" + ifcopenshell.util.attribute.get_primitive_type(a), a.name(), "")
for a in IfcStore.get_schema().declaration_by_name("IfcTaskTime").all_attributes()
if ifcopenshell.util.attribute.get_primitive_type(a)
in ["string", "float", "integer", "boolean", "enum"]
]
)
return tasktimecolumns_enum
def getWorkSchedules(self, context):
return [(str(k), v["Name"], "") for k, v in Data.work_schedules.items()]
def getWorkCalendars(self, context):
return [(str(k), v["Name"], "") for k, v in Data.work_calendars.items()]
def updateTaskName(self, context):
props = context.scene.BIMWorkScheduleProperties
if not props.is_task_update_enabled or self.name == "Unnamed":
@@ -104,7 +152,7 @@ def updateTaskTimeDateTime(self, context, startfinish):
**{"task_time": task_time, "attributes": {startfinish_key: startfinish_datetime}},
)
Data.load(IfcStore.get_file())
bpy.ops.bim.load_task_properties(task=props.active_task_id)
bpy.ops.bim.load_task_properties()
setattr(self, startfinish, canonicalise_time(startfinish_datetime))
@@ -127,8 +175,9 @@ def updateTaskduration(self, context):
Data.load(IfcStore.get_file())
if props.active_task_id == self.ifc_definition_id:
attribute = props.task_time_attributes.get("Duration")
attribute.string_value = self.duration
bpy.ops.bim.load_task_properties(task=props.active_task_id)
if attribute:
attribute.string_value = self.duration
bpy.ops.bim.load_task_properties()
def updateVisualisationStart(self, context):
@@ -159,22 +208,12 @@ def updateVisualisationStartFinish(self, context, startfinish):
setattr(self, startfinish, canonical_value)
workschedule_enum = []
def getWorkSchedules(self, context):
return [(str(k), v["Name"], "") for k, v in Data.work_schedules.items()]
def getWorkCalendars(self, context):
return [(str(k), v["Name"], "") for k, v in Data.work_calendars.items()]
class Task(PropertyGroup):
name: StringProperty(name="Name", update=updateTaskName)
identification: StringProperty(name="Identification", update=updateTaskIdentification)
ifc_definition_id: IntProperty(name="IFC Definition ID")
has_children: BoolProperty(name="Has Children")
is_selected: BoolProperty(name="Is Selected")
is_expanded: BoolProperty(name="Is Expanded")
level_index: IntProperty(name="Level Index")
duration: StringProperty(name="Duration", update=updateTaskduration)
@@ -214,8 +253,27 @@ class BIMWorkScheduleProperties(PropertyGroup):
active_task_id: IntProperty(name="Active Task Id")
task_attributes: CollectionProperty(name="Task Attributes", type=Attribute)
should_show_visualisation_ui: BoolProperty(name="Should Show Visualisation UI", default=False)
should_show_times: BoolProperty(name="Should Show Times", default=False)
should_show_calendars: BoolProperty(name="Should Show Calendars", default=False)
should_show_column_ui: BoolProperty(name="Should Show Column UI", default=False)
columns: CollectionProperty(name="Columns", type=Attribute)
active_column_index: IntProperty(name="Active Column Index")
sort_column: StringProperty(name="Sort Column")
is_sort_reversed: BoolProperty(name="Is Sort Reversed")
column_types: EnumProperty(
items=[
("IfcTask", "IfcTask", ""),
("IfcTaskTime", "IfcTaskTime", ""),
("Special", "Special", ""),
],
name="Column Types",
)
task_columns: EnumProperty(items=getTaskColumns, name="Task Columns")
task_time_columns: EnumProperty(items=getTaskTimeColumns, name="Task Time Columns")
other_columns: EnumProperty(
items=[
("Controls.Calendar", "Calendar", ""),
],
name="Special Columns",
)
active_task_time_id: IntProperty(name="Active Task Id")
task_time_attributes: CollectionProperty(name="Task Time Attributes", type=Attribute)
contracted_tasks: StringProperty(name="Contracted Task Items", default="[]")
@@ -243,7 +301,7 @@ class BIMWorkScheduleProperties(PropertyGroup):
class BIMTaskTreeProperties(PropertyGroup):
# This belongs by itself for performance reasons. https://developer.blender.org/T87737
# In Blender if you add thousands of tasks it makes other property access in the same group really slow.
# In Blender if you add many collection items it makes other property access in the same group really slow.
tasks: CollectionProperty(name="Tasks", type=Task)
@@ -1,4 +1,5 @@
import isodate
import blenderbim.bim.helper
from bpy.types import Panel, UIList
from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.sequence.data import Data
@@ -110,8 +111,7 @@ class BIM_PT_work_schedules(Panel):
if self.props.editing_type == "WORK_SCHEDULE":
row.operator("bim.edit_work_schedule", text="", icon="CHECKMARK")
elif self.props.editing_type == "TASKS":
row.prop(self.props, "should_show_times", text="", icon="TIME")
row.prop(self.props, "should_show_calendars", text="", icon="VIEW_ORTHO")
row.prop(self.props, "should_show_column_ui", text="", icon="SHORTDISPLAY")
row.prop(self.props, "should_show_visualisation_ui", text="", icon="CAMERA_STEREO")
row.operator("bim.generate_gantt_chart", text="", icon="NLA").work_schedule = work_schedule_id
row.operator("bim.recalculate_schedule", text="", icon="FILE_REFRESH").work_schedule = work_schedule_id
@@ -127,6 +127,8 @@ class BIM_PT_work_schedules(Panel):
row.operator("bim.remove_work_schedule", text="", icon="X").work_schedule = work_schedule_id
if self.props.active_work_schedule_id == work_schedule_id:
if self.props.should_show_column_ui:
self.draw_column_ui()
if self.props.should_show_visualisation_ui:
self.draw_visualisation_ui()
if self.props.editing_type == "WORK_SCHEDULE":
@@ -134,6 +136,31 @@ class BIM_PT_work_schedules(Panel):
elif self.props.editing_type == "TASKS":
self.draw_editable_task_ui(work_schedule_id)
def draw_column_ui(self):
row = self.layout.row(align=True)
row.prop(self.props, "column_types", text="")
column_type = self.props.column_types
if self.props.column_types == "IfcTask":
row.prop(self.props, "task_columns", text="")
name, data_type = self.props.task_columns.split("/")
elif self.props.column_types == "IfcTaskTime":
row.prop(self.props, "task_time_columns", text="")
name, data_type = self.props.task_time_columns.split("/")
elif self.props.column_types == "Special":
row.prop(self.props, "other_columns", text="")
column_type, name = self.props.other_columns.split(".")
data_type = "string"
row.operator("bim.set_task_sort_column", text="", icon="SORTALPHA").column = f"{column_type}.{name}"
row.prop(
self.props, "is_sort_reversed", text="", icon="SORT_DESC" if self.props.is_sort_reversed else "SORT_ASC"
)
op = row.operator("bim.add_task_column", text="", icon="ADD")
op.column_type = column_type
op.name = name
op.data_type = data_type
self.layout.template_list("BIM_UL_task_columns", "", self.props, "columns", self.props, "active_column_index")
def draw_visualisation_ui(self):
row = self.layout.row(align=True)
target_prop = "BIMWorkScheduleProperties.visualisation_start"
@@ -229,36 +256,10 @@ class BIM_PT_work_schedules(Panel):
op.sequence = sequence["id"]
def draw_editable_sequence_attributes_ui(self):
for attribute in self.props.sequence_attributes:
row = self.layout.row(align=True)
if attribute.data_type == "string":
row.prop(attribute, "string_value", text=attribute.name)
elif attribute.data_type == "boolean":
row.prop(attribute, "bool_value", text=attribute.name)
elif attribute.data_type == "integer":
row.prop(attribute, "int_value", text=attribute.name)
elif attribute.data_type == "float":
row.prop(attribute, "float_value", text=attribute.name)
elif attribute.data_type == "enum":
row.prop(attribute, "enum_value", text=attribute.name)
if attribute.is_optional:
row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
blenderbim.bim.helper.draw_attributes(self.props.sequence_attributes, self.layout)
def draw_editable_sequence_time_lag_ui(self):
for attribute in self.props.time_lag_attributes:
row = self.layout.row(align=True)
if attribute.data_type == "string":
row.prop(attribute, "string_value", text=attribute.name)
elif attribute.data_type == "boolean":
row.prop(attribute, "bool_value", text=attribute.name)
elif attribute.data_type == "integer":
row.prop(attribute, "int_value", text=attribute.name)
elif attribute.data_type == "float":
row.prop(attribute, "float_value", text=attribute.name)
elif attribute.data_type == "enum":
row.prop(attribute, "enum_value", text=attribute.name)
if attribute.is_optional:
row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
blenderbim.bim.helper.draw_attributes(self.props.time_lag_attributes, self.layout)
def draw_editable_task_calendar_ui(self):
task = Data.tasks[self.props.active_task_id]
@@ -277,40 +278,30 @@ class BIM_PT_work_schedules(Panel):
op.task = self.props.active_task_id
def draw_editable_task_attributes_ui(self):
for attribute in self.props.task_attributes:
row = self.layout.row(align=True)
if attribute.data_type == "string":
row.prop(attribute, "string_value", text=attribute.name)
elif attribute.data_type == "boolean":
row.prop(attribute, "bool_value", text=attribute.name)
elif attribute.data_type == "integer":
row.prop(attribute, "int_value", text=attribute.name)
elif attribute.data_type == "enum":
row.prop(attribute, "enum_value", text=attribute.name)
if attribute.is_optional:
row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
blenderbim.bim.helper.draw_attributes(
self.props.task_attributes, self.layout, copy_operator="bim.copy_task_attribute"
)
def draw_editable_task_time_attributes_ui(self):
for attribute in self.props.task_time_attributes:
row = self.layout.row(align=True)
if attribute.data_type == "string":
row.prop(attribute, "string_value", text=attribute.name)
elif attribute.data_type == "boolean":
row.prop(attribute, "bool_value", text=attribute.name)
elif attribute.data_type == "integer":
row.prop(attribute, "int_value", text=attribute.name)
elif attribute.data_type == "float":
row.prop(attribute, "float_value", text=attribute.name)
elif attribute.data_type == "enum":
row.prop(attribute, "enum_value", text=attribute.name)
if attribute.is_optional:
row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
blenderbim.bim.helper.draw_attributes(self.props.task_time_attributes, self.layout)
class BIM_UL_task_columns(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
props = context.scene.BIMWorkScheduleProperties
if item:
row = layout.row(align=True)
row.prop(item, "name", emboss=False, text="")
if props.sort_column == item.name:
row.label(text="", icon="SORTALPHA")
row.operator("bim.remove_task_column", text="", icon="X")
class BIM_UL_tasks(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
if item:
props = context.scene.BIMWorkScheduleProperties
task = IfcStore.get_file().by_id(item.ifc_definition_id)
row = layout.row(align=True)
for i in range(0, item.level_index):
row.label(text="", icon="BLANK1")
@@ -328,31 +319,48 @@ class BIM_UL_tasks(UIList):
row.prop(item, "identification", emboss=False, text="")
row.prop(item, "name", emboss=False, text="")
if props.should_show_times:
if item.derived_start:
row.label(text=item.derived_start + "*")
for column in props.columns:
if column.name == "IfcTaskTime.ScheduleStart":
if item.derived_start:
row.label(text=item.derived_start + "*")
else:
row.prop(item, "start", emboss=False, text="")
elif column.name == "IfcTaskTime.ScheduleFinish":
if item.derived_finish:
row.label(text=item.derived_finish + "*")
else:
row.prop(item, "finish", emboss=False, text="")
elif column.name == "IfcTaskTime.ScheduleDuration":
if item.derived_duration:
row.label(text=item.derived_duration + "*")
else:
row.prop(item, "duration", emboss=False, text="")
elif column.name == "Controls.Calendar":
if item.derived_calendar:
row.label(text=item.derived_calendar + "*")
else:
row.label(text=item.calendar or "-")
else:
row.prop(item, "start", emboss=False, text="")
if item.derived_finish:
row.label(text=item.derived_finish + "*")
else:
row.prop(item, "finish", emboss=False, text="")
if item.derived_duration:
row.label(text=item.derived_duration + "*")
else:
row.prop(item, "duration", emboss=False, text="")
if props.should_show_calendars:
if item.derived_calendar:
row.label(text=item.derived_calendar + "*")
else:
row.label(text=item.calendar or "-")
ifc_class, name = column.name.split(".")
if ifc_class == "IfcTask":
value = getattr(task, name)
elif ifc_class == "IfcTaskTime":
value = getattr(task.TaskTime, name) if task.TaskTime else None
if value is None:
value = "-"
row.label(text=str(value))
if context.active_object:
oprops = context.active_object.BIMObjectProperties
row = layout.row(align=True)
if oprops.ifc_definition_id in Data.tasks[item.ifc_definition_id]["OperatesOn"]:
op = row.operator("bim.unassign_process", text="", icon="MARKER_HLT", emboss=False)
op.task = item.ifc_definition_id
else:
op = row.operator("bim.assign_process", text="", icon="MARKER", emboss=False)
op.task = item.ifc_definition_id
if oprops.ifc_definition_id in Data.tasks[item.ifc_definition_id]["RelatingProducts"]:
op = row.operator("bim.unassign_product", text="", icon="KEYFRAME_HLT", emboss=False)
op.task = item.ifc_definition_id
@@ -360,6 +368,15 @@ class BIM_UL_tasks(UIList):
op = row.operator("bim.assign_product", text="", icon="KEYFRAME", emboss=False)
op.task = item.ifc_definition_id
if props.active_task_id and props.editing_task_type == "ATTRIBUTES":
row.prop(
item,
"is_selected",
icon="CHECKBOX_HLT" if item.is_selected else "CHECKBOX_DEHLT",
text="",
emboss=False,
)
if props.active_task_id == item.ifc_definition_id:
if props.editing_task_type == "TASKTIME":
row.operator("bim.edit_task_time", text="", icon="CHECKMARK")
@@ -7,6 +7,7 @@ classes = (
operator.EnableEditingContainer,
operator.DisableEditingContainer,
operator.ChangeSpatialLevel,
operator.CopyToContainer,
prop.SpatialElement,
prop.BIMSpatialProperties,
prop.BIMObjectSpatialProperties,
@@ -1,5 +1,6 @@
import bpy
import ifcopenshell.api
import ifcopenshell.util.element
from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.spatial.data import Data
from blenderbim.bim.module.spatial.prop import getSpatialContainers
@@ -8,10 +9,14 @@ from blenderbim.bim.module.spatial.prop import getSpatialContainers
class AssignContainer(bpy.types.Operator):
bl_idname = "bim.assign_container"
bl_label = "Assign Container"
bl_options = {"REGISTER", "UNDO"}
relating_structure: bpy.props.IntProperty()
related_element: bpy.props.StringProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
self.file = IfcStore.get_file()
related_elements = (
[bpy.data.objects.get(self.related_element)] if self.related_element else bpy.context.selected_objects
@@ -84,6 +89,7 @@ class ChangeSpatialLevel(bpy.types.Operator):
class DisableEditingContainer(bpy.types.Operator):
bl_idname = "bim.disable_editing_container"
bl_label = "Disable Editing Container"
bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
def execute(self, context):
@@ -123,3 +129,34 @@ class RemoveContainer(bpy.types.Operator):
parent.children.unlink(child)
except:
pass
class CopyToContainer(bpy.types.Operator):
bl_idname = "bim.copy_to_container"
bl_label = "Copy To Container"
obj: bpy.props.StringProperty()
def execute(self, context):
self.file = IfcStore.get_file()
objects = [bpy.data.objects.get(self.obj)] if self.obj else bpy.context.selected_objects
sprops = context.scene.BIMSpatialProperties
container_ids = [c.ifc_definition_id for c in sprops.spatial_elements if c.is_selected]
for obj in objects:
container = ifcopenshell.util.element.get_container(self.file.by_id(obj.BIMObjectProperties.ifc_definition_id))
if container:
container_obj = IfcStore.get_element(container.id())
local_position = container_obj.matrix_world.inverted() @ obj.matrix_world
else:
local_position = obj.matrix_world
for container_id in container_ids:
container_obj = IfcStore.get_element(container_id)
if not container_obj:
continue
new_obj = obj.copy()
new_obj.data = obj.data.copy()
new_obj.matrix_world = container_obj.matrix_world @ local_position
bpy.ops.bim.copy_class(obj=new_obj.name)
bpy.ops.bim.assign_container(relating_structure=container_id, related_element=new_obj.name)
obj.BIMObjectSpatialProperties.is_editing = False
return {"FINISHED"}
@@ -54,6 +54,7 @@ class SpatialElement(PropertyGroup):
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):
@@ -17,6 +17,8 @@ class BIM_PT_spatial(Panel):
oprops = context.active_object.BIMObjectProperties
if not oprops.ifc_definition_id:
return False
if not IfcStore.get_element(oprops.ifc_definition_id):
return False
if oprops.ifc_definition_id not in Data.products:
Data.load(IfcStore.get_file(), oprops.ifc_definition_id)
if not Data.products[oprops.ifc_definition_id]:
@@ -38,7 +40,8 @@ class BIM_PT_spatial(Panel):
op = row.operator("bim.change_spatial_level", text="", icon="FRAME_PREV")
op.parent_id = sprops.active_decomposes_parent_id
row.operator("bim.assign_container", icon="CHECKMARK")
row.operator("bim.disable_editing_container", icon="X", text="")
row.operator("bim.copy_to_container", icon="COPYDOWN", text="")
row.operator("bim.disable_editing_container", icon="CANCEL", text="")
self.layout.template_list(
"BIM_UL_spatial_elements", "", sprops, "spatial_elements", sprops, "active_spatial_element_index"
@@ -63,3 +66,10 @@ class BIM_UL_spatial_elements(UIList):
op.parent_id = 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,
)
@@ -43,9 +43,19 @@ classes = (
operator.DisableEditingStructuralLoad,
operator.RemoveStructuralLoad,
operator.EditStructuralLoad,
operator.ToggleFilterStructuralLoads,
operator.LoadBoundaryConditions,
operator.DisableBoundaryConditionEditingUI,
operator.AddBoundaryCondition,
operator.EnableEditingBoundaryCondition,
operator.DisableEditingBoundaryCondition,
operator.RemoveBoundaryCondition,
operator.EditBoundaryCondition,
operator.ToggleFilterBoundaryConditions,
prop.StructuralAnalysisModel,
prop.StructuralActivity,
prop.StructuralLoad,
prop.BoundaryCondition,
prop.BIMStructuralProperties,
prop.BIMObjectStructuralProperties,
ui.BIM_PT_structural_analysis_models,
@@ -58,6 +68,8 @@ classes = (
ui.BIM_PT_structural_load_cases,
ui.BIM_UL_structural_loads,
ui.BIM_PT_structural_loads,
ui.BIM_UL_boundary_conditions,
ui.BIM_PT_boundary_conditions,
)
@@ -14,8 +14,12 @@ from ifcopenshell.api.context.data import Data as ContextData
class AddStructuralMemberConnection(bpy.types.Operator):
bl_idname = "bim.add_structural_member_connection"
bl_label = "Add Structural Member Connection"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
obj = bpy.context.active_object
oprops = obj.BIMObjectProperties
props = obj.BIMStructuralProperties
@@ -38,6 +42,7 @@ class AddStructuralMemberConnection(bpy.types.Operator):
class EnableEditingStructuralConnectionCondition(bpy.types.Operator):
bl_idname = "bim.enable_editing_structural_connection_condition"
bl_label = "Enable Editing Structural Connection Condition"
bl_options = {"REGISTER", "UNDO"}
connects_structural_member: bpy.props.IntProperty()
def execute(self, context):
@@ -52,6 +57,7 @@ class EnableEditingStructuralConnectionCondition(bpy.types.Operator):
class DisableEditingStructuralConnectionCondition(bpy.types.Operator):
bl_idname = "bim.disable_editing_structural_connection_condition"
bl_label = "Disable Editing Structural Connection Condition"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
obj = bpy.context.active_object
@@ -63,9 +69,13 @@ class DisableEditingStructuralConnectionCondition(bpy.types.Operator):
class RemoveStructuralConnectionCondition(bpy.types.Operator):
bl_idname = "bim.remove_structural_connection_condition"
bl_label = "Remove Structural Connection Condition"
bl_options = {"REGISTER", "UNDO"}
connects_structural_member: bpy.props.IntProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
file = IfcStore.get_file()
relation = file.by_id(self.connects_structural_member)
connection = relation.RelatedStructuralConnection
@@ -78,9 +88,13 @@ class RemoveStructuralConnectionCondition(bpy.types.Operator):
class AddStructuralBoundaryCondition(bpy.types.Operator):
bl_idname = "bim.add_structural_boundary_condition"
bl_label = "Add Structural Boundary Condition"
bl_options = {"REGISTER", "UNDO"}
connection: bpy.props.IntProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
file = IfcStore.get_file()
connection = file.by_id(self.connection)
ifcopenshell.api.run("structural.add_structural_boundary_condition", file, **{"connection": connection})
@@ -94,9 +108,13 @@ class AddStructuralBoundaryCondition(bpy.types.Operator):
class RemoveStructuralBoundaryCondition(bpy.types.Operator):
bl_idname = "bim.remove_structural_boundary_condition"
bl_label = "Remove Structural Boundary Condition"
bl_options = {"REGISTER", "UNDO"}
connection: bpy.props.IntProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
file = IfcStore.get_file()
connection = file.by_id(self.connection)
ifcopenshell.api.run("structural.remove_structural_boundary_condition", file, **{"connection": connection})
@@ -110,6 +128,7 @@ class RemoveStructuralBoundaryCondition(bpy.types.Operator):
class EnableEditingStructuralBoundaryCondition(bpy.types.Operator):
bl_idname = "bim.enable_editing_structural_boundary_condition"
bl_label = "Enable Editing Structural Boundary Condition"
bl_options = {"REGISTER", "UNDO"}
boundary_condition: bpy.props.IntProperty()
def execute(self, context):
@@ -149,9 +168,13 @@ class EnableEditingStructuralBoundaryCondition(bpy.types.Operator):
class EditStructuralBoundaryCondition(bpy.types.Operator):
bl_idname = "bim.edit_structural_boundary_condition"
bl_label = "Edit Structural Boundary Condition"
bl_options = {"REGISTER", "UNDO"}
connection: bpy.props.IntProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
obj = context.active_object
props = obj.BIMStructuralProperties
@@ -184,6 +207,7 @@ class EditStructuralBoundaryCondition(bpy.types.Operator):
class DisableEditingStructuralBoundaryCondition(bpy.types.Operator):
bl_idname = "bim.disable_editing_structural_boundary_condition"
bl_label = "Disable Editing Structural Boundary Condition"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
context.active_object.BIMStructuralProperties.active_boundary_condition = 0
@@ -193,6 +217,7 @@ class DisableEditingStructuralBoundaryCondition(bpy.types.Operator):
class LoadStructuralAnalysisModels(bpy.types.Operator):
bl_idname = "bim.load_structural_analysis_models"
bl_label = "Load Structural Analysis Models"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
props = context.scene.BIMStructuralProperties
@@ -210,6 +235,7 @@ class LoadStructuralAnalysisModels(bpy.types.Operator):
class DisableStructuralAnalysisModelEditingUI(bpy.types.Operator):
bl_idname = "bim.disable_structural_analysis_model_editing_ui"
bl_label = "Disable Structural Analysis Model Editing UI"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
context.scene.BIMStructuralProperties.is_editing = False
@@ -219,8 +245,12 @@ class DisableStructuralAnalysisModelEditingUI(bpy.types.Operator):
class AddStructuralAnalysisModel(bpy.types.Operator):
bl_idname = "bim.add_structural_analysis_model"
bl_label = "Add Structural Analysis Model"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
result = ifcopenshell.api.run("structural.add_structural_analysis_model", IfcStore.get_file())
has_context = False
@@ -242,8 +272,12 @@ class AddStructuralAnalysisModel(bpy.types.Operator):
class EditStructuralAnalysisModel(bpy.types.Operator):
bl_idname = "bim.edit_structural_analysis_model"
bl_label = "Edit Structural Analysis Model"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
props = context.scene.BIMStructuralProperties
attributes = {}
for attribute in props.structural_analysis_model_attributes:
@@ -271,9 +305,13 @@ class EditStructuralAnalysisModel(bpy.types.Operator):
class RemoveStructuralAnalysisModel(bpy.types.Operator):
bl_idname = "bim.remove_structural_analysis_model"
bl_label = "Remove Structural Analysis Model"
bl_options = {"REGISTER", "UNDO"}
structural_analysis_model: bpy.props.IntProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
props = context.scene.BIMStructuralProperties
self.file = IfcStore.get_file()
ifcopenshell.api.run(
@@ -289,6 +327,7 @@ class RemoveStructuralAnalysisModel(bpy.types.Operator):
class EnableEditingStructuralAnalysisModel(bpy.types.Operator):
bl_idname = "bim.enable_editing_structural_analysis_model"
bl_label = "Enable Editing Structural Analysis Model"
bl_options = {"REGISTER", "UNDO"}
structural_analysis_model: bpy.props.IntProperty()
def execute(self, context):
@@ -321,6 +360,7 @@ class EnableEditingStructuralAnalysisModel(bpy.types.Operator):
class DisableEditingStructuralAnalysisModel(bpy.types.Operator):
bl_idname = "bim.disable_editing_structural_analysis_model"
bl_label = "Disable Editing Structural Analysis Model"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
context.scene.BIMStructuralProperties.active_structural_analysis_model_id = 0
@@ -330,10 +370,14 @@ class DisableEditingStructuralAnalysisModel(bpy.types.Operator):
class AssignStructuralAnalysisModel(bpy.types.Operator):
bl_idname = "bim.assign_structural_analysis_model"
bl_label = "Assign Structural Analysis Model"
bl_options = {"REGISTER", "UNDO"}
product: bpy.props.StringProperty()
structural_analysis_model: bpy.props.IntProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
product = bpy.data.objects.get(self.product) if self.product else context.active_object
self.file = IfcStore.get_file()
ifcopenshell.api.run(
@@ -351,10 +395,14 @@ class AssignStructuralAnalysisModel(bpy.types.Operator):
class UnassignStructuralAnalysisModel(bpy.types.Operator):
bl_idname = "bim.unassign_structural_analysis_model"
bl_label = "Unassign Structural Analysis Model"
bl_options = {"REGISTER", "UNDO"}
product: bpy.props.StringProperty()
structural_analysis_model: bpy.props.IntProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
product = bpy.data.objects.get(self.product) if self.product else context.active_object
self.file = IfcStore.get_file()
ifcopenshell.api.run(
@@ -372,6 +420,7 @@ class UnassignStructuralAnalysisModel(bpy.types.Operator):
class EnableEditingStructuralItemAxis(bpy.types.Operator):
bl_idname = "bim.enable_editing_structural_item_axis"
bl_label = "Enable Editing Structural Item Axis"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
obj = bpy.context.active_object
@@ -411,6 +460,7 @@ class EnableEditingStructuralItemAxis(bpy.types.Operator):
class DisableEditingStructuralItemAxis(bpy.types.Operator):
bl_idname = "bim.disable_editing_structural_item_axis"
bl_label = "Disable Editing Structural Item Axis"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
obj = bpy.context.active_object
@@ -426,6 +476,9 @@ class EditStructuralItemAxis(bpy.types.Operator):
bl_label = "Edit Structural Item Axis"
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
obj = bpy.context.active_object
oprops = obj.BIMObjectProperties
props = obj.BIMStructuralProperties
@@ -445,6 +498,7 @@ class EditStructuralItemAxis(bpy.types.Operator):
class EnableEditingStructuralConnectionCS(bpy.types.Operator):
bl_idname = "bim.enable_editing_structural_connection_cs"
bl_label = "Enable Editing Structural Item Connection CS"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
obj = bpy.context.active_object
@@ -496,6 +550,7 @@ class EnableEditingStructuralConnectionCS(bpy.types.Operator):
class DisableEditingStructuralConnectionCS(bpy.types.Operator):
bl_idname = "bim.disable_editing_structural_connection_cs"
bl_label = "Disable Editing Structural Connection CS"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
obj = bpy.context.active_object
@@ -509,8 +564,12 @@ class DisableEditingStructuralConnectionCS(bpy.types.Operator):
class EditStructuralConnectionCS(bpy.types.Operator):
bl_idname = "bim.edit_structural_connection_cs"
bl_label = "Edit Structural Connection CS"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
obj = bpy.context.active_object
oprops = obj.BIMObjectProperties
props = obj.BIMStructuralProperties
@@ -540,10 +599,7 @@ class AssignStructuralLoadCase(bpy.types.Operator):
ifcopenshell.api.run(
"aggregate.assign_object",
self.file,
**{
"relating_object": self.file.by_id(self.work_plan),
"product": self.file.by_id(self.load_case),
},
**{"relating_object": self.file.by_id(self.work_plan), "product": self.file.by_id(self.load_case),},
)
Data.load(IfcStore.get_file())
return {"FINISHED"}
@@ -560,10 +616,7 @@ class UnassignStructuralLoadCase(bpy.types.Operator):
ifcopenshell.api.run(
"aggregate.unassign_object",
self.file,
**{
"relating_object": self.file.by_id(self.work_plan),
"product": self.file.by_id(self.load_case),
},
**{"relating_object": self.file.by_id(self.work_plan), "product": self.file.by_id(self.load_case),},
)
Data.load(IfcStore.get_file())
return {"FINISHED"}
@@ -572,8 +625,12 @@ class UnassignStructuralLoadCase(bpy.types.Operator):
class AddStructuralLoadCase(bpy.types.Operator):
bl_idname = "bim.add_structural_load_case"
bl_label = "Add Structural Load Case"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
ifcopenshell.api.run("structural.add_structural_load_case", IfcStore.get_file())
Data.load(IfcStore.get_file())
return {"FINISHED"}
@@ -582,8 +639,12 @@ class AddStructuralLoadCase(bpy.types.Operator):
class EditStructuralLoadCase(bpy.types.Operator):
bl_idname = "bim.edit_structural_load_case"
bl_label = "Edit Structural Load Case"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
props = context.scene.BIMStructuralProperties
attributes = blenderbim.bim.helper.export_attributes(props.load_case_attributes)
self.file = IfcStore.get_file()
@@ -600,9 +661,13 @@ class EditStructuralLoadCase(bpy.types.Operator):
class RemoveStructuralLoadCase(bpy.types.Operator):
bl_idname = "bim.remove_structural_load_case"
bl_label = "Remove Structural Load Case"
bl_options = {"REGISTER", "UNDO"}
load_case: bpy.props.IntProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"structural.remove_structural_load_case", self.file, load_case=self.file.by_id(self.load_case)
@@ -614,6 +679,7 @@ class RemoveStructuralLoadCase(bpy.types.Operator):
class EnableEditingStructuralLoadCase(bpy.types.Operator):
bl_idname = "bim.enable_editing_structural_load_case"
bl_label = "Enable Editing Structural Load Case"
bl_options = {"REGISTER", "UNDO"}
load_case: bpy.props.IntProperty()
def execute(self, context):
@@ -636,6 +702,7 @@ class EnableEditingStructuralLoadCase(bpy.types.Operator):
class DisableEditingStructuralLoadCase(bpy.types.Operator):
bl_idname = "bim.disable_editing_structural_load_case"
bl_label = "Disable Editing Structural Load Case"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
context.scene.BIMStructuralProperties.active_load_case_id = 0
@@ -645,9 +712,13 @@ class DisableEditingStructuralLoadCase(bpy.types.Operator):
class EnableEditingStructuralLoadCaseGroups(bpy.types.Operator):
bl_idname = "bim.enable_editing_structural_load_case_groups"
bl_label = "Enable Editing Structural Load Case Groups"
bl_options = {"REGISTER", "UNDO"}
load_case: bpy.props.IntProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
self.props = context.scene.BIMStructuralProperties
self.props.active_load_case_id = self.load_case
self.props.load_case_editing_type = "GROUPS"
@@ -657,9 +728,13 @@ class EnableEditingStructuralLoadCaseGroups(bpy.types.Operator):
class AddStructuralLoadGroup(bpy.types.Operator):
bl_idname = "bim.add_structural_load_group"
bl_label = "Add Structural Load Group"
bl_options = {"REGISTER", "UNDO"}
load_case: bpy.props.IntProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
self.file = IfcStore.get_file()
load_group = ifcopenshell.api.run("structural.add_structural_load_group", self.file)
ifcopenshell.api.run("group.assign_group", self.file, product=load_group, group=self.file.by_id(self.load_case))
@@ -670,6 +745,7 @@ class AddStructuralLoadGroup(bpy.types.Operator):
class RemoveStructuralLoadGroup(bpy.types.Operator):
bl_idname = "bim.remove_structural_load_group"
bl_label = "Remove Structural Load Group"
bl_options = {"REGISTER", "UNDO"}
load_group: bpy.props.IntProperty()
def execute(self, context):
@@ -684,6 +760,7 @@ class RemoveStructuralLoadGroup(bpy.types.Operator):
class EnableEditingStructuralLoadGroupActivities(bpy.types.Operator):
bl_idname = "bim.enable_editing_structural_load_group_activities"
bl_label = "Enable Editing Structural Load Group Activities"
bl_options = {"REGISTER", "UNDO"}
load_group: bpy.props.IntProperty()
def execute(self, context):
@@ -709,9 +786,13 @@ class EnableEditingStructuralLoadGroupActivities(bpy.types.Operator):
class AddStructuralActivity(bpy.types.Operator):
bl_idname = "bim.add_structural_activity"
bl_label = "Add Structural Activity"
bl_options = {"REGISTER", "UNDO"}
load_group: bpy.props.IntProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
self.props = context.scene.BIMStructuralProperties
self.file = IfcStore.get_file()
for obj in context.selected_objects:
@@ -759,15 +840,32 @@ class AddStructuralActivity(bpy.types.Operator):
class LoadStructuralLoads(bpy.types.Operator):
bl_idname = "bim.load_structural_loads"
bl_label = "Load Structural Loads"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
self.file = IfcStore.get_file()
props = context.scene.BIMStructuralProperties
while len(props.structural_loads) > 0:
props.structural_loads.remove(0)
for ifc_definition_id, structural_loads in Data.structural_loads.items():
new = props.structural_loads.add()
new.ifc_definition_id = ifc_definition_id
new.name = structural_loads["Name"] or "Unnamed"
if props.filtered_structural_loads:
names = [structural_load["Name"] or "Unnamed" for _, structural_load in Data.structural_loads.items()]
for ifc_definition_id, structural_load in Data.structural_loads.items():
if (
names.count(structural_load["Name"] or "Unnamed") > 1
and len(self.file.get_inverse(self.file.by_id(ifc_definition_id))) < 2
):
continue
new = props.structural_loads.add()
new.ifc_definition_id = ifc_definition_id
new.name = structural_load["Name"] or "Unnamed"
new.number_of_inverse_references = len(self.file.get_inverse(self.file.by_id(ifc_definition_id)))
else:
for ifc_definition_id, structural_load in Data.structural_loads.items():
new = props.structural_loads.add()
new.ifc_definition_id = ifc_definition_id
new.name = structural_load["Name"] or "Unnamed"
new.number_of_inverse_references = len(self.file.get_inverse(self.file.by_id(ifc_definition_id)))
props.is_editing_loads = True
bpy.ops.bim.disable_editing_structural_load()
return {"FINISHED"}
@@ -776,6 +874,7 @@ class LoadStructuralLoads(bpy.types.Operator):
class DisableStructuralLoadEditingUI(bpy.types.Operator):
bl_idname = "bim.disable_structural_load_editing_ui"
bl_label = "Disable Structural Load Editing UI"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
context.scene.BIMStructuralProperties.is_editing_loads = False
@@ -785,9 +884,13 @@ class DisableStructuralLoadEditingUI(bpy.types.Operator):
class AddStructuralLoad(bpy.types.Operator):
bl_idname = "bim.add_structural_load"
bl_label = "Add Structural Load"
bl_options = {"REGISTER", "UNDO"}
ifc_class: bpy.props.StringProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
result = ifcopenshell.api.run(
"structural.add_structural_load", IfcStore.get_file(), name="New Load", ifc_class=self.ifc_class
)
@@ -800,6 +903,7 @@ class AddStructuralLoad(bpy.types.Operator):
class EnableEditingStructuralLoad(bpy.types.Operator):
bl_idname = "bim.enable_editing_structural_load"
bl_label = "Enable Editing Structural Load"
bl_options = {"REGISTER", "UNDO"}
structural_load: bpy.props.IntProperty()
def execute(self, context):
@@ -816,6 +920,7 @@ class EnableEditingStructuralLoad(bpy.types.Operator):
class DisableEditingStructuralLoad(bpy.types.Operator):
bl_idname = "bim.disable_editing_structural_load"
bl_label = "Disable Editing Structural Load"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
context.scene.BIMStructuralProperties.active_structural_load_id = 0
@@ -825,9 +930,13 @@ class DisableEditingStructuralLoad(bpy.types.Operator):
class RemoveStructuralLoad(bpy.types.Operator):
bl_idname = "bim.remove_structural_load"
bl_label = "Remove Structural Load"
bl_options = {"REGISTER", "UNDO"}
structural_load: bpy.props.IntProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
props = context.scene.BIMStructuralProperties
self.file = IfcStore.get_file()
ifcopenshell.api.run(
@@ -843,19 +952,214 @@ class RemoveStructuralLoad(bpy.types.Operator):
class EditStructuralLoad(bpy.types.Operator):
bl_idname = "bim.edit_structural_load"
bl_label = "Edit Structural Load"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
props = context.scene.BIMStructuralProperties
attributes = blenderbim.bim.helper.export_attributes(props.structural_load_attributes)
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"structural.edit_structural_load",
self.file,
**{
"structural_load": self.file.by_id(props.active_structural_load_id),
"attributes": attributes,
},
**{"structural_load": self.file.by_id(props.active_structural_load_id), "attributes": attributes,},
)
Data.load(IfcStore.get_file())
bpy.ops.bim.load_structural_loads()
return {"FINISHED"}
class ToggleFilterStructuralLoads(bpy.types.Operator):
bl_idname = "bim.toggle_filter_structural_loads"
bl_label = "Toggle Filter Structural Loads"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
props = context.scene.BIMStructuralProperties
props.filtered_structural_loads = not props.filtered_structural_loads
bpy.ops.bim.load_structural_loads()
return {"FINISHED"}
class LoadBoundaryConditions(bpy.types.Operator):
bl_idname = "bim.load_boundary_conditions"
bl_label = "Load Boundary Conditions"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
self.file = IfcStore.get_file()
props = context.scene.BIMStructuralProperties
while len(props.boundary_conditions) > 0:
props.boundary_conditions.remove(0)
if props.filtered_boundary_conditions:
names = [
boundary_condition["Name"] or "Unnamed" for _, boundary_condition in Data.boundary_conditions.items()
]
for ifc_definition_id, boundary_condition in Data.boundary_conditions.items():
if (
names.count(boundary_condition["Name"] or "Unnamed") > 1
and len(self.file.get_inverse(self.file.by_id(ifc_definition_id))) < 2
):
continue
new = props.boundary_conditions.add()
new.ifc_definition_id = ifc_definition_id
new.name = boundary_condition["Name"] or "Unnamed"
new.number_of_inverse_references = len(self.file.get_inverse(self.file.by_id(ifc_definition_id)))
else:
for ifc_definition_id, boundary_condition in Data.boundary_conditions.items():
new = props.boundary_conditions.add()
new.ifc_definition_id = ifc_definition_id
new.name = boundary_condition["Name"] or "Unnamed"
new.number_of_inverse_references = len(self.file.get_inverse(self.file.by_id(ifc_definition_id)))
props.is_editing_boundary_conditions = True
bpy.ops.bim.disable_editing_boundary_condition()
return {"FINISHED"}
class ToggleFilterBoundaryConditions(bpy.types.Operator):
bl_idname = "bim.toggle_filter_boundary_conditions"
bl_label = "Toggle Filter Boundary Conditions"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
props = context.scene.BIMStructuralProperties
props.filtered_boundary_conditions = not props.filtered_boundary_conditions
bpy.ops.bim.load_boundary_conditions()
return {"FINISHED"}
class DisableBoundaryConditionEditingUI(bpy.types.Operator):
bl_idname = "bim.disable_boundary_condition_editing_ui"
bl_label = "Disable Boundary Condition Editing UI"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
context.scene.BIMStructuralProperties.is_editing_boundary_conditions = False
return {"FINISHED"}
class AddBoundaryCondition(bpy.types.Operator):
bl_idname = "bim.add_boundary_condition"
bl_label = "Add Boundary Condition"
bl_options = {"REGISTER", "UNDO"}
ifc_class: bpy.props.StringProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
result = ifcopenshell.api.run(
"structural.add_structural_boundary_condition",
IfcStore.get_file(),
name="New Load",
ifc_class=self.ifc_class,
)
Data.load(IfcStore.get_file())
bpy.ops.bim.load_boundary_conditions()
bpy.ops.bim.enable_editing_boundary_condition(boundary_condition=result.id())
return {"FINISHED"}
class EnableEditingBoundaryCondition(bpy.types.Operator):
bl_idname = "bim.enable_editing_boundary_condition"
bl_label = "Enable Editing Boundary Condition"
bl_options = {"REGISTER", "UNDO"}
boundary_condition: bpy.props.IntProperty()
def execute(self, context):
props = context.scene.BIMStructuralProperties
while len(props.boundary_condition_attributes) > 0:
props.boundary_condition_attributes.remove(0)
data = Data.boundary_conditions[self.boundary_condition]
# blenderbim.bim.helper.import_attributes(data["type"], props.boundary_condition_attributes, data)
for attribute in IfcStore.get_schema().declaration_by_name(data["type"]).all_attributes():
value = data[attribute.name()]
data_type = ifcopenshell.util.attribute.get_primitive_type(attribute)
new = props.boundary_condition_attributes.add()
new.name = attribute.name()
new.is_null = value is None
new.is_optional = attribute.optional()
if data_type == "select":
enum_items = [s.name() for s in ifcopenshell.util.attribute.get_select_items(attribute)]
new.enum_items = json.dumps(enum_items)
if isinstance(value, bool):
new.bool_value = False if new.is_null else data[attribute.name()]
new.data_type = "bool"
new.enum_value = "IfcBoolean"
elif isinstance(value, float):
new.float_value = 0.0 if new.is_null else data[attribute.name()]
new.data_type = "float"
new.enum_value = [i for i in enum_items if i != "IfcBoolean"][0]
elif data_type == "string":
new.string_value = "" if new.is_null else data[attribute.name()]
new.data_type = "string"
props.active_boundary_condition_id = self.boundary_condition
return {"FINISHED"}
class DisableEditingBoundaryCondition(bpy.types.Operator):
bl_idname = "bim.disable_editing_boundary_condition"
bl_label = "Disable Editing Boundary Condition"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
context.scene.BIMStructuralProperties.active_boundary_condition_id = 0
return {"FINISHED"}
class RemoveBoundaryCondition(bpy.types.Operator):
bl_idname = "bim.remove_boundary_condition"
bl_label = "Remove Boundary Condition"
bl_options = {"REGISTER", "UNDO"}
boundary_condition: bpy.props.IntProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
props = context.scene.BIMStructuralProperties
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"structural.remove_structural_boundary_condition",
self.file,
**{"boundary_condition": self.file.by_id(self.boundary_condition)},
)
Data.load(IfcStore.get_file())
bpy.ops.bim.load_boundary_conditions()
return {"FINISHED"}
class EditBoundaryCondition(bpy.types.Operator):
bl_idname = "bim.edit_boundary_condition"
bl_label = "Edit Boundary Condition"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
props = context.scene.BIMStructuralProperties
self.file = IfcStore.get_file()
# attributes = blenderbim.bim.helper.export_attributes(props.boundary_condition_attributes)
attributes = {}
for attribute in props.boundary_condition_attributes:
if attribute.is_null:
attributes[attribute.name] = {"value": None, "type": "null"}
elif attribute.data_type == "string":
attributes[attribute.name] = {"value": attribute.string_value, "type": "string"}
elif attribute.enum_value == "IfcBoolean":
attributes[attribute.name] = {"value": attribute.bool_value, "type": attribute.enum_value}
else:
attributes[attribute.name] = {"value": attribute.float_value, "type": attribute.enum_value}
ifcopenshell.api.run(
"structural.edit_structural_boundary_condition",
self.file,
**{"condition": self.file.by_id(props.active_boundary_condition_id), "attributes": attributes},
)
Data.load(IfcStore.get_file())
bpy.ops.bim.load_boundary_conditions()
return {"FINISHED"}
@@ -73,6 +73,15 @@ def getStructuralLoadTypes(self, context):
return structuralloadtypes_enum
def getBoundaryConditionTypes(self, context):
file = IfcStore.get_file()
if file:
declaration = IfcStore.get_schema().declaration_by_name("IfcBoundaryCondition")
boundaryconditiontypes_enum = [(d.name(), d.name(), "") for d in declaration.subtypes()]
return boundaryconditiontypes_enum
return []
def updateAxisAngle(self, context):
if not self.axis_empty:
return
@@ -112,6 +121,13 @@ class StructuralActivity(PropertyGroup):
class StructuralLoad(PropertyGroup):
name: StringProperty(name="Name")
ifc_definition_id: IntProperty(name="IFC Definition ID")
number_of_inverse_references: IntProperty(name="Number of Inverse References")
class BoundaryCondition(PropertyGroup):
name: StringProperty(name="Name")
ifc_definition_id: IntProperty(name="IFC Definition ID")
number_of_inverse_references: IntProperty(name="Number of Inverse References")
class BIMStructuralProperties(PropertyGroup):
@@ -129,20 +145,29 @@ class BIMStructuralProperties(PropertyGroup):
# load_group_attributes: CollectionProperty(name="Load Group Attributes", type=Attribute)
active_load_group_id: IntProperty(name="Active Load Group Id")
applicable_structural_load_types: EnumProperty(
items=getApplicableStructuralLoadTypes, name="Applicable Structural Load Types",
update=updateApplicableStructuralLoadTypes
)
applicable_structural_loads: EnumProperty(
items=getApplicableStructuralLoads, name="Applicable Structural Loads"
items=getApplicableStructuralLoadTypes,
name="Applicable Structural Load Types",
update=updateApplicableStructuralLoadTypes,
)
applicable_structural_loads: EnumProperty(items=getApplicableStructuralLoads, name="Applicable Structural Loads")
load_group_activities: CollectionProperty(name="Load Group Activities", type=StructuralActivity)
active_load_group_activity_index: IntProperty(name="Active Load Group Activity Index")
structural_loads: CollectionProperty(name="Structural Loads", type=StructuralLoad)
active_structural_load_index: IntProperty(name="Active Structural Load Index")
active_structural_load_id: IntProperty(name="Active Structural Load Id")
is_editing_loads: BoolProperty(name="Is Editing Loads", default=False)
structural_load_types: EnumProperty(items=getStructuralLoadTypes, name="Structural Load Types")
structural_load_attributes: CollectionProperty(name="Structural Load Attributes", type=Attribute)
filtered_structural_loads: BoolProperty(name="Filtered Structural Loads", default=False)
boundary_conditions: CollectionProperty(name="Boundary Conditions", type=BoundaryCondition)
active_boundary_condition_index: IntProperty(name="Active Boundary Condition Index")
active_boundary_condition_id: IntProperty(name="Active Boundary Condition Id")
is_editing_boundary_conditions: BoolProperty(name="Is Editing Boundary Conditions", default=False)
boundary_condition_types: EnumProperty(items=getBoundaryConditionTypes, name="Boundary Condition Types")
boundary_condition_attributes: CollectionProperty(name="Boundary Condition Attributes", type=Attribute)
filtered_boundary_conditions: BoolProperty(name="Filtered Boundary Conditions", default=False)
class BIMObjectStructuralProperties(PropertyGroup):
@@ -6,7 +6,11 @@ from ifcopenshell.api.structural.data import Data
def draw_boundary_condition_ui(layout, boundary_condition_id, connection_id, props):
data = Data.boundary_conditions[boundary_condition_id] if boundary_condition_id else {}
data = (
Data.boundary_conditions[boundary_condition_id]
if boundary_condition_id and boundary_condition_id in Data.boundary_conditions.keys()
else {}
)
row = layout.row(align=True)
if not data:
row.label(text="No Boundary Condition Found", icon="CON_TRACKTO")
@@ -76,6 +80,8 @@ class BIM_PT_structural_boundary_conditions(Panel):
props = context.active_object.BIMObjectProperties
if not props.ifc_definition_id:
return False
if not IfcStore.get_element(props.ifc_definition_id):
return False
if not IfcStore.get_file().by_id(props.ifc_definition_id).is_a("IfcStructuralConnection"):
return False
return True
@@ -106,6 +112,8 @@ class BIM_PT_connected_structural_members(Panel):
props = context.active_object.BIMObjectProperties
if not props.ifc_definition_id:
return False
if not IfcStore.get_element(props.ifc_definition_id):
return False
if not IfcStore.get_file().by_id(props.ifc_definition_id).is_a("IfcStructuralConnection"):
return False
return True
@@ -160,6 +168,8 @@ class BIM_PT_structural_member(Panel):
props = context.active_object.BIMObjectProperties
if not props.ifc_definition_id:
return False
if not IfcStore.get_element(props.ifc_definition_id):
return False
if not IfcStore.get_file().by_id(props.ifc_definition_id).is_a("IfcStructuralMember"):
return False
return True
@@ -198,6 +208,8 @@ class BIM_PT_structural_connection(Panel):
props = context.active_object.BIMObjectProperties
if not props.ifc_definition_id:
return False
if not IfcStore.get_element(props.ifc_definition_id):
return False
if not IfcStore.get_file().by_id(props.ifc_definition_id).is_a("IfcStructuralConnection"):
return False
return True
@@ -445,11 +457,18 @@ class BIM_PT_structural_loads(Panel):
self.props = context.scene.BIMStructuralProperties
row = self.layout.row(align=True)
row.label(text="{} Structural Loads Found".format(len(Data.structural_loads)), icon="GHOST_ENABLED")
row.label(text="{} Structural Loads Found".format(len(Data.structural_loads)), icon="ANIM_DATA")
if self.props.is_editing_loads:
row.operator(
"bim.toggle_filter_structural_loads",
text="FILTER - OFF" if not self.props.filtered_structural_loads else "FILTER - ON",
icon="FILTER",
)
row.operator("bim.disable_structural_load_editing_ui", text="", icon="SCREEN_BACK")
row = self.layout.row(align=True)
row.prop(self.props, "structural_load_types", text="")
row.operator("bim.add_structural_load", text="", icon="ADD").ifc_class = self.props.structural_load_types
row.operator("bim.disable_structural_load_editing_ui", text="", icon="CANCEL")
else:
row.operator("bim.load_structural_loads", text="", icon="GREASEPENCIL")
@@ -471,7 +490,7 @@ class BIM_UL_structural_loads(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
if item:
row = layout.row(align=True)
row.label(text=item.name)
row.label(text=f"{item.name} ({item.number_of_inverse_references})")
row.label(text=Data.structural_loads[item.ifc_definition_id]["type"])
if context.scene.BIMStructuralProperties.active_structural_load_id == item.ifc_definition_id:
@@ -485,3 +504,73 @@ class BIM_UL_structural_loads(UIList):
op.structural_load = item.ifc_definition_id
op = row.operator("bim.remove_structural_load", text="", icon="X")
op.structural_load = item.ifc_definition_id
class BIM_PT_boundary_conditions(Panel):
bl_label = "IFC Boundary Conditions"
bl_idname = "BIM_PT_boundary_conditions"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
@classmethod
def poll(cls, context):
return IfcStore.get_file()
def draw(self, context):
if not Data.is_loaded:
Data.load(IfcStore.get_file())
self.props = context.scene.BIMStructuralProperties
row = self.layout.row(align=True)
row.label(text="{} Boundary Conditions Found".format(len(Data.boundary_conditions)), icon="CON_TRACKTO")
if self.props.is_editing_boundary_conditions:
row.operator(
"bim.toggle_filter_boundary_conditions",
text="FILTER - OFF" if not self.props.filtered_boundary_conditions else "FILTER - ON",
icon="FILTER",
)
row.operator("bim.disable_boundary_condition_editing_ui", text="", icon="SCREEN_BACK")
row = self.layout.row(align=True)
row.prop(self.props, "boundary_condition_types", text="")
row.operator(
"bim.add_boundary_condition", text="", icon="ADD"
).ifc_class = self.props.boundary_condition_types
else:
row.operator("bim.load_boundary_conditions", text="", icon="GREASEPENCIL")
if self.props.is_editing_boundary_conditions:
self.layout.template_list(
"BIM_UL_boundary_conditions",
"",
self.props,
"boundary_conditions",
self.props,
"active_boundary_condition_index",
)
if self.props.active_boundary_condition_id:
draw_boundary_condition_editable_ui(self.layout, self.props)
# blenderbim.bim.helper.draw_attributes(self.props.boundary_condition_attributes, self.layout)
class BIM_UL_boundary_conditions(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
if item:
row = layout.row(align=True)
row.label(text=f"{item.name} ({item.number_of_inverse_references})")
row.label(text=Data.boundary_conditions[item.ifc_definition_id]["type"])
if context.scene.BIMStructuralProperties.active_boundary_condition_id == item.ifc_definition_id:
row.operator("bim.edit_boundary_condition", text="", icon="CHECKMARK")
row.operator("bim.disable_editing_boundary_condition", text="", icon="CANCEL")
elif context.scene.BIMStructuralProperties.active_boundary_condition_id:
op = row.operator("bim.remove_boundary_condition", text="", icon="X")
op.boundary_condition = item.ifc_definition_id
else:
op = row.operator("bim.enable_editing_boundary_condition", text="", icon="GREASEPENCIL")
op.boundary_condition = item.ifc_definition_id
op = row.operator("bim.remove_boundary_condition", text="", icon="X")
op.boundary_condition = item.ifc_definition_id
@@ -10,6 +10,7 @@ def get_colour_settings(material):
bsdf = material.node_tree.nodes["Principled BSDF"]
transparency = bsdf.inputs["Alpha"].default_value
diffuse_colour = bsdf.inputs["Base Color"].default_value
transparency = 1 - transparency
return {
"surface_colour": tuple(material.diffuse_color),
"transparency": transparency,
@@ -20,9 +21,13 @@ def get_colour_settings(material):
class EditStyle(bpy.types.Operator):
bl_idname = "bim.edit_style"
bl_label = "Edit Style"
bl_options = {"REGISTER", "UNDO"}
material: bpy.props.StringProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
self.file = IfcStore.get_file()
material = bpy.data.objects.get(self.material) if self.material else bpy.context.active_object.active_material
settings = get_colour_settings(material)
@@ -34,9 +39,13 @@ class EditStyle(bpy.types.Operator):
class AddStyle(bpy.types.Operator):
bl_idname = "bim.add_style"
bl_label = "Add Style"
bl_options = {"REGISTER", "UNDO"}
material: bpy.props.StringProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
self.file = IfcStore.get_file()
material = bpy.data.materials.get(self.material) if self.material else bpy.context.active_object.active_material
settings = get_colour_settings(material)
@@ -50,6 +59,7 @@ class AddStyle(bpy.types.Operator):
class UnlinkStyle(bpy.types.Operator):
bl_idname = "bim.unlink_style"
bl_label = "Unlink Style"
bl_options = {"REGISTER", "UNDO"}
material: bpy.props.StringProperty()
def execute(self, context):
@@ -11,10 +11,14 @@ from ifcopenshell.api.material.data import Data as MaterialData
class AssignType(bpy.types.Operator):
bl_idname = "bim.assign_type"
bl_label = "Assign Type"
bl_options = {"REGISTER", "UNDO"}
relating_type: bpy.props.IntProperty()
related_object: bpy.props.StringProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
self.file = IfcStore.get_file()
relating_type = self.relating_type or int(context.active_object.BIMTypeProperties.relating_type)
related_objects = (
@@ -85,6 +89,7 @@ class EnableEditingType(bpy.types.Operator):
class DisableEditingType(bpy.types.Operator):
bl_idname = "bim.disable_editing_type"
bl_label = "Disable Editing Type"
bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
def execute(self, context):
@@ -35,17 +35,7 @@ def getIfcTypes(self, context):
global type_classes_enum
file = IfcStore.get_file()
if len(type_classes_enum) < 1 and file:
declaration = IfcStore.get_schema().declaration_by_name("IfcElementType")
def get_classes(declaration):
results = []
if not declaration.is_abstract():
results.append(declaration.name())
for subtype in declaration.subtypes():
results.extend(get_classes(subtype))
return results
classes = get_classes(declaration)
classes = set([e.is_a() for e in file.by_type("IfcElementType")])
type_classes_enum.extend([(c, c, "") for c in sorted(classes)])
return type_classes_enum
@@ -59,7 +49,9 @@ def getAvailableTypes(self, context):
def updateTypeInstanceIfcClass(self, context):
global type_classes_enum
global available_types_enum
type_classes_enum.clear()
available_types_enum.clear()
@@ -15,6 +15,8 @@ class BIM_PT_type(Panel):
props = context.active_object.BIMObjectProperties
if not props.ifc_definition_id:
return False
if not IfcStore.get_element(props.ifc_definition_id):
return False
if props.ifc_definition_id not in Data.products:
Data.load(IfcStore.get_file(), props.ifc_definition_id)
if not Data.products[props.ifc_definition_id]:
@@ -57,10 +57,14 @@ class AddOpening(bpy.types.Operator):
class RemoveOpening(bpy.types.Operator):
bl_idname = "bim.remove_opening"
bl_label = "Remove Opening"
bl_options = {"REGISTER", "UNDO"}
opening_id: bpy.props.IntProperty()
obj: bpy.props.StringProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
self.file = IfcStore.get_file()
for modifier in obj.modifiers:
@@ -14,6 +14,8 @@ class BIM_PT_voids(Panel):
@classmethod
def poll(cls, context):
if not IfcStore.get_element(context.active_object.BIMObjectProperties.ifc_definition_id):
return False
return IfcStore.get_file()
def draw(self, context):
+6 -3
View File
@@ -27,7 +27,11 @@ target_views_enum = []
def getAttributeEnumValues(self, context):
return [(e, e, "") for e in json.loads(self.enum_items)]
# Support weird buildingSMART dictionary mappings which behave like enums
data = json.loads(self.enum_items)
if isinstance(data, dict):
return [(str(k), v, "") for k, v in data.items()]
return [(e, e, "") for e in data]
def updateSchemaDir(self, context):
@@ -170,9 +174,8 @@ class BIMProperties(PropertyGroup):
default=os.path.join(cwd, "data") + os.path.sep, name="Data Directory", update=updateDataDir
)
ifc_file: StringProperty(name="IFC File", update=updateIfcFile)
id_map: StringProperty(name="ID Map")
guid_map: StringProperty(name="GUID Map")
export_schema: EnumProperty(items=[("IFC4", "IFC4", ""), ("IFC2X3", "IFC2X3", "")], name="IFC Schema")
last_transaction: StringProperty(name="Last Transaction")
contexts: EnumProperty(items=getContexts, name="Contexts")
available_contexts: EnumProperty(items=[("Model", "Model", ""), ("Plan", "Plan", "")], name="Available Contexts")
available_subcontexts: EnumProperty(items=getSubcontexts, name="Available Subcontexts")
+2
View File
@@ -12,6 +12,8 @@ this document.
:caption: Contents:
blenderbim/installation
blenderbim/developer_guide
blenderbim/undo_system
blenderbim/scene
Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

@@ -0,0 +1,264 @@
Developer guide
===============
The BlenderBIM Add-on takes a unique approach to authoring BIM data. Traditional
BIM authoring apps create features that are tailored for a single discipline's
paradigm, such as a 3D environment, or a spreadsheet view, and store their data
structure in a schema that is unique to their application. In order to
interoperate with others, there is an export or import process that translates
between their discipline-specific schema and the ISO standard for BIM: IFC.
After translation, they then serialise it typically into one of the various IFC
formats.
The BlenderBIM Add-on does things differently.
The BlenderBIM Add-on modifies ISO standard BIM data directly using the IFC
schema in memory. Every user operation reads or writes this data structure in
memory, and the IFC data becomes the source of truth for all data. There is no
such thing as an import or export, but simply a serialisation or deserialisation
operation. This also means that the ``.blend`` container is largely unnecessary,
as nothing of significance is stored in the Blender system. Unlike traditional
BIM which relies on translated IFC data, the BlenderBIM Add-on works with Native
IFC, and also works equally across all disciplines.
Due to this significant difference, hacking on the BlenderBIM Add-on requires
knowledge not just about how Blender works, but also how IFC works.
Technology stack
----------------
At a high level, the BlenderBIM Add-on works through two integration of two
systems. The IFC based system takes care of reading and writing IFC data using
the IfcOpenShell library. This system is agnostic of Blender and the BlenderBIM
Add-on. Data is read from the IFC dataset using ``Data`` classes. Data is
written into the IFC dataset using ``Usecase`` classes.
The Blender based system takes care of turning Blender into a BIM authoring
client. Like all Blender add-ons, ``Operators`` define user operations, such as
those triggered when pressing a button. ``UI`` defines the interface, such as
the layout of buttons, input fields, and panels. Finally, ``Props`` define
properties that Blender keeps track of, typically for interface input fields or
user settings.
The Blender system depends on the IFC system. The IFC system does not depend on
the Blender system.
.. image:: architecture.png
..
digraph G {rankdir=LR;
node [fontname = "Handlee", shape=rect];
subgraph cluster_0 {
node [style=filled,color=pink];
IfcOpenShell -> Data;
Usecase -> IfcOpenShell;
label = "*IFC based*";
fontsize = 20;
color=grey
}
subgraph cluster_1 {
node [style=filled,color=lightblue];
Operators -> Usecase
Data->UI
Data->Operators
Operators -> Props
Props -> Operators
Props -> UI
label = "*Blender based*";
fontsize = 20;
color=grey
}
}
Of course, there are many details that we are glossing over, but it provides a
good representation of data flow in the BlenderBIM Add-on.
The code for these two systems may be found here:
- `ifcopenshell <https://github.com/IfcOpenShell/IfcOpenShell/tree/v0.6.0/src/ifcopenshell-python>`__
- `blenderbim <https://github.com/IfcOpenShell/IfcOpenShell/tree/v0.6.0/src/blenderbim>`__
Module architecture
-------------------
Because BIM authoring is a vast field, the architecture is further grouped into
modules. Modules include things like project related capabilities, structural
related capabilities, costing related capabilities, and so on.
Each module consists an IFC based system, which does all the IFC manipulations,
and a Blender based counterpart, which exposes all of these operations to the
user via the Blender interface.
.. image:: module-architecture.png
..
digraph G {
node [fontname = "Handlee", shape=rect];
subgraph cluster_1 {
rank=same;
{
blender1 [label="Blender", style=filled, color=lightblue];
ifc1 [label="IFC", style=filled, color=pink];
ifc1 -> blender1
blender1 -> ifc1
}
label = "*Project Module*";
fontsize = 20;
color=grey
}
subgraph cluster_2 {
rank=same;
{
blender2 [label="Blender", style=filled, color=lightblue];
ifc2 [label="IFC", style=filled, color=pink];
ifc2 -> blender2
blender2 -> ifc2
}
label = "*Structural Module*";
fontsize = 20;
color=grey
}
subgraph cluster_3 {
rank=same;
{
blender3 [label="Blender", style=filled, color=lightblue];
ifc3 [label="IFC", style=filled, color=pink];
ifc3 -> blender3
blender3 -> ifc3
}
label = "*XYZ Module*";
fontsize = 20;
color=grey
}
}
Modules are not arbitrary divisions, but instead reflect subgraphs of IFC
relationships that minimise overlap with other subgraphs. Therefore modules are
mostly self contained, and allow developers to work on a single portion of the
code with relative certainty that their actions will not affects other
developers. Sometimes, overlap is inevitable.
There are over 40 modules, so we won't list them here.
- `IFC modules <https://github.com/IfcOpenShell/IfcOpenShell/tree/v0.6.0/src/ifcopenshell-python/ifcopenshell/api>`__
- `Blender modules <https://github.com/IfcOpenShell/IfcOpenShell/tree/v0.6.0/src/blenderbim/blenderbim/bim/module>`__
IfcOpenShell Architecture
-------------------------
Manipulating IFC data is not simple. IFC may be serialised into multiple
formats, multiple schema versions must be supported, and geometry may be defined
in a highly parametric or implicit manner, which geometry kernels do not
natively support. All this heavy lifting is performed by the IfcOpenShell
library.
The IfcOpenShell library consists of a C++ based core. Its geometry processing
is done using OpenCascade, and optionally CGAL as an experimental option. By the
time the BlenderBIM Add-on interacts with IFC, it uses the IfcOpenShell Python
bindings, so all IFC data is already deserialised into Python objects. The inner
workings of the C++ base is out of scope.
.. image:: ifcopenshell-architecture.png
..
digraph G {rankdir=LR;
node [fontname = "Handlee", shape=rect, style=filled,color=pink];
IfcOpenShell [label="IfcOpenShell C++", color=grey]
ifcopenshell [label="IfcOpenShell-python"]
OpenCascade [color=grey]
CGAL [color=grey]
OpenCascade -> IfcOpenShell
CGAL -> IfcOpenShell
IfcOpenShell -> ifcopenshell
ifcopenshell -> Core
ifcopenshell -> Utils
ifcopenshell -> API
API -> Module01
API -> Module02
API -> Module03
Module03[label="..."]
Module01 -> Data
Module01 -> Usecase
}
IfcOpenShell offers a core set of low-level functionality to read and write this
data. An example of the core functionality would be:
.. code-block:: python
import ifcopenshell
model = ifcopenshell.open("foo.ifc")
wall = model.create_entity("IfcWall")
wall.Name = "Foobar"
Core functions are simple read and write operations with no post processing.
Core functions also include geometry processing, which converts IFC geometry
into OpenCascade objects.
Sometimes, there are repetitive actions that need to be performed. These
functions are grouped into a ``util`` module. These include utility functions
for coordinate calculations, date conversions, filtering elements, unit
conversions, and more. Utility functions make no assumption about the context in
which they are used, and so perform highly specific tasks and nothing else.
Here's an example of utility functionality:
.. code-block:: python
import ifcopenshell
import ifcopenshell.util.date
import ifcopenshell.util.geolocation
start = ifcopenshell.util.date.ifc2datetime(task_time.ScheduleStart)
coordinates = ifcopenshell.util.geolocation.local2global(matrix, eastings, ...)
When authoring, core and utility functions are usually too low-level. To cater
for this, a high level API is provided. The API is divided into mostly isolated
modules, each module representing a distinct set of concepts in the IFC schema.
Unlike the util module, these API modules are highly context-sensitive, and
assume that you intend to be authoring native IFC.
This context-sensitive assumption means that the functions within the modules
are designed around typical usecases in an authoring environment. It performs
all the necessary manipulations to achieve a domain-specific usecase. Authoring
is complex and requires a deep knowledge of IFC to perform correctly and ensure
that the IFC graph state is well maintained. Typically, any authoring operation
that does not use the API is likely to contain mistakes.
Each module contains a Data class to extract various IFC data related to the IFC
concept that the module relates to. The ``Data`` classes parse the complex IFC
graph and convert it into a cache of primitive Python data. The ``Usecase``
clases perform a defined user operation. Here's an example of it in action:
.. code-block:: python
import ifcopenshell.api
ifcopenshell.api.run("grid.create_grid_axis", model, ...)
ifcopenshell.api.run("structural.add_structural_load", model, ...)
Because the API performs all the IFC manipulations to achieve a usecase, no
further interaction is required in a typical native IFC authoring environment.
For this reason, the BlenderBIM Add-on only interacts with the API for its
authoring capabilities.
The code for IfcOpenShell's various systems can be found here:
- `ifcopenshell (core) <https://github.com/IfcOpenShell/IfcOpenShell/tree/v0.6.0/src/ifcopenshell-python/ifcopenshell>`__
- `ifcopenshell.util <https://github.com/IfcOpenShell/IfcOpenShell/tree/v0.6.0/src/ifcopenshell-python/ifcopenshell/util>`__
- `ifcopenshell.api <https://github.com/IfcOpenShell/IfcOpenShell/tree/v0.6.0/src/ifcopenshell-python/ifcopenshell/api>`__
BlenderBIM Add-on architecture
------------------------------
TODO
Binary file not shown.

After

Width:  |  Height:  |  Size: 408 KiB

+179 -49
View File
@@ -1,91 +1,221 @@
Installation
============
BlenderBIM is packaged as a Blender add-on, so installation is the same as any
other Blender add-on. The full instructions for end-user installation is
available at the `Get BlenderBIM <https://blenderbim.org/download.html>`_
website.
There are different methods of installation, depending on your situation.
1. **Packaged installation** is recommended for regular users.
2. **Daily build installation** is recommended for power users helping with testing.
3. **Unpackaged installation** is recommended for package managers.
4. **Source installation** is recommended for developers.
Packaged installation
---------------------
The BlenderBIM Add-on is packaged like a regular Blender add-on, so installation
is the same as any other Blender add-on. The full instructions for end-user
installation is available at the `Get BlenderBIM
<https://blenderbim.org/download.html>`__ website. The latest release is
typically updated every few weeks.
Like all Blender add-ons, they can be installed using ``Edit > Preferences >
Addons > Install > Choose Downloaded ZIP > Enable Add-on Checkbox``. You can
enable add-ons permanently by using ``Save User Settings`` from the Addons menu.
If you downloaded Blender as a ``.zip`` file without running an installer, you
will find the BlenderBIM plug-in installed in:
will find the BlenderBIM Add-on installed in the following directory, where
``2.XX`` is the Blender version:
::
/path/to/blender/2.81/scripts/addons/
/path/to/blender/2.XX/scripts/addons/
Otherwise, if you installed Blender using an installation package, the add-ons
folder depends on which operating system you use. On Linux:
::
~/.config/blender/2.81/scripts/addons/
~/.config/blender/2.XX/scripts/addons/
On Mac:
::
/Users/{YOUR_USER}/Library/Application Support/Blender/2.81/
/Users/{YOUR_USER}/Library/Application Support/Blender/2.XX/
On Windows:
::
C:\Users\{YOUR_USER}\AppData\Roaming\Blender Foundation\2.81\scripts\addons
C:\Users\{YOUR_USER}\AppData\Roaming\Blender Foundation\2.XX\scripts\addons
Upon installation, a series of files will be created. This is necessary as
BlenderBIM has a variety of complex dependencies. A full list is below:
Upon installation, the BlenderBIM Add-on is stored in the ``blenderbim/``
directory.
Daily build installation
------------------------
Daily builds are almost the same as **Packaged installation**, except that they
are typically updated every day. Simply download a daily build from the `Github
releases page <https://github.com/IfcOpenShell/IfcOpenShell/releases>`__, then
follow the same instructions as a packaged installation.
You will need to choose which daily build to download.
- If you are on Blender <2.93, choose py37
- If you are on Blender >=2.93, choose py39
- Choose linux, macos, or win depending on your operating system
Daily builds are not always stable. Sometimes, a build may be delayed, or
contain broken code. We try to avoid this, but it happens.
Unpackaged installation
-----------------------
The BlenderBIM Add-on is fully contained in the ``blenderbim/`` subfolder of the
Blender add-ons directory. This is typically distributed as a zipfile as per
Blender add-on conventions. Within this folder, you'll find the following file
structure:
::
blenderbim/
ifcopenshell/
OCC/
pystache/
svgwrite/
deepdiff/
jsonpickle/
lib/ # Note: this only exists on MacOS and Linux
ordered_set.py
pyparsing.py
bim/ (core code)
libs/ (dependencies)
__init__.py
If you are not on Windows, when BlenderBIM first launches, it will create a
bunch of library files in the ``2.81/`` folder too. This is a non-standard
location to place files, but is a hack to allow people to use precompiled builds
from Conda.
This corresponds to the structure found in the source code `here
<https://github.com/IfcOpenShell/IfcOpenShell/tree/v0.6.0/src/blenderbim/blenderbim>`__.
If you receive an error when enabling the add-on, you may have installed the
package for the wrong platform.
The BlenderBIM Add-on is complex, and requires many dependencies, including
Python modules, binaries, and static assets. These dependencies are bundled with
the add-on for convenience in the **Packaged installation** and **Daily build
installation** methods.
Updating
--------
If you choose to install the BlenderBIM Add-on and use your own system
dependencies, the source of truth for how dependencies are bundled are found in
the `Makefile
<https://github.com/IfcOpenShell/IfcOpenShell/blob/v0.6.0/src/blenderbim/Makefile>`__.
It is recommended to uninstall the current BlenderBIM add-on before installing
the latest version to ensure the update goes well.
Required Python modules to be stored in ``libs/site/packages/`` are:
::
From Source
-----------
ifcopenshell
bcf
ifcclash
bimtester
ifccobie
ifcdiff
ifccsv
ifcpatch
ifcp6
pystache
svgwrite
dateutil
isodate
networkx
deepdiff
jsonpickle
ordered_set
pyparsing
xmlschema
elementpath
six
lark-parser
fcl
behave
parse
parse_type
xlsxwriter
odfpy
defusedxml
boto3
botocore
jmespath
s3transfer
ifcjson
Notes:
1. ``ifcopenshell`` almost always requires the latest version due to the fast paced nature of the add-on development.
2. ``fcl`` is not bundled for MacOS, due to lack of maintained community build. This is required for clash detection.
3. ``behave`` requires `patches <https://github.com/IfcOpenShell/IfcOpenShell/tree/v0.6.0/src/ifcbimtester/patch>`__.
4. ``ifcjson`` can be found `here <https://github.com/IFCJSON-Team/IFC2JSON_python/tree/master/file_converters>`__.
Required binaries are:
::
libs/IfcConvert
Required static assets are:
::
bim/data/gantt/jsgantt.js (from jsgantt-improved)
bim/data/gantt/jsgantt.css (from jsgantt-improved)
Source installation
-------------------
It is possible to run the latest bleeding edge version of BlenderBIM without
having to wait for an official release, since BlenderBIM is coded in Python and
doesn't require any compilation. First, install the latest official release, and
then `download the latest source code
<https://github.com/IfcOpenShell/IfcOpenShell/archive/v0.6.0.zip>`_. If you know
how to use Git, you can also stay up to date like so:
doesn't require any compilation.
You can create your own package by using the Makefile as shown below. You can
choose between a ``PLATFORM`` of ``linux``, ``macos``, and ``win``. You can
choose between a ``PYVERSION`` of ``py39`` and ``py37``.
::
$ cd src/blenderbim
$ make dist PLATFORM=linux PYVERSION=py39
$ ls dist/
However, creating a build, uninstalling the old add-on, and installing a new
build is a slow process. A more rapid approach is to follow the **Daily build
installation** method, as this provides all dependencies for you out of the box.
Then, we can replace certain Python files that tend to be updated frequently
with those from the Git repository. We're going to use symlinks (Windows user
can use ``mklink``), so we can code in our Git repository, and see the changes
in our Blender installation.
In addition, we're also going to replace the Python code of the IfcOpenShell
dependency with our Git repository, since most of the BlenderBIM Add-on
functionality is agnostic of Blender, and is actually part of IfcOpenShell.
Therefore, we need to keep this dependency highly updated as well.
The downside with this approach is that if a new dependency is added, or a
compiled dependency version requirement has changed, or the build system
changes, you'll need to fix your setup manually. But this is relatively rare.
::
$ git clone https://github.com/IfcOpenShell/IfcOpenShell.git
$ cd IfcOpenShell
$ git checkout v0.6.0
Then, just copy the files from the source code's
``src/blenderbim/blenderbim/`` folder and replace the files in your
Blender add-on's ``blenderbim/`` folder.
# Remove the Blender add-on Python code
$ rm -r /path/to/blender/2.XX/scripts/addons/blenderbim/bim/
Restart Blender for the changes to take effect. In ``Edit > Preferences >
Add-ons`` you will see that the version number of BlenderBIM has changed to
``0.0.999999``, which represents an un-versioned BlenderBIM.
# Replace them with links to the Git repository
$ ln -s src/blenderbim/blenderbim/bim /path/to/blender/2.XX/scripts/addons/blenderbim/bim
# Remove the IfcOpenShell dependency Python code
$ rm -r /path/to/blender/2.XX/scripts/addons/blenderbim/libs/site/packages/ifcopenshell/api
$ rm -r /path/to/blender/2.XX/scripts/addons/blenderbim/libs/site/packages/ifcopenshell/util
# Replace them with links to the Git repository
$ ln -s src/ifcopenshell-python/ifcopenshell/api /path/to/blender/2.XX/scripts/addons/blenderbim/libs/site/packages/ifcopenshell/api
$ ln -s src/ifcopenshell-python/ifcopenshell/util /path/to/blender/2.XX/scripts/addons/blenderbim/libs/site/packages/ifcopenshell/util
After you modify your code in the Git repository, you will need to restart
Blender for the changes to take effect. In ``Edit > Preferences > Add-ons`` you
will see that the version number of BlenderBIM has changed to ``0.0.999999``,
which represents an un-versioned BlenderBIM.
Updating
--------
First uninstall the current BlenderBIM add-on, then install the latest version.
Uninstalling
------------
You can remove all of the files added by BlenderBIM in the Blender add-ons
folder and then remove the add-on using the Blender interface through ``Edit >
Preferences > Add-ons`` just like any other add-on.
Navigate to ``Edit > Preferences > Add-ons``. Due to a limitation in Blender,
you have to first disable the BlenderBIM Add-on in your Blender preferences by
pressing the checkbox next to the add-on, then restart Blender. After
restarting, you can uninstall the BlenderBIM Add-on by pressing the ``Remove``
button in the Blender preferences window.
If you are not on Windows, then ensure that all library files are deleted in the
``2.81/`` directory. Do not delete any of Blender's own folders.
Alternatively, you may uninstall manually by deleting the ``blenderbim/``
directory in your Blender add-ons directory.
Binary file not shown.

After

Width:  |  Height:  |  Size: 159 KiB

@@ -0,0 +1,117 @@
Undo system
===========
Supporting undo and redo is quite a complex problem because the Blender undo
system only keeps track of changes occuring in the Blender system. However,
changes actually occur in two other locations that Blender doesn't know about:
the IFC dataset, and the BlenderBIM Add-on system that synchronises Blender and
the IFC dataset.
Let's see how undo works in a basic Blender add-on without IFC or the BlenderBIM
Add-on getting involved.
.. code-block:: python
:emphasize-lines: 4
class Foobar(bpy.types.Operator):
bl_idname = "foobar"
bl_label = "Foobar"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
context.scene.name = "Foobar"
return {"FINISHED"}
This operation changes Blender data. The important line is ``bl_options =
{"REGISTER", "UNDO"}``, which tells Blender to keep track of it as a single
transaction in its undo history. When you press undo or redo, Blender figures
out all the changes automatically and you don't need to do anything.
If you have an operator that only manipulates (creates, removes, or edits)
Blender data, this solution is sufficient.
Now let's look at pure IfcOpenShell.
.. code-block:: python
:emphasize-lines: 3,5
import ifcopenshell
model = ifcopenshell.open("foo.ifc")
model.begin_transaction()
model.create_entity("IfcWall")
model.end_transaction()
model.undo()
model.redo()
Pure IfcOpenShell let's you start and stop recording transactions whenever you
want. Since IfcOpenShell has no interface, you manually run code like
``model.undo()`` and ``model.redo()`` to undo and redo.
This scenario where there is pure IfcOpenShell never occurs with the BlenderBIM
Add-on. Instead, stuff happens in Blender operators.
.. code-block:: python
:emphasize-lines: 6,7
class Foobar(bpy.types.Operator):
bl_idname = "foobar"
bl_label = "Foobar"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
ifcopenshell.api.run("foo.bar", IfcStore.get_file())
return {"FINISHED"}
When your operator manipulates (creates, removes, or edits) IFC data directly or
indirectly (i.e. through calling another operator), your operator must be
wrapped in an ``IfcStore.execute_ifc_operator`` call. This wrapper will:
1. Begin a BlenderBIM Add-on transaction
2. Begin an IfcOpenShell transaction
3. Runs your operator's ``_execute``.
4. Ends the IfcOpenShell transaction
5. Ends the BlenderBIM Add-on transaction
The IfcOpenShell transaction keeps track of IFC data changes, and the BlenderBIM
Add-on transaction keeps track of all other custom data changes, like changes in
the ``id_map`` and ``guid_map``. For the vast majority of operations, this
wrapper provides everything that you need.
If, however, your operator manipulates data that is not tracked by Blender, is
not tracked in the IFC data, and is not tracked in the element map, then you
will have to write your own rollback (undo) and commit (redo) code for your
operator. Here is an example.
.. code-block:: python
class Foobar(bpy.types.Operator):
bl_idname = "foobar"
bl_label = "Foobar"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
IfcStore.begin_transaction(operator)
old_value = Foo.bar
result = self._execute(context)
new_value = Foo.bar
self.transaction_data = {"old_value": old_value, "new_value": new_value}
IfcStore.add_transaction_operation(self)
IfcStore.end_transaction(operator)
return result
def _execute(self, context):
Foo.bar = "baz"
return {"FINISHED"}
def rollback(self, data):
Foo.baz = data["old_value"]
def commit(self, data):
Foo.baz = data["new_value"]
Note that there is still a distinction between ``execute`` and ``_execute``.
This recommended convention allows you to quickly discern undo state tracking
code from regular operation code.
+18
View File
@@ -0,0 +1,18 @@
IfcSverchok
===========
This documentation is free software! You are free to contribute and help write
this document.
.. toctree::
:maxdepth: 1
:caption: Contents:
ifcsverchok/installation
Indices and tables
------------------
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`
@@ -0,0 +1,99 @@
Installation
============
There are different methods of installation, depending on your situation.
1. **Packaged installation** is recommended for regular users.
2. **Daily build installation** is recommended for power users helping with testing.
3. **Source installation** is recommended for developers.
Packaged installation
---------------------
IfcSverchok is packaged like a regular Blender add-on, so installation is the
same as any other Blender add-on. You can download the package for installation
at the `Get BlenderBIM <https://blenderbim.org/download.html>`__ website.
Like all Blender add-ons, they can be installed using ``Edit > Preferences >
Addons > Install > Choose Downloaded ZIP > Enable Add-on Checkbox``. You can
enable add-ons permanently by using ``Save User Settings`` from the Addons menu.
Before installing, you will also need to `install the BlenderBIM Add-on
<../blenderbim/installation>`__ and `install Sverchok
<https://github.com/nortikin/sverchok#installation>`__.
If you downloaded Blender as a ``.zip`` file without running an installer, you
will find IfcSverchok installed in the following directory, where ``2.XX`` is
the Blender version:
::
/path/to/blender/2.XX/scripts/addons/
Otherwise, if you installed Blender using an installation package, the add-ons
folder depends on which operating system you use. On Linux:
::
~/.config/blender/2.XX/scripts/addons/
On Mac:
::
/Users/{YOUR_USER}/Library/Application Support/Blender/2.XX/
On Windows:
::
C:\Users\{YOUR_USER}\AppData\Roaming\Blender Foundation\2.XX\scripts\addons
Upon installation, IfcSverchok is stored in the ``ifcsverchok/`` directory.
Daily build installation
------------------------
Daily builds are almost the same as **Packaged installation**, except that they
are typically updated every day. Simply download a daily build from the `Github
releases page <https://github.com/IfcOpenShell/IfcOpenShell/releases>`__, then
follow the same instructions as a packaged installation.
TODO: daily builds not yet available
Daily builds are not always stable. Sometimes, a build may be delayed, or
contain broken code. We try to avoid this, but it happens.
Source installation
-------------------
It is possible to run the latest bleeding edge version of IfcSverchok without
having to wait for an official release, since IfcSverchok is coded in Python and
doesn't require any compilation.
Just symbolically link the IfcSverchok add-on files to your Git repository. If
you're on Windows, use ``mklink`` instead. This allows us to code in our Git
repository, and see the changes in our Blender installation.
::
$ git clone https://github.com/IfcOpenShell/IfcOpenShell.git
$ cd IfcOpenShell
# Link the repository to blender
$ ln -s src/ifcsverchok /path/to/blender/2.XX/scripts/addons/ifcsverchok
After you modify your code in the Git repository, you will need to restart
Blender for the changes to take effect. In ``Edit > Preferences > Add-ons`` you
will see that the version number of IfcSverchok has changed to ``0.0.999999``,
which represents an un-versioned IfcSverchok.
Updating
--------
First uninstall the current IfcSverchok, then install the latest version.
Uninstalling
------------
Navigate to ``Edit > Preferences > Add-ons``, find the IfcSverchok add-on, and
press ``Remove``.
Alternatively, you may uninstall manually by deleting the ``ifcsverchok/``
directory in your Blender add-ons directory.
+1
View File
@@ -14,6 +14,7 @@ IfcOpenShell Documentation
ifcclash
ifccobie
ifcmax
ifcsverchok
bimserver-plugin
Binary file not shown.
+2 -2
View File
@@ -42,7 +42,7 @@ class Client:
if self.access_token and self.access_token_expires_on > time.time():
return self.access_token
elif self.refresh_token and self.refresh_token_expires_on > time.time():
self.refresh_token()
self.get_new_access_token()
else:
self.login()
return self.access_token
@@ -75,7 +75,7 @@ class Client:
).json()
)
def refresh_token(self):
def get_new_access_token(self):
self.set_tokens_from_response(
requests.post(
self.token_endpoint,
+1 -1
View File
@@ -944,7 +944,7 @@ int main(int argc, char** argv) {
}
std::stringstream msg;
msg << std::setprecision (17) << "Using model offset (" << offset[0] << "," << offset[1] << "," << offset[2] << ")";
msg << std::setprecision (std::numeric_limits< double >::max_digits10) << "Using model offset (" << offset[0] << "," << offset[1] << "," << offset[2] << ")";
Logger::Notice(msg.str());
}
+1 -1
View File
@@ -1179,7 +1179,7 @@ bool IfcGeom::Kernel::convert(const IfcSchema::IfcDerivedProfileDef* l, TopoDS_S
gp_Trsf2d trsf2d;
if (convert_face(l->ParentProfile(), f) && IfcGeom::Kernel::convert(l->Operator(), trsf2d)) {
gp_Trsf trsf = trsf2d;
face = TopoDS::Face(BRepBuilderAPI_Transform(f, trsf));
face = BRepBuilderAPI_Transform(f, trsf).Shape();
return true;
} else {
return false;
+24 -9
View File
@@ -28,6 +28,7 @@
#include <NCollection_UBTree.hxx>
#include <BRepBndLib.hxx>
#include <Bnd_Box.hxx>
#include <BRep_Builder.hxx>
#include <BRepAlgoAPI_Common.hxx>
#include <BRepAlgoAPI_Cut.hxx>
#include <BRepExtrema_DistShapeShape.hxx>
@@ -93,9 +94,10 @@ namespace IfcGeom {
return select_box(b, completely_within);
}
std::vector<T> select_box(const gp_Pnt& p) const {
std::vector<T> select_box(const gp_Pnt& p, double extend=0.0) const {
Bnd_Box b;
b.Add(p);
b.SetGap(b.GetGap() + extend);
return select_box(b);
}
@@ -198,8 +200,8 @@ namespace IfcGeom {
return ts_filtered;
}
std::vector<T> select(const gp_Pnt& p) const {
std::vector<T> ts = select_box(p);
std::vector<T> select(const gp_Pnt& p, double extend=0.0) const {
std::vector<T> ts = select_box(p, extend);
if (ts.empty()) {
return ts;
}
@@ -207,17 +209,30 @@ namespace IfcGeom {
std::vector<T> ts_filtered;
ts_filtered.reserve(ts.size());
TopoDS_Vertex v;
if (extend > 0.) {
BRep_Builder B;
B.MakeVertex(v, p, Precision::Confusion());
}
typename std::vector<T>::const_iterator it = ts.begin();
for (it = ts.begin(); it != ts.end(); ++it) {
const TopoDS_Shape& B = shapes_.find(*it)->second;
TopExp_Explorer exp(B, TopAbs_SOLID);
for (; exp.More(); exp.Next()) {
BRepClass3d_SolidClassifier cls(exp.Current(), p, 1e-5);
if (cls.State() != TopAbs_OUT) {
if (extend > 0.0) {
BRepExtrema_DistShapeShape dss(v, B);
if (dss.Perform() && dss.NbSolution() >= 1) {
ts_filtered.push_back(*it);
break;
}
}
} else {
TopExp_Explorer exp(B, TopAbs_SOLID);
for (; exp.More(); exp.Next()) {
BRepClass3d_SolidClassifier cls(exp.Current(), p, 1e-5);
if (cls.State() != TopAbs_OUT) {
ts_filtered.push_back(*it);
break;
}
}
}
}
return ts_filtered;
+129 -70
View File
@@ -1,9 +1,11 @@
import operator
from dataclasses import dataclass
import numpy
import ifcopenshell
import ifcopenshell.geom
import ifcopenshell
import ifcopenshell.geom
import ifcopenshell.express
import ifcopenshell.transition_curve
@@ -14,42 +16,90 @@ import ifcopenshell.transition_curve
# does it make handling the variety of distance expressions and
# interpolation harder?
@dataclass
class line:
start_point: numpy.ndarray
direction_vector: numpy.ndarray
def __call__(self, u):
return self.start_point + \
self.direction_vector * u
def __call__(self, u):
p = numpy.ndarray((3,))
p[0:2] = self.start_point + self.direction_vector * u
p[2] = numpy.nan
return p
@dataclass
class circle:
radius: numpy.ndarray
def __call__(self, u):
return numpy.array([
self.radius * numpy.cos(u),
self.radius * numpy.sin(u)
])
def __call__(self, u):
return numpy.array(
[self.radius * numpy.cos(u), self.radius * numpy.sin(u), numpy.nan]
)
def place(matrix, func):
"""
Higher order function for application of a 3x3 matrix
to a 2D point. Assumes a functor such as line or circle.
"""
def inner(*args):
v = func(*args)
# homogenize
v = numpy.insert(v, v.shape[-1], 1, axis=-1)
return (matrix @ v)[0:2]
v = numpy.insert(v[0:2], v[0:2].shape, 1, axis=-1)
p = numpy.ndarray((3,))
p[0:2] = (matrix @ v)[0:2]
p[2] = numpy.nan
return p
return inner
# primitives for manipulating and joining curve functor domains
def reparametrized_curve(fn, a, b):
return lambda u: fn(a * u + b)
def normalized_curve(fn):
return lambda u: fn(u / fn.length)
class trimmed_curve:
def __init__(self, fn, length):
self.fn = fn
self.length = length
def __call__(self, u):
assert u >= 0.0 and u <= self.length
return self.fn(u)
class piecewise:
# takes a set of functors and returns a function f(u) that delegates to the correct segment
def __init__(self, fns):
self.fns = fns
self.length = sum(map(operator.attrgetter("length"), fns))
def __call__(self, u):
# this is silly, assuming `u` is monotonically increases we should not always start
# searching from the first segment or at least binary search into the segment
# lengths
u0 = 0
for fn in self.fns:
u1 = u0 + fn.length
if u >= u0 and u <= u1:
return fn(u - u0)
u0 = u1
# mapping functions from IFC entities
def map_inst(inst):
"""
Looks up one of the implementation functions below in the global namespace
@@ -60,14 +110,12 @@ def map_inst(inst):
def impl_IfcLine(inst):
return line(
numpy.array(inst.Pnt.Coordinates),
numpy.array(inst.Dir.Orientation.DirectionRatios) * inst.Dir.Magnitude
numpy.array(inst.Dir.Orientation.DirectionRatios) * inst.Dir.Magnitude,
)
def impl_IfcCircle(inst):
return place(map_inst(inst.Position), circle(
inst.Radius
))
return place(map_inst(inst.Position), circle(inst.Radius))
def impl_IfcClothoid(inst):
@@ -76,109 +124,120 @@ def impl_IfcClothoid(inst):
# ifcopenshell.transition_curve.TransitionCurve(
# StartPoint = place.T[2]
# StartDirection = numpy.arctan2(place.T[0][1], place.T[0][0]),
# SegmentLength =
# IsStartRadiusCCW =
# IsEndRadiusCCW =
# TransitionCurveType =
# StartRadius =
# EndRadius =
# SegmentLength =
# IsStartRadiusCCW =
# IsEndRadiusCCW =
# TransitionCurveType =
# StartRadius =
# EndRadius =
# )
return lambda *args: numpy.array((0.,0.))
return lambda *args: numpy.array((0.0, 0.0))
def impl_IfcAxis2Placement2D(inst):
arr = numpy.eye(3)
if inst is None:
return arr
arr.T[2, 0:2] = inst.Location.Coordinates
if inst.RefDirection is None:
return arr
arr.T[0, 0:2] = inst.RefDirection.DirectionRatios
arr.T[0, 0:2] /= numpy.linalg.norm(arr.T[0, 0:2])
arr.T[1, 0:2] = -arr.T[0,1], arr.T[0,0]
arr.T[1, 0:2] = -arr.T[0, 1], arr.T[0, 0]
return arr
# conversion functions for semantic design parameters (not used atm)
def convert(inst):
"""
Looks up one of the conversion functions below in the global namespace
"""
yield from globals()[f"convert_{inst.is_a()}_{inst.PredefinedType}"](inst)
def convert_IfcAlignmentHorizontalSegment_LINE(data):
xy = numpy.array(data.StartPoint.Coordinates)
yield xy
di = numpy.array([
numpy.cos(data.StartDirection),
numpy.sin(data.StartDirection)
])
di = numpy.array([numpy.cos(data.StartDirection), numpy.sin(data.StartDirection)])
yield xy + di * data.SegmentLength
# Two approaches, either DesignParameters or Representation
# Two approaches, either DesignParameters or Representation
def interpret_linear_element_semantics(settings, crv):
# traverse decomposition
for rel in crv.IsNestedBy:
for obj in rel.RelatedObjects:
yield from interpret_linear_element_semantics(settings, obj)
# lookup design parameters and dispatch to conversion function
if crv.is_a("IfcAlignmentSegment"):
dp = crv.DesignParameters
yield from convert(dp)
def interpret_linear_element_geometry(settings, crv):
for segment in crv.Representation.Representations[0].Items[0].Segments:
print(segment)
print(segment.ParentCurve)
print()
func = place(
map_inst(segment.Placement),
map_inst(segment.ParentCurve)
)
for u in numpy.linspace(
segment.SegmentStart[0],
segment.SegmentStart[0] + segment.SegmentLength[0],
num=32
):
yield func(u)
def evaluate_segment(segment):
# print(segment)
# print(segment.ParentCurve)
# print()
func = place(map_inst(segment.Placement), map_inst(segment.ParentCurve))
# reparam so domain starts at zero
reparam = reparametrized_curve(func, 1.0, -segment.SegmentStart[0])
# embed curve length (doesn't do much, just make length recoverable)
trimmed = trimmed_curve(reparam, segment.SegmentLength[0])
return trimmed
def interpret_linear_element_geometry(settings, crv):
func = piecewise(
list(
map(
evaluate_segment,
crv.Representation.Representations[0].Items[0].Segments,
)
)
)
for u in numpy.linspace(0, func.length, num=int(numpy.ceil(func.length / 0.05))):
yield func(u)
interpret_linear_element = interpret_linear_element_geometry
def create_shape(settings, elem):
if elem.is_a("IfcLinearPositioningElement") or elem.is_a("IfcLinearElement"):
return numpy.row_stack(list(interpret_linear_element(settings, elem)))
else:
return ifcopenshell.geom.create_shape(settings, elem)
def print_structure(alignment, indent=0):
"""
Debugging function to print alignment decomposition
"""
print(" " * indent, str(alignment)[0:100])
for rel in alignment.IsNestedBy:
for child in rel.RelatedObjects:
print_structure(child, indent+2)
print(" " * indent, str(alignment)[0:100])
for rel in alignment.IsNestedBy:
for child in rel.RelatedObjects:
print_structure(child, indent + 2)
if __name__ == "__main__":
import sys
from matplotlib import pyplot as plt
s = ifcopenshell.express.parse("IFC4x3_RC3.exp")
ifcopenshell.register_schema(s)
f = ifcopenshell.open(sys.argv[1])
@@ -186,6 +245,6 @@ if __name__ == "__main__":
al_hor = f.by_type("IfcAlignmentHorizontal")[0]
xy = create_shape({}, al_hor)
plt.plot(xy.T[0], xy.T[1])
plt.savefig("horizontal_alignment.png")
@@ -5,22 +5,14 @@ import ifcopenshell
import ifcopenshell.api
registered_ifcs = {}
pre_listeners = {}
post_listeners = {}
def run(usecase_path, ifc_file=None, should_run_listeners=True, **settings):
ifc_key = registered_ifcs.setdefault(ifc_file, ifcopenshell.guid.new())
if should_run_listeners:
for listener in pre_listeners.get(".".join([ifc_key, usecase_path]), []):
listener(usecase_path, ifc_file, **settings)
if None in registered_ifcs and ifc_key != registered_ifcs[None]:
global_key = registered_ifcs[None]
for listener in pre_listeners.get(".".join([global_key, usecase_path]), []):
listener(usecase_path, ifc_file, **settings)
for listener in pre_listeners.get(usecase_path, {}).values():
listener(usecase_path, ifc_file, settings)
def serialise_entity_instance(entity):
return {"cast_type": "entity_instance", "value": entity.id(), "Name": getattr(entity, "Name", None)}
@@ -35,16 +27,16 @@ def run(usecase_path, ifc_file=None, should_run_listeners=True, **settings):
vcs_settings[key] = [serialise_entity_instance(i) for i in value]
if "add_representation" in usecase_path:
pass
# print(ifc_key, usecase_path, "{ ... settings too complex right now ... }")
# print(usecase_path, "{ ... settings too complex right now ... }")
elif "owner." in usecase_path:
pass
else:
pass
# print(vcs_settings)
# try:
# print(ifc_key, usecase_path, json.dumps(vcs_settings))
# print(usecase_path, json.dumps(vcs_settings))
# except:
# print(ifc_key, usecase_path, vcs_settings)
# print(usecase_path, vcs_settings)
importlib.import_module(f"ifcopenshell.api.{usecase_path}")
module, usecase = usecase_path.split(".")
@@ -56,82 +48,52 @@ def run(usecase_path, ifc_file=None, should_run_listeners=True, **settings):
result = usecase_class(**settings).execute()
if should_run_listeners:
for listener in post_listeners.get(".".join([ifc_key, usecase_path]), []):
listener(usecase_path, ifc_file, **settings)
if None in registered_ifcs and ifc_key != registered_ifcs[None]:
global_key = registered_ifcs[None]
for listener in post_listeners.get(".".join([global_key, usecase_path]), []):
listener(usecase_path, ifc_file, **settings)
for listener in post_listeners.get(usecase_path, {}).values():
listener(usecase_path, ifc_file, settings)
return result
def add_pre_listener(usecase_path, ifc_file, callback):
def add_pre_listener(usecase_path, name, callback):
"""Add a pre listener
There are 2 kind of listeners:
when ifc file is defined the listener will only run for specified file
when ifc file is None, the listener will run globally only based on usecase
:param usecase_path: string, ifcopenshell api use case path
:param ifc_file: ifc file object or None for global listener
:param name: string, name of listener
:param callback: callback function
:return: ifc_key, uuid of listener, this is the prefix only, postfix is the usecase_path dot separated.
"""
ifc_key = registered_ifcs.setdefault(ifc_file, ifcopenshell.guid.new())
pre_listeners.setdefault(".".join([ifc_key, usecase_path]), set()).add(callback)
return ifc_key
pre_listeners.setdefault(usecase_path, {})[name] = callback
def add_post_listener(usecase_path, ifc_file, callback):
def add_post_listener(usecase_path, name, callback):
"""Add a post listener
There are 2 kind of listeners:
when ifc file is defined the listener will only run for specified file
when ifc file is None, the listener will run globally only based on usecase
:param usecase_path: string, ifcopenshell api use case path
:param ifc_file: ifc file object or None for global listener
:param name: string, name of listener
:param callback: callback function
:return: ifc_key, uuid of listener, this is the prefix only, postfix is the usecase_path dot separated.
"""
ifc_key = registered_ifcs.setdefault(ifc_file, ifcopenshell.guid.new())
post_listeners.setdefault(".".join([ifc_key, usecase_path]), set()).add(callback)
return ifc_key
post_listeners.setdefault(usecase_path, {})[name] = callback
def remove_pre_listener(callback, usecase_path=""):
def remove_pre_listener(usecase_path, name, callback):
"""Remove a pre listener
:param callback: callback function to remove
:param usecase_path: string, optional, ifcopenshell api usecase path, may be prefixed with ifc_key, dot separated.
:return:
:param usecase_path: string, ifcopenshell api use case path
:param name: string, name of listener
:param callback: callback function
"""
for listener_key, callbacks in pre_listeners.items():
if not listener_key.endswith(usecase_path):
continue
to_remove = set()
for fun in callbacks:
if fun == callback:
to_remove.add(callback)
for callback in to_remove:
pre_listeners[listener_key].remove(callback)
pre_listeners.get(usecase_path, {}).pop(name, None)
def remove_post_listener(callback, usecase_path=""):
def remove_post_listener(usecase_path, name, callback):
"""Remove a post listener
:param callback: callback function to remove
:param usecase_path: string, optional ifcopenshell api usecase path, may be prefixed with ifc_key, dot separated.
:return:
:param usecase_path: string, ifcopenshell api use case path
:param name: string, name of listener
:param callback: callback function
"""
for listener_key, callbacks in post_listeners.items():
if not listener_key.endswith(usecase_path):
continue
to_remove = set()
for fun in callbacks:
if fun == callback:
to_remove.add(callback)
for callback in to_remove:
post_listeners[listener_key].remove(callback)
post_listeners.get(usecase_path, {}).pop(name, None)
def remove_all_listeners():
registered_ifcs.clear()
pre_listeners.clear()
post_listeners.clear()
@@ -2,6 +2,7 @@ import ifcopenshell
import ifcopenshell.util.schema
import ifcopenshell.util.date
class Usecase:
def __init__(self, file, **settings):
self.file = file
@@ -22,16 +23,21 @@ class Usecase:
# TODO: should auto date migration be part of the migrator?
if self.file.schema == "IFC2X3" and edition_date:
result.EditionDate = ifcopenshell.util.date.datetime2ifc(edition_date, "IfcCalendarDate")
result.EditionDate = self.file.create_entity(
"IfcCalendarDate", **ifcopenshell.util.date.datetime2ifc(edition_date, "IfcCalendarDate")
)
else:
result.EditionDate = ifcopenshell.util.date.datetime2ifc(edition_date, "IfcDate")
self.file.create_entity("IfcRelAssociatesClassification", **{
"GlobalId": ifcopenshell.guid.new(),
"RelatedObjects": [self.file.by_type("IfcProject")[0]],
"RelatingClassification": result
})
return # See bug #1272
self.file.create_entity(
"IfcRelAssociatesClassification",
**{
"GlobalId": ifcopenshell.guid.new(),
"RelatedObjects": [self.file.by_type("IfcProject")[0]],
"RelatingClassification": result,
}
)
return result # See bug #1272
try:
result = self.file.add(self.settings["classification"])
except:
@@ -29,8 +29,10 @@ class Usecase:
# IfcExtrudedAreaSolid/IfcRectangleProfileDef
# IfcExtrudedAreaSolid/IfcCircleProfileDef
# IfcExtrudedAreaSolid/IfcArbitraryClosedProfileDef
# IfcExtrudedAreaSolid/IfcArbitraryProfileDef
# IfcExtrudedAreaSolid/IfcArbitraryProfileDefWithVoids
# IfcExtrudedAreaSolid/IfcMaterialProfileSetUsage
"ifc_representation_class": None, # Whether to cast a mesh into a particular class
"profile_set_usage": None, # The material profile set if the extrusion requires it
}
self.ifc_vertices = []
for key, value in settings.items():
@@ -49,7 +51,7 @@ class Usecase:
elif self.settings["context"].ContextType == "Plan":
return self.create_plan_representation()
return self.create_variable_representation()
def should_triangulate_face(self, face, threshold=EPSILON):
vz = face.normal
co = face.verts[0].co
@@ -60,15 +62,12 @@ class Usecase:
else:
vx = vz.cross(X_AXIS)
vy = vx.cross(vz)
tM = Matrix([
[vx.x, vy.x, vz.x, co.x],
[vx.y, vy.y, vz.y, co.y],
[vx.z, vy.z, vz.z, co.z],
[0, 0, 0, 1]
]).inverted()
tM = Matrix(
[[vx.x, vy.x, vz.x, co.x], [vx.y, vy.y, vz.y, co.y], [vx.z, vy.z, vz.z, co.z], [0, 0, 0, 1]]
).inverted()
return any([abs((tM @ v.co).z) > threshold for v in face.verts])
return any([abs((tM @ v.co).z) > threshold for v in face.verts])
def evaluate_geometry(self):
for modifier in self.settings["blender_object"].modifiers:
if modifier.type == "BOOLEAN":
@@ -83,6 +82,7 @@ class Usecase:
faces = [f for f in bm.faces if self.should_triangulate_face(f)]
bmesh.ops.triangulate(bm, faces=faces)
bm.to_mesh(mesh)
mesh.update()
bm.free()
del bm
@@ -137,7 +137,7 @@ class Usecase:
elif self.settings["context"].ContextIdentifier == "CoG":
pass
elif self.settings["context"].ContextIdentifier == "FootPrint":
if self.settings["context"].TargetView in ["PLAN_VIEW", "REFLECTED_PLAN_VIEW"]:
if self.settings["context"].TargetView in ["SKETCH_VIEW", "PLAN_VIEW", "REFLECTED_PLAN_VIEW"]:
return self.create_geometric_curve_set_representation(is_2d=True)
elif self.settings["context"].ContextIdentifier == "Reference":
pass
@@ -220,6 +220,8 @@ class Usecase:
return self.create_arbitrary_extrusion_representation()
elif self.settings["ifc_representation_class"] == "IfcExtrudedAreaSolid/IfcArbitraryProfileDefWithVoids":
return self.create_arbitrary_void_extrusion_representation()
elif self.settings["ifc_representation_class"] == "IfcExtrudedAreaSolid/IfcMaterialProfileSetUsage":
return self.create_material_profile_set_extrusion_representation()
return self.create_mesh_representation()
def create_camera_block_representation(self):
@@ -265,7 +267,7 @@ class Usecase:
"Curve3D",
self.create_curves(),
)
def create_curve2d_representation(self):
return self.file.createIfcShapeRepresentation(
self.settings["context"],
@@ -394,6 +396,8 @@ class Usecase:
def create_arbitrary_void_extrusion_representation(self):
helper = Helper(self.file)
indices = helper.auto_detect_arbitrary_profile_with_voids_extruded_area_solid(self.settings["geometry"])
if not indices["inner_curves"]:
return self.create_arbitrary_extrusion_representation()
profile_def = helper.create_arbitrary_profile_def_with_voids(
self.settings["geometry"], indices["profile"], indices["inner_curves"]
)
@@ -405,6 +409,29 @@ class Usecase:
[item],
)
def create_material_profile_set_extrusion_representation(self):
profile_set = self.settings["profile_set_usage"].ForProfileSet
profile_def = profile_set.CompositeProfile or profile_set.MaterialProfiles[0].Profile
position = None
if self.file.schema == "IFC2X3":
position = self.file.createIfcAxis2Placement3D(
self.file.createIfcCartesianPoint((0.0, 0.0, 0.0)),
self.file.createIfcDirection((0.0, 0.0, 1.0)),
self.file.createIfcDirection((1.0, 0.0, 0.0)),
)
item = self.file.createIfcExtrudedAreaSolid(
profile_def,
position,
self.file.createIfcDirection((0.0, 0.0, 1.0)),
self.convert_si_to_unit(self.settings["blender_object"].dimensions[2]),
)
return self.file.createIfcShapeRepresentation(
self.settings["context"],
self.settings["context"].ContextIdentifier,
"SweptSolid",
[item],
)
def create_mesh_representation(self):
if self.file.schema == "IFC2X3" or self.settings["should_force_faceted_brep"]:
return self.create_faceted_brep()
@@ -1,6 +1,7 @@
import ifcopenshell
import ifcopenshell.api
import ifcopenshell.util.element
import ifcopenshell.util.representation
class Usecase:
@@ -38,7 +39,17 @@ class Usecase:
material_set = self.file.create_entity(self.settings["type"])
self.create_material_association(material_set)
elif self.settings["type"] == "IfcMaterialProfileSetUsage":
material_set = self.file.create_entity("IfcMaterialProfileSet")
element_type = ifcopenshell.util.element.get_type(self.settings["product"])
if element_type:
element_type_material = ifcopenshell.util.element.get_material(element_type)
if element_type_material and element_type_material.is_a("IfcMaterialProfileSet"):
material_set = element_type_material
else:
material_set = self.file.create_entity("IfcMaterialProfileSet")
else:
material_set = self.file.create_entity("IfcMaterialProfileSet")
self.update_representation_profile(material_set)
material_set_usage = self.create_profile_set_usage(material_set)
self.create_material_association(material_set_usage)
elif self.settings["type"] == "IfcMaterialList":
@@ -46,6 +57,21 @@ class Usecase:
material_set.Materials = [self.settings["material"]]
self.create_material_association(material_set)
def update_representation_profile(self, material_set):
profile = material_set.CompositeProfile
if not profile and material_set.MaterialProfiles:
profile = material_set.MaterialProfiles[0].Profile
if not profile:
return
representation = ifcopenshell.util.representation.get_representation(
self.settings["product"], "Model", "Body", "MODEL_VIEW"
)
if not representation:
return
for subelement in self.file.traverse(representation):
if subelement.is_a("IfcSweptAreaSolid"):
subelement.SweptArea = profile
def create_layer_set_usage(self, material_set):
return self.file.create_entity(
"IfcMaterialLayerSetUsage",
@@ -1,3 +1,6 @@
import ifcopenshell.util.representation
class Usecase:
def __init__(self, file, **settings):
self.file = file
@@ -6,9 +9,31 @@ class Usecase:
self.settings[key] = value
def execute(self):
if (
self.settings["material_profile"].Profile
and len(self.file.get_inverse(self.settings["material_profile"].Profile)) == 1
):
self.file.remove(self.settings["material_profile"].Profile)
# TODO: handle composite profiles
old_profile = self.settings["material_profile"].Profile
self.settings["material_profile"].Profile = self.settings["profile"]
for profile_set in self.settings["material_profile"].ToMaterialProfileSet:
for inverse in self.file.get_inverse(profile_set):
if not inverse.is_a("IfcMaterialProfileSetUsage"):
continue
if self.file.schema == "IFC2X3":
for rel in self.file.get_inverse(inverse):
if not rel.is_a("IfcRelAssociatesMaterial"):
continue
for element in rel.RelatedObjects:
self.change_profile(element)
else:
for rel in inverse.AssociatedTo:
for element in rel.RelatedObjects:
self.change_profile(element)
if old_profile and len(self.file.get_inverse(old_profile)) == 0:
self.file.remove(old_profile)
def change_profile(self, element):
representation = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
if not representation:
return
for subelement in self.file.traverse(representation):
if subelement.is_a("IfcSweptAreaSolid"):
subelement.SweptArea = self.settings["profile"]
@@ -0,0 +1,146 @@
import ifcopenshell.geom
import ifcopenshell.util.representation
class Usecase:
def __init__(self, file, **settings):
self.file = file
self.settings = {"usage": None, "attributes": {}}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
self.cardinal_point = self.settings["attributes"].get("CardinalPoint")
if self.cardinal_point and self.cardinal_point != self.settings["usage"].CardinalPoint:
self.update_cardinal_point()
for name, value in self.settings["attributes"].items():
setattr(self.settings["usage"], name, value)
def update_cardinal_point(self):
material_set = self.settings["usage"].ForProfileSet
self.profile = material_set.CompositeProfile
if not self.profile and material_set.MaterialProfiles:
self.profile = material_set.MaterialProfiles[0].Profile
if not self.profile:
return
self.position = self.calculate_position()
if self.file.schema == "IFC2X3":
for rel in self.file.get_inverse(self.settings["usage"]):
if not rel.is_a("IfcRelAssociatesMaterial"):
continue
for element in rel.RelatedObjects:
self.update_representation(element)
else:
for rel in self.settings["usage"].AssociatedTo:
for element in rel.RelatedObjects:
self.update_representation(element)
def calculate_position(self):
self.dummy = ifcopenshell.file(schema=self.file.schema)
dummy_profile = self.dummy.add(self.profile)
# We clear all radiuses so that we can calculate geometric centroid easily
for i, attribute in enumerate(dummy_profile):
name = dummy_profile.attribute_name(i)
if "Radius" in name and name != "RoundingRadius":
dummy_profile[i] = None
dummy_solid = self.dummy.create_entity("IfcExtrudedAreaSolid", **{
"SweptArea": dummy_profile,
"ExtrudedDirection": self.dummy.createIfcDirection((0., 0., 1.)),
"Depth": 1
})
self.settings_2d = ifcopenshell.geom.settings()
self.settings_2d.set(self.settings_2d.INCLUDE_CURVES, True)
shape = ifcopenshell.geom.create_shape(self.settings_2d, dummy_solid)
if self.cardinal_point == 1:
return self.get_bottom_left(shape)
elif self.cardinal_point == 2:
return self.get_bottom_centre(shape)
elif self.cardinal_point == 3:
return self.get_bottom_right(shape)
elif self.cardinal_point == 4:
return self.get_mid_depth_left(shape)
elif self.cardinal_point == 5:
return self.get_mid_depth_centre(shape)
elif self.cardinal_point == 6:
return self.get_mid_depth_right(shape)
elif self.cardinal_point == 7:
return self.get_top_left(shape)
elif self.cardinal_point == 8:
return self.get_top_centre(shape)
elif self.cardinal_point == 9:
return self.get_top_right(shape)
def get_bottom_left(self, shape):
v = shape.verts
x = [v[i] for i in range(0, len(v), 3)]
y = [v[i+1] for i in range(0, len(v), 3)]
width = max(x) - min(x)
height = max(y) - min(y)
return self.file.createIfcAxis2Placement3D(self.file.createIfcCartesianPoint((-width/2, height/2, 0.)))
def get_bottom_centre(self, shape):
v = shape.verts
y = [v[i+1] for i in range(0, len(v), 3)]
height = max(y) - min(y)
return self.file.createIfcAxis2Placement3D(self.file.createIfcCartesianPoint((0., height/2, 0.)))
def get_bottom_right(self, shape):
v = shape.verts
x = [v[i] for i in range(0, len(v), 3)]
y = [v[i+1] for i in range(0, len(v), 3)]
width = max(x) - min(x)
height = max(y) - min(y)
return self.file.createIfcAxis2Placement3D(self.file.createIfcCartesianPoint((width/2, height/2, 0.)))
def get_mid_depth_left(self, shape):
v = shape.verts
x = [v[i] for i in range(0, len(v), 3)]
width = max(x) - min(x)
return self.file.createIfcAxis2Placement3D(self.file.createIfcCartesianPoint((-width/2, 0., 0.)))
def get_mid_depth_centre(self, shape):
return self.file.createIfcAxis2Placement3D(self.file.createIfcCartesianPoint((0., 0., 0.)))
def get_mid_depth_right(self, shape):
v = shape.verts
x = [v[i] for i in range(0, len(v), 3)]
width = max(x) - min(x)
return self.file.createIfcAxis2Placement3D(self.file.createIfcCartesianPoint((width/2, 0., 0.)))
def get_top_left(self, shape):
v = shape.verts
x = [v[i] for i in range(0, len(v), 3)]
y = [v[i+1] for i in range(0, len(v), 3)]
width = max(x) - min(x)
height = max(y) - min(y)
return self.file.createIfcAxis2Placement3D(self.file.createIfcCartesianPoint((-width/2, -height/2, 0.)))
def get_top_centre(self, shape):
v = shape.verts
y = [v[i+1] for i in range(0, len(v), 3)]
height = max(y) - min(y)
return self.file.createIfcAxis2Placement3D(self.file.createIfcCartesianPoint((0., -height/2, 0.)))
def get_top_right(self, shape):
v = shape.verts
x = [v[i] for i in range(0, len(v), 3)]
y = [v[i+1] for i in range(0, len(v), 3)]
width = max(x) - min(x)
height = max(y) - min(y)
return self.file.createIfcAxis2Placement3D(self.file.createIfcCartesianPoint((width/2, -height/2, 0.)))
def update_representation(self, element):
representation = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
if not representation:
return
for subelement in self.file.traverse(representation):
if subelement.is_a("IfcSweptAreaSolid") and subelement.SweptArea == self.profile:
self.update_swept_area_solid(subelement)
def update_swept_area_solid(self, element):
element.Position = self.position
@@ -0,0 +1,68 @@
import ifcopenshell.api
class Usecase:
def __init__(self, file, **settings):
self.file = file
self.settings = {
"application_developer": None,
"version": "0.6.0",
"application_full_name": "IfcOpenShell",
"application_identifier": "IfcOpenShell",
}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
if not self.settings["application_developer"]:
self.settings["application_developer"] = self.create_application_organisation()
return self.file.create_entity(
"IfcApplication",
**{
"ApplicationDeveloper": self.settings["application_developer"],
"Version": self.settings["version"],
"ApplicationFullName": self.settings["application_full_name"],
"ApplicationIdentifier": self.settings["application_identifier"],
},
)
def create_application_organisation(self):
return self.file.create_entity(
"IfcOrganization",
**{
"Name": "IfcOpenShell",
"Description": "IfcOpenShell is an open source (LGPL) software library that helps users and software developers to work with the IFC file format.",
"Roles": [
self.file.create_entity("IfcActorRole", **{"Role": "USERDEFINED", "UserDefinedRole": "CONTRIBUTOR"})
],
"Addresses": [
self.file.create_entity(
"IfcTelecomAddress",
**{
"Purpose": "USERDEFINED",
"UserDefinedPurpose": "WEBPAGE",
"Description": "The main webpage of the software collection.",
"WWWHomePageURL": "https://ifcopenshell.org",
},
),
self.file.create_entity(
"IfcTelecomAddress",
**{
"Purpose": "USERDEFINED",
"UserDefinedPurpose": "WEBPAGE",
"Description": "The BlenderBIM Add-on webpage of the software collection.",
"WWWHomePageURL": "https://blenderbim.org",
},
),
self.file.create_entity(
"IfcTelecomAddress",
**{
"Purpose": "USERDEFINED",
"UserDefinedPurpose": "REPOSITORY",
"Description": "The source code repository of the software collection.",
"WWWHomePageURL": "https://github.com/IfcOpenShell/IfcOpenShell.git",
},
),
],
},
)
@@ -13,11 +13,11 @@ class Usecase:
def execute(self):
self.settings["person"] = ifcopenshell.api.owner.settings.get_person(self.file)
self.settings["organisation"] = ifcopenshell.api.owner.settings.get_organisation(self.file)
application = ifcopenshell.api.owner.settings.get_application(self.file)
if self.file.schema != "IFC2X3":
if not self.settings["person"] or not self.settings["organisation"]:
if not self.settings["person"] or not self.settings["organisation"] or not application:
return
user = self.get_user()
application = ifcopenshell.api.owner.settings.get_application(self.file)
return self.file.create_entity(
"IfcOwnerHistory",
**{
@@ -1,10 +1,16 @@
# Note: it is the intent for you to override these with your own functions
def get_person(ifc):
pass
people = ifc.by_type("IfcPerson") or [None]
return people [0]
def get_organisation(ifc):
pass
organisations = ifc.by_type("IfcOrganization") or [None]
return organisations[0]
def get_application(ifc):
pass
applications = ifc.by_type("IfcApplication") or [None]
return applications[0]
@@ -5,12 +5,22 @@ import ifcopenshell.api
class Usecase:
def __init__(self, file, **settings):
self.file = file
self.settings = {"element": None}
self.settings = {"library": None, "element": None}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
element = self.file.add(self.settings["element"])
self.added_elements = set()
if self.settings["element"].is_a("IfcTypeProduct"):
return self.append_type_product()
def append_type_product(self):
self.whitelisted_inverse_attributes = {
"IfcObjectDefinition": ["HasAssociations"],
"IfcMaterialDefinition": ["HasExternalReferences", "HasProperties"],
"IfcRepresentationItem": ["StyledByItem"],
}
element = self.add_element(self.settings["element"])
self.existing_contexts = self.file.by_type("IfcGeometricRepresentationContext")
added_contexts = [e for e in self.file.traverse(element) if e.is_a("IfcGeometricRepresentationContext")]
for added_context in added_contexts:
@@ -24,6 +34,24 @@ class Usecase:
ifcopenshell.util.element.remove_deep(self.file, added_context)
return element
def add_element(self, element):
if element.id() == 0 or element.id() in self.added_elements:
return
new = self.file.add(element)
self.added_elements.add(element.id())
self.add_inverse(element)
for subelement in self.settings["library"].traverse(element):
self.add_inverse(subelement)
return new
def add_inverse(self, element):
inverse_attributes = []
[inverse_attributes.extend(v) for k, v in self.whitelisted_inverse_attributes.items() if element.is_a(k)]
inverse_attributes = set(inverse_attributes)
for attribute in inverse_attributes:
for inverse in getattr(element, attribute, []):
self.add_element(inverse)
def get_equivalent_existing_context(self, added_context):
for context in self.existing_contexts:
if context.is_a() != added_context.is_a():
@@ -18,7 +18,12 @@ class Usecase:
return rel.RelatingPropertyDefinition
pset = self.file.create_entity(
"IfcPropertySet", **{"GlobalId": ifcopenshell.guid.new(), "Name": self.settings["name"]}
"IfcPropertySet",
**{
"GlobalId": ifcopenshell.guid.new(),
"OwnerHistory": ifcopenshell.api.run("owner.create_owner_history", self.file),
"Name": self.settings["name"],
}
)
self.file.create_entity(
"IfcRelDefinesByProperties",
@@ -36,7 +41,12 @@ class Usecase:
return definition
pset = self.file.create_entity(
"IfcPropertySet", **{"GlobalId": ifcopenshell.guid.new(), "Name": self.settings["name"]}
"IfcPropertySet",
**{
"GlobalId": ifcopenshell.guid.new(),
"OwnerHistory": ifcopenshell.api.run("owner.create_owner_history", self.file),
"Name": self.settings["name"],
}
)
has_property_sets = list(self.settings["product"].HasPropertySets or [])
has_property_sets.append(pset)
@@ -19,7 +19,12 @@ class Usecase:
return rel.RelatingPropertyDefinition
qto = self.file.create_entity(
"IfcElementQuantity", **{"GlobalId": ifcopenshell.guid.new(), "Name": self.settings["name"]}
"IfcElementQuantity",
**{
"GlobalId": ifcopenshell.guid.new(),
"OwnerHistory": ifcopenshell.api.run("owner.create_owner_history", self.file),
"Name": self.settings["name"],
}
)
self.file.create_entity(
"IfcRelDefinesByProperties",
@@ -32,9 +32,12 @@ class Usecase:
if prop.Name not in self.settings["properties"]:
return
value = self.settings["properties"][prop.Name]
if prop.is_a("IfcPhysicalSimpleQuantity"):
prop[3] = float(value) if value else None
del self.settings["properties"][prop.Name]
name = prop.Name
if value is None:
self.file.remove(prop)
elif prop.is_a("IfcPhysicalSimpleQuantity"):
prop[3] = float(value)
del self.settings["properties"][name]
def add_new_properties(self):
properties = []
@@ -28,4 +28,12 @@ class Usecase:
element.ObjectType = self.settings["predefined_type"]
elif hasattr(element, "ObjectType"):
element.ObjectType = self.settings["predefined_type"]
if self.file.schema == "IFC2X3":
self.handle_2x3_defaults(element)
return element
def handle_2x3_defaults(self, element):
if element.is_a("IfcSpatialStructureElement"):
element.CompositionType = "ELEMENT"
elif element.is_a("IfcRoof"):
element.ShapeType = "NOTDEFINED"
@@ -19,7 +19,6 @@ class Usecase:
ifc_class="IfcTask",
name=None,
predefined_type="NOTDEFINED",
identification="none",
)
task.IsMilestone = False
if self.settings["work_schedule"]:
@@ -33,7 +32,9 @@ class Usecase:
}
)
elif self.settings["parent_task"]:
ifcopenshell.api.run(
rel = ifcopenshell.api.run(
"nest.assign_object", self.file, related_object=task, relating_object=self.settings["parent_task"]
)
if self.settings["parent_task"].Identification:
task.Identification = self.settings["parent_task"].Identification + "." + str(len(rel.RelatedObjects))
return task

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