mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 01:11:40 +00:00
Compare commits
99 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cbe8165e08 | |||
| 6a7a122de2 | |||
| e44db61fbd | |||
| 72b60e85d4 | |||
| c48263ade9 | |||
| 5ec081c6bc | |||
| cc3f94097c | |||
| 253d478fdc | |||
| 549bc0e1a9 | |||
| 9286de4813 | |||
| b8015644e9 | |||
| 8a917dc050 | |||
| 68b6389794 | |||
| 85bd616f6e | |||
| 4ff60553cc | |||
| dcd6cace03 | |||
| b7cfa53b4d | |||
| e0048f7a3c | |||
| a8dcb2f306 | |||
| a8f0a4807c | |||
| d5539399e8 | |||
| eb89308019 | |||
| e7e7b22fd5 | |||
| b4a422c2b8 | |||
| 30f778e0a7 | |||
| 2032b361c8 | |||
| 529b3a9867 | |||
| cac8c64ac8 | |||
| 3847c9bd7a | |||
| ffbaa6f0e0 | |||
| b5c3812849 | |||
| 30ad915649 | |||
| c2cf3e8639 | |||
| c1527b4199 | |||
| a1032a3bf0 | |||
| 733b7c0e1e | |||
| 6b0a58db7d | |||
| 10bf875e92 | |||
| 2fd2b49709 | |||
| 37a61c549f | |||
| da10a1060e | |||
| ebfb29d6da | |||
| 6e4e6a88a4 | |||
| 312de694b6 | |||
| 788a5a829e | |||
| a4391df9ca | |||
| 1071ac00d4 | |||
| 6ecedaa128 | |||
| 8f154c753c | |||
| e863015e85 | |||
| 2c49b3bde8 | |||
| 650ebeddd6 | |||
| 5d0d3aaa16 | |||
| 54526960c8 | |||
| e0b93a70d3 | |||
| e569b97c56 | |||
| a0b7fe2736 | |||
| aadb28bb79 | |||
| 2f1f332d31 | |||
| 1ee2f420c8 | |||
| 567bc7b667 | |||
| 2a2f4d692a | |||
| 1b49a10a15 | |||
| a08918fd22 | |||
| 50608d9459 | |||
| 97d0286b57 | |||
| 2a7fbea9c3 | |||
| 257997c2cb | |||
| 2dfc35877c | |||
| 68233a8fb2 | |||
| 76e2f688e5 | |||
| 4d4bedcf9a | |||
| 36a18ea0f9 | |||
| fa1984e89e | |||
| 3be0a74316 | |||
| 90cb56220f | |||
| fc05bdf6d6 | |||
| 4c50b2d224 | |||
| b9472c9bae | |||
| 7d841c3f27 | |||
| ca0dac1353 | |||
| 1a962a7f60 | |||
| f3c1d1df31 | |||
| 6a0562d00a | |||
| 137a00225c | |||
| 5a822d5e39 | |||
| d844223367 | |||
| 6a29ab0d61 | |||
| e9c7b21bfc | |||
| ed316ba691 | |||
| cfd5742b5b | |||
| feac601340 | |||
| a8dc734351 | |||
| e0cbbf2eb7 | |||
| 082ee3c95d | |||
| ece6e6c8ca | |||
| 5c1d26ac91 | |||
| 153b6ee24d | |||
| 8b3b1eeb8e |
+17
-17
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
Binary file not shown.
@@ -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):
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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 |
@@ -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.
|
||||
@@ -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.
|
||||
@@ -14,6 +14,7 @@ IfcOpenShell Documentation
|
||||
ifcclash
|
||||
ifccobie
|
||||
ifcmax
|
||||
ifcsverchok
|
||||
bimserver-plugin
|
||||
|
||||
|
||||
|
||||
Binary file not shown.
+2
-2
@@ -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,
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user