diff --git a/choco/blenderbim/check_repo_infos.py b/choco/blenderbim/check_repo_infos.py
index d00a38ac72..af99f3cdea 100644
--- a/choco/blenderbim/check_repo_infos.py
+++ b/choco/blenderbim/check_repo_infos.py
@@ -70,7 +70,7 @@ elif sys.argv[1] == "--pyver?":
found = re.findall(re_blender_version_min_maj_pat, html_txt)
if found:
latest_blender_version_tag = f"v{found[0]}"
- re_blender_python_version_maj_min = r"SET\(PYTHON_VERSION (\d+.\d+) "
+ re_blender_python_version_maj_min = r"^SET\(_PYTHON_VERSION_SUPPORTED (\d+\.\d+)\)$"
url = f"https://raw.githubusercontent.com/blender/blender/{latest_blender_version_tag}/build_files/cmake/Modules/FindPythonLibsUnix.cmake"
resp = request_repo_info(url)
html_txt = str(resp.read())
diff --git a/src/blenderbim/blenderbim/bim/__init__.py b/src/blenderbim/blenderbim/bim/__init__.py
index 9db6058d50..3f4e755006 100644
--- a/src/blenderbim/blenderbim/bim/__init__.py
+++ b/src/blenderbim/blenderbim/bim/__init__.py
@@ -95,6 +95,7 @@ classes = [
operator.OpenUpstream,
operator.OpenUri,
operator.SwitchTab,
+ operator.SetTab,
operator.ReloadIfcFile,
operator.RemoveIfcFile,
operator.SelectDataDir,
@@ -125,24 +126,37 @@ classes = [
ui.BIM_UL_generic,
ui.BIM_UL_topics,
ui.BIM_ADDON_preferences,
- # Scene panel groups
- ui.BIM_PT_root,
+ # Project overview
ui.BIM_PT_project_info,
ui.BIM_PT_project_setup,
- ui.BIM_PT_collaboration,
- ui.BIM_PT_selection,
ui.BIM_PT_geometry,
- ui.BIM_PT_services,
- ui.BIM_PT_structural,
- ui.BIM_PT_4D5D,
- ui.BIM_PT_quality_control,
- ui.BIM_PT_integrations,
- # Object panel groups
- ui.BIM_PT_object_metadata,
- ui.BIM_PT_geometry_object,
- ui.BIM_PT_services_object,
- ui.BIM_PT_utilities_object,
- ui.BIM_PT_misc_object,
+ ui.BIM_PT_selection,
+ # Tabs panel
+ ui.BIM_PT_tabs,
+ # Object information
+ ui.BIM_PT_tab_object_metadata,
+ ui.BIM_PT_tab_misc,
+ # Geometry and materials
+ ui.BIM_PT_tab_representations,
+ ui.BIM_PT_tab_geometric_relationships,
+ ui.BIM_PT_tab_parametric_geometry,
+ ui.BIM_PT_tab_materials,
+ ui.BIM_PT_tab_styles,
+ # Drawings and documents
+ # Services and systems
+ ui.BIM_PT_tab_services,
+ ui.BIM_PT_tab_services_object,
+ # Structural analysis
+ ui.BIM_PT_tab_structural,
+ # Construction scheduling
+ ui.BIM_PT_tab_4D5D,
+ # Facility management
+ ui.BIM_PT_tab_handover,
+ ui.BIM_PT_tab_operations,
+ # Quality and coordination
+ ui.BIM_PT_tab_quality_control,
+ ui.BIM_PT_tab_collaboration,
+ ui.BIM_PT_tab_integrations,
]
for mod in modules.values():
@@ -170,9 +184,7 @@ def register():
for cls in classes:
bpy.utils.register_class(cls)
bpy.app.handlers.depsgraph_update_post.append(on_register)
- bpy.app.handlers.undo_pre.append(handler.undo_pre)
bpy.app.handlers.undo_post.append(handler.undo_post)
- bpy.app.handlers.redo_pre.append(handler.redo_pre)
bpy.app.handlers.redo_post.append(handler.redo_post)
bpy.app.handlers.load_post.append(handler.load_post)
bpy.app.handlers.load_post.append(handler.loadIfcStore)
diff --git a/src/blenderbim/blenderbim/bim/data/assets/symbols.svg b/src/blenderbim/blenderbim/bim/data/assets/symbols.svg
index a657f279c0..efcf0ddd77 100644
--- a/src/blenderbim/blenderbim/bim/data/assets/symbols.svg
+++ b/src/blenderbim/blenderbim/bim/data/assets/symbols.svg
@@ -41,4 +41,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/blenderbim/blenderbim/bim/handler.py b/src/blenderbim/blenderbim/bim/handler.py
index b56c001690..af272745e3 100644
--- a/src/blenderbim/blenderbim/bim/handler.py
+++ b/src/blenderbim/blenderbim/bim/handler.py
@@ -54,10 +54,12 @@ def mode_callback(obj, data):
def name_callback(obj, data):
- # TODO Do we still need this, now that we are monitoring the undo redo objects?
try:
obj.name
except:
+ # The object is invalid but somehow still has a callback. Clear all
+ # msgbus subscriptions to prevent useless further triggers.
+ bpy.msgbus.clear_by_owner(obj)
return # In case the object RNA is gone during an undo / redo operation
# Blender names are up to 63 UTF-8 bytes
if len(bytes(obj.name, "utf-8")) >= 63:
@@ -203,34 +205,22 @@ def loadIfcStore(scene):
IfcStore.relink_all_objects()
-@persistent
-def undo_pre(scene):
- IfcStore.track_undo_redo_stack_object_map()
-
-
@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.undo(until_key=bpy.context.scene.BIMProperties.last_transaction)
purge_module_data()
- IfcStore.track_undo_redo_stack_selected_objects()
- IfcStore.reload_undo_redo_stack_objects()
-
-
-@persistent
-def redo_pre(scene):
- IfcStore.track_undo_redo_stack_object_map()
+ tool.Ifc.rebuild_element_maps()
@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.redo(until_key=bpy.context.scene.BIMProperties.last_transaction)
purge_module_data()
- IfcStore.track_undo_redo_stack_selected_objects()
- IfcStore.reload_undo_redo_stack_objects()
+ tool.Ifc.rebuild_element_maps()
def get_application(ifc):
diff --git a/src/blenderbim/blenderbim/bim/helper.py b/src/blenderbim/blenderbim/bim/helper.py
index 5b73dadd2a..b062aeb552 100644
--- a/src/blenderbim/blenderbim/bim/helper.py
+++ b/src/blenderbim/blenderbim/bim/helper.py
@@ -17,6 +17,7 @@
# along with BlenderBIM Add-on. If not, see .
# from datetime import date
+import re
import bpy
import json
import math
@@ -31,6 +32,11 @@ import blenderbim.tool as tool
from blenderbim.bim.ifc import IfcStore
+def uncamel(s):
+ s = re.sub('([a-z0-9])([A-Z])', r'\1 \2', s[3:] if s.startswith("Ifc") else s)
+ return re.sub('([A-Z])([A-Z][a-z])', r'\1 \2', s)
+
+
def draw_attributes(props, layout, copy_operator=None, popup_active_attribute=None):
"""you can set attribute active in popup with `active_attribute`
meaning you will be able to type into attribute's field without having to click
diff --git a/src/blenderbim/blenderbim/bim/ifc.py b/src/blenderbim/blenderbim/bim/ifc.py
index 875614e1bd..4af2d0453f 100644
--- a/src/blenderbim/blenderbim/bim/ifc.py
+++ b/src/blenderbim/blenderbim/bim/ifc.py
@@ -44,9 +44,6 @@ class IfcStore:
classification_file = None
library_path = ""
library_file = None
- element_listeners = set()
- undo_redo_stack_objects = set()
- undo_redo_stack_object_names = {}
current_transaction = ""
last_transaction = ""
history = []
@@ -161,96 +158,6 @@ class IfcStore:
return
return obj
- @staticmethod
- def add_element_listener(callback):
- IfcStore.element_listeners.add(callback)
-
- @staticmethod
- def track_undo_redo_stack_object_map():
- """Keeps track of currently mapped object names, typically during undo and redo
-
- When any Blender object is stored outside a Blender PointerProperty, such as
- in a regular Python list, there is the likely probability that the object
- will be invalidated when undo or redo occurs. Object invalidation seems to
- occur whenever an object is affected during an operation.
-
- For example, if an operator deletes a modifier on o1, then o1 will be invalidated.
- """
- for key, value in IfcStore.id_map.items():
- try:
- IfcStore.undo_redo_stack_object_names[key] = value.name
- except:
- continue
-
- @staticmethod
- def track_undo_redo_stack_selected_objects():
- """Keeps track of selected object names, typically during undo and redo
-
- When any Blender object is stored outside a Blender PointerProperty, such as
- in a regular Python list, there is the likely probability that the object
- will be invalidated when undo or redo occurs. Object invalidation seems to
- occur for selected objects either pre/post undo/redo event, including
- selected objects for consecutive undo/redos, and all children. This is
- important because selected objects are often deleted from the scene.
-
- So if I first select o1, then o2, then o3, then press undo, o3 will be
- invalidated. If instead I press undo twice, o3 and o2 will be invalidated.
- """
- if bpy.context.active_object:
- objects = set([o.name for o in bpy.context.selected_objects + [bpy.context.active_object]])
- objects.update([o.name for o in bpy.context.active_object.children])
- else:
- objects = set([o.name for o in bpy.context.selected_objects])
- for obj in bpy.context.selected_objects:
- objects.update([o.name for o in obj.children])
- IfcStore.undo_redo_stack_objects |= objects
-
- @staticmethod
- def reload_undo_redo_stack_objects():
- """Reloads any invalidated objects after undo or redo
-
- After an undo or redo operation, objects may have been invalidated in
- our id_map and guid_map. Invalidated objects are typically those that
- have been manipulated or deleted. This checks the cache of mapped and
- selected objects prior to the operation and ensures that if the object
- is invalidated, they are reloaded based on the object name that was
- tracked prior to the undo / redo.
- """
- file = IfcStore.get_file()
- if not file:
- return
-
- # First, reload objects that were selected or active
- for name in IfcStore.undo_redo_stack_objects:
- obj = bpy.data.objects.get(name)
- if not obj:
- continue
- 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)
-
- # Scan for any straggling invalidated objects which were indirectly affected and reload them too.
- for key, value in IfcStore.id_map.items():
- try:
- value.name
- except:
- # TODO not so sure about this obj_name check
- obj_name = IfcStore.undo_redo_stack_object_names.get(key, None)
- if not obj_name:
- continue
- obj = bpy.data.objects.get(obj_name)
- if not obj or 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 relink_all_objects():
if not IfcStore.get_file():
@@ -283,6 +190,13 @@ class IfcStore:
@staticmethod
def link_element(element, obj):
+ # Please use tool.Ifc.link() instead of this method. We want to
+ # refactor this class and deprecate usage of IfcStore in favour of
+ # tools.
+ if isinstance(obj, bpy.types.Mesh):
+ obj.BIMMeshProperties.ifc_definition_id = element.id()
+ return
+
existing_obj = IfcStore.id_map.get(element.id(), None)
if existing_obj == obj:
return
@@ -309,9 +223,6 @@ class IfcStore:
blenderbim.bim.handler.subscribe_to(obj, "mode", blenderbim.bim.handler.mode_callback)
blenderbim.bim.handler.subscribe_to(obj, "active_material_index", blenderbim.bim.handler.active_material_index_callback)
- 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(
@@ -332,10 +243,12 @@ class IfcStore:
IfcStore.id_map[data["id"]] = obj
if "guid" in data:
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)
if isinstance(obj, bpy.types.Material):
blenderbim.bim.handler.subscribe_to(obj, "diffuse_color", blenderbim.bim.handler.color_callback)
+ elif isinstance(obj, bpy.types.Object):
+ blenderbim.bim.handler.subscribe_to(obj, "mode", blenderbim.bim.handler.mode_callback)
+ blenderbim.bim.handler.subscribe_to(obj, "active_material_index", blenderbim.bim.handler.active_material_index_callback)
# TODO Listeners are not re-registered. Does this cause nasty problems to debug later on?
# TODO We're handling id_map and guid_map, but what about edited_objs? This might cause big problems.
@@ -435,8 +348,6 @@ class IfcStore:
@staticmethod
def begin_transaction(operator):
- IfcStore.undo_redo_stack_objects = set()
- IfcStore.undo_redo_stack_object_names = {}
IfcStore.current_transaction = str(uuid.uuid4())
operator.transaction_key = IfcStore.current_transaction
@@ -462,21 +373,35 @@ class IfcStore:
IfcStore.future = []
@staticmethod
- def undo():
+ def undo(until_key=None):
BrickStore.undo()
if not IfcStore.history:
return
- event = IfcStore.history.pop()
- for transaction in event["operations"][::-1]:
- transaction["rollback"](transaction["data"])
- IfcStore.future.append(event)
+
+ while IfcStore.history:
+ if IfcStore.history[-1]["key"] == until_key:
+ return
+
+ event = IfcStore.history.pop()
+ for transaction in event["operations"][::-1]:
+ transaction["rollback"](transaction["data"])
+ IfcStore.future.append(event)
@staticmethod
- def redo():
+ def redo(until_key=None):
BrickStore.redo()
+
if not IfcStore.future:
return
- event = IfcStore.future.pop()
- for transaction in event["operations"]:
- transaction["commit"](transaction["data"])
- IfcStore.history.append(event)
+
+ has_encountered_key = False
+ while IfcStore.future:
+ if has_encountered_key and IfcStore.future[-1]["key"] != until_key:
+ return
+ elif IfcStore.future[-1]["key"] == until_key:
+ has_encountered_key = True
+
+ event = IfcStore.future.pop()
+ for transaction in event["operations"]:
+ transaction["commit"](transaction["data"])
+ IfcStore.history.append(event)
diff --git a/src/blenderbim/blenderbim/bim/import_ifc.py b/src/blenderbim/blenderbim/bim/import_ifc.py
index 8018f7db72..2a1bc11511 100644
--- a/src/blenderbim/blenderbim/bim/import_ifc.py
+++ b/src/blenderbim/blenderbim/bim/import_ifc.py
@@ -692,7 +692,7 @@ class IfcImporter:
mesh = self.create_native_faceted_brep(element, mesh_name)
elif native_data["type"] == "IfcFaceBasedSurfaceModel":
mesh = self.create_native_faceted_brep(element, mesh_name)
- mesh.BIMMeshProperties.ifc_definition_id = representation.id()
+ tool.Ifc.link(representation, mesh)
mesh.name = mesh_name
self.meshes[mesh_name] = mesh
self.create_product(element, mesh=mesh)
@@ -921,7 +921,7 @@ class IfcImporter:
return result
def create_pointcloud(self, product, representation):
- placement_matrix = ifcopenshell.util.placement.get_local_placement(product.ObjectPlacement)
+ placement_matrix = self.get_element_matrix(product)
vertex_list = []
for item in representation.Items:
if item.is_a("IfcCartesianPointList"):
@@ -937,6 +937,7 @@ class IfcImporter:
mesh_name = f"{representation.ContextOfItems.id()}/{representation.id()}"
mesh = bpy.data.meshes.new(mesh_name)
mesh.from_pydata(vertex_list, [], [])
+ tool.Ifc.link(representation, mesh)
obj = bpy.data.objects.new("{}/{}".format(product.is_a(), product.Name), mesh)
self.set_matrix_world(obj, self.apply_blender_offset_to_matrix_world(obj, placement_matrix))
@@ -1342,12 +1343,21 @@ class IfcImporter:
last_obj = obj
if not last_obj:
return
+
+ # temporarily unhide types collection to make sure all objects will be cleaned
+ project_collection = bpy.context.view_layer.layer_collection.children[self.project["blender"].name]
+ types_collection = project_collection.children[self.type_collection.name]
+ types_collection.hide_viewport = False
bpy.context.view_layer.objects.active = last_obj
+
context_override = {}
bpy.ops.object.editmode_toggle(context_override)
bpy.ops.mesh.tris_convert_to_quads(context_override)
bpy.ops.mesh.normals_make_consistent(context_override)
bpy.ops.object.editmode_toggle(context_override)
+
+ types_collection.hide_viewport = True
+ bpy.context.view_layer.objects.active = last_obj
IfcStore.edited_objs.clear()
def load_file(self):
diff --git a/src/blenderbim/blenderbim/bim/module/aggregate/ui.py b/src/blenderbim/blenderbim/bim/module/aggregate/ui.py
index 50bc8007f6..6d5404c057 100644
--- a/src/blenderbim/blenderbim/bim/module/aggregate/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/aggregate/ui.py
@@ -22,13 +22,13 @@ from blenderbim.bim.ifc import IfcStore
class BIM_PT_aggregate(Panel):
- bl_label = "IFC Aggregates"
+ bl_label = "Aggregates"
bl_idname = "BIM_PT_aggregate"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
- bl_parent_id = "BIM_PT_object_metadata"
+ bl_parent_id = "BIM_PT_tab_object_metadata"
@classmethod
def poll(cls, context):
diff --git a/src/blenderbim/blenderbim/bim/module/attribute/ui.py b/src/blenderbim/blenderbim/bim/module/attribute/ui.py
index 510b2c537e..7ffd360c19 100644
--- a/src/blenderbim/blenderbim/bim/module/attribute/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/attribute/ui.py
@@ -54,12 +54,12 @@ def draw_ui(context, layout, obj_type, attributes):
class BIM_PT_object_attributes(Panel):
- bl_label = "IFC Attributes"
+ bl_label = "Attributes"
bl_idname = "BIM_PT_object_attributes"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
- bl_parent_id = "BIM_PT_object_metadata"
+ bl_parent_id = "BIM_PT_tab_object_metadata"
@classmethod
def poll(cls, context):
@@ -76,7 +76,7 @@ class BIM_PT_object_attributes(Panel):
class BIM_PT_material_attributes(Panel):
- bl_label = "IFC Material Attributes"
+ bl_label = "Material Attributes"
bl_idname = "BIM_PT_material_attributes"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
diff --git a/src/blenderbim/blenderbim/bim/module/augin/ui.py b/src/blenderbim/blenderbim/bim/module/augin/ui.py
index 896a8e5386..4daed80129 100644
--- a/src/blenderbim/blenderbim/bim/module/augin/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/augin/ui.py
@@ -26,7 +26,7 @@ class BIM_PT_augin(bpy.types.Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
- bl_parent_id = "BIM_PT_integrations"
+ bl_parent_id = "BIM_PT_tab_integrations"
def draw(self, context):
layout = self.layout
diff --git a/src/blenderbim/blenderbim/bim/module/bcf/ui.py b/src/blenderbim/blenderbim/bim/module/bcf/ui.py
index 821d028dbb..58f3c0a858 100644
--- a/src/blenderbim/blenderbim/bim/module/bcf/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/bcf/ui.py
@@ -29,7 +29,7 @@ class BIM_PT_bcf(Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
- bl_parent_id = "BIM_PT_collaboration"
+ bl_parent_id = "BIM_PT_tab_collaboration"
def draw(self, context):
layout = self.layout
diff --git a/src/blenderbim/blenderbim/bim/module/bimtester/ui.py b/src/blenderbim/blenderbim/bim/module/bimtester/ui.py
index 9b58f29b12..5c0843d618 100644
--- a/src/blenderbim/blenderbim/bim/module/bimtester/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/bimtester/ui.py
@@ -27,7 +27,7 @@ class BIM_PT_qa(Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
- bl_parent_id = "BIM_PT_quality_control"
+ bl_parent_id = "BIM_PT_tab_quality_control"
def draw(self, context):
self.layout.use_property_split = True
diff --git a/src/blenderbim/blenderbim/bim/module/boundary/ui.py b/src/blenderbim/blenderbim/bim/module/boundary/ui.py
index 6abad5b6e6..1e1068077f 100644
--- a/src/blenderbim/blenderbim/bim/module/boundary/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/boundary/ui.py
@@ -25,7 +25,7 @@ from blenderbim.bim.module.boundary.data import SpaceBoundariesData
class BIM_PT_SceneBoundaries(Panel):
- bl_label = "IFC Space Boundaries"
+ bl_label = "Space Boundaries"
bl_id_name = "BIM_PT_scene_boundaries"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
@@ -45,12 +45,12 @@ class BIM_PT_SceneBoundaries(Panel):
class BIM_PT_Boundary(Panel):
- bl_label = "IFC Space Boundary"
+ bl_label = "Space Boundary"
bl_idname = "BIM_PT_Boundary"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
- bl_parent_id = "BIM_PT_geometry_object"
+ bl_parent_id = "BIM_PT_tab_geometric_relationships"
@classmethod
def poll(cls, context):
@@ -117,14 +117,14 @@ class BIM_PT_Boundary(Panel):
class BIM_PT_SpaceBoundaries(Panel):
- bl_label = "IFC Space Boundaries"
+ bl_label = "Space Boundaries"
bl_idname = "BIM_PT_SpaceBoundaries"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
bl_order = 1
- bl_parent_id = "BIM_PT_geometry_object"
+ bl_parent_id = "BIM_PT_tab_geometric_relationships"
@classmethod
def poll(cls, context):
diff --git a/src/blenderbim/blenderbim/bim/module/brick/ui.py b/src/blenderbim/blenderbim/bim/module/brick/ui.py
index b769057d37..2303ac6498 100644
--- a/src/blenderbim/blenderbim/bim/module/brick/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/brick/ui.py
@@ -26,14 +26,10 @@ from blenderbim.tool.brick import BrickStore
class BIM_PT_brickschema(Panel):
bl_label = "Brickschema Project"
bl_idname = "BIM_PT_brickschema"
- bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
-
- @classmethod
- def poll(cls, context):
- return tool.Blender.is_tab(context, "OTHER")
+ bl_parent_id = "BIM_PT_tab_operations"
def draw(self, context):
if not BrickschemaData.is_loaded:
@@ -108,7 +104,7 @@ class BIM_PT_brickschema(Panel):
class BIM_PT_ifc_brickschema_references(Panel):
- bl_label = "IFC Brickschema References"
+ bl_label = "Brickschema References"
bl_idname = "BIM_PT_ifc_brickschema_references"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
diff --git a/src/blenderbim/blenderbim/bim/module/clash/ui.py b/src/blenderbim/blenderbim/bim/module/clash/ui.py
index 21e6547e57..26c32321c4 100644
--- a/src/blenderbim/blenderbim/bim/module/clash/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/clash/ui.py
@@ -21,13 +21,13 @@ from bpy.types import Panel
class BIM_PT_ifcclash(Panel):
- bl_label = "IFC Clash Sets"
+ bl_label = "Clash Sets"
bl_idname = "BIM_PT_ifcclash"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
- bl_parent_id = "BIM_PT_quality_control"
+ bl_parent_id = "BIM_PT_tab_quality_control"
def draw(self, context):
layout = self.layout
diff --git a/src/blenderbim/blenderbim/bim/module/classification/ui.py b/src/blenderbim/blenderbim/bim/module/classification/ui.py
index 8bf5f660c7..5a4de6bf65 100644
--- a/src/blenderbim/blenderbim/bim/module/classification/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/classification/ui.py
@@ -30,7 +30,7 @@ from blenderbim.bim.module.classification.data import (
class BIM_PT_classifications(Panel):
- bl_label = "IFC Classifications"
+ bl_label = "Classifications"
bl_idname = "BIM_PT_classifications"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
@@ -156,13 +156,13 @@ class ReferenceUI:
class BIM_PT_classification_references(Panel, ReferenceUI):
- bl_label = "IFC Classification References"
+ bl_label = "Classification References"
bl_idname = "BIM_PT_classification_references"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
- bl_parent_id = "BIM_PT_object_metadata"
+ bl_parent_id = "BIM_PT_tab_object_metadata"
@classmethod
def poll(cls, context):
@@ -180,7 +180,7 @@ class BIM_PT_classification_references(Panel, ReferenceUI):
class BIM_PT_material_classifications(Panel, ReferenceUI):
- bl_label = "IFC Material Classifications"
+ bl_label = "Material Classifications"
bl_idname = "BIM_PT_material_classifications"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
@@ -206,7 +206,7 @@ class BIM_PT_material_classifications(Panel, ReferenceUI):
class BIM_PT_cost_classifications(Panel, ReferenceUI):
- bl_label = "IFC Cost Classifications"
+ bl_label = "Cost Classifications"
bl_idname = "BIM_PT_cost_classifications"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
diff --git a/src/blenderbim/blenderbim/bim/module/cobie/ui.py b/src/blenderbim/blenderbim/bim/module/cobie/ui.py
index 075dba6bba..a78cff2b62 100644
--- a/src/blenderbim/blenderbim/bim/module/cobie/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/cobie/ui.py
@@ -16,18 +16,18 @@
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see .
+import blenderbim.tool as tool
from bpy.types import Panel
from blenderbim.bim.ifc import IfcStore
class BIM_PT_cobie(Panel):
- bl_label = "IFC COBie"
+ bl_label = "COBie"
bl_idname = "BIM_PT_cobie"
- bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
- bl_parent_id = "BIM_PT_collaboration"
+ bl_parent_id = "BIM_PT_tab_handover"
def draw(self, context):
layout = self.layout
diff --git a/src/blenderbim/blenderbim/bim/module/constraint/ui.py b/src/blenderbim/blenderbim/bim/module/constraint/ui.py
index 38bd07bfd3..2b017e89c3 100644
--- a/src/blenderbim/blenderbim/bim/module/constraint/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/constraint/ui.py
@@ -23,7 +23,7 @@ from blenderbim.bim.module.constraint.data import ConstraintsData, ObjectConstra
class BIM_PT_constraints(Panel):
- bl_label = "IFC Constraints"
+ bl_label = "Constraints"
bl_idname = "BIM_PT_constraints"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
@@ -68,14 +68,14 @@ class BIM_PT_constraints(Panel):
class BIM_PT_object_constraints(Panel):
- bl_label = "IFC Constraints"
+ bl_label = "Constraints"
bl_idname = "BIM_PT_object_constraints"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
bl_order = 1
- bl_parent_id = "BIM_PT_misc_object"
+ bl_parent_id = "BIM_PT_tab_misc"
@classmethod
def poll(cls, context):
diff --git a/src/blenderbim/blenderbim/bim/module/context/ui.py b/src/blenderbim/blenderbim/bim/module/context/ui.py
index 58449f6d45..c8fe010625 100644
--- a/src/blenderbim/blenderbim/bim/module/context/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/context/ui.py
@@ -23,7 +23,7 @@ from blenderbim.bim.module.context.data import ContextData
class BIM_PT_context(bpy.types.Panel):
- bl_label = "IFC Geometric Representation Contexts"
+ bl_label = "Geometric Representation Contexts"
bl_idname = "BIM_PT_context"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
diff --git a/src/blenderbim/blenderbim/bim/module/cost/ui.py b/src/blenderbim/blenderbim/bim/module/cost/ui.py
index 3e92163157..c083331c76 100644
--- a/src/blenderbim/blenderbim/bim/module/cost/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/cost/ui.py
@@ -24,13 +24,13 @@ from blenderbim.bim.module.cost.data import CostSchedulesData
class BIM_PT_cost_schedules(Panel):
- bl_label = "IFC Cost Schedules"
+ bl_label = "Cost Schedules"
bl_idname = "BIM_PT_cost_schedules"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
- bl_parent_id = "BIM_PT_4D5D"
+ bl_parent_id = "BIM_PT_tab_4D5D"
@classmethod
def poll(cls, context):
@@ -267,7 +267,7 @@ class BIM_PT_cost_schedules(Panel):
class BIM_PT_cost_item_types(Panel):
- bl_label = "IFC Cost Item Types"
+ bl_label = "Cost Item Types"
bl_idname = "BIM_PT_cost_item_types"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
@@ -353,7 +353,7 @@ class BIM_PT_cost_item_types(Panel):
class BIM_PT_cost_item_quantities(Panel):
- bl_label = "IFC Cost Item Quantities"
+ bl_label = "Cost Item Quantities"
bl_idname = "BIM_PT_cost_item_quantities"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
@@ -525,7 +525,7 @@ class BIM_PT_cost_item_quantities(Panel):
class BIM_PT_cost_item_rates(Panel):
- bl_label = "IFC Cost Item Rates"
+ bl_label = "Cost Item Rates"
bl_idname = "BIM_PT_cost_item_rates"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
diff --git a/src/blenderbim/blenderbim/bim/module/covetool/ui.py b/src/blenderbim/blenderbim/bim/module/covetool/ui.py
index f74aa1a97b..4c055db03e 100644
--- a/src/blenderbim/blenderbim/bim/module/covetool/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/covetool/ui.py
@@ -26,7 +26,7 @@ class BIM_PT_covetool(bpy.types.Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
- bl_parent_id = "BIM_PT_integrations"
+ bl_parent_id = "BIM_PT_tab_integrations"
def draw(self, context):
layout = self.layout
diff --git a/src/blenderbim/blenderbim/bim/module/csv/ui.py b/src/blenderbim/blenderbim/bim/module/csv/ui.py
index 7c1df7a4d2..9f690a762a 100644
--- a/src/blenderbim/blenderbim/bim/module/csv/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/csv/ui.py
@@ -21,13 +21,13 @@ from blenderbim.bim.ifc import IfcStore
class BIM_PT_ifccsv(Panel):
- bl_label = "IFC CSV Import/Export"
+ bl_label = "CSV Import/Export"
bl_idname = "BIM_PT_ifccsv"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
- bl_parent_id = "BIM_PT_collaboration"
+ bl_parent_id = "BIM_PT_tab_collaboration"
def draw(self, context):
layout = self.layout
diff --git a/src/blenderbim/blenderbim/bim/module/debug/ui.py b/src/blenderbim/blenderbim/bim/module/debug/ui.py
index 7e06e2fb19..1ca48978be 100644
--- a/src/blenderbim/blenderbim/bim/module/debug/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/debug/ui.py
@@ -21,13 +21,13 @@ from bpy.types import Panel
class BIM_PT_debug(Panel):
- bl_label = "IFC Debug"
+ bl_label = "Debug"
bl_idname = "BIM_PT_debug"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
- bl_parent_id = "BIM_PT_quality_control"
+ bl_parent_id = "BIM_PT_tab_quality_control"
def draw(self, context):
layout = self.layout
diff --git a/src/blenderbim/blenderbim/bim/module/diff/ui.py b/src/blenderbim/blenderbim/bim/module/diff/ui.py
index 520a26ae39..a0343e2042 100644
--- a/src/blenderbim/blenderbim/bim/module/diff/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/diff/ui.py
@@ -24,13 +24,13 @@ import json
class BIM_PT_diff(Panel):
- bl_label = "IFC Diff"
+ bl_label = "Diff"
bl_idname = "BIM_PT_diff"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
- bl_parent_id = "BIM_PT_quality_control"
+ bl_parent_id = "BIM_PT_tab_quality_control"
def draw(self, context):
if not DiffData.is_loaded:
diff --git a/src/blenderbim/blenderbim/bim/module/document/ui.py b/src/blenderbim/blenderbim/bim/module/document/ui.py
index b20f0319e9..a656f4f576 100644
--- a/src/blenderbim/blenderbim/bim/module/document/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/document/ui.py
@@ -23,7 +23,7 @@ from blenderbim.bim.module.document.data import DocumentData, ObjectDocumentData
class BIM_PT_documents(Panel):
- bl_label = "IFC Documents"
+ bl_label = "Documents"
bl_idname = "BIM_PT_documents"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
@@ -76,14 +76,14 @@ class BIM_PT_documents(Panel):
class BIM_PT_object_documents(Panel):
- bl_label = "IFC Documents"
+ bl_label = "Documents"
bl_idname = "BIM_PT_object_documents"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
bl_order = 1
- bl_parent_id = "BIM_PT_misc_object"
+ bl_parent_id = "BIM_PT_tab_misc"
@classmethod
def poll(cls, context):
diff --git a/src/blenderbim/blenderbim/bim/module/drawing/decoration.py b/src/blenderbim/blenderbim/bim/module/drawing/decoration.py
index f5ce7c2840..4f3764df3c 100644
--- a/src/blenderbim/blenderbim/bim/module/drawing/decoration.py
+++ b/src/blenderbim/blenderbim/bim/module/drawing/decoration.py
@@ -748,7 +748,7 @@ class AngleDecorator(BaseDecorator):
arcs_color = None
edges_color = UNSPECIAL_ELEMENT_COLOR
- if context.object == obj and obj.data.is_editmode:
+ if context.active_object == obj and obj.data.is_editmode:
arcs_color = context.preferences.addons["blenderbim"].preferences.decorator_color_special
edges_color = None
diff --git a/src/blenderbim/blenderbim/bim/module/drawing/helper.py b/src/blenderbim/blenderbim/bim/module/drawing/helper.py
index 100880fb26..95bd35b77b 100644
--- a/src/blenderbim/blenderbim/bim/module/drawing/helper.py
+++ b/src/blenderbim/blenderbim/bim/module/drawing/helper.py
@@ -192,6 +192,8 @@ def format_distance(
tx_dist += str(feet) + "'"
if feet and add_inches:
tx_dist += " - "
+ if not feet and value < 0:
+ tx_dist += "-"
if add_inches:
tx_dist += str(inches)
if add_inches and frac:
diff --git a/src/blenderbim/blenderbim/bim/module/drawing/operator.py b/src/blenderbim/blenderbim/bim/module/drawing/operator.py
index 6ef6f9bbc0..8c5bdfd3ee 100644
--- a/src/blenderbim/blenderbim/bim/module/drawing/operator.py
+++ b/src/blenderbim/blenderbim/bim/module/drawing/operator.py
@@ -23,6 +23,7 @@ import json
import time
import bmesh
import shutil
+import hashlib
import shapely
import subprocess
import webbrowser
@@ -365,7 +366,6 @@ class CreateDrawing(bpy.types.Operator):
return svg_path
def generate_linework(self, context):
- global ifcopenshell
if not ifcopenshell.util.element.get_pset(self.drawing, "EPset_Drawing", "HasLinework"):
return
svg_path = self.get_svg_path(cache_type="linework")
@@ -395,10 +395,6 @@ class CreateDrawing(bpy.types.Operator):
}
cached_linework -= edited_guids
- # This is a work in progress. See #1153 and #1564.
- import hashlib
- import ifcopenshell.draw
-
files = {context.scene.BIMProperties.ifc_file: tool.Ifc.get()}
for ifc_path, ifc in files.items():
@@ -569,7 +565,7 @@ class CreateDrawing(bpy.types.Operator):
for projection in projections:
boundary_lines = []
for path in projection.findall("./{http://www.w3.org/2000/svg}path"):
- start, end = [co[1:].split(",") for co in path.attrib["d"].split()]
+ start, end = [[round(float(o), 1) for o in co[1:].split(",")] for co in path.attrib["d"].split()]
boundary_lines.append(shapely.LineString([start, end]))
unioned_boundaries = shapely.union_all(shapely.GeometryCollection(boundary_lines))
closed_polygons = shapely.polygonize(unioned_boundaries.geoms)
@@ -896,6 +892,42 @@ class CreateDrawing(bpy.types.Operator):
classes.append("cut")
el.set("class", " ".join(classes))
+ # An element group will contain a bunch of paths representing the
+ # cut of that element. However IfcOpenShell may not correctly
+ # create closed paths. We post-process all paths with shapely to
+ # ensure things that should be closed (i.e.
+ # shapely.polygonize_full) are, and things which aren't are left
+ # alone (e.g. dangles, cuts, invalids). See #3421.
+ line_strings = []
+ old_paths = []
+ for path in el.findall("{http://www.w3.org/2000/svg}path"):
+ for subpath in path.attrib["d"].split("M")[1:]:
+ subpath = "M" + subpath.strip()
+ coords = [[round(float(o), 1) for o in co[1:].split(",")] for co in subpath.split()]
+ line_strings.append(shapely.LineString(coords))
+ old_paths.append(path)
+ unioned_line_strings = shapely.union_all(shapely.GeometryCollection(line_strings))
+ if hasattr(unioned_line_strings, "geoms"):
+ results = shapely.polygonize_full(unioned_line_strings.geoms)
+ else:
+ results = []
+
+ # If we succeeded in generating new path geometry, remove all the
+ # old paths and add new ones.
+ if results:
+ for path in old_paths:
+ path.getparent().remove(path)
+ for result in results:
+ for geom in result.geoms:
+ path = etree.SubElement(el, "path")
+ if isinstance(geom, shapely.Polygon):
+ d = "M" + " L".join([",".join([str(o) for o in co]) for co in geom.exterior.coords[0:-1]]) + " Z"
+ for interior in geom.interiors:
+ d += " M" + " L".join([",".join([str(o) for o in co]) for co in interior.coords[0:-1]]) + " Z"
+ elif isinstance(geom, shapely.LineString):
+ d = "M" + " L".join([",".join([str(o) for o in co]) for co in geom.coords]) + " Z"
+ path.attrib["d"] = d
+
# Architectural convention only merges these objects. E.g. pipe segments and fittings shouldn't merge.
if not element.is_a("IfcWall") and not element.is_a("IfcSlab"):
continue
diff --git a/src/blenderbim/blenderbim/bim/module/drawing/svgwriter.py b/src/blenderbim/blenderbim/bim/module/drawing/svgwriter.py
index bb65b52f67..625bb8af47 100644
--- a/src/blenderbim/blenderbim/bim/module/drawing/svgwriter.py
+++ b/src/blenderbim/blenderbim/bim/module/drawing/svgwriter.py
@@ -332,9 +332,10 @@ class SvgWriter:
return
classes = self.get_attribute_classes(obj)
- if len(obj.data.polygons) == 0:
- self.draw_edge_annotation(obj, classes)
- return
+ if len(obj.data.vertices) and not len(obj.data.edges):
+ return self.draw_point_annotation(obj, classes)
+ elif len(obj.data.polygons) == 0:
+ return self.draw_edge_annotation(obj, classes)
bm = bmesh.new()
bm.from_mesh(obj.data)
@@ -501,9 +502,9 @@ class SvgWriter:
return pattern
def get_scale(size, direction):
- vector = direction * size
- shrinked_vector = size // segment_width * segment_width * direction
- scale = [1 if vector[i] == 0 else vector[i] / shrinked_vector[i] for i in range(2)]
+ original_edge = direction * size
+ current_svg_segments = ceil(size / segment_width) * segment_width * direction
+ scale = [1 if original_edge[i] == 0 else original_edge[i] / current_svg_segments[i] for i in range(2)]
return "scale(%f, %f)" % (scale[0], scale[1])
def poly_to_edges(poly):
@@ -557,11 +558,16 @@ class SvgWriter:
pattern_dir = pattern_edge.normalized()
pattern_length = pattern_edge.length
- segments = int(pattern_length // segment_width)
+ segments = ceil(pattern_length / segment_width)
pattern_dir_step = pattern_dir * segment_width
- points = [pattern_dir_step * i for i in range(segments)]
+ # it takes atleast 2 points to preserve the edge direction
+ # if there is just 1 segment then we still add second point and then hide the "marker-end"
+ n_points = max(segments, 2)
+ points = [pattern_dir_step * i for i in range(n_points)]
- polyline_style = f"marker: url(#{marker_id}); stroke: none;"
+ polyline_style = f"marker: url(#{marker_id}); stroke: none; "
+ if segments == 1:
+ polyline_style += "marker-end: none; "
polyline_transform = f"translate({start_svg.x}, {start_svg.y}) {get_scale(pattern_length, pattern_dir)}"
polyline = self.svg.polyline(
points=points, class_=" ".join(classes), style=polyline_style, transform=polyline_transform
@@ -837,6 +843,26 @@ class SvgWriter:
self.svg.add(tag)
line_number += len(tag.elements)
+ def draw_point_annotation(self, obj, classes):
+ x_offset = self.raw_width / 2
+ y_offset = self.raw_height / 2
+
+ matrix_world = obj.matrix_world
+ projected_points = [self.project_point_onto_camera(matrix_world @ v.co) for v in obj.data.vertices]
+
+ element = tool.Ifc.get_entity(obj)
+ svg_id = str(ifcopenshell.util.element.get_predefined_type(element))
+
+ # EPset_AnnotationSurveyArea is not standard! See bSI-4.3 proposal #660.
+ point_type = ifcopenshell.util.element.get_pset(element, "EPset_AnnotationSurveyArea", "PointType")
+ if point_type:
+ svg_id += f"-{point_type}"
+
+ for symbol_position in projected_points:
+ symbol_position = Vector(((x_offset + symbol_position.x), (y_offset - symbol_position.y)))
+ symbol_position_svg = symbol_position * self.svg_scale
+ self.svg.add(self.svg.use(f"#{svg_id}", insert=symbol_position_svg))
+
def draw_break_annotations(self, obj):
x_offset = self.raw_width / 2
y_offset = self.raw_height / 2
@@ -880,8 +906,13 @@ class SvgWriter:
d = "M{}".format(d[1:])
path = self.svg.add(self.svg.path(d=d, class_=" ".join(classes)))
text_position = projected_points_svg[0] - Vector((0, base_offset_y))
- vector = projected_points_svg[1] - projected_points_svg[0]
- angle = math.degrees(vector.angle_signed(Vector((1, 0))))
+ text_dir = projected_points_svg[1] - projected_points_svg[0]
+ if text_dir.x < 0:
+ box_alignment = "bottom-right"
+ text_dir *= -1
+ else:
+ box_alignment = "bottom-left"
+ angle = math.degrees(text_dir.angle_signed(Vector((1, 0))))
# TODO: allow metric to be configurable
def get_text():
@@ -895,7 +926,6 @@ class SvgWriter:
text = "{}{}".format("" if z < 0 else "+", rl)
return text
- box_alignment = "bottom-left" if projected_points[0].x <= projected_points[-1].x else "bottom-right"
self.draw_dimension_text(
get_text,
description,
@@ -931,7 +961,7 @@ class SvgWriter:
p0 = points_2d[1] + dir0 * angle_radius
p2 = points_2d[1] + dir1 * angle_radius
points_chunk = [view3d_utils.region_2d_to_origin_3d(region, region_3d, p) for p in [p0, p3, p2]]
- # points = [p.co.xyz for p in bpy.context.object.data.splines[0].points[:3]]
+ # points = [p.co.xyz for p in bpy.context.active_object.data.splines[0].points[:3]]
bm = bmesh.new()
bm.verts.index_update()
diff --git a/src/blenderbim/blenderbim/bim/module/drawing/ui.py b/src/blenderbim/blenderbim/bim/module/drawing/ui.py
index dad42d1ca6..a0a461a8dd 100644
--- a/src/blenderbim/blenderbim/bim/module/drawing/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/drawing/ui.py
@@ -387,7 +387,7 @@ class BIM_PT_sheets(Panel):
class BIM_PT_product_assignments(Panel):
- bl_label = "IFC Product Assignments"
+ bl_label = "Product Assignments"
bl_idname = "BIM_PT_product_assignments"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
@@ -422,7 +422,7 @@ class BIM_PT_product_assignments(Panel):
class BIM_PT_text(Panel):
- bl_label = "IFC Text"
+ bl_label = "Text"
bl_idname = "BIM_PT_text"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
diff --git a/src/blenderbim/blenderbim/bim/module/drawing/workspace.py b/src/blenderbim/blenderbim/bim/module/drawing/workspace.py
index 3193a23271..73704e32e7 100644
--- a/src/blenderbim/blenderbim/bim/module/drawing/workspace.py
+++ b/src/blenderbim/blenderbim/bim/module/drawing/workspace.py
@@ -101,6 +101,8 @@ class LaunchAnnotationTypeManager(bpy.types.Operator):
op = row.operator("bim.select_type", icon="OBJECT_DATA")
op.relating_type = relating_type["id"]
+ op = row.operator("bim.rename_type", icon="GREASEPENCIL", text="")
+ op.element = relating_type["id"]
op = row.operator("bim.duplicate_type", icon="DUPLICATE", text="")
op.element = relating_type["id"]
op = row.operator("bim.remove_type", icon="X", text="")
@@ -227,7 +229,7 @@ class AnnotationToolUI:
@classmethod
def draw_edit_object_interface(cls, context):
- if DecoratorData.get_ifc_text_data(bpy.context.object):
+ if DecoratorData.get_ifc_text_data(bpy.context.active_object):
add_layout_hotkey_operator(cls.layout, "Edit Text", "S_E", "")
@classmethod
@@ -246,16 +248,16 @@ class AnnotationToolUI:
add_layout_hotkey_operator(cls.layout, "Add", "S_A", "Create a new annotation")
- if object_type in ("TEXT", "STAIR_ARROW"):
+ if object_type in tool.Drawing.ANNOTATION_TYPES_SUPPORT_SETUP:
add_layout_hotkey_operator(
cls.layout,
"Bulk Tag",
"S_T",
"Create new annotations and automatically adjust them to the selected objects",
)
- add_layout_hotkey_operator(
- cls.layout, "Readjust", "S_G", "Readjust tags based on the products they are assigned to"
- )
+ add_layout_hotkey_operator(
+ cls.layout, "Readjust", "S_G", "Readjust tags based on the products they are assigned to"
+ )
class Hotkey(bpy.types.Operator, Operator):
@@ -287,23 +289,27 @@ class Hotkey(bpy.types.Operator, Operator):
def hotkey_S_T(self):
props = bpy.context.scene.BIMAnnotationProperties
- object_type = props.object_type
+ annotation_type = props.object_type
+
+ if annotation_type not in tool.Drawing.ANNOTATION_TYPES_SUPPORT_SETUP:
+ self.report({"ERROR"}, f"Annotation type {annotation_type} is not supported for tagging.")
+ return
related_objects = bpy.context.selected_objects
for related_object in related_objects:
create_annotation()
obj = bpy.context.active_object
bpy.ops.object.mode_set(mode="OBJECT")
- tool.Drawing.setup_annotation_object(obj, object_type, related_object)
+ tool.Drawing.setup_annotation_object(obj, annotation_type, related_object)
def hotkey_S_A(self):
create_annotation()
def hotkey_S_E(self):
- if not bpy.context.object:
+ if not bpy.context.active_object:
return
- if DecoratorData.get_ifc_text_data(bpy.context.object):
+ if DecoratorData.get_ifc_text_data(bpy.context.active_object):
bpy.ops.bim.edit_text_popup()
def hotkey_S_G(self):
@@ -312,9 +318,15 @@ class Hotkey(bpy.types.Operator, Operator):
if not element or not element.is_a("IfcAnnotation"):
continue
+ annotation_type = element.ObjectType
+ if annotation_type not in tool.Drawing.ANNOTATION_TYPES_SUPPORT_SETUP:
+ self.report({"ERROR"}, f"Annotation type {annotation_type} is not supported for readjustment.")
+ continue
+
related_product = tool.Drawing.get_assigned_product(element)
if not related_product:
+ self.report({"ERROR"}, "Selected annotation has no product assigned.")
continue
related_object = tool.Ifc.get_object(related_product)
- tool.Drawing.setup_annotation_object(obj, element.ObjectType, related_object)
+ tool.Drawing.setup_annotation_object(obj, annotation_type, related_object)
diff --git a/src/blenderbim/blenderbim/bim/module/geometry/operator.py b/src/blenderbim/blenderbim/bim/module/geometry/operator.py
index a6f29b3354..6d1f671d2d 100644
--- a/src/blenderbim/blenderbim/bim/module/geometry/operator.py
+++ b/src/blenderbim/blenderbim/bim/module/geometry/operator.py
@@ -598,6 +598,9 @@ class OverrideDuplicateMove(bpy.types.Operator):
relationships = tool.Root.get_decomposition_relationships(context.selected_objects)
old_to_new = {}
for obj in context.selected_objects:
+ element = tool.Ifc.get_entity(obj)
+ if element and element.is_a("IfcAnnotation") and element.ObjectType == "DRAWING":
+ continue # For now, don't copy drawings until we stabilise a bit more. It's tricky.
new_obj = obj.copy()
if obj.data:
new_obj.data = obj.data.copy()
@@ -1017,7 +1020,6 @@ class RefreshAggregate(bpy.types.Operator):
return parents
def duplicate_children(entity):
-
pset = ifcopenshell.util.element.get_pset(entity, "BBIM_Aggregate_Data")
pset_data = json.loads(pset["Data"])[0]
instance_of = pset_data["instance_of"][0]
@@ -1236,6 +1238,7 @@ class OverridePasteBuffer(bpy.types.Operator):
class OverrideModeSetEdit(bpy.types.Operator):
+ bl_description = "Switch from Object mode to Edit mode"
bl_idname = "bim.override_mode_set_edit"
bl_label = "IFC Mode Set Edit"
bl_options = {"REGISTER", "UNDO"}
@@ -1244,7 +1247,7 @@ class OverrideModeSetEdit(bpy.types.Operator):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
- objs = context.selected_objects or ([context.active_object] if context.active_object else [])
+ selected_objs = context.selected_objects or ([context.active_object] if context.active_object else [])
active_obj = context.active_object
if context.active_object:
@@ -1254,19 +1257,15 @@ class OverrideModeSetEdit(bpy.types.Operator):
if element and element.is_a("IfcRelSpaceBoundary"):
return bpy.ops.bim.enable_editing_boundary_geometry()
- for obj in objs:
+ for obj in selected_objs:
if not obj:
continue
-
if not obj.data:
obj.select_set(False)
continue
-
element = tool.Ifc.get_entity(obj)
if not element:
continue
-
- # We are switching from OBJECT to EDIT mode.
usage_type = tool.Model.get_usage_type(element)
if usage_type is not None and usage_type not in ("PROFILE", "LAYER3"):
# Parametric objects shall not be edited as meshes as they
@@ -1274,57 +1273,37 @@ class OverrideModeSetEdit(bpy.types.Operator):
# constraints.
obj.select_set(False)
continue
-
representation = tool.Geometry.get_active_representation(obj)
if not representation:
continue
-
- if (
- tool.Pset.get_element_pset(element, "BBIM_Door")
- or tool.Pset.get_element_pset(element, "BBIM_Window")
- or tool.Pset.get_element_pset(element, "BBIM_Stair")
- ):
+ if tool.Blender.Modifier.is_modifier_with_non_editable_path(element):
obj.select_set(False)
continue
+ is_profile = True
if usage_type == "PROFILE":
- if len(context.selected_objects) == 1:
- bpy.ops.bim.hotkey(hotkey="A_E", description="")
- return {"FINISHED"}
- else:
- self.report({"INFO"}, "Only a single profile-based representation can be edited at a time.")
- obj.select_set(False)
- continue
-
- # TODO: refactor repetitive code
- if tool.Pset.get_element_pset(element, "BBIM_Roof"):
- if len(context.selected_objects) == 1:
- bpy.ops.bim.enable_editing_roof_path()
- return {"FINISHED"}
- else:
- self.report({"INFO"}, "Only a single profile-based representation can be edited at a time.")
- obj.select_set(False)
- continue
-
- if tool.Pset.get_element_pset(element, "BBIM_Railing"):
- if len(context.selected_objects) == 1:
- if obj.BIMRailingProperties.is_editing == 1:
- self.report({"INFO"}, "Can't edit path while the modifier parameters are being modified")
- return {"FINISHED"}
- bpy.ops.bim.enable_editing_railing_path()
- return {"FINISHED"}
- else:
- self.report({"INFO"}, "Only a single profile-based representation can be edited at a time.")
- obj.select_set(False)
- continue
-
- if (
+ operator = lambda: bpy.ops.bim.hotkey(hotkey="A_E")
+ elif (
tool.Geometry.is_profile_based(obj.data)
or usage_type == "LAYER3"
or tool.Geometry.is_swept_profile(representation)
):
- if len(context.selected_objects) == 1:
- bpy.ops.bim.hotkey(hotkey="S_E", description="")
+ operator = lambda: bpy.ops.bim.hotkey(hotkey="S_E")
+ elif tool.Blender.Modifier.is_editing_parameters(obj):
+ # This should go BEFORE the modifiers
+ self.report({"INFO"}, "Can't edit path while the modifier parameters are being modified")
+ obj.select_set(False)
+ continue
+ elif tool.Blender.Modifier.is_roof(element):
+ operator = lambda: bpy.ops.bim.enable_editing_roof_path()
+ elif tool.Blender.Modifier.is_railing(element):
+ operator = lambda: bpy.ops.bim.enable_editing_railing_path()
+ else:
+ is_profile = False
+ if is_profile:
+ if len(context.selected_objects) == 1 and context.active_object == context.selected_objects[0]:
+ tool.Blender.select_and_activate_single_object(context, obj)
+ operator()
return {"FINISHED"}
else:
self.report({"INFO"}, "Only a single profile-based representation can be edited at a time.")
@@ -1350,17 +1329,18 @@ class OverrideModeSetEdit(bpy.types.Operator):
obj.select_set(False)
continue
- if not context.selected_objects or len(context.selected_objects) != len(objs):
+ if not context.selected_objects or len(context.selected_objects) != len(selected_objs):
# We are trying to edit at least one non-mesh-like object : Display a hint to the user
- self.report({"INFO"}, "Only mesh-compatible representations may be edited in edit mode.")
+ self.report({"INFO"}, "Only mesh-compatible representations may be edited concurrently in edit mode.")
if context.active_object not in context.selected_objects:
# The active object is non-mesh-like. Set a valid object (or None) as active
context.view_layer.objects.active = context.selected_objects[0] if context.selected_objects else None
if context.active_object:
return tool.Blender.toggle_edit_mode(context)
+
# Restore the selection if nothing worked
- for obj in objs:
+ for obj in selected_objs:
obj.select_set(True)
context.view_layer.objects.active = active_obj
return {"FINISHED"}
diff --git a/src/blenderbim/blenderbim/bim/module/geometry/ui.py b/src/blenderbim/blenderbim/bim/module/geometry/ui.py
index 26d1a9ddc6..4725373914 100644
--- a/src/blenderbim/blenderbim/bim/module/geometry/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/geometry/ui.py
@@ -17,6 +17,7 @@
# along with BlenderBIM Add-on. If not, see .
import bpy
+import blenderbim.tool as tool
from bpy.types import Panel
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.helper import prop_with_search
@@ -29,19 +30,21 @@ def object_menu(self, context):
self.layout.operator("bim.override_object_delete", icon="PLUGIN")
self.layout.operator("bim.override_paste_buffer", icon="PLUGIN")
+
def outliner_menu(self, context):
self.layout.separator()
- self.layout.operator("bim.override_outliner_delete", icon='X')
+ self.layout.operator("bim.override_outliner_delete", icon="X")
+
class BIM_PT_representations(Panel):
- bl_label = "IFC Representations"
+ bl_label = "Representations"
bl_idname = "BIM_PT_representations"
- bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
- bl_context = "object"
+ bl_context = "scene"
bl_order = 1
- bl_parent_id = "BIM_PT_geometry_object"
+ bl_parent_id = "BIM_PT_tab_representations"
+ bl_options = {"HIDE_HEADER"}
@classmethod
def poll(cls, context):
@@ -58,20 +61,24 @@ class BIM_PT_representations(Panel):
layout = self.layout
props = context.active_object.BIMObjectProperties
- if not RepresentationsData.data["representations"]:
- layout.label(text="No representations found")
-
row = layout.row(align=True)
prop_with_search(row, context.active_object.BIMGeometryProperties, "contexts", text="")
row.operator("bim.add_representation", icon="ADD", text="")
+ if not RepresentationsData.data["representations"]:
+ layout.label(text="No Representations Found")
+
for representation in RepresentationsData.data["representations"]:
row = self.layout.row(align=True)
row.label(text=representation["ContextType"])
row.label(text=representation["ContextIdentifier"])
row.label(text=representation["TargetView"])
row.label(text=representation["RepresentationType"])
- op = row.operator("bim.switch_representation", icon="FILE_REFRESH" if representation["is_active"] else "OUTLINER_DATA_MESH", text="")
+ op = row.operator(
+ "bim.switch_representation",
+ icon="FILE_REFRESH" if representation["is_active"] else "OUTLINER_DATA_MESH",
+ text="",
+ )
op.should_switch_all_meshes = True
op.should_reload = True
op.ifc_definition_id = representation["id"]
@@ -80,14 +87,14 @@ class BIM_PT_representations(Panel):
class BIM_PT_connections(Panel):
- bl_label = "IFC Connections"
+ bl_label = "Connections"
bl_idname = "BIM_PT_connections"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
bl_order = 1
- bl_parent_id = "BIM_PT_geometry_object"
+ bl_parent_id = "BIM_PT_tab_geometric_relationships"
@classmethod
def poll(cls, context):
@@ -118,7 +125,7 @@ class BIM_PT_connections(Panel):
class BIM_PT_mesh(Panel):
- bl_label = "IFC Representation"
+ bl_label = "Representation"
bl_idname = "BIM_PT_mesh"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
@@ -185,7 +192,7 @@ def BIM_PT_transform(self, context):
class BIM_PT_derived_placements(Panel):
- bl_label = "IFC Derived Placements"
+ bl_label = "Derived Placements"
bl_idname = "BIM_PT_derived_placements"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
@@ -220,7 +227,7 @@ class BIM_PT_derived_placements(Panel):
class BIM_PT_workarounds(Panel):
- bl_label = "IFC Vendor Workarounds"
+ bl_label = "Vendor Workarounds"
bl_idname = "BIM_PT_workarounds"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
diff --git a/src/blenderbim/blenderbim/bim/module/georeference/ui.py b/src/blenderbim/blenderbim/bim/module/georeference/ui.py
index d7c3131a0a..bb75f06f5e 100644
--- a/src/blenderbim/blenderbim/bim/module/georeference/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/georeference/ui.py
@@ -23,7 +23,7 @@ from blenderbim.bim.module.georeference.data import GeoreferenceData
class BIM_PT_gis(Panel):
- bl_label = "IFC Georeferencing"
+ bl_label = "Georeferencing"
bl_idname = "BIM_PT_gis"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
diff --git a/src/blenderbim/blenderbim/bim/module/gis/ui.py b/src/blenderbim/blenderbim/bim/module/gis/ui.py
index 1ccc930928..a57b8d2c1c 100644
--- a/src/blenderbim/blenderbim/bim/module/gis/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/gis/ui.py
@@ -20,13 +20,13 @@ from bpy.types import Panel
class BIM_PT_cityjson_converter(Panel):
- bl_label = "IFC CityJSON"
+ bl_label = "CityJSON"
bl_idname = "BIM_PT_ifccityjson"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
- bl_parent_id = "BIM_PT_collaboration"
+ bl_parent_id = "BIM_PT_tab_collaboration"
def draw(self, context):
layout = self.layout
diff --git a/src/blenderbim/blenderbim/bim/module/group/ui.py b/src/blenderbim/blenderbim/bim/module/group/ui.py
index 874d239bf2..a2d6d8a362 100644
--- a/src/blenderbim/blenderbim/bim/module/group/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/group/ui.py
@@ -22,7 +22,7 @@ from blenderbim.bim.module.group.data import GroupsData, ObjectGroupsData
class BIM_PT_groups(Panel):
- bl_label = "IFC Groups"
+ bl_label = "Groups"
bl_idname = "BIM_PT_groups"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
@@ -69,14 +69,14 @@ class BIM_PT_groups(Panel):
class BIM_PT_object_groups(Panel):
- bl_label = "IFC Groups"
+ bl_label = "Groups"
bl_idname = "BIM_PT_object_groups"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
bl_order = 1
- bl_parent_id = "BIM_PT_utilities_object"
+ bl_parent_id = "BIM_PT_tab_misc"
@classmethod
def poll(cls, context):
diff --git a/src/blenderbim/blenderbim/bim/module/ifcgit/ui.py b/src/blenderbim/blenderbim/bim/module/ifcgit/ui.py
index 286e900adc..d54591a509 100644
--- a/src/blenderbim/blenderbim/bim/module/ifcgit/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/ifcgit/ui.py
@@ -6,7 +6,7 @@ from blenderbim.bim.module.ifcgit.data import IfcGitData
class IFCGIT_PT_panel(bpy.types.Panel):
"""Scene Properties panel to interact with IFC repository data"""
- bl_label = "IFC Git"
+ bl_label = "Git"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
diff --git a/src/blenderbim/blenderbim/bim/module/layer/ui.py b/src/blenderbim/blenderbim/bim/module/layer/ui.py
index 179ea568a2..218fd4046d 100644
--- a/src/blenderbim/blenderbim/bim/module/layer/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/layer/ui.py
@@ -22,13 +22,13 @@ from blenderbim.bim.module.layer.data import LayersData
class BIM_PT_layers(Panel):
- bl_label = "IFC Presentation Layers"
+ bl_label = "Presentation Layers"
bl_idname = "BIM_PT_layers"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
- bl_parent_id = "BIM_PT_geometry_object"
+ bl_parent_id = "BIM_PT_tab_geometric_relationships"
@classmethod
def poll(cls, context):
diff --git a/src/blenderbim/blenderbim/bim/module/lca/ui.py b/src/blenderbim/blenderbim/bim/module/lca/ui.py
index a76b38d8f8..5e06fd0098 100644
--- a/src/blenderbim/blenderbim/bim/module/lca/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/lca/ui.py
@@ -27,7 +27,7 @@ class BIM_PT_lca(Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
- bl_parent_id = "BIM_PT_integrations"
+ bl_parent_id = "BIM_PT_tab_integrations"
def draw(self, context):
props = context.scene.BIMLCAProperties
diff --git a/src/blenderbim/blenderbim/bim/module/library/ui.py b/src/blenderbim/blenderbim/bim/module/library/ui.py
index 2057e45a98..c963203cd7 100644
--- a/src/blenderbim/blenderbim/bim/module/library/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/library/ui.py
@@ -23,7 +23,7 @@ from blenderbim.bim.module.library.data import LibrariesData, LibraryReferencesD
class BIM_PT_libraries(Panel):
- bl_label = "IFC Libraries"
+ bl_label = "Libraries"
bl_idname = "BIM_PT_libraries"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
@@ -94,14 +94,14 @@ class BIM_PT_libraries(Panel):
class BIM_PT_library_references(Panel):
- bl_label = "IFC Library References"
+ bl_label = "Library References"
bl_idname = "BIM_PT_library_references"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
bl_order = 1
- bl_parent_id = "BIM_PT_misc_object"
+ bl_parent_id = "BIM_PT_tab_misc"
@classmethod
def poll(cls, context):
diff --git a/src/blenderbim/blenderbim/bim/module/material/ui.py b/src/blenderbim/blenderbim/bim/module/material/ui.py
index da1490aae1..773a3e14db 100644
--- a/src/blenderbim/blenderbim/bim/module/material/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/material/ui.py
@@ -17,6 +17,7 @@
# along with BlenderBIM Add-on. If not, see .
import blenderbim.bim.helper
+import blenderbim.tool as tool
from bpy.types import Panel, UIList
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.helper import draw_attributes
@@ -25,7 +26,7 @@ from blenderbim.bim.module.material.data import MaterialsData, ObjectMaterialDat
class BIM_PT_materials(Panel):
- bl_label = "IFC Materials"
+ bl_label = "Materials"
bl_idname = "BIM_PT_materials"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
@@ -88,7 +89,7 @@ class BIM_PT_materials(Panel):
blenderbim.bim.helper.draw_attributes(self.props.material_attributes, self.layout)
class BIM_PT_material(Panel):
- bl_label = "IFC Material"
+ bl_label = "Material"
bl_idname = "BIM_PT_material"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
@@ -111,15 +112,17 @@ class BIM_PT_material(Panel):
class BIM_PT_object_material(Panel):
- bl_label = "IFC Object Material"
+ bl_label = "Object Material"
bl_idname = "BIM_PT_object_material"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
- bl_context = "object"
- bl_parent_id = "BIM_PT_object_metadata"
+ bl_context = "scene"
+ bl_parent_id = "BIM_PT_tab_materials"
@classmethod
def poll(cls, context):
+ if not tool.Blender.is_tab(context, "GEOMETRY"):
+ return False
if not context.active_object:
return False
props = context.active_object.BIMObjectProperties
diff --git a/src/blenderbim/blenderbim/bim/module/model/data.py b/src/blenderbim/blenderbim/bim/module/model/data.py
index 66a4ca1cf5..e9a18c02af 100644
--- a/src/blenderbim/blenderbim/bim/module/model/data.py
+++ b/src/blenderbim/blenderbim/bim/module/model/data.py
@@ -67,6 +67,7 @@ class AuthoringData:
cls.data["active_material_usage"] = cls.active_material_usage()
cls.data["active_representation_type"] = cls.active_representation_type()
cls.data["boundary_class"] = cls.boundary_class()
+ cls.data["selected_material_usages"] = cls.selected_material_usages()
@classmethod
def boundary_class(cls):
@@ -238,6 +239,23 @@ class AuthoringData:
return [(str(e.id()), e.Name or "Unnamed", e.Description or "") for e in results]
return []
+ @classmethod
+ def selected_material_usages(cls):
+ selected_usages = {}
+ for obj in bpy.context.selected_objects:
+ element = tool.Ifc.get_entity(obj)
+ if not element:
+ continue
+ usage = tool.Model.get_usage_type(element)
+ if not usage:
+ representation = tool.Geometry.get_active_representation(obj)
+ if representation and representation.RepresentationType == "SweptSolid":
+ usage = "SWEPTSOLID"
+ else:
+ continue
+ selected_usages.setdefault(usage, []).append(obj)
+ return selected_usages
+
class ArrayData:
data = {}
@@ -272,12 +290,27 @@ class StairData:
@classmethod
def load(cls):
cls.is_loaded = True
- cls.data = {"pset_data": cls.pset_data()}
+ cls.data = {}
+ cls.data["pset_data"] = cls.pset_data()
+ if not cls.data["pset_data"]:
+ return
+ cls.data["general_params"] = cls.general_params()
@classmethod
def pset_data(cls):
return tool.Model.get_modeling_bbim_pset_data(bpy.context.active_object, "BBIM_Stair")
+ @classmethod
+ def general_params(cls):
+ props = bpy.context.active_object.BIMStairProperties
+ data = cls.data["pset_data"]["data_dict"]
+ general_params = {}
+ general_props = props.get_props_kwargs(stair_type=data["stair_type"])
+ for prop_name in general_props:
+ prop_readable_name, prop_value = get_prop_from_data(props, data, prop_name)
+ general_params[prop_readable_name] = prop_value
+ return general_params
+
class SverchokData:
data = {}
diff --git a/src/blenderbim/blenderbim/bim/module/model/door.py b/src/blenderbim/blenderbim/bim/module/model/door.py
index 838e42bf9a..6150f472c6 100644
--- a/src/blenderbim/blenderbim/bim/module/model/door.py
+++ b/src/blenderbim/blenderbim/bim/module/model/door.py
@@ -276,7 +276,7 @@ def create_bm_door_lining(bm, size: Vector, thickness: list, position: Vector =
def update_door_modifier_bmesh(context):
- obj = context.object
+ obj = context.active_object
props = obj.BIMDoorProperties
overall_width = props.overall_width
@@ -456,7 +456,7 @@ def update_door_modifier_bmesh(context):
bmesh.ops.translate(bm, vec=V(0, lining_offset, 0), verts=lining_offset_verts)
bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.0001)
- if bpy.context.object.mode == "EDIT":
+ if bpy.context.active_object.mode == "EDIT":
bmesh.update_edit_mesh(obj.data)
else:
bm.to_mesh(obj.data)
@@ -475,9 +475,9 @@ class BIM_OT_add_door(bpy.types.Operator, tool.Ifc.Operator):
self.report({"ERROR"}, "You need to start IFC project first to create a door.")
return {"CANCELLED"}
- if context.object is not None:
- spawn_location = context.object.location.copy()
- context.object.select_set(False)
+ if context.active_object is not None:
+ spawn_location = context.active_object.location.copy()
+ context.active_object.select_set(False)
else:
spawn_location = bpy.context.scene.cursor.location.copy()
diff --git a/src/blenderbim/blenderbim/bim/module/model/grid.py b/src/blenderbim/blenderbim/bim/module/model/grid.py
index adb4a240c4..3159f03c3b 100644
--- a/src/blenderbim/blenderbim/bim/module/model/grid.py
+++ b/src/blenderbim/blenderbim/bim/module/model/grid.py
@@ -98,7 +98,7 @@ def add_object(self, context):
class BIM_OT_add_object(Operator, tool.Ifc.Operator):
bl_idname = "mesh.add_grid"
- bl_label = "IFC Grid"
+ bl_label = "Grid"
bl_options = {"REGISTER", "UNDO"}
u_spacing: FloatProperty(name="U Spacing", default=10)
diff --git a/src/blenderbim/blenderbim/bim/module/model/opening.py b/src/blenderbim/blenderbim/bim/module/model/opening.py
index ef7ea63bc8..c527348eab 100644
--- a/src/blenderbim/blenderbim/bim/module/model/opening.py
+++ b/src/blenderbim/blenderbim/bim/module/model/opening.py
@@ -498,10 +498,12 @@ class AddBoolean(Operator, tool.Ifc.Operator):
bl_label = "Add Boolean"
bl_options = {"REGISTER", "UNDO"}
+ @classmethod
+ def poll(cls, context):
+ return len(context.selected_objects) == 2
+
def _execute(self, context):
props = context.scene.BIMModelProperties
- if len(context.selected_objects) != 2:
- return {"FINISHED"}
obj1, obj2 = context.selected_objects
element1 = tool.Ifc.get_entity(obj1)
element2 = tool.Ifc.get_entity(obj2)
@@ -546,14 +548,19 @@ class ShowBooleans(Operator, tool.Ifc.Operator, AddObjectHelper):
bl_label = "Show Booleans"
bl_options = {"REGISTER", "UNDO"}
+ @classmethod
+ def poll(cls, context):
+ obj = context.active_object
+ return (
+ obj is not None
+ and obj.data
+ and hasattr(obj.data, "BIMMeshProperties")
+ and obj.data.BIMMeshProperties.ifc_definition_id
+ )
+
+
def _execute(self, context):
obj = context.active_object
- if (
- not obj.data
- or not hasattr(obj.data, "BIMMeshProperties")
- or not obj.data.BIMMeshProperties.ifc_definition_id
- ):
- return {"FINISHED"}
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
representation = tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id)
booleans = []
diff --git a/src/blenderbim/blenderbim/bim/module/model/pie.py b/src/blenderbim/blenderbim/bim/module/model/pie.py
index f29e286af9..f43af88132 100644
--- a/src/blenderbim/blenderbim/bim/module/model/pie.py
+++ b/src/blenderbim/blenderbim/bim/module/model/pie.py
@@ -92,7 +92,7 @@ class VIEW3D_MT_PIE_bim(bpy.types.Menu):
class VIEW3D_MT_PIE_bim_class(bpy.types.Menu):
- bl_label = "IFC Class"
+ bl_label = "Class"
def draw(self, context):
pie = self.layout.menu_pie()
diff --git a/src/blenderbim/blenderbim/bim/module/model/prop.py b/src/blenderbim/blenderbim/bim/module/model/prop.py
index 10b8473e9d..90adc41ec5 100644
--- a/src/blenderbim/blenderbim/bim/module/model/prop.py
+++ b/src/blenderbim/blenderbim/bim/module/model/prop.py
@@ -99,7 +99,7 @@ class BIMModelProperties(PropertyGroup):
)
occurrence_name_function: bpy.props.StringProperty(name="Occurrence Name Function")
getter_enum = {"ifc_class": get_ifc_class, "relating_type": get_relating_type_id}
- extrusion_depth: bpy.props.FloatProperty(default=42.0, subtype="DISTANCE")
+ extrusion_depth: bpy.props.FloatProperty(min=0.001, default=42.0, subtype="DISTANCE")
cardinal_point: bpy.props.EnumProperty(
items=(
# TODO: complain to buildingSMART
@@ -201,16 +201,18 @@ class BIMStairProperties(PropertyGroup):
has_top_nib: bpy.props.BoolProperty(name="Has top nib", default=True)
stair_type: bpy.props.EnumProperty(name="Stair type", items=stair_types, default="CONCRETE")
- def get_props_kwargs(self, convert_to_project_units=False):
+ def get_props_kwargs(self, convert_to_project_units=False, stair_type=None):
+ if not stair_type:
+ stair_type = self.stair_type
stair_kwargs = {
- "stair_type": self.stair_type,
+ "stair_type": stair_type,
"width": self.width,
"height": self.height,
"number_of_treads": self.number_of_treads,
"tread_run": self.tread_run,
}
- if self.stair_type == "CONCRETE":
+ if stair_type == "CONCRETE":
concrete_props = {
"base_slab_depth": self.base_slab_depth,
"top_slab_depth": self.top_slab_depth,
@@ -219,13 +221,13 @@ class BIMStairProperties(PropertyGroup):
}
stair_kwargs.update(concrete_props)
- elif self.stair_type == "WOOD/STEEL":
+ elif stair_type == "WOOD/STEEL":
wood_steel_props = {
"tread_depth": self.tread_depth,
}
stair_kwargs.update(wood_steel_props)
- elif self.stair_type == "GENERIC":
+ elif stair_type == "GENERIC":
pass
if not convert_to_project_units:
diff --git a/src/blenderbim/blenderbim/bim/module/model/railing.py b/src/blenderbim/blenderbim/bim/module/model/railing.py
index 71c07a986b..245a963f61 100644
--- a/src/blenderbim/blenderbim/bim/module/model/railing.py
+++ b/src/blenderbim/blenderbim/bim/module/model/railing.py
@@ -120,7 +120,7 @@ def update_railing_modifier_bmesh(context):
"""before using should make sure that Data contains up-to-date information.
If BBIM Pset just changed should call refresh() before updating bmesh
"""
- obj = context.object
+ obj = context.active_object
props = obj.BIMRailingProperties
# NOTE: using Data since bmesh update will hapen very often
@@ -274,9 +274,9 @@ class BIM_OT_add_railing(bpy.types.Operator, tool.Ifc.Operator):
self.report({"ERROR"}, "You need to start IFC project first to create a railing.")
return {"CANCELLED"}
- if context.object is not None:
- spawn_location = context.object.location.copy()
- context.object.select_set(False)
+ if context.active_object is not None:
+ spawn_location = context.active_object.location.copy()
+ context.active_object.select_set(False)
else:
spawn_location = bpy.context.scene.cursor.location.copy()
@@ -348,8 +348,7 @@ class EnableEditingRailing(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
obj = context.active_object
props = obj.BIMRailingProperties
- element = tool.Ifc.get_entity(obj)
- data = json.loads(ifcopenshell.util.element.get_pset(element, "BBIM_Railing", "Data"))
+ data = tool.Model.get_modeling_bbim_pset_data(obj, "BBIM_Railing")["data_dict"]
data["path_data"] = json.dumps(data["path_data"])
# required since we could load pset from .ifc and BIMRailingProperties won't be set
@@ -366,14 +365,13 @@ class CancelEditingRailing(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
obj = context.active_object
- element = tool.Ifc.get_entity(obj)
- data = json.loads(ifcopenshell.util.element.get_pset(element, "BBIM_Railing", "Data"))
+ data = tool.Model.get_modeling_bbim_pset_data(obj, "BBIM_Railing")["data_dict"]
props = obj.BIMRailingProperties
# restore previous settings since editing was canceled
props.set_props_kwargs_from_ifc_data(data)
update_railing_modifier_bmesh(context)
-
+
props.is_editing = False
return {"FINISHED"}
@@ -438,11 +436,14 @@ class EnableEditingRailingPath(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
obj = context.active_object
props = obj.BIMRailingProperties
+ data = tool.Model.get_modeling_bbim_pset_data(obj, "BBIM_Railing")["data_dict"]
+ # required since we could load pset from .ifc and BIMRoofProperties won't be set
+ props.set_props_kwargs_from_ifc_data(data)
props.is_editing_path = True
update_railing_modifier_bmesh(context)
- if bpy.context.object.mode != "EDIT":
+ if bpy.context.active_object.mode != "EDIT":
bpy.ops.object.mode_set(mode="EDIT")
bpy.ops.wm.tool_set_by_id(tool.Blender.get_viewport_context(), name="bim.cad_tool")
ProfileDecorator.install(context, exit_edit_mode_callback=lambda: cancel_editing_railing_path(context))
@@ -456,9 +457,24 @@ def cancel_editing_railing_path(context):
ProfileDecorator.uninstall()
props.is_editing_path = False
- update_railing_modifier_bmesh(context)
- if bpy.context.object.mode == "EDIT":
+ if bpy.context.active_object.mode == "EDIT":
bpy.ops.object.mode_set(mode="OBJECT")
+
+ if props.railing_type == "FRAMELESS_PANEL":
+ update_railing_modifier_bmesh(context)
+ else:
+ element = tool.Ifc.get_entity(obj)
+ body = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
+ blenderbim.core.geometry.switch_representation(
+ tool.Ifc,
+ tool.Geometry,
+ obj=obj,
+ representation=body,
+ should_reload=True,
+ is_global=True,
+ should_sync_changes_first=False,
+ )
+
return {"FINISHED"}
@@ -492,7 +508,7 @@ class FinishEditingRailingPath(bpy.types.Operator, tool.Ifc.Operator):
# since we know that BBIM_Railing could have changed
refresh()
update_railing_modifier_bmesh(context)
- if bpy.context.object.mode == "EDIT":
+ if bpy.context.active_object.mode == "EDIT":
bpy.ops.object.mode_set(mode="OBJECT")
update_railing_modifier_ifc_data(context)
return {"FINISHED"}
diff --git a/src/blenderbim/blenderbim/bim/module/model/roof.py b/src/blenderbim/blenderbim/bim/module/model/roof.py
index f3d90c0c73..13d4b402c6 100644
--- a/src/blenderbim/blenderbim/bim/module/model/roof.py
+++ b/src/blenderbim/blenderbim/bim/module/model/roof.py
@@ -424,7 +424,7 @@ def update_roof_modifier_bmesh(context):
"""before using should make sure that Data contains up-to-date information.
If BBIM Pset just changed should call refresh() before updating bmesh
"""
- obj = context.object
+ obj = context.active_object
props = obj.BIMRoofProperties
# NOTE: using Data since bmesh update will hapen very often
@@ -506,9 +506,9 @@ class BIM_OT_add_roof(bpy.types.Operator, tool.Ifc.Operator):
self.report({"ERROR"}, "You need to start IFC project first to create a roof.")
return {"CANCELLED"}
- if context.object is not None:
- spawn_location = context.object.location.copy()
- context.object.select_set(False)
+ if context.active_object is not None:
+ spawn_location = context.active_object.location.copy()
+ context.active_object.select_set(False)
else:
spawn_location = bpy.context.scene.cursor.location.copy()
@@ -593,8 +593,7 @@ class EnableEditingRoof(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
obj = context.active_object
props = obj.BIMRoofProperties
- element = tool.Ifc.get_entity(obj)
- data = json.loads(ifcopenshell.util.element.get_pset(element, "BBIM_Roof", "Data"))
+ data = tool.Model.get_modeling_bbim_pset_data(obj, "BBIM_Roof")["data_dict"]
# required since we could load pset from .ifc and BIMRoofProperties won't be set
props.set_props_kwargs_from_ifc_data(data)
props.is_editing = True
@@ -608,8 +607,7 @@ class CancelEditingRoof(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
obj = context.active_object
- element = tool.Ifc.get_entity(obj)
- data = json.loads(ifcopenshell.util.element.get_pset(element, "BBIM_Roof", "Data"))
+ data = tool.Model.get_modeling_bbim_pset_data(obj, "BBIM_Roof")["data_dict"]
props = obj.BIMRoofProperties
# restore previous settings since editing was canceled
@@ -650,11 +648,14 @@ class EnableEditingRoofPath(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
obj = context.active_object
props = obj.BIMRoofProperties
+ data = tool.Model.get_modeling_bbim_pset_data(obj, "BBIM_Roof")["data_dict"]
+ # required since we could load pset from .ifc and BIMRoofProperties won't be set
+ props.set_props_kwargs_from_ifc_data(data)
props.is_editing_path = True
update_roof_modifier_bmesh(context)
- if bpy.context.object.mode != "EDIT":
+ if bpy.context.active_object.mode != "EDIT":
bpy.ops.object.mode_set(mode="EDIT")
bpy.ops.wm.tool_set_by_id(tool.Blender.get_viewport_context(), name="bim.cad_tool")
@@ -706,7 +707,7 @@ def cancel_editing_roof_path(context):
props.is_editing_path = False
update_roof_modifier_bmesh(context)
- if bpy.context.object.mode == "EDIT":
+ if bpy.context.active_object.mode == "EDIT":
bpy.ops.object.mode_set(mode="OBJECT")
return {"FINISHED"}
@@ -747,7 +748,7 @@ class FinishEditingRoofPath(bpy.types.Operator, tool.Ifc.Operator):
update_roof_modifier_bmesh(context)
update_roof_modifier_ifc_data(context)
- if bpy.context.object.mode == "EDIT":
+ if bpy.context.active_object.mode == "EDIT":
bpy.ops.object.mode_set(mode="OBJECT")
update_roof_modifier_ifc_data(context)
return {"FINISHED"}
@@ -789,7 +790,7 @@ class SetGableRoofEdgeAngle(bpy.types.Operator):
# tried to avoid bmesh with foreach_get and foreach_set
# but in EDIT mode it's only possible to change attributes by working with bmesh
- me = context.object.data
+ me = context.active_object.data
bm = tool.Blender.get_bmesh_for_mesh(me)
# check if attribute exists or create one
diff --git a/src/blenderbim/blenderbim/bim/module/model/stair.py b/src/blenderbim/blenderbim/bim/module/model/stair.py
index 331173f381..dbf24d3ae7 100644
--- a/src/blenderbim/blenderbim/bim/module/model/stair.py
+++ b/src/blenderbim/blenderbim/bim/module/model/stair.py
@@ -215,7 +215,7 @@ def update_stair_modifier(context):
props_kwargs = obj.BIMStairProperties.get_props_kwargs()
vertices, edges, faces = generate_stair_2d_profile(**props_kwargs)
- obj = context.object
+ obj = context.active_object
bm = bmesh.new()
bm.verts.index_update()
bm.edges.index_update()
@@ -234,7 +234,7 @@ def update_stair_modifier(context):
translate_verts = [v for v in extruded["geom"] if isinstance(v, BMVert)]
bmesh.ops.translate(bm, vec=extrusion_vector, verts=translate_verts)
- if context.object.mode == "EDIT":
+ if context.active_object.mode == "EDIT":
bmesh.update_edit_mesh(obj.data)
else:
bm.to_mesh(obj.data)
@@ -316,9 +316,9 @@ class BIM_OT_add_clever_stair(bpy.types.Operator, tool.Ifc.Operator):
self.report({"ERROR"}, "You need to start IFC project first to create a stair.")
return {"CANCELLED"}
- if context.object is not None:
- spawn_location = context.object.location.copy()
- context.object.select_set(False)
+ if context.active_object is not None:
+ spawn_location = context.active_object.location.copy()
+ context.active_object.select_set(False)
else:
spawn_location = bpy.context.scene.cursor.location.copy()
diff --git a/src/blenderbim/blenderbim/bim/module/model/sverchok_modifier.py b/src/blenderbim/blenderbim/bim/module/model/sverchok_modifier.py
index 4444870fe9..7c6428a4eb 100644
--- a/src/blenderbim/blenderbim/bim/module/model/sverchok_modifier.py
+++ b/src/blenderbim/blenderbim/bim/module/model/sverchok_modifier.py
@@ -51,7 +51,7 @@ def update_sverchok_modifier(context):
bm.edges.index_update()
bm.faces.index_update()
- if context.object.mode == "EDIT":
+ if context.active_object.mode == "EDIT":
bmesh.update_edit_mesh(obj.data)
else:
bm.to_mesh(obj.data)
diff --git a/src/blenderbim/blenderbim/bim/module/model/ui.py b/src/blenderbim/blenderbim/bim/module/model/ui.py
index 12aa343c9b..75837229cb 100644
--- a/src/blenderbim/blenderbim/bim/module/model/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/model/ui.py
@@ -41,7 +41,7 @@ from blenderbim.bim.helper import prop_with_search
class LaunchTypeManager(bpy.types.Operator):
bl_idname = "bim.launch_type_manager"
bl_label = "Launch Type Manager"
- bl_options = {"REGISTER"}
+ bl_options = {"REGISTER", "UNDO"}
bl_description = "Display all available Construction Types to add new instances"
def execute(self, context):
@@ -127,6 +127,8 @@ class LaunchTypeManager(bpy.types.Operator):
op.ifc_class = relating_type["ifc_class"]
op.relating_type_id = relating_type["id"]
+ op = row.operator("bim.rename_type", icon="GREASEPENCIL", text="")
+ op.element = relating_type["id"]
op = row.operator("bim.select_type", icon="OBJECT_DATA", text="")
op.relating_type = relating_type["id"]
op = row.operator("bim.duplicate_type", icon="DUPLICATE", text="")
@@ -166,12 +168,13 @@ class BIM_PT_Grids(Panel):
class BIM_PT_array(bpy.types.Panel):
- bl_label = "IFC Array"
+ bl_label = "Array"
bl_idname = "BIM_PT_array"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
- bl_context = "modifier"
+ bl_context = "scene"
bl_options = {"DEFAULT_CLOSED"}
+ bl_parent_id = "BIM_PT_tab_parametric_geometry"
@classmethod
def poll(cls, context):
@@ -234,12 +237,13 @@ class BIM_PT_array(bpy.types.Panel):
class BIM_PT_stair(bpy.types.Panel):
- bl_label = "IFC Stair"
+ bl_label = "Stair"
bl_idname = "BIM_PT_stair"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
- bl_context = "modifier"
+ bl_context = "scene"
bl_options = {"DEFAULT_CLOSED"}
+ bl_parent_id = "BIM_PT_tab_parametric_geometry"
@classmethod
def poll(cls, context):
@@ -269,11 +273,9 @@ class BIM_PT_stair(bpy.types.Panel):
row.operator("bim.enable_editing_stair", icon="GREASEPENCIL", text="")
row.operator("bim.remove_stair", icon="X", text="")
row = self.layout.row(align=True)
- for prop in props.get_props_kwargs():
- prop_value = stair_data[prop]
- prop_value = round(prop_value, 5) if type(prop_value) is float else prop_value
+ for prop_name, prop_value in StairData.data["general_params"].items():
row = self.layout.row(align=True)
- row.label(text=f"{props.bl_rna.properties[prop].name}")
+ row.label(text=prop_name)
row.label(text=str(prop_value))
# calculated properties
@@ -294,12 +296,13 @@ class BIM_PT_stair(bpy.types.Panel):
class BIM_PT_sverchok(bpy.types.Panel):
- bl_label = "IFC Sverchok"
+ bl_label = "Sverchok"
bl_idname = "BIM_PT_sverchok"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
- bl_context = "modifier"
+ bl_context = "scene"
bl_options = {"DEFAULT_CLOSED"}
+ bl_parent_id = "BIM_PT_tab_parametric_geometry"
@classmethod
def poll(cls, context):
@@ -332,12 +335,13 @@ class BIM_PT_sverchok(bpy.types.Panel):
class BIM_PT_window(bpy.types.Panel):
- bl_label = "IFC Window"
+ bl_label = "Window"
bl_idname = "BIM_PT_window"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
- bl_context = "modifier"
+ bl_context = "scene"
bl_options = {"DEFAULT_CLOSED"}
+ bl_parent_id = "BIM_PT_tab_parametric_geometry"
@classmethod
def poll(cls, context):
@@ -441,12 +445,13 @@ class BIM_PT_window(bpy.types.Panel):
class BIM_PT_door(bpy.types.Panel):
- bl_label = "IFC Door"
+ bl_label = "Door"
bl_idname = "BIM_PT_door"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
- bl_context = "modifier"
+ bl_context = "scene"
bl_options = {"DEFAULT_CLOSED"}
+ bl_parent_id = "BIM_PT_tab_parametric_geometry"
@classmethod
def poll(cls, context):
@@ -514,12 +519,13 @@ class BIM_PT_door(bpy.types.Panel):
class BIM_PT_railing(bpy.types.Panel):
- bl_label = "IFC Railing"
+ bl_label = "Railing"
bl_idname = "BIM_PT_railing"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
- bl_context = "modifier"
+ bl_context = "scene"
bl_options = {"DEFAULT_CLOSED"}
+ bl_parent_id = "BIM_PT_tab_parametric_geometry"
@classmethod
def poll(cls, context):
@@ -576,12 +582,13 @@ class BIM_PT_railing(bpy.types.Panel):
class BIM_PT_roof(bpy.types.Panel):
- bl_label = "IFC Roof"
+ bl_label = "Roof"
bl_idname = "BIM_PT_roof"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
- bl_context = "modifier"
+ bl_context = "scene"
bl_options = {"DEFAULT_CLOSED"}
+ bl_parent_id = "BIM_PT_tab_parametric_geometry"
@classmethod
def poll(cls, context):
@@ -631,7 +638,7 @@ class BIM_PT_roof(bpy.types.Panel):
class BIM_MT_model(Menu):
bl_idname = "BIM_MT_model"
- bl_label = "IFC Objects"
+ bl_label = "Objects"
def draw(self, context):
layout = self.layout
diff --git a/src/blenderbim/blenderbim/bim/module/model/wall.py b/src/blenderbim/blenderbim/bim/module/model/wall.py
index aa94348596..7494146aee 100644
--- a/src/blenderbim/blenderbim/bim/module/model/wall.py
+++ b/src/blenderbim/blenderbim/bim/module/model/wall.py
@@ -60,11 +60,11 @@ class JoinWall(bpy.types.Operator, tool.Ifc.Operator):
for obj in selected_objs:
joiner.unjoin(obj)
return {"FINISHED"}
-
+
if not context.active_object or not context.active_object.BIMObjectProperties.ifc_definition_id:
self.report({"ERROR"}, f"No active object selected")
return {"CANCELLED"}
-
+
for obj in selected_objs:
tool.Geometry.clear_scale(obj)
@@ -75,7 +75,7 @@ class JoinWall(bpy.types.Operator, tool.Ifc.Operator):
if len(selected_objs) == 1:
joiner.join_E(context.active_object, context.scene.cursor.location)
return {"FINISHED"}
-
+
if self.join_type in ("L", "V"):
if len(selected_objs) != 2:
self.report({"ERROR"}, f"It requires 2 selected objects to do join of type {self.join_type}")
@@ -86,7 +86,7 @@ class JoinWall(bpy.types.Operator, tool.Ifc.Operator):
elif self.join_type == "V":
joiner.join_V(another_selected_object, context.active_object)
return {"FINISHED"}
-
+
if self.join_type == "T":
elements = [tool.Ifc.get_entity(o) for o in context.selected_objects]
layer2_elements = []
@@ -560,7 +560,7 @@ class DumbWallGenerator:
rotation = math.atan2(normal[1], normal[0])
rotated_y_axis = Matrix.Rotation(-rotation, 4, "Z")[1].xyz
-
+
# since wall thickness goes by local Y+ axis
# we find best position for the next wall
# by finding the face of another wall that will be very close to the some test point.
@@ -1137,7 +1137,7 @@ class DumbWallJoiner:
previous_matrix = obj.matrix_world.copy()
previous_origin = previous_matrix.col[3].to_2d()
- obj.matrix_world[0][3], obj.matrix_world[1][3] = self.body[0]
+ obj.matrix_world.col[3].xy = self.body[0]
bpy.context.view_layer.update()
for rel in element.ConnectedFrom:
@@ -1432,6 +1432,7 @@ class DumbWallJoiner:
return True
def clip(self, wall1, slab2):
+ """returns height of the clipped wall, adds clipping plane to `clippings`"""
element1 = tool.Ifc.get_entity(wall1)
element2 = tool.Ifc.get_entity(slab2)
@@ -1441,19 +1442,15 @@ class DumbWallJoiner:
bases = [axis1["base"][0].to_3d(), axis1["base"][1].to_3d(), axis1["side"][0].to_3d(), axis1["side"][1].to_3d()]
extrusion = self.get_extrusion_data(tool.Ifc.get().by_id(wall1.data.BIMMeshProperties.ifc_definition_id))
- d = wall1.matrix_world.to_quaternion() @ extrusion["direction"]
+ wall_dir = wall1.matrix_world.to_quaternion() @ extrusion["direction"]
slab_pt = slab2.matrix_world @ Vector((0, 0, 0))
slab_dir = slab2.matrix_world.to_quaternion() @ Vector((0, 0, -1))
- tops = [mathutils.geometry.intersect_line_plane(b, b + d, slab_pt, slab_dir) for b in bases]
-
- i_bottom = None
- i_top = None
- for i, co in enumerate(tops):
- if i_top is None or co[2] > i_top[2]:
- i_top = co
- i_bottom = bases[i]
+ tops = [mathutils.geometry.intersect_line_plane(b, b + wall_dir, slab_pt, slab_dir) for b in bases]
+ top_index = max(range(4), key=lambda i: tops[i].z)
+ i_top = tops[top_index]
+ i_bottom = bases[top_index]
quaternion = slab2.matrix_world.to_quaternion()
x_axis = quaternion @ Vector((1, 0, 0))
diff --git a/src/blenderbim/blenderbim/bim/module/model/window.py b/src/blenderbim/blenderbim/bim/module/model/window.py
index d19763b445..ea5458da4d 100644
--- a/src/blenderbim/blenderbim/bim/module/model/window.py
+++ b/src/blenderbim/blenderbim/bim/module/model/window.py
@@ -292,7 +292,7 @@ def create_bm_window(
def update_window_modifier_bmesh(context):
- obj = context.object
+ obj = context.active_object
props = obj.BIMWindowProperties
panel_schema = DEFAULT_PANEL_SCHEMAS[props.window_type]
accumulated_height = [0] * len(panel_schema[0])
@@ -425,7 +425,7 @@ def update_window_modifier_bmesh(context):
bmesh.ops.translate(bm, vec=V(0, lining_offset, 0), verts=bm.verts)
bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.0001)
- if bpy.context.object.mode == "EDIT":
+ if bpy.context.active_object.mode == "EDIT":
bmesh.update_edit_mesh(obj.data)
else:
bm.to_mesh(obj.data)
@@ -444,9 +444,9 @@ class BIM_OT_add_window(bpy.types.Operator, tool.Ifc.Operator):
self.report({"ERROR"}, "You need to start IFC project first to create a window.")
return {"CANCELLED"}
- if context.object is not None:
- spawn_location = context.object.location.copy()
- context.object.select_set(False)
+ if context.active_object is not None:
+ spawn_location = context.active_object.location.copy()
+ context.active_object.select_set(False)
else:
spawn_location = bpy.context.scene.cursor.location.copy()
diff --git a/src/blenderbim/blenderbim/bim/module/model/workspace.py b/src/blenderbim/blenderbim/bim/module/model/workspace.py
index 6fefce75b1..26f5631947 100644
--- a/src/blenderbim/blenderbim/bim/module/model/workspace.py
+++ b/src/blenderbim/blenderbim/bim/module/model/workspace.py
@@ -176,7 +176,11 @@ class BimToolUI:
row.operator("bim.join_wall", icon="X", text="").join_type = ""
elif AuthoringData.data["active_material_usage"] == "LAYER3":
- add_layout_hotkey_operator(cls.layout, "Edit Profile", "S_E", "")
+
+ if len(context.selected_objects) == 1:
+ add_layout_hotkey_operator(cls.layout, "Edit Profile", "S_E", "")
+ elif "LAYER2" in AuthoringData.data["selected_material_usages"]:
+ add_layout_hotkey_operator(cls.layout, "Extend Wall To Slab", "S_E", "")
row = cls.layout.row(align=True)
row.prop(data=cls.props, property="x_angle", text="X Angle")
@@ -457,7 +461,7 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator):
if len(bpy.context.selected_objects) == 1:
if self.active_material_usage == "LAYER3":
- # Edit LAYER2 profile
+ # Edit LAYER3 profile
if bpy.context.active_object and bpy.context.active_object.mode == "OBJECT":
bpy.ops.bim.enable_editing_extrusion_profile()
elif self.active_material_usage == "LAYER2":
@@ -469,6 +473,7 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator):
else:
# Edit SWEPTSOLID profile (assuming single profile for now)
bpy.ops.bim.enable_editing_extrusion_profile()
+
elif self.active_material_usage == "LAYER2" and selected_usages.get("PROFILE", []):
# Extend PROFILEs to LAYER2
[o.select_set(False) for o in selected_usages.get("LAYER3", [])]
diff --git a/src/blenderbim/blenderbim/bim/module/owner/ui.py b/src/blenderbim/blenderbim/bim/module/owner/ui.py
index a9a5404e4e..622808c6f0 100644
--- a/src/blenderbim/blenderbim/bim/module/owner/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/owner/ui.py
@@ -77,7 +77,7 @@ def draw_addresses(box, parent):
class BIM_PT_people(bpy.types.Panel):
- bl_label = "IFC People"
+ bl_label = "People"
bl_idname = "BIM_PT_people"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
@@ -136,7 +136,7 @@ class BIM_PT_people(bpy.types.Panel):
class BIM_PT_organisations(bpy.types.Panel):
- bl_label = "IFC Organisations"
+ bl_label = "Organisations"
bl_idname = "BIM_PT_organisations"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
@@ -183,7 +183,7 @@ class BIM_PT_organisations(bpy.types.Panel):
class BIM_PT_owner(bpy.types.Panel):
- bl_label = "IFC Owner History"
+ bl_label = "Owner History"
bl_idname = "BIM_PT_owner"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
@@ -234,7 +234,7 @@ class BIM_PT_owner(bpy.types.Panel):
class BIM_PT_actor(bpy.types.Panel):
- bl_label = "IFC Actor"
+ bl_label = "Actor"
bl_idname = "BIM_PT_actor"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
@@ -284,14 +284,14 @@ class BIM_PT_actor(bpy.types.Panel):
class BIM_PT_object_actor(bpy.types.Panel):
- bl_label = "IFC Actor"
+ bl_label = "Actor"
bl_idname = "BIM_PT_object_actor"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
bl_order = 1
- bl_parent_id = "BIM_PT_misc_object"
+ bl_parent_id = "BIM_PT_tab_misc"
@classmethod
def poll(cls, context):
diff --git a/src/blenderbim/blenderbim/bim/module/patch/ui.py b/src/blenderbim/blenderbim/bim/module/patch/ui.py
index 7b0416bd14..c5013c31ff 100644
--- a/src/blenderbim/blenderbim/bim/module/patch/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/patch/ui.py
@@ -23,13 +23,13 @@ from blenderbim.bim.helper import draw_attributes
class BIM_PT_patch(bpy.types.Panel):
- bl_label = "IFC Patch"
+ bl_label = "Patch"
bl_idname = "BIM_PT_patch"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
- bl_parent_id = "BIM_PT_quality_control"
+ bl_parent_id = "BIM_PT_tab_quality_control"
def draw(self, context):
layout = self.layout
diff --git a/src/blenderbim/blenderbim/bim/module/profile/ui.py b/src/blenderbim/blenderbim/bim/module/profile/ui.py
index 4caf801b6f..5f4b998c98 100644
--- a/src/blenderbim/blenderbim/bim/module/profile/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/profile/ui.py
@@ -25,7 +25,7 @@ from blenderbim.bim.module.profile.prop import generate_thumbnail_for_active_pro
class BIM_PT_profiles(Panel):
- bl_label = "IFC Profiles"
+ bl_label = "Profiles"
bl_idname = "BIM_PT_profiles"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
diff --git a/src/blenderbim/blenderbim/bim/module/project/operator.py b/src/blenderbim/blenderbim/bim/module/project/operator.py
index 3cd930fe0c..369d8d5c97 100644
--- a/src/blenderbim/blenderbim/bim/module/project/operator.py
+++ b/src/blenderbim/blenderbim/bim/module/project/operator.py
@@ -582,34 +582,34 @@ class LoadProject(bpy.types.Operator, IFCFileSelector):
@persistent
def load_handler(*args):
bpy.app.handlers.load_post.remove(load_handler)
- if tool.Blender.is_default_scene():
- for obj in bpy.data.objects:
- bpy.data.objects.remove(obj)
self.finish_loading_project(context)
if self.should_start_fresh_session:
- # WARNING: wm.read_homefile clears context
- # which could lead to some operators to fail:
+ # WARNING: wm.read_homefile clears context which could lead to some
+ # operators to fail:
# https://blender.stackexchange.com/a/282558/135166
- # So we continue using the load_post handler
- # thats triggered when context is already restored
+ # So we continue using the load_post handler thats triggered when
+ # context is already restored
bpy.app.handlers.load_post.append(load_handler)
bpy.ops.wm.read_homefile()
return {"FINISHED"}
else:
return self.finish_loading_project(context)
-
def finish_loading_project(self, context):
if not self.is_existing_ifc_file():
return {"FINISHED"}
+
+ if tool.Blender.is_default_scene():
+ for obj in bpy.data.objects:
+ bpy.data.objects.remove(obj)
+
context.scene.BIMProperties.ifc_file = self.get_filepath()
context.scene.BIMProjectProperties.is_loading = True
context.scene.BIMProjectProperties.total_elements = len(tool.Ifc.get().by_type("IfcElement"))
if not self.is_advanced:
bpy.ops.bim.load_project_elements()
- return {"FINISHED"}
-
+ return {"FINISHED"}
def invoke(self, context, event):
context.window_manager.fileselect_add(self)
@@ -987,7 +987,7 @@ class ExportIFC(bpy.types.Operator):
if bpy.data.is_saved and bpy.data.is_dirty and bpy.data.filepath:
bpy.ops.wm.save_mainfile(filepath=bpy.data.filepath)
blenderbim.bim.handler.purge_module_data()
- self.report({"INFO"}, f"IFC Project \"{os.path.basename(output_file)}\" Saved")
+ self.report({"INFO"}, f'IFC Project "{os.path.basename(output_file)}" Saved')
if bpy.data.is_saved:
bpy.ops.wm.save_mainfile("INVOKE_DEFAULT")
diff --git a/src/blenderbim/blenderbim/bim/module/project/ui.py b/src/blenderbim/blenderbim/bim/module/project/ui.py
index 882211e716..34fb692b07 100644
--- a/src/blenderbim/blenderbim/bim/module/project/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/project/ui.py
@@ -41,6 +41,12 @@ class BIM_MT_new_project(Menu):
bl_label = "New Project"
def draw(self, context):
+ self.layout.operator_context = "INVOKE_DEFAULT"
+ op = self.layout.operator("bim.load_project", text="Open IFC Project", icon="FILEBROWSER")
+ op.should_start_fresh_session = True
+ # Do we need to set it back to exec default?
+ # self.layout.operator_context = "EXEC_DEFAULT"
+ self.layout.separator()
self.layout.label(text="New IFC Project", icon_value=blenderbim.bim.icons["IFC"].icon_id)
self.layout.operator("bim.new_project", text="Metric (m) Project").preset = "metric_m"
self.layout.operator("bim.new_project", text="Metric (mm) Project").preset = "metric_mm"
@@ -70,7 +76,7 @@ def file_menu(self, context):
class BIM_PT_project(Panel):
- bl_label = "IFC Project"
+ bl_label = "Current Project"
bl_idname = "BIM_PT_project"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
@@ -209,7 +215,6 @@ class BIM_PT_project(Panel):
row = self.layout.row()
row.label(text="File Not Saved", icon="ERROR")
-
def draw_create_project_ui(self, context):
props = context.scene.BIMProperties
pprops = context.scene.BIMProjectProperties
@@ -226,11 +231,11 @@ class BIM_PT_project(Panel):
row = self.layout.row(align=True)
row.operator("bim.create_project")
- row.operator("bim.load_project").should_start_fresh_session = True
+ row.operator("bim.load_project").should_start_fresh_session = False
class BIM_PT_project_library(Panel):
- bl_label = "IFC Project Library"
+ bl_label = "Project Library"
bl_idname = "BIM_PT_project_library"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
@@ -272,7 +277,7 @@ class BIM_PT_project_library(Panel):
class BIM_PT_links(Panel):
- bl_label = "IFC Links"
+ bl_label = "Links"
bl_idname = "BIM_PT_links"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
diff --git a/src/blenderbim/blenderbim/bim/module/pset/ui.py b/src/blenderbim/blenderbim/bim/module/pset/ui.py
index 6eb1ab8419..56ff428e00 100644
--- a/src/blenderbim/blenderbim/bim/module/pset/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/pset/ui.py
@@ -170,12 +170,12 @@ def draw_psetqto_editable_ui(box, props, prop):
class BIM_PT_object_psets(Panel):
- bl_label = "IFC Object Property Sets"
+ bl_label = "Object Property Sets"
bl_idname = "BIM_PT_object_psets"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
- bl_parent_id = "BIM_PT_object_metadata"
+ bl_parent_id = "BIM_PT_tab_object_metadata"
@classmethod
def poll(cls, context):
@@ -212,12 +212,12 @@ class BIM_PT_object_psets(Panel):
class BIM_PT_object_qtos(Panel):
- bl_label = "IFC Object Quantity Sets"
+ bl_label = "Object Quantity Sets"
bl_idname = "BIM_PT_object_qtos"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
- bl_parent_id = "BIM_PT_object_metadata"
+ bl_parent_id = "BIM_PT_tab_object_metadata"
@classmethod
def poll(cls, context):
@@ -249,7 +249,7 @@ class BIM_PT_object_qtos(Panel):
class BIM_PT_material_psets(Panel):
- bl_label = "IFC Material Property Sets"
+ bl_label = "Material Property Sets"
bl_idname = "BIM_PT_material_psets"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
@@ -293,7 +293,7 @@ class BIM_PT_material_psets(Panel):
class BIM_PT_material_set_psets(Panel):
- bl_label = "IFC Material Set Property Sets"
+ bl_label = "Material Set Property Sets"
bl_idname = "BIM_PT_material_set_psets"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
@@ -330,7 +330,7 @@ class BIM_PT_material_set_psets(Panel):
class BIM_PT_material_set_item_psets(Panel):
- bl_label = "IFC Material Set Item Property Sets"
+ bl_label = "Material Set Item Property Sets"
bl_idname = "BIM_PT_material_set_item_psets"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
@@ -367,7 +367,7 @@ class BIM_PT_material_set_item_psets(Panel):
class BIM_PT_task_qtos(Panel):
- bl_label = "IFC Task Quantity Sets"
+ bl_label = "Task Quantity Sets"
bl_idname = "BIM_PT_task_qtos"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
@@ -403,7 +403,7 @@ class BIM_PT_task_qtos(Panel):
class BIM_PT_resource_qtos(Panel):
- bl_label = "IFC Resource Quantity Sets"
+ bl_label = "Resource Quantity Sets"
bl_idname = "BIM_PT_resource_qtos"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
@@ -437,7 +437,7 @@ class BIM_PT_resource_qtos(Panel):
class BIM_PT_resource_psets(Panel):
- bl_label = "IFC Resource Property Sets"
+ bl_label = "Resource Property Sets"
bl_idname = "BIM_PT_resource_psets"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
@@ -471,7 +471,7 @@ class BIM_PT_resource_psets(Panel):
class BIM_PT_profile_psets(Panel):
- bl_label = "IFC Profile Property Sets"
+ bl_label = "Profile Property Sets"
bl_idname = "BIM_PT_profile_psets"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
@@ -507,7 +507,7 @@ class BIM_PT_profile_psets(Panel):
class BIM_PT_work_schedule_psets(Panel):
- bl_label = "IFC Work Schedule Property Sets"
+ bl_label = "Work Schedule Property Sets"
bl_idname = "BIM_PT_work_schedule_psets"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
@@ -539,12 +539,12 @@ class BIM_PT_work_schedule_psets(Panel):
class BIM_PT_bulk_property_editor(Panel):
- bl_label = "IFC Bulk Property Editor"
+ bl_label = "Bulk Property Editor"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
bl_options = {"DEFAULT_CLOSED"}
- bl_parent_id = "BIM_PT_utilities_object"
+ bl_parent_id = "BIM_PT_tab_misc"
def draw(self, context):
pass
diff --git a/src/blenderbim/blenderbim/bim/module/pset_template/ui.py b/src/blenderbim/blenderbim/bim/module/pset_template/ui.py
index 324aee459d..969a355ed3 100644
--- a/src/blenderbim/blenderbim/bim/module/pset_template/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/pset_template/ui.py
@@ -23,7 +23,7 @@ from blenderbim.bim.module.pset_template.data import PsetTemplatesData
class BIM_PT_pset_template(Panel):
- bl_label = "IFC Property Set Templates"
+ bl_label = "Property Set Templates"
bl_idname = "BIM_PT_pset_template"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
diff --git a/src/blenderbim/blenderbim/bim/module/resource/ui.py b/src/blenderbim/blenderbim/bim/module/resource/ui.py
index 433c0e73e1..4ce36e12bf 100644
--- a/src/blenderbim/blenderbim/bim/module/resource/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/resource/ui.py
@@ -23,13 +23,13 @@ from blenderbim.bim.module.resource.data import ResourceData
class BIM_PT_resources(Panel):
- bl_label = "IFC Resources"
+ bl_label = "Resources"
bl_idname = "BIM_PT_resources"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
- bl_parent_id = "BIM_PT_4D5D"
+ bl_parent_id = "BIM_PT_tab_4D5D"
@classmethod
def poll(cls, context):
diff --git a/src/blenderbim/blenderbim/bim/module/root/data.py b/src/blenderbim/blenderbim/bim/module/root/data.py
index d77664249b..4bd5cecab7 100644
--- a/src/blenderbim/blenderbim/bim/module/root/data.py
+++ b/src/blenderbim/blenderbim/bim/module/root/data.py
@@ -21,6 +21,7 @@ import bpy
import ifcopenshell.util.element
from ifcopenshell.util.doc import get_entity_doc, get_predefined_type_doc
import blenderbim.tool as tool
+import blenderbim.bim.helper
from blenderbim.bim.ifc import IfcStore
@@ -70,7 +71,7 @@ class IfcClassData:
"IfcAnnotation",
"IfcRelSpaceBoundary",
]
- return [(e, e, (get_entity_doc(version, e) or {}).get("description", "")) for e in products]
+ return [(e, blenderbim.bim.helper.uncamel(e), (get_entity_doc(version, e) or {}).get("description", "")) for e in products]
@classmethod
def ifc_classes(cls):
@@ -86,7 +87,7 @@ class IfcClassData:
# Yeah, weird isn't it.
names.remove("IfcOpeningStandardCase")
version = tool.Ifc.get_schema()
- return [(c, c, (get_entity_doc(version, c) or {}).get("description", "")) for c in sorted(names)]
+ return [(c, blenderbim.bim.helper.uncamel(c), (get_entity_doc(version, c) or {}).get("description", "")) for c in sorted(names)]
@classmethod
def ifc_predefined_types(cls):
@@ -163,9 +164,10 @@ class IfcClassData:
if not element:
return
name = element.is_a()
+ name = blenderbim.bim.helper.uncamel(name)
predefined_type = ifcopenshell.util.element.get_predefined_type(element)
if predefined_type:
- name += f"[{predefined_type}]"
+ name += f" [{predefined_type}]"
return name
@classmethod
diff --git a/src/blenderbim/blenderbim/bim/module/root/ui.py b/src/blenderbim/blenderbim/bim/module/root/ui.py
index 2004f7ed08..76f5546594 100644
--- a/src/blenderbim/blenderbim/bim/module/root/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/root/ui.py
@@ -25,12 +25,13 @@ from blenderbim.bim.module.root.data import IfcClassData
class BIM_PT_class(Panel):
- bl_label = "IFC Class"
+ bl_label = "Class"
bl_idname = "BIM_PT_class"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
- bl_parent_id = "BIM_PT_object_metadata"
+ bl_parent_id = "BIM_PT_tab_object_metadata"
+ bl_options = {"HIDE_HEADER"}
@classmethod
def poll(cls, context):
diff --git a/src/blenderbim/blenderbim/bim/module/search/ui.py b/src/blenderbim/blenderbim/bim/module/search/ui.py
index b84c821cfa..fc9b016012 100644
--- a/src/blenderbim/blenderbim/bim/module/search/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/search/ui.py
@@ -22,7 +22,7 @@ from blenderbim.bim.ifc import IfcStore
class BIM_PT_search(Panel):
- bl_label = "IFC Search"
+ bl_label = "Search"
bl_idname = "BIM_PT_search"
# bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
@@ -109,7 +109,7 @@ class BIM_UL_ifc_building_storey_filter(bpy.types.UIList):
class BIM_PT_IFCSelector(Panel):
- bl_label = "IFC Selector"
+ bl_label = "Selector"
bl_idname = "BIM_PT_ifc_selector"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
diff --git a/src/blenderbim/blenderbim/bim/module/sequence/ui.py b/src/blenderbim/blenderbim/bim/module/sequence/ui.py
index 79e7191b6a..eb0ccc477d 100644
--- a/src/blenderbim/blenderbim/bim/module/sequence/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/sequence/ui.py
@@ -25,13 +25,13 @@ from blenderbim.bim.module.sequence.data import WorkPlansData, WorkScheduleData,
class BIM_PT_work_plans(Panel):
- bl_label = "IFC Work Plans"
+ bl_label = "Work Plans"
bl_idname = "BIM_PT_work_plans"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
- bl_parent_id = "BIM_PT_4D5D"
+ bl_parent_id = "BIM_PT_tab_4D5D"
@classmethod
def poll(cls, context):
@@ -97,13 +97,13 @@ class BIM_PT_work_plans(Panel):
class BIM_PT_work_schedules(Panel):
- bl_label = "IFC Work Schedules"
+ bl_label = "Work Schedules"
bl_idname = "BIM_PT_work_schedules"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
- bl_parent_id = "BIM_PT_4D5D"
+ bl_parent_id = "BIM_PT_tab_4D5D"
@classmethod
def poll(cls, context):
@@ -526,7 +526,7 @@ class BIM_PT_work_schedules(Panel):
class BIM_PT_task_icom(Panel):
- bl_label = "IFC Task ICOM"
+ bl_label = "Task ICOM"
bl_idname = "BIM_PT_task_icom"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
@@ -811,13 +811,13 @@ class BIM_UL_tasks(UIList):
class BIM_PT_work_calendars(Panel):
- bl_label = "IFC Work Calendars"
+ bl_label = "Work Calendars"
bl_idname = "BIM_PT_work_calendars"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
- bl_parent_id = "BIM_PT_4D5D"
+ bl_parent_id = "BIM_PT_tab_4D5D"
@classmethod
def poll(cls, context):
diff --git a/src/blenderbim/blenderbim/bim/module/spatial/data.py b/src/blenderbim/blenderbim/bim/module/spatial/data.py
index 9623045536..930a19b84a 100644
--- a/src/blenderbim/blenderbim/bim/module/spatial/data.py
+++ b/src/blenderbim/blenderbim/bim/module/spatial/data.py
@@ -43,7 +43,11 @@ class SpatialData:
@classmethod
def containers(cls):
results = {}
- for container in tool.Ifc.get().by_type("IfcSpatialElement"):
+ if tool.Ifc.get_schema() == "IFC2X3":
+ spatial_elements = tool.Ifc.get().by_type("IfcSpatialStructureElement")
+ else:
+ spatial_elements = tool.Ifc.get().by_type("IfcSpatialElement")
+ for container in spatial_elements:
results[container.id()] = {
"type": container.is_a(),
"id": container.id(),
diff --git a/src/blenderbim/blenderbim/bim/module/spatial/ui.py b/src/blenderbim/blenderbim/bim/module/spatial/ui.py
index e6d0c62432..8f73b18a4f 100644
--- a/src/blenderbim/blenderbim/bim/module/spatial/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/spatial/ui.py
@@ -23,12 +23,12 @@ import blenderbim.tool as tool
class BIM_PT_spatial(Panel):
- bl_label = "IFC Spatial Container"
+ bl_label = "Spatial Container"
bl_idname = "BIM_PT_spatial"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
- bl_parent_id = "BIM_PT_object_metadata"
+ bl_parent_id = "BIM_PT_tab_object_metadata"
@classmethod
def poll(cls, context):
@@ -72,7 +72,7 @@ class BIM_PT_spatial(Panel):
if SpatialData.data["is_directly_contained"]:
row.operator("bim.remove_container", icon="X", text="")
else:
- row.label(text="This object is not spatially contained")
+ row.label(text="No Spatial Container")
row.operator("bim.enable_editing_container", icon="GREASEPENCIL", text="")
for reference in SpatialData.data["references"]:
row = self.layout.row()
@@ -97,7 +97,7 @@ class BIM_UL_containers(UIList):
class BIM_PT_SpatialManager(Panel):
- bl_label = "IFC Spatial Manager"
+ bl_label = "Spatial Manager"
bl_idname = "BIM_PT_SpatialManager"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
diff --git a/src/blenderbim/blenderbim/bim/module/structural/ui.py b/src/blenderbim/blenderbim/bim/module/structural/ui.py
index bee2b9f25e..b828c124f4 100644
--- a/src/blenderbim/blenderbim/bim/module/structural/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/structural/ui.py
@@ -86,14 +86,14 @@ def draw_boundary_condition_read_only_ui(layout, boundary_condition):
class BIM_PT_structural_boundary_conditions(Panel):
- bl_label = "IFC Structural Boundary Conditions"
+ bl_label = "Structural Boundary Conditions"
bl_idname = "BIM_PT_structural_boundary_conditions"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
bl_order = 1
- bl_parent_id = "BIM_PT_misc_object"
+ bl_parent_id = "BIM_PT_tab_misc"
@classmethod
def poll(cls, context):
@@ -121,14 +121,14 @@ class BIM_PT_structural_boundary_conditions(Panel):
class BIM_PT_connected_structural_members(Panel):
- bl_label = "IFC Connected Structural Members"
+ bl_label = "Connected Structural Members"
bl_idname = "BIM_PT_connected_structural_members"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
bl_order = 1
- bl_parent_id = "BIM_PT_misc_object"
+ bl_parent_id = "BIM_PT_tab_misc"
@classmethod
def poll(cls, context):
@@ -174,14 +174,14 @@ class BIM_PT_connected_structural_members(Panel):
class BIM_PT_structural_member(Panel):
- bl_label = "IFC Structural Member"
+ bl_label = "Structural Member"
bl_idname = "BIM_PT_structural_member"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
bl_order = 1
- bl_parent_id = "BIM_PT_misc_object"
+ bl_parent_id = "BIM_PT_tab_misc"
@classmethod
def poll(cls, context):
@@ -217,14 +217,14 @@ class BIM_PT_structural_member(Panel):
class BIM_PT_structural_connection(Panel):
- bl_label = "IFC Structural Connection"
+ bl_label = "Structural Connection"
bl_idname = "BIM_PT_structural_connection"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
bl_order = 1
- bl_parent_id = "BIM_PT_misc_object"
+ bl_parent_id = "BIM_PT_tab_misc"
@classmethod
def poll(cls, context):
@@ -277,13 +277,13 @@ class BIM_PT_structural_connection(Panel):
class BIM_PT_structural_analysis_models(Panel):
- bl_label = "IFC Structural Analysis Models"
+ bl_label = "Structural Analysis Models"
bl_idname = "BIM_PT_structural_analysis_models"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
- bl_parent_id = "BIM_PT_structural"
+ bl_parent_id = "BIM_PT_tab_structural"
@classmethod
def poll(cls, context):
@@ -352,13 +352,13 @@ class BIM_UL_structural_analysis_models(UIList):
class BIM_PT_structural_load_cases(Panel):
- bl_label = "IFC Structural Load Cases"
+ bl_label = "Structural Load Cases"
bl_idname = "BIM_PT_structural_load_cases"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
- bl_parent_id = "BIM_PT_structural"
+ bl_parent_id = "BIM_PT_tab_structural"
@classmethod
def poll(cls, context):
@@ -448,13 +448,13 @@ class BIM_UL_structural_activities(UIList):
class BIM_PT_structural_loads(Panel):
- bl_label = "IFC Structural Loads"
+ bl_label = "Structural Loads"
bl_idname = "BIM_PT_structural_loads"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
- bl_parent_id = "BIM_PT_structural"
+ bl_parent_id = "BIM_PT_tab_structural"
@classmethod
def poll(cls, context):
@@ -518,13 +518,13 @@ class BIM_UL_structural_loads(UIList):
class BIM_PT_boundary_conditions(Panel):
- bl_label = "IFC Boundary Conditions"
+ bl_label = "Boundary Conditions"
bl_idname = "BIM_PT_boundary_conditions"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
- bl_parent_id = "BIM_PT_structural"
+ bl_parent_id = "BIM_PT_tab_structural"
@classmethod
def poll(cls, context):
diff --git a/src/blenderbim/blenderbim/bim/module/style/ui.py b/src/blenderbim/blenderbim/bim/module/style/ui.py
index 3ebbe20810..bb266d9542 100644
--- a/src/blenderbim/blenderbim/bim/module/style/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/style/ui.py
@@ -26,7 +26,7 @@ from blenderbim.tool.style import TEXTURE_MAPS_BY_METHODS, STYLE_TEXTURE_PROPS_M
class BIM_PT_styles(Panel):
- bl_label = "IFC Styles"
+ bl_label = "Styles"
bl_idname = "BIM_PT_styles"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
@@ -86,7 +86,7 @@ def draw_style_ui(self, context):
class BIM_PT_style(MaterialButtonsPanel, Panel):
- bl_label = "IFC Style"
+ bl_label = "Style"
bl_idname = "BIM_PT_style"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
@@ -111,7 +111,7 @@ class BIM_PT_style(MaterialButtonsPanel, Panel):
class BIM_PT_style_attributes(Panel):
- bl_label = "IFC Style Attributes"
+ bl_label = "Style Attributes"
bl_idname = "BIM_PT_style_attributes"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
@@ -159,7 +159,7 @@ class BIM_PT_style_attributes(Panel):
class BIM_PT_external_style_attributes(Panel):
- bl_label = "IFC External Surface Style"
+ bl_label = "External Surface Style"
bl_idname = "BIM_PT_external_style_attributes"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
@@ -226,7 +226,7 @@ class BIM_UL_styles(UIList):
class BIM_PT_STYLE_GRAPH(Panel):
bl_idname = "BIM_PT_style_graph"
bl_space_type = "NODE_EDITOR"
- bl_label = "IFC Style Graph Settings"
+ bl_label = "Style Graph Settings"
bl_region_type = "UI"
bl_category = "BBIM"
diff --git a/src/blenderbim/blenderbim/bim/module/system/ui.py b/src/blenderbim/blenderbim/bim/module/system/ui.py
index 5db712324f..9aea14b027 100644
--- a/src/blenderbim/blenderbim/bim/module/system/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/system/ui.py
@@ -24,13 +24,13 @@ from blenderbim.bim.module.system.data import SystemData, ObjectSystemData, Port
class BIM_PT_systems(Panel):
- bl_label = "IFC Systems"
+ bl_label = "Systems"
bl_idname = "BIM_PT_systems"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
- bl_parent_id = "BIM_PT_services"
+ bl_parent_id = "BIM_PT_tab_services"
@classmethod
def poll(cls, context):
@@ -74,14 +74,14 @@ class BIM_PT_systems(Panel):
class BIM_PT_object_systems(Panel):
- bl_label = "IFC Systems"
+ bl_label = "Systems"
bl_idname = "BIM_PT_object_systems"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
bl_order = 1
- bl_parent_id = "BIM_PT_services_object"
+ bl_parent_id = "BIM_PT_tab_services_object"
@classmethod
def poll(cls, context):
@@ -130,14 +130,14 @@ class BIM_PT_object_systems(Panel):
class BIM_PT_ports(Panel):
- bl_label = "IFC Ports"
+ bl_label = "Ports"
bl_idname = "BIM_PT_ports"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
bl_order = 1
- bl_parent_id = "BIM_PT_services_object"
+ bl_parent_id = "BIM_PT_tab_services_object"
@classmethod
def poll(cls, context):
@@ -163,14 +163,14 @@ class BIM_PT_ports(Panel):
class BIM_PT_port(Panel):
- bl_label = "IFC Port"
+ bl_label = "Port"
bl_idname = "BIM_PT_port"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
bl_order = 1
- bl_parent_id = "BIM_PT_services_object"
+ bl_parent_id = "BIM_PT_tab_services_object"
@classmethod
def poll(cls, context):
diff --git a/src/blenderbim/blenderbim/bim/module/tester/ui.py b/src/blenderbim/blenderbim/bim/module/tester/ui.py
index 1708774393..feaffa30aa 100644
--- a/src/blenderbim/blenderbim/bim/module/tester/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/tester/ui.py
@@ -28,7 +28,7 @@ class BIM_PT_tester(Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
- bl_parent_id = "BIM_PT_quality_control"
+ bl_parent_id = "BIM_PT_tab_quality_control"
def draw(self, context):
self.layout.use_property_split = True
diff --git a/src/blenderbim/blenderbim/bim/module/type/__init__.py b/src/blenderbim/blenderbim/bim/module/type/__init__.py
index 50accd37ea..d52d470062 100644
--- a/src/blenderbim/blenderbim/bim/module/type/__init__.py
+++ b/src/blenderbim/blenderbim/bim/module/type/__init__.py
@@ -27,6 +27,7 @@ classes = (
operator.EnableEditingType,
operator.PurgeUnusedTypes,
operator.RemoveType,
+ operator.RenameType,
operator.SelectSimilarType,
operator.SelectType,
operator.SelectTypeObjects,
diff --git a/src/blenderbim/blenderbim/bim/module/type/data.py b/src/blenderbim/blenderbim/bim/module/type/data.py
index 1d23c3363f..27a9ef5c13 100644
--- a/src/blenderbim/blenderbim/bim/module/type/data.py
+++ b/src/blenderbim/blenderbim/bim/module/type/data.py
@@ -56,6 +56,8 @@ class TypeData:
return []
version = tool.Ifc.get_schema()
types = ifcopenshell.util.type.get_applicable_types(element.is_a(), schema=version)
+ if element.is_a("IfcAnnotation"):
+ types.append("IfcTypeProduct")
results.extend((t, t, get_entity_doc(version, t).get("description", "")) for t in types)
return results
diff --git a/src/blenderbim/blenderbim/bim/module/type/operator.py b/src/blenderbim/blenderbim/bim/module/type/operator.py
index fda53d560a..4a0e9e06cd 100644
--- a/src/blenderbim/blenderbim/bim/module/type/operator.py
+++ b/src/blenderbim/blenderbim/bim/module/type/operator.py
@@ -436,7 +436,7 @@ class AddType(bpy.types.Operator, tool.Ifc.Operator):
class RemoveType(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.remove_type"
bl_label = "Remove Type"
- bl_options = {"REGISTER"}
+ bl_options = {"REGISTER", "UNDO"}
element: bpy.props.IntProperty()
def _execute(self, context):
@@ -446,13 +446,35 @@ class RemoveType(bpy.types.Operator, tool.Ifc.Operator):
if obj:
tool.Ifc.unlink(obj=obj)
bpy.data.objects.remove(obj)
- return {"FINISHED"}
+
+
+class RenameType(bpy.types.Operator, tool.Ifc.Operator):
+ bl_idname = "bim.rename_type"
+ bl_label = "Rename Type"
+ bl_options = {"REGISTER", "UNDO"}
+ element: bpy.props.IntProperty()
+ name: bpy.props.StringProperty(name="Name")
+
+ def _execute(self, context):
+ element = tool.Ifc.get().by_id(self.element)
+ obj = tool.Ifc.get_object(element)
+ element.Name = self.name
+ if obj:
+ tool.Root.set_object_name(obj, element)
+
+ def invoke(self, context, event):
+ element = tool.Ifc.get().by_id(self.element)
+ self.name = element.Name or "Unnamed"
+ return context.window_manager.invoke_props_dialog(self)
+
+ def draw(self, context):
+ self.layout.prop(self, "name")
class DuplicateType(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.duplicate_type"
bl_label = "Duplicate Type"
- bl_options = {"REGISTER"}
+ bl_options = {"REGISTER", "UNDO"}
element: bpy.props.IntProperty()
def _execute(self, context):
@@ -478,7 +500,7 @@ class DuplicateType(bpy.types.Operator, tool.Ifc.Operator):
class PurgeUnusedTypes(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.purge_unused_types"
bl_label = "Purge Unused Types"
- bl_options = {"REGISTER"}
+ bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
core.purge_unused_types(tool.Ifc, tool.Type)
diff --git a/src/blenderbim/blenderbim/bim/module/type/ui.py b/src/blenderbim/blenderbim/bim/module/type/ui.py
index d7feaf34bb..7be2c50897 100644
--- a/src/blenderbim/blenderbim/bim/module/type/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/type/ui.py
@@ -25,12 +25,12 @@ from blenderbim.bim.module.type.data import TypeData
class BIM_PT_type(Panel):
- bl_label = "IFC Type"
+ bl_label = "Type"
bl_idname = "BIM_PT_type"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
- bl_parent_id = "BIM_PT_object_metadata"
+ bl_parent_id = "BIM_PT_tab_object_metadata"
@classmethod
def poll(cls, context):
@@ -92,7 +92,7 @@ class BIM_PT_type(Panel):
row.operator("bim.enable_editing_type", icon="GREASEPENCIL", text="")
row.operator("bim.unassign_type", icon="X", text="")
else:
- row.label(text="This object has no type")
+ row.label(text="No Relating Type")
row.operator("bim.enable_editing_type", icon="GREASEPENCIL", text="")
diff --git a/src/blenderbim/blenderbim/bim/module/unit/ui.py b/src/blenderbim/blenderbim/bim/module/unit/ui.py
index 10a3682296..19d45eb825 100644
--- a/src/blenderbim/blenderbim/bim/module/unit/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/unit/ui.py
@@ -24,7 +24,7 @@ from blenderbim.bim.module.unit.data import UnitsData
class BIM_PT_units(Panel):
- bl_label = "IFC Units"
+ bl_label = "Units"
bl_idname = "BIM_PT_units"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
diff --git a/src/blenderbim/blenderbim/bim/module/void/ui.py b/src/blenderbim/blenderbim/bim/module/void/ui.py
index fd1020fac9..4197189a61 100644
--- a/src/blenderbim/blenderbim/bim/module/void/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/void/ui.py
@@ -23,14 +23,14 @@ from blenderbim.bim.module.void.data import BooleansData, VoidsData
class BIM_PT_voids(Panel):
- bl_label = "IFC Voids"
+ bl_label = "Voids"
bl_idname = "BIM_PT_voids"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
bl_order = 1
- bl_parent_id = "BIM_PT_geometry_object"
+ bl_parent_id = "BIM_PT_tab_geometric_relationships"
@classmethod
def poll(cls, context):
@@ -79,7 +79,7 @@ class BIM_PT_voids(Panel):
class BIM_PT_booleans(Panel):
- bl_label = "IFC Booleans"
+ bl_label = "Booleans"
bl_idname = "BIM_PT_booleans"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
@@ -109,8 +109,10 @@ class BIM_PT_booleans(Panel):
if context.active_object.data.BIMMeshProperties.ifc_definition_id:
row = layout.row(align=True)
row.label(text=f"{BooleansData.data['total_booleans']} Booleans Found")
- row.operator("bim.add_boolean", text="", icon="ADD")
- row.operator("bim.show_booleans", text="", icon="HIDE_OFF")
+ row.operator("bim.add_boolean", text="Apply Boolean", icon="ADD")
+ show_boolean_button = row.row(align=True)
+ show_boolean_button.operator("bim.show_booleans", text="", icon="HIDE_OFF")
+ show_boolean_button.enabled = BooleansData.data['total_booleans'] > 0
row.operator("bim.hide_booleans", text="", icon="HIDE_ON")
elif context.active_object.data.BIMMeshProperties.ifc_boolean_id:
row = layout.row()
diff --git a/src/blenderbim/blenderbim/bim/operator.py b/src/blenderbim/blenderbim/bim/operator.py
index 75a8875c5e..5e2fbe31ac 100644
--- a/src/blenderbim/blenderbim/bim/operator.py
+++ b/src/blenderbim/blenderbim/bim/operator.py
@@ -40,6 +40,21 @@ from mathutils import Vector, Matrix, Euler
from math import radians
+class SetTab(bpy.types.Operator):
+ bl_idname = "bim.set_tab"
+ bl_label = "Set Tab"
+ bl_options = {"REGISTER", "UNDO"}
+ bl_description = "Sets the current property tab"
+ tab: bpy.props.StringProperty()
+
+ def execute(self, context):
+ if context.area.spaces.active.search_filter:
+ return {"FINISHED"}
+ aprops = context.screen.BIMAreaProperties[context.screen.areas[:].index(context.area)]
+ aprops.tab = self.tab
+ return {"FINISHED"}
+
+
class SwitchTab(bpy.types.Operator):
bl_idname = "bim.switch_tab"
bl_label = "Switch Tab"
diff --git a/src/blenderbim/blenderbim/bim/prop.py b/src/blenderbim/blenderbim/bim/prop.py
index 9ca579413b..cf8c806b38 100644
--- a/src/blenderbim/blenderbim/bim/prop.py
+++ b/src/blenderbim/blenderbim/bim/prop.py
@@ -345,13 +345,13 @@ def get_tab(self, context):
return [
("PROJECT", "Project Overview", "", blenderbim.bim.icons["IFC"].icon_id, 0),
("OBJECT", "Object Information", "", "FILE_3D", 1),
- ("MATERIALS", "Materials and Styles", "", "MATERIAL", 2),
+ ("GEOMETRY", "Geometry and Materials", "", "MATERIAL", 2),
("DRAWINGS", "Drawings and Documents", "", "DOCUMENTS", 3),
("SERVICES", "Services and Systems", "", "NETWORK_DRIVE", 4),
("STRUCTURE", "Structural Analysis", "", "EDITMODE_HLT", 5),
("SCHEDULING", "Construction Scheduling", "", "NLA", 6),
("FM", "Facility Management", "", "PACKAGE", 7),
- ("OTHER", "Other Utilities", "", "COLLAPSEMENU", 8),
+ ("QUALITY", "Quality and Coordination", "", "COMMUNITY", 8),
("BLENDER", "Blender Properties", "", "BLENDER", 9),
]
@@ -359,7 +359,9 @@ def get_tab(self, context):
class BIMAreaProperties(PropertyGroup):
tab: EnumProperty(default=0, items=get_tab, name="Tab", update=update_tab)
previous_tab: StringProperty(default="PROJECT", name="Previous Tab")
- alt_tab: StringProperty(default="PROJECT", name="Alt Tab")
+ alt_tab: StringProperty(default="OBJECT", name="Alt Tab")
+ active_tab: BoolProperty(default=True, name="Active Tab")
+ inactive_tab: BoolProperty(default=False, name="Inactive Tab")
class BIMProperties(PropertyGroup):
diff --git a/src/blenderbim/blenderbim/bim/ui.py b/src/blenderbim/blenderbim/bim/ui.py
index 3b6f3f2b45..f320450ed5 100644
--- a/src/blenderbim/blenderbim/bim/ui.py
+++ b/src/blenderbim/blenderbim/bim/ui.py
@@ -270,7 +270,7 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
# Scene panel groups
-class BIM_PT_root(Panel):
+class BIM_PT_tabs(Panel):
bl_label = "BlenderBIM Add-on"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
@@ -281,15 +281,52 @@ class BIM_PT_root(Panel):
def draw(self, context):
try:
aprops = context.screen.BIMAreaProperties[context.screen.areas[:].index(context.area)]
+
+ row = self.layout.row()
+ row.operator(
+ "bim.set_tab", text="", emboss=False, icon_value=blenderbim.bim.icons["IFC"].icon_id
+ ).tab = "PROJECT"
+ row.operator("bim.set_tab", text="", emboss=False, icon="FILE_3D").tab = "OBJECT"
+ row.operator("bim.set_tab", text="", emboss=False, icon="MATERIAL").tab = "GEOMETRY"
+ row.operator("bim.set_tab", text="", emboss=False, icon="DOCUMENTS").tab = "DRAWINGS"
+ row.operator("bim.set_tab", text="", emboss=False, icon="NETWORK_DRIVE").tab = "SERVICES"
+ row.operator("bim.set_tab", text="", emboss=False, icon="EDITMODE_HLT").tab = "STRUCTURE"
+ row.operator("bim.set_tab", text="", emboss=False, icon="NLA").tab = "SCHEDULING"
+ row.operator("bim.set_tab", text="", emboss=False, icon="PACKAGE").tab = "FM"
+ row.operator("bim.set_tab", text="", emboss=False, icon="COMMUNITY").tab = "QUALITY"
+ row.operator("bim.set_tab", text="", emboss=False, icon="BLENDER").tab = "BLENDER"
+ row.operator("bim.switch_tab", text="", emboss=False, icon="UV_SYNC_SELECT")
+
+ # Yes, that's right.
+ row = self.layout.row()
+ row.scale_y = 0.2
+ for tab in [
+ "PROJECT",
+ "OBJECT",
+ "GEOMETRY",
+ "DRAWINGS",
+ "SERVICES",
+ "STRUCTURE",
+ "SCHEDULING",
+ "FM",
+ "QUALITY",
+ "BLENDER",
+ "SWITCH",
+ ]:
+ if aprops.tab == tab:
+ row.prop(aprops, "active_tab", text="", icon="BLANK1")
+ else:
+ row.prop(aprops, "inactive_tab", text="", icon="BLANK1", emboss=False)
+
+ aprops = context.screen.BIMAreaProperties[context.screen.areas[:].index(context.area)]
row = self.layout.row(align=True)
row.prop(aprops, "tab", text="")
- row.operator("bim.switch_tab", text="", icon="UV_SYNC_SELECT")
except:
pass # Prior to load_post, we may not have any area properties setup
class BIM_PT_project_info(Panel):
- bl_label = "IFC Project Info"
+ bl_label = "Project Info"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
@@ -303,7 +340,7 @@ class BIM_PT_project_info(Panel):
class BIM_PT_project_setup(Panel):
- bl_label = "IFC Project Setup"
+ bl_label = "Project Setup"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
@@ -317,23 +354,22 @@ class BIM_PT_project_setup(Panel):
pass
-class BIM_PT_collaboration(Panel):
- bl_label = "IFC Collaboration"
+class BIM_PT_tab_collaboration(Panel):
+ bl_label = "Collaboration"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
- bl_options = {"DEFAULT_CLOSED"}
@classmethod
def poll(cls, context):
- return tool.Blender.is_tab(context, "OTHER")
+ return tool.Blender.is_tab(context, "QUALITY")
def draw(self, context):
pass
class BIM_PT_selection(Panel):
- bl_label = "IFC Selection"
+ bl_label = "Selection"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
@@ -348,11 +384,10 @@ class BIM_PT_selection(Panel):
class BIM_PT_geometry(Panel):
- bl_label = "IFC Geometry"
+ bl_label = "Geometry"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
- bl_options = {"DEFAULT_CLOSED"}
@classmethod
def poll(cls, context):
@@ -362,12 +397,11 @@ class BIM_PT_geometry(Panel):
pass
-class BIM_PT_4D5D(Panel):
- bl_label = "IFC Costing and Scheduling"
+class BIM_PT_tab_4D5D(Panel):
+ bl_label = "Costing and Scheduling"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
- bl_options = {"DEFAULT_CLOSED"}
@classmethod
def poll(cls, context):
@@ -377,12 +411,11 @@ class BIM_PT_4D5D(Panel):
pass
-class BIM_PT_structural(Panel):
- bl_label = "IFC Structural"
+class BIM_PT_tab_structural(Panel):
+ bl_label = "Structural"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
- bl_options = {"DEFAULT_CLOSED"}
@classmethod
def poll(cls, context):
@@ -392,12 +425,11 @@ class BIM_PT_structural(Panel):
pass
-class BIM_PT_services(Panel):
- bl_label = "IFC Services"
+class BIM_PT_tab_services(Panel):
+ bl_label = "Services"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
- bl_options = {"DEFAULT_CLOSED"}
@classmethod
def poll(cls, context):
@@ -407,22 +439,21 @@ class BIM_PT_services(Panel):
pass
-class BIM_PT_quality_control(Panel):
- bl_label = "IFC Quality Control"
+class BIM_PT_tab_quality_control(Panel):
+ bl_label = "Quality Control"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
- bl_options = {"DEFAULT_CLOSED"}
@classmethod
def poll(cls, context):
- return tool.Blender.is_tab(context, "OTHER")
+ return tool.Blender.is_tab(context, "QUALITY")
def draw(self, context):
pass
-class BIM_PT_integrations(Panel):
+class BIM_PT_tab_integrations(Panel):
bl_label = "BIM Integrations"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
@@ -431,87 +462,161 @@ class BIM_PT_integrations(Panel):
@classmethod
def poll(cls, context):
- return tool.Blender.is_tab(context, "OTHER")
+ return tool.Blender.is_tab(context, "QUALITY")
def draw(self, context):
pass
# Object panel groups
-class BIM_PT_object_metadata(Panel):
- bl_label = "IFC Object Metadata"
+class BIM_PT_tab_object_metadata(Panel):
+ bl_label = "Object Metadata"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
- bl_context = "object"
+ bl_context = "scene"
bl_order = 1
@classmethod
def poll(cls, context):
- return tool.Ifc.get()
+ return tool.Blender.is_tab(context, "OBJECT") and tool.Ifc.get()
def draw(self, context):
pass
-class BIM_PT_geometry_object(Panel):
- bl_label = "IFC Geometry"
+class BIM_PT_tab_representations(Panel):
+ bl_label = "Representations"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
- bl_context = "object"
+ bl_context = "scene"
bl_order = 1
- bl_options = {"DEFAULT_CLOSED"}
@classmethod
def poll(cls, context):
- return tool.Ifc.get()
+ return tool.Blender.is_tab(context, "GEOMETRY") and tool.Ifc.get()
def draw(self, context):
pass
-class BIM_PT_services_object(Panel):
- bl_label = "IFC Services"
+class BIM_PT_tab_geometric_relationships(Panel):
+ bl_label = "Geometric Relationships"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
- bl_context = "object"
+ bl_context = "scene"
bl_order = 1
bl_options = {"DEFAULT_CLOSED"}
@classmethod
def poll(cls, context):
- return tool.Ifc.get()
+ return tool.Blender.is_tab(context, "GEOMETRY") and tool.Ifc.get()
def draw(self, context):
pass
-class BIM_PT_utilities_object(Panel):
- bl_label = "IFC Utilities"
+class BIM_PT_tab_parametric_geometry(Panel):
+ bl_label = "Parametric Geometry"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
- bl_context = "object"
+ bl_context = "scene"
bl_order = 1
bl_options = {"DEFAULT_CLOSED"}
@classmethod
def poll(cls, context):
- return tool.Ifc.get()
+ return tool.Blender.is_tab(context, "GEOMETRY") and tool.Ifc.get()
def draw(self, context):
pass
-class BIM_PT_misc_object(Panel):
- bl_label = "IFC Misc."
+class BIM_PT_tab_materials(Panel):
+ bl_label = "Materials"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
- bl_context = "object"
+ bl_context = "scene"
+ bl_order = 1
+
+ @classmethod
+ def poll(cls, context):
+ return tool.Blender.is_tab(context, "GEOMETRY") and tool.Ifc.get()
+
+ def draw(self, context):
+ pass
+
+
+class BIM_PT_tab_styles(Panel):
+ bl_label = "Styles"
+ bl_space_type = "PROPERTIES"
+ bl_region_type = "WINDOW"
+ bl_context = "scene"
+ bl_order = 1
+
+ @classmethod
+ def poll(cls, context):
+ return tool.Blender.is_tab(context, "GEOMETRY") and tool.Ifc.get()
+
+ def draw(self, context):
+ pass
+
+
+class BIM_PT_tab_services_object(Panel):
+ bl_label = "Services"
+ bl_space_type = "PROPERTIES"
+ bl_region_type = "WINDOW"
+ bl_context = "scene"
+ bl_order = 1
+
+ @classmethod
+ def poll(cls, context):
+ return tool.Blender.is_tab(context, "SERVICES") and tool.Ifc.get()
+
+ def draw(self, context):
+ pass
+
+
+class BIM_PT_tab_misc(Panel):
+ bl_label = "Misc."
+ bl_space_type = "PROPERTIES"
+ bl_region_type = "WINDOW"
+ bl_context = "scene"
bl_order = 1
bl_options = {"DEFAULT_CLOSED"}
@classmethod
def poll(cls, context):
- return tool.Ifc.get()
+ return tool.Blender.is_tab(context, "OBJECT") and tool.Ifc.get()
+
+ def draw(self, context):
+ pass
+
+
+class BIM_PT_tab_handover(Panel):
+ bl_label = "Commissioning and Handover"
+ bl_space_type = "PROPERTIES"
+ bl_region_type = "WINDOW"
+ bl_context = "scene"
+ bl_order = 1
+
+ @classmethod
+ def poll(cls, context):
+ return tool.Blender.is_tab(context, "FM") and tool.Ifc.get()
+
+ def draw(self, context):
+ pass
+
+
+class BIM_PT_tab_operations(Panel):
+ bl_label = "Operations and Maintenance"
+ bl_space_type = "PROPERTIES"
+ bl_region_type = "WINDOW"
+ bl_context = "scene"
+ bl_order = 2
+
+ @classmethod
+ def poll(cls, context):
+ return tool.Blender.is_tab(context, "FM") and tool.Ifc.get()
def draw(self, context):
pass
diff --git a/src/blenderbim/blenderbim/core/drawing.py b/src/blenderbim/blenderbim/core/drawing.py
index ca6f2b9b33..12bfcf0950 100644
--- a/src/blenderbim/blenderbim/core/drawing.py
+++ b/src/blenderbim/blenderbim/core/drawing.py
@@ -339,6 +339,9 @@ def update_drawing_name(ifc, drawing_tool, drawing=None, name=None):
if drawing_tool.does_file_exist(uri):
drawing_tool.update_embedded_svg_location(uri, old_location, new_location)
+ if drawing_tool.is_editing_sheets():
+ drawing_tool.import_sheets()
+
def add_annotation(ifc, collector, drawing_tool, drawing=None, object_type=None):
context = drawing_tool.get_annotation_context(
diff --git a/src/blenderbim/blenderbim/core/geometry.py b/src/blenderbim/blenderbim/core/geometry.py
index 01dcde0555..b25ec4703a 100644
--- a/src/blenderbim/blenderbim/core/geometry.py
+++ b/src/blenderbim/blenderbim/core/geometry.py
@@ -88,7 +88,15 @@ def switch_representation(
should_sync_changes_first=False,
apply_openings=True,
):
- """Function can switch to representation that wasn't yet assigned to that object. See #2766."""
+ """Function can switch to representation that wasn't yet assigned to that object. See #2766.
+
+ `should_sync_changes_first` - sync ifc representation with current state of `obj.data`;
+
+ `should_reload` - reload `obj.data` from ifc representation;
+
+ `is_global` - replace mesh data for all users of `obj.data`, not just `obj`;
+
+ """
if should_sync_changes_first and geometry.is_edited(obj) and not geometry.is_box_representation(representation):
representation_id = geometry.get_representation_id(representation)
geometry.run_geometry_update_representation(obj=obj)
diff --git a/src/blenderbim/blenderbim/core/tool.py b/src/blenderbim/blenderbim/core/tool.py
index 25fa289a5d..7520251c62 100644
--- a/src/blenderbim/blenderbim/core/tool.py
+++ b/src/blenderbim/blenderbim/core/tool.py
@@ -252,16 +252,16 @@ class Drawing:
def delete_object(cls, obj): pass
def disable_editing_assigned_product(cls, obj): pass
def disable_editing_drawings(cls): pass
- def disable_editing_schedules(cls): pass
def disable_editing_references(cls): pass
+ def disable_editing_schedules(cls): pass
def disable_editing_sheets(cls): pass
def disable_editing_text(cls, obj): pass
def does_file_exist(cls, uri): pass
def enable_editing(cls, obj): pass
def enable_editing_assigned_product(cls, obj): pass
def enable_editing_drawings(cls): pass
- def enable_editing_schedules(cls): pass
def enable_editing_references(cls): pass
+ def enable_editing_schedules(cls): pass
def enable_editing_sheets(cls): pass
def enable_editing_text(cls, obj): pass
def ensure_unique_drawing_name(cls, name): pass
@@ -297,13 +297,14 @@ class Drawing:
def get_text_literal(cls, obj): pass
def get_unit_system(cls): pass
def import_assigned_product(cls, obj): pass
- def import_drawings(cls): pass
def import_documents(cls, document_type): pass
+ def import_drawings(cls): pass
def import_sheets(cls): pass
def import_text_attributes(cls, obj): pass
def is_active_drawing(cls, drawing): pass
def is_camera_orthographic(cls): pass
def is_drawing_active(cls): pass
+ def is_editing_sheets(cls): pass
def move_file(cls, src, dest): pass
def open_spreadsheet(cls, uri): pass
def open_svg(cls, filepath): pass
diff --git a/src/blenderbim/blenderbim/tool/blender.py b/src/blenderbim/blenderbim/tool/blender.py
index e315341eaa..c6f08fe548 100644
--- a/src/blenderbim/blenderbim/tool/blender.py
+++ b/src/blenderbim/blenderbim/tool/blender.py
@@ -398,12 +398,12 @@ class Blender:
)
bmesh.update_edit_mesh(mesh)
if not obj:
- if not bpy.context.object or bpy.context.object.data != mesh:
+ if not bpy.context.active_object or bpy.context.active_object.data != mesh:
raise Exception(
"Error applying bmesh in EDIT object - object is "
"not provided and can't be acquired from the context. "
)
- obj = bpy.context.object
+ obj = bpy.context.active_object
obj.update_from_editmode()
else:
bm.to_mesh(mesh)
@@ -456,3 +456,32 @@ class Blender:
return bpy.ops.object.mode_set(mode="EDIT_GPENCIL", toggle=True)
else:
return {"CANCELLED"}
+
+ class Modifier:
+ @classmethod
+ def is_railing(cls, element):
+ return tool.Pset.get_element_pset(element, "BBIM_Railing")
+
+ @classmethod
+ def is_roof(cls, element):
+ return tool.Pset.get_element_pset(element, "BBIM_Roof")
+
+ @classmethod
+ def is_window(cls, element):
+ return tool.Pset.get_element_pset(element, "BBIM_Window")
+
+ @classmethod
+ def is_door(cls, element):
+ return tool.Pset.get_element_pset(element, "BBIM_Door")
+
+ @classmethod
+ def is_stair(cls, element):
+ return tool.Pset.get_element_pset(element, "BBIM_Stair")
+
+ @classmethod
+ def is_editing_parameters(cls, obj):
+ return obj.BIMRailingProperties.is_editing or obj.BIMRoofProperties.is_editing
+
+ @classmethod
+ def is_modifier_with_non_editable_path(cls, element):
+ return cls.is_stair(element) or cls.is_door(element) or cls.is_window(element)
diff --git a/src/blenderbim/blenderbim/tool/drawing.py b/src/blenderbim/blenderbim/tool/drawing.py
index 0f179915d6..9d3bb0f884 100644
--- a/src/blenderbim/blenderbim/tool/drawing.py
+++ b/src/blenderbim/blenderbim/tool/drawing.py
@@ -24,6 +24,8 @@ import lark
import bmesh
import shutil
import logging
+import shapely
+from shapely.ops import unary_union
import mathutils
import webbrowser
import subprocess
@@ -97,6 +99,8 @@ class Drawing(blenderbim.core.tool.Drawing):
current_pos = camera.matrix_world @ current_pos
obj.location = current_pos
+ ANNOTATION_TYPES_SUPPORT_SETUP = ("STAIR_ARROW", "TEXT", "REVISION_CLOUD", "FILL_AREA")
+
@classmethod
def setup_annotation_object(cls, obj, object_type, related_object=None):
"""Finish object's adjustments after both object and entity are created"""
@@ -135,6 +139,30 @@ class Drawing(blenderbim.core.tool.Drawing):
cls.ensure_annotation_in_drawing_plane(obj)
assign_product = True
+ elif object_type == "REVISION_CLOUD":
+ revised_object, cloud = related_object, obj
+
+ verts = [np.array(revised_object.matrix_world @ v.co) for v in revised_object.data.vertices]
+ verts = [(np.around(v[[0, 1]], decimals=3)).tolist() for v in verts]
+ edges = [e.vertices for e in revised_object.data.edges]
+
+ # shapely magic
+ boundary_lines = [shapely.LineString([verts[v] for v in e]) for e in edges]
+ unioned_boundaries = shapely.union_all(shapely.GeometryCollection(boundary_lines))
+ all_polygons = shapely.polygonize(unioned_boundaries.geoms).geoms
+ outer_shell = unary_union(all_polygons)
+
+ bm = tool.Blender.get_bmesh_for_mesh(obj.data, clean=True)
+ new_verts = list(outer_shell.exterior.coords)
+ bm_verts = [bm.verts.new(v + (0,)) for v in new_verts]
+ bm_edges = [bm.edges.new([bm_verts[i], bm_verts[i + 1]]) for i in range(len(new_verts) - 1)]
+ bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.0001)
+
+ tool.Blender.apply_bmesh(obj.data, bm, obj)
+ cloud.location = Vector((0, 0, 0))
+ cls.ensure_annotation_in_drawing_plane(cloud)
+ assign_product = True
+
if assign_product and not cls.get_assigned_product(obj_entity):
tool.Ifc.run("drawing.assign_product", relating_product=related_entity, related_object=obj_entity)
@@ -466,6 +494,10 @@ class Drawing(blenderbim.core.tool.Drawing):
return items
return items[0]
+ @classmethod
+ def is_editing_sheets(cls):
+ return bpy.context.scene.DocProperties.is_editing_sheets
+
@classmethod
def remove_literal_from_annotation(cls, obj, literal):
element = tool.Ifc.get_entity(obj)
diff --git a/src/blenderbim/blenderbim/tool/geometry.py b/src/blenderbim/blenderbim/tool/geometry.py
index 5e13ab5dd2..5369741dbb 100644
--- a/src/blenderbim/blenderbim/tool/geometry.py
+++ b/src/blenderbim/blenderbim/tool/geometry.py
@@ -35,6 +35,8 @@ class Geometry(blenderbim.core.tool.Geometry):
@classmethod
def change_object_data(cls, obj, data, is_global=False):
if is_global:
+ if obj.mode == "EDIT":
+ raise Exception("user_remap is not supported in EDIT mode")
obj.data.user_remap(data)
else:
obj.data = data
@@ -75,6 +77,7 @@ class Geometry(blenderbim.core.tool.Geometry):
if element.is_a("IfcRelSpaceBoundary"):
ifcopenshell.api.run("boundary.remove_boundary", tool.Ifc.get(), boundary=element)
return bpy.data.objects.remove(obj)
+
collection = obj.BIMObjectProperties.collection
if collection:
parent = ifcopenshell.util.element.get_aggregate(element)
@@ -98,7 +101,7 @@ class Geometry(blenderbim.core.tool.Geometry):
if element.VoidsElements:
bpy.ops.bim.remove_opening(opening_id=element.id())
else:
- is_spatial = element.is_a("IfcSpatialElement")
+ is_spatial = element.is_a() in ("IfcSpatialElement", "IfcSpatialStructureElement")
if getattr(element, "HasOpenings", None):
for rel in element.HasOpenings:
bpy.ops.bim.remove_opening(opening_id=rel.RelatedOpeningElement.id())
diff --git a/src/blenderbim/blenderbim/tool/ifc.py b/src/blenderbim/blenderbim/tool/ifc.py
index 36ab2f22c5..c6050492e3 100644
--- a/src/blenderbim/blenderbim/tool/ifc.py
+++ b/src/blenderbim/blenderbim/tool/ifc.py
@@ -21,6 +21,8 @@ import bpy
import numpy as np
import ifcopenshell.api
import blenderbim.core.tool
+import blenderbim.bim.handler
+import blenderbim.tool as tool
from blenderbim.bim.ifc import IfcStore
@@ -88,6 +90,54 @@ class Ifc(blenderbim.core.tool.Ifc):
def get_object(cls, element):
return IfcStore.get_element(element.id())
+ @classmethod
+ def rebuild_element_maps(cls):
+ """Rebuilds the id_map and guid_map
+
+ When any Blender object is stored outside a Blender PointerProperty,
+ such as in a regular Python list, there is the likely probability that
+ the object will be invalidated when undo or redo occurs. Object
+ invalidation seems to occur whenever an object is affected during an
+ operation, or selected, or has a related modifier, and so on ... to
+ cover all bases, this completely rebuilds the element maps.
+ """
+
+ IfcStore.id_map = {}
+ IfcStore.guid_map = {}
+
+ if not cls.get():
+ return
+
+ for obj in bpy.data.objects:
+ bpy.msgbus.clear_by_owner(obj)
+
+ element = cls.get_entity(obj)
+ if not element:
+ continue
+ IfcStore.id_map[element.id()] = obj
+ global_id = getattr(element, "GlobalId", None)
+ if global_id:
+ IfcStore.guid_map[global_id] = obj
+
+ blenderbim.bim.handler.subscribe_to(obj, "name", blenderbim.bim.handler.name_callback)
+ blenderbim.bim.handler.subscribe_to(obj, "mode", blenderbim.bim.handler.mode_callback)
+ blenderbim.bim.handler.subscribe_to(
+ obj, "active_material_index", blenderbim.bim.handler.active_material_index_callback
+ )
+
+ for obj in bpy.data.materials:
+ bpy.msgbus.clear_by_owner(obj)
+
+ material = cls.get_entity(obj)
+ style = tool.Style.get_style(obj)
+ if material:
+ IfcStore.id_map[material.id()] = obj
+ if style:
+ IfcStore.id_map[style.id()] = obj
+
+ blenderbim.bim.handler.subscribe_to(obj, "name", blenderbim.bim.handler.name_callback)
+ blenderbim.bim.handler.subscribe_to(obj, "diffuse_color", blenderbim.bim.handler.color_callback)
+
@classmethod
def link(cls, element, obj):
IfcStore.link_element(element, obj)
diff --git a/src/blenderbim/blenderbim/tool/root.py b/src/blenderbim/blenderbim/tool/root.py
index fb7eaa4e51..b7424ff3bc 100644
--- a/src/blenderbim/blenderbim/tool/root.py
+++ b/src/blenderbim/blenderbim/tool/root.py
@@ -111,8 +111,12 @@ class Root(blenderbim.core.tool.Root):
if obj.data and obj.data.BIMMeshProperties.ifc_definition_id:
return tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id)
element = tool.Ifc.get_entity(obj)
- if not obj.data and getattr(element, "ObjectType", None) == "TEXT":
- return element.Representation.Representations[0]
+ if element.is_a("IfcTypeProduct"):
+ if element.RepresentationMaps:
+ return element.RepresentationMaps[0].MappedRepresentation
+ elif element.is_a("IfcProduct"):
+ if element.Representation:
+ return element.Representation.Representations[0]
@classmethod
def get_representation_context(cls, representation):
@@ -157,8 +161,5 @@ class Root(blenderbim.core.tool.Root):
@classmethod
def set_object_name(cls, obj, element):
- name = obj.name
- if "/" in name and name.split("/")[0][0:3] == "Ifc":
- name = "/".join(name.split("/")[1:])
- name = "{}/{}".format(element.is_a(), name)
- obj.name = name
+ name = getattr(element, "Name", getattr(element, "AxisTag", None))
+ obj.name = "{}/{}".format(element.is_a(), name or "Unnamed")
diff --git a/src/blenderbim/blenderbim/tool/spatial.py b/src/blenderbim/blenderbim/tool/spatial.py
index 95369338fd..75f5f20110 100644
--- a/src/blenderbim/blenderbim/tool/spatial.py
+++ b/src/blenderbim/blenderbim/tool/spatial.py
@@ -52,7 +52,7 @@ class Spatial(blenderbim.core.tool.Spatial):
if not structure.is_a("IfcSpatialStructureElement"):
return False
else:
- if not structure.is_a("IfcSpatialElement"):
+ if not structure.is_a() in ("IfcSpatialElement", "IfcSpatialStructureElement"):
return False
if not hasattr(element, "ReferencedInStructures"):
return False
@@ -204,7 +204,7 @@ class Spatial(blenderbim.core.tool.Spatial):
parent = tool.Ifc.get().by_type("IfcProject")[0]
for object in ifcopenshell.util.element.get_parts(parent) or []:
- if object.is_a("IfcSpatialElement"):
+ if object.is_a() in ("IfcSpatialElement", "IfcSpatialStructureElement"):
cls.create_new_storey_li(object, 0)
cls.props.is_container_update_enabled = True
@@ -223,7 +223,7 @@ class Spatial(blenderbim.core.tool.Spatial):
new.has_children = True
if new.is_expanded:
for related_object in ifcopenshell.util.element.get_parts(element) or []:
- if related_object.is_a("IfcSpatialElement"):
+ if related_object.is_a() in ("IfcSpatialElement", "IfcSpatialStructureElement"):
cls.create_new_storey_li(related_object, level_index + 1)
@classmethod
diff --git a/src/blenderbim/docs/devs/writing_docs.rst b/src/blenderbim/docs/devs/writing_docs.rst
index 87374de599..c5be24d5b2 100644
--- a/src/blenderbim/docs/devs/writing_docs.rst
+++ b/src/blenderbim/docs/devs/writing_docs.rst
@@ -15,7 +15,48 @@ All documentation is written in ReStructured Text and is available in the
The following colours and annotation styles should be used for annotating
images. All stroke widths are 3px with a corner radius of 3px. Horizontal
-underlines are 5px with a corner radius of 2px. The dark green is **39b54a** and
-the light green is **d9e021**.
+underlines are 5px with a corner radius of 2px. The dark green is ``39b54a`` and
+the light green is ``d9e021``.
.. image:: documentation-style.png
+
+Special keywords such as **Technical Terminology** that the user should be
+aware of should be bolded, titlecased, and used consistently. You *may*
+use italics to emphasize words or phrases. Inline code must be ``quoted`` and
+longer code snippets may use code blocks.
+
+.. code-block:: console
+
+ $ cd /path/to/blenderbim
+ $ ls
+
+Be sure to specify the language to enable syntax highlighting.
+
+.. code-block:: python
+
+ print("Hello, world!")
+
+A button may be used to point users to a critical sample file or
+download.
+
+.. container:: blockbutton
+
+ `Visit critical link `__
+
+.. note::
+
+ Instead of writing "Note that XYZ ..." you should use notes sparingly to
+ highlight "gotchas".
+
+.. tip::
+
+ Tips may be used to add a useful but optional suggestion.
+
+.. warning::
+
+ Warnings may be used to highlight common mistakes.
+
+.. seealso::
+
+ See also blocks should be used to reference `further reading
+ `__ links.
diff --git a/src/blenderbim/scripts/geonodes_modifier_prototype.py b/src/blenderbim/scripts/geonodes_modifier_prototype.py
index f536b046f1..77b7b660a6 100644
--- a/src/blenderbim/scripts/geonodes_modifier_prototype.py
+++ b/src/blenderbim/scripts/geonodes_modifier_prototype.py
@@ -106,7 +106,7 @@ def update_geonodes_modifier():
bm.edges.index_update()
bm.faces.index_update()
- if bpy.context.object.mode == "EDIT":
+ if bpy.context.active_object.mode == "EDIT":
bmesh.update_edit_mesh(obj.data)
else:
bm.to_mesh(obj.data)
diff --git a/src/blenderbim/test/bim/feature/pset.feature b/src/blenderbim/test/bim/feature/pset.feature
index c7ad1db8c9..b4c87ffb1f 100644
--- a/src/blenderbim/test/bim/feature/pset.feature
+++ b/src/blenderbim/test/bim/feature/pset.feature
@@ -347,7 +347,7 @@ Scenario: Remove pset - multiple objects
Scenario: Edit pset length property
Given an empty IFC project
And I press "mesh.add_clever_stair"
- And the variable "pset" is "tool.Pset.get_element_pset(tool.Ifc.get_entity(bpy.context.object), 'Pset_StairFlightCommon').id()"
+ And the variable "pset" is "tool.Pset.get_element_pset(tool.Ifc.get_entity(bpy.context.active_object), 'Pset_StairFlightCommon').id()"
And the variable "si_conversion" is "ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())"
And I press "bim.enable_pset_editing(pset_id={pset}, obj='IfcStairFlight/StairFlight', obj_type='Object')"
@@ -380,7 +380,7 @@ Scenario: Edit qset length property
Given an empty IFC project
And I press "mesh.add_clever_stair"
And I press "bim.calculate_all_quantities"
- And the variable "pset" is "tool.Pset.get_element_pset(tool.Ifc.get_entity(bpy.context.object), 'Qto_StairFlightBaseQuantities').id()"
+ And the variable "pset" is "tool.Pset.get_element_pset(tool.Ifc.get_entity(bpy.context.active_object), 'Qto_StairFlightBaseQuantities').id()"
And the variable "si_conversion" is "ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())"
And I press "bim.enable_pset_editing(pset_id={pset}, obj='IfcStairFlight/StairFlight', obj_type='Object')"
diff --git a/src/blenderbim/test/core/test_drawing.py b/src/blenderbim/test/core/test_drawing.py
index 2cadacdc26..ba839a1869 100644
--- a/src/blenderbim/test/core/test_drawing.py
+++ b/src/blenderbim/test/core/test_drawing.py
@@ -492,6 +492,9 @@ class TestUpdateDrawingName:
drawing.does_file_exist("absolute_layout_uri").should_be_called().will_return(True)
drawing.update_embedded_svg_location("absolute_layout_uri", "old_location", "new_location").should_be_called()
+ drawing.is_editing_sheets().should_be_called().will_return(True)
+ drawing.import_sheets().should_be_called()
+
subject.update_drawing_name(ifc, drawing, drawing="drawing", name="name")
diff --git a/src/ifcopenshell-python/docs/ifcopenshell-python.rst b/src/ifcopenshell-python/docs/ifcopenshell-python.rst
index 7991426418..ddaa9a33eb 100644
--- a/src/ifcopenshell-python/docs/ifcopenshell-python.rst
+++ b/src/ifcopenshell-python/docs/ifcopenshell-python.rst
@@ -13,5 +13,6 @@ system, as well as high level analysis and authoring functions.
ifcopenshell-python/hello_world
ifcopenshell-python/code_examples
ifcopenshell-python/geometry_processing
+ ifcopenshell-python/geometry_creation
ifcopenshell-python/geometry_tree
ifcopenshell-python/developer_guide
diff --git a/src/ifcopenshell-python/docs/ifcopenshell-python/geometry_creation.rst b/src/ifcopenshell-python/docs/ifcopenshell-python/geometry_creation.rst
new file mode 100644
index 0000000000..75b3abf473
--- /dev/null
+++ b/src/ifcopenshell-python/docs/ifcopenshell-python/geometry_creation.rst
@@ -0,0 +1,350 @@
+Geometry creation
+=================
+
+Walls, doors, slabs, and other physical products in IFC can be represented with
+2D or 3D geometry. Most commonly, this geometry is created using graphical
+frontends, like the BlenderBIM Add-on. IfcOpenShell can create and edit
+geometry with code.
+
+.. note::
+
+ Geometry is optional in IFC. For many usecases, geometry is not required,
+ such as in facility management.
+
+General concepts
+----------------
+
+Any IFC element may have a location in the 3D world known as the **Object
+Placement**. The **Object Placement** is the "local origin" of the object. This
+is sometimes known as the object's center or insertion point in other software.
+The **Object Placement** is typically somewhere at a corner, center, or
+midpoint of the object. The **Object Placement** may be used to identify a
+rough "coordinate" location of the object's start / center, and used as a
+center of transformation when moving or rotating the object's geometry. The
+object's geometry is always relative to the **Object Placement**.
+
+.. note::
+
+ **Object Placements** are optional if the object has no geometry. However,
+ any object with a geometry must have an **Object Placement**.
+
+
+IFC products may have multiple geometric representations, positioned relative
+to the **Object Placement**. For example, a door might have a 3D body of a
+"closed door" as one geometric representation, a 2D linework of an "open door"
+intended to be shown in a plan, a 3D box showing the clearance of the door for
+disabled access, and 3D dashed linework showing the hinge and swing of a door
+in an elevation or section. Of course, you might not want to see all this
+geometry at the same time. What you see depends on the context you are viewing
+the door in.
+
+For this reason, each one of these geometric representations is called a
+**Representation**. Each **Representation** belongs to a **Representation
+Context**. The **Representation Context** determines how the **Representation**
+is intended to be viewed. For example, a "2D Plan View" might be a
+**Representation Context**. This allows the user to choose to see the
+appropriate **Representation**.
+
+
+Project units
+-------------
+
+All coordinates in IFC are stored using project units. This means that prior to
+creating **Object Placements** or **Representations** you have to define a
+project length unit as a minimum.
+
+Assuming you are creating a project from scratch with code, here is how you
+might define units:
+
+.. code-block:: python
+
+ # You need a project before you can assign units.
+ run("root.create_entity", model, ifc_class="IfcProject")
+
+ # Let's say we want coordinates to be in millimeters.
+ length = run("unit.add_si_unit", model, unit_type="LENGTHUNIT", prefix="MILLI")
+ run("unit.assign_unit", model, units=[length])
+
+ # Alternatively, you may specify without any arguments to automatically
+ # create millimeters, square meters, and cubic meters as a convenience for
+ # testing purposes. Sorry imperial folks, we prioritise metric here.
+ run("unit.assign_unit", model)
+
+
+Object placements
+-----------------
+
+The **Object Placement** describes **Location** and **Rotation**. The
+**Location** is given as an XYZ coordinate, and the **Rotation** is given as
+two vectors: a local X axis and a local Z axis vector. The local Y axis vector
+is derived via a right-handed coordinate system. This means that the global X
+axis points to "Project East", the global Y axis points to "Project North", and
+the global Z axis points up (i.e. to the sky). This coordinate system is the
+same system used in Blender.
+
+.. image:: images/object-placement.png
+
+The recommended way to set an **Object Placement** is to specify the placement
+as a 4x4 matrix. You can use the ``numpy`` library to create and edit matrices.
+A 4x4 matrix looks like this:
+
+.. code-block::
+
+ 1, 0, 0, 0
+ 0, 1, 0, 0
+ 0, 0, 1, 0
+ 0, 0, 0, 1
+
+This type of matrix is known as the **Identity Matrix**. It represents no
+translation (i.e. a location at the origin of ``0, 0, 0``) and no rotation
+(i.e. the X axis is ``1, 0, 0``, the Y axis is ``0, 1, 0``, and the Z axis is
+``0, 0, 1``). The numbers in the matrix correlate to the location and rotation
+axes as follows:
+
+.. code-block::
+
+ XAxis_X, YAxis_X, ZAxis_X, X
+ XAxis_Y, YAxis_Y, ZAxis_Y, Y
+ XAxis_Z, YAxis_Z, ZAxis_Z, Z
+ 0, 0, 0, 1
+
+Notice how the last line is always fixed to ``0, 0, 0, 1``. For example, here
+is another matrix of an object at ``2, 3, 5`` that is rotated anti-clockwise by
+90 degrees.
+
+.. code-block::
+
+ 0, -1, 0, 2
+ 1, 0, 0, 3
+ 0, 0, 1, 5
+ 0, 0, 0, 1
+
+.. image:: images/object-placement-example.png
+
+Here's how we might do the same operation with Python code:
+
+.. code-block:: python
+
+ import numpy
+
+ # Create a wall. Our wall currently has no object placement or representations.
+ wall = run("root.create_entity", model, ifc_class="IfcWall")
+
+ # Create a 4x4 identity matrix. This matrix is at the origin with no rotation.
+ matrix = numpy.eye(4)
+
+ # Rotate the matix 90 degrees anti-clockwise around the Z axis (i.e. in plan).
+ # Anti-clockwise is positive. Clockwise is negative.
+ matrix = ifcopenshell.util.placement.rotation(90, "Z") @ matrix
+
+ # Set the X, Y, Z coordinates. Notice how we rotate first then translate.
+ # This is because the rotation origin is always at 0, 0, 0.
+ matrix[:,3][0:3] = (2, 3, 5)
+
+ # Set our wall's Object Placement using our matrix.
+ # `is_si=True` states that we are using SI units instead of project units.
+ run("geometry.edit_object_placement", model, product=wall, matrix=matrix, is_si=True)
+
+Representation contexts
+-----------------------
+
+As an object may have multiple **Representations**, we need to use
+**Representation Contexts** to distinguish the purpose and intended context of
+each **Representation**.
+
+A **Representation Context** is defined in terms of X paramters:
+
+1. **Context Type**: 3D Model or 2D Plan
+2. **Context Identifier**: The purpose of the **Representation**
+3. **Target View**: The drafting convention of the **Representation**
+4. **Target Scale**: The scale for the **representation** to be shown at
+
+The **Context Type** must either be set to **Model** for 3D **Representations**
+or **Plan** for 2D **Representations**.
+
+The most common **Context Identifiers** you might use are:
+
+- Body: for the actual physical shape of the object
+- Box: the bounding box of the object (useful for shape analytics)
+- Axis: the parametric line determining the shape of the object
+- Profile: the elevation silhouette of the object, useful for cutting out holes
+ for the object to fit into host elements
+- Footprint: the plan view silhouette of the object, useful for certain
+ quantity take-off rules
+- Clearance: the clearance zone of the object
+- Annotation: symbolic annotations typically used in diagrams or drawings
+
+The most common **Target Views** you might use are:
+
+- MODEL_VIEW: for general 3D geometry you might see in a BIM viewer or any
+ generic fallback representation
+- PLAN_VIEW: for 2D geometry you might see in a plan representation
+- ELEVATION_VIEW: for 2D geometry you might see in an elevation representation
+- SECTION_VIEW: for 2D geometry you might see in a section representation
+- GRAPH_VIEW: for 2D or 3D line or frame or path connectivity diagrams you
+ might use for structural frame analysis, axis-based parametric modeling
+- SKETCH_VIEW: for viewing abstract high-level representations such as in
+ bubble diagrams of spatial topology
+
+The vast majority of the time, you will only be interested in using a 3D Body
+MODEL_VIEW **Representation Context**.
+
+.. code-block:: python
+
+ # If we plan to store 3D geometry in our IFC model, we have to setup
+ # a "Model" context.
+ model3d = run("context.add_context", model, context_type="Model")
+
+ # And/Or, if we plan to store 2D geometry, we need a "Plan" context
+ plan = run("context.add_context", model, context_type="Plan")
+
+ # Now we setup the subcontexts with each of the geometric "purposes"
+ # we plan to store in our model. "Body" is by far the most important
+ # and common context, as most IFC models are assumed to be viewable
+ # in 3D.
+ body = run("context.add_context", model,
+ context_type="Model", context_identifier="Body", target_view="MODEL_VIEW", parent=model3d)
+
+ # The 3D Axis subcontext is important if any "axis-based" parametric
+ # geometry is going to be created. For example, a beam, or column
+ # may be drawn using a single 3D axis line, and for this we need an
+ # Axis subcontext.
+ run("context.add_context", model,
+ context_type="Model", context_identifier="Axis", target_view="GRAPH_VIEW", parent=model3d)
+
+ # It's also important to have a 2D Axis subcontext for things like
+ # walls and claddings which can be drawn using a 2D axis line.
+ run("context.add_context", model,
+ context_type="Plan", context_identifier="Axis", target_view="GRAPH_VIEW", parent=plan)
+
+ # The 3D Box subcontext is useful for clash detection or shape
+ # analysis, or even lazy-loading of large models.
+ run("context.add_context", model,
+ context_type="Model", context_identifier="Box", target_view="MODEL_VIEW", parent=model3d)
+
+ # A 2D annotation subcontext for plan views are important for door
+ # swings, window cuts, and symbols for equipment like GPOs, fire
+ # extinguishers, and so on.
+ run("context.add_context", model,
+ context_type="Plan", context_identifier="Annotation", target_view="PLAN_VIEW", parent=plan)
+
+ # You may also create 2D annotation subcontexts for sections and
+ # elevation views.
+ run("context.add_context", model,
+ context_type="Plan", context_identifier="Annotation", target_view="SECTION_VIEW", parent=plan)
+ run("context.add_context", model,
+ context_type="Plan", context_identifier="Annotation", target_view="ELEVATION_VIEW", parent=plan)
+
+Representations
+---------------
+
+Once you have an **Object Placement** and a **Representation Context**, you can
+now create a **Representation**.
+
+Each **Representations** must choose a geometry modeling technique. For
+example, you may specify a mesh-like geometry, which uses vertices, edges, and
+faces. Alternatively, you may specify 2D profiles extruded into solid shapes
+and potentially having boolean voids and subtractions. You may even specify
+single edges and linework without any surfaces or solids. Representations may
+even be single points, such as for survey points or structual point
+connections.
+
+After the **Representation** is created, you will need to assign the
+**Representation** to the IFC object (e.g. wall, door, slab, etc). Here's the
+general pattern in code:
+
+.. code-block:: python
+
+ # Let's create a new project using millimeters with a single furniture element at the origin.
+ model = run("project.create_file")
+ run("root.create_entity", model, ifc_class="IfcProject")
+ run("unit.assign_unit", model)
+
+ # We want our representation to be the 3D body of the element.
+ # This representation context is only created once per project.
+ # You must reuse the same body context every time you create a new representation.
+ model3d = run("context.add_context", model, context_type="Model")
+ body = run("context.add_context", model,
+ context_type="Model", context_identifier="Body", target_view="MODEL_VIEW", parent=model3d)
+
+ # Create our element with an object placement.
+ element = run("root.create_entity", model, ifc_class="IfcFurniture")
+ run("geometry.edit_object_placement", model, product=element)
+
+ # Let's create our representation!
+ # See below sections for examples on how to create representations.
+ representation = ...
+
+ # Assign our new body representation back to our element
+ run("geometry.assign_representation", model, product=element, representation=representation)
+
+
+Mesh representations
+--------------------
+
+Mesh **Representations** are specified in terms of a list of vertices, edges,
+and faces. The faces may be triangles, quads, or n-gons. Faces may also contain
+inner loops, or holes. Mesh **Representations** are most appropriately used for
+complex shapes that only need to approximately represent physical products,
+such as furniture or equipment, or flat, panellised design (e.g. triangulated
+facade elements). Mesh **Representations** are also suitable for box-like
+shapes that have bespoke indents, protrusions, TINs, textured, or as-built
+geometry.
+
+In IFC, meshes may be stored as **Faceted BReps**, **Tessellations**, or
+**Triangulations** (specifically only for triangles).
+
+.. code-block:: python
+
+ # These vertices and faces represent a 2m square 1m high pyramid in SI units.
+ # Note how they are nested lists. Each nested list represents a "mesh". There may be multiple meshes.
+ vertices = [[(0.,0.,0.), (0.,2.,0.), (2.,2.,0.), (2.,0.,0.), (1.,1.,1.)]]
+ faces = [[(0,1,2,3), (0,4,1), (1,4,2), (2,4,3), (3,4,0)]]
+ representation = run("geometry.add_mesh_representation", model, context=body, vertices=vertices, faces=faces)
+
+.. image:: images/mesh-representation.png
+
+Wall representations
+--------------------
+
+Wall-like **Representations** are simple blocks with a length, height, and
+thickness. They are most appropriately used for walls, insulation, bulkhead
+ends, cladding, and other uniformly thick blocks that extend along an imaginary
+2D line in the XY plane.
+
+.. note::
+
+ Even though the function is named ``add_wall_representation``, you may use
+ this geometry for any element, not just walls.
+
+.. code-block:: python
+
+ # A wall-like representation, 5 meters long, 3 meters high, and 200mm thick
+ representation = run("geometry.add_wall_representation", model,
+ context=body, length=5, height=3, thickness=0.2)
+
+.. image:: images/wall-representation.png
+
+A wall-like **Representation** always starts at the **Object Placement** and
+runs along the local +X axis. The thickness is always along the local Y axis.
+This means that if you want the wall-like object to start and end at a
+particular point, you have to set the **Object Placement** location and
+rotation as appropriate. This can be done using the API:
+
+.. code-block:: python
+
+ # A wall-like representation starting and ending at a particular 2D point
+ # It is not necessary to assign the representation after using this function.
+ run("geometry.create_2pt_wall", model,
+ element=element, context=body, p1=(1., 1.), p2=(3., 2.), elevation=0, height=3, thickness=0.2)
+
+.. image:: images/wall-2pt-representation.png
+
+Profile representations
+-----------------------
+
+Custom representations
+----------------------
+
+Manual representations
+----------------------
diff --git a/src/ifcopenshell-python/docs/ifcopenshell-python/images/mesh-representation.png b/src/ifcopenshell-python/docs/ifcopenshell-python/images/mesh-representation.png
new file mode 100644
index 0000000000..b4c2d50873
Binary files /dev/null and b/src/ifcopenshell-python/docs/ifcopenshell-python/images/mesh-representation.png differ
diff --git a/src/ifcopenshell-python/docs/ifcopenshell-python/images/object-placement-example.png b/src/ifcopenshell-python/docs/ifcopenshell-python/images/object-placement-example.png
new file mode 100644
index 0000000000..3b86432bea
Binary files /dev/null and b/src/ifcopenshell-python/docs/ifcopenshell-python/images/object-placement-example.png differ
diff --git a/src/ifcopenshell-python/docs/ifcopenshell-python/images/object-placement.png b/src/ifcopenshell-python/docs/ifcopenshell-python/images/object-placement.png
new file mode 100644
index 0000000000..dddb49f23e
Binary files /dev/null and b/src/ifcopenshell-python/docs/ifcopenshell-python/images/object-placement.png differ
diff --git a/src/ifcopenshell-python/docs/ifcopenshell-python/images/wall-2pt-representation.png b/src/ifcopenshell-python/docs/ifcopenshell-python/images/wall-2pt-representation.png
new file mode 100644
index 0000000000..76929f061a
Binary files /dev/null and b/src/ifcopenshell-python/docs/ifcopenshell-python/images/wall-2pt-representation.png differ
diff --git a/src/ifcopenshell-python/docs/ifcopenshell-python/images/wall-representation.png b/src/ifcopenshell-python/docs/ifcopenshell-python/images/wall-representation.png
new file mode 100644
index 0000000000..7fd262f50c
Binary files /dev/null and b/src/ifcopenshell-python/docs/ifcopenshell-python/images/wall-representation.png differ
diff --git a/src/ifcopenshell-python/ifcopenshell/api/geometry/add_sverchok_representation.py b/src/ifcopenshell-python/ifcopenshell/api/geometry/add_mesh_representation.py
similarity index 100%
rename from src/ifcopenshell-python/ifcopenshell/api/geometry/add_sverchok_representation.py
rename to src/ifcopenshell-python/ifcopenshell/api/geometry/add_mesh_representation.py
diff --git a/src/ifcopenshell-python/ifcopenshell/api/geometry/create_2pt_wall.py b/src/ifcopenshell-python/ifcopenshell/api/geometry/create_2pt_wall.py
new file mode 100644
index 0000000000..45860fe836
--- /dev/null
+++ b/src/ifcopenshell-python/ifcopenshell/api/geometry/create_2pt_wall.py
@@ -0,0 +1,73 @@
+# IfcOpenShell - IFC toolkit and geometry engine
+# Copyright (C) 2023 Dion Moult
+#
+# This file is part of IfcOpenShell.
+#
+# IfcOpenShell is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# IfcOpenShell is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with IfcOpenShell. If not, see .
+
+import numpy as np
+import ifcopenshell.api
+import ifcopenshell.util.unit
+
+
+class Usecase:
+ def __init__(self, file, element=None, context=None, p1=None, p2=None, elevation=None, height=None, thickness=None):
+ self.file = file
+ self.settings = {
+ "element": element,
+ "context": context,
+ "p1": p1,
+ "p2": p2,
+ "elevation": elevation,
+ "height": height,
+ "thickness": thickness,
+ }
+
+ def execute(self):
+ self.settings["unit_scale"] = ifcopenshell.util.unit.calculate_unit_scale(self.file)
+
+ self.settings["p1"] = np.array(self.settings["p1"])
+ self.settings["p2"] = np.array(self.settings["p2"])
+
+ length = float(np.linalg.norm(self.settings["p2"] - self.settings["p1"]))
+ representation = ifcopenshell.api.run(
+ "geometry.add_wall_representation",
+ self.file,
+ context=self.settings["context"],
+ length=length,
+ height=self.settings["height"],
+ thickness=self.settings["thickness"],
+ )
+ v = self.settings["p2"] - self.settings["p1"]
+ v /= float(np.linalg.norm(v))
+ matrix = np.array(
+ [
+ [v[0], -v[1], 0, self.settings["p1"][0]],
+ [v[1], v[0], 0, self.settings["p1"][1]],
+ [0, 0, 1, self.convert_si_to_unit(self.settings["elevation"])],
+ [0, 0, 0, 1],
+ ]
+ )
+ ifcopenshell.api.run(
+ "geometry.edit_object_placement", self.file, product=self.settings["element"], matrix=matrix
+ )
+ ifcopenshell.api.run(
+ "geometry.assign_representation", self.file, product=self.settings["element"], representation=representation
+ )
+ return representation
+
+ def convert_si_to_unit(self, co):
+ if isinstance(co, (tuple, list)):
+ return [self.convert_si_to_unit(o) for o in co]
+ return co / self.settings["unit_scale"]
diff --git a/src/ifcopenshell-python/ifcopenshell/api/root/copy_class.py b/src/ifcopenshell-python/ifcopenshell/api/root/copy_class.py
index a2618078e1..061b695d24 100644
--- a/src/ifcopenshell-python/ifcopenshell/api/root/copy_class.py
+++ b/src/ifcopenshell-python/ifcopenshell/api/root/copy_class.py
@@ -86,24 +86,40 @@ class Usecase:
inverse.RelatedObjects = [to_element]
pset = ifcopenshell.util.element.copy_deep(self.file, inverse.RelatingPropertyDefinition)
inverse.RelatingPropertyDefinition = pset
- elif inverse.is_a("IfcRelNests") and inverse.RelatingObject == from_element:
- ports = [e for e in inverse.RelatedObjects if e.is_a("IfcDistributionPort")]
- if ports:
- new_ports = [ifcopenshell.api.run("root.copy_class", self.file, product=p) for p in ports]
- inverse = ifcopenshell.util.element.copy(self.file, inverse)
+ elif (
+ inverse.is_a("IfcRelNests")
+ and inverse.RelatingObject == from_element
+ or inverse.is_a("IfcRelConnectsPortToElement")
+ and inverse.RelatedElement == from_element
+ ):
+ # IfcRelConnectsPortToElement was used in IFC2X3
+ if inverse.is_a("IfcRelNests"):
+ ports = [e for e in inverse.RelatedObjects if e.is_a("IfcDistributionPort")]
+ else: # IfcRelConnectsPortToElement
+ ports = [inverse.RelatingPort]
+ if not ports:
+ continue
+ new_ports = [ifcopenshell.api.run("root.copy_class", self.file, product=p) for p in ports]
+ inverse = ifcopenshell.util.element.copy(self.file, inverse)
+
+ if inverse.is_a("IfcRelNests"):
inverse.RelatingObject = to_element
inverse.RelatedObjects = new_ports
- for port in new_ports:
- ifcopenshell.api.run("system.unassign_port", self.file, element=from_element, port=port)
- matrix = ifcopenshell.util.placement.get_local_placement(port.ObjectPlacement)
- ifcopenshell.api.run(
- "geometry.edit_object_placement",
- self.file,
- product=port,
- matrix=matrix,
- is_si=False,
- should_transform_children=False,
- )
+ else:
+ inverse.RelatedElement = to_element
+ inverse.RelatingPort = new_ports[0]
+
+ for port in new_ports:
+ ifcopenshell.api.run("system.unassign_port", self.file, element=from_element, port=port)
+ matrix = ifcopenshell.util.placement.get_local_placement(port.ObjectPlacement)
+ ifcopenshell.api.run(
+ "geometry.edit_object_placement",
+ self.file,
+ product=port,
+ matrix=matrix,
+ is_si=False,
+ should_transform_children=False,
+ )
elif inverse.is_a("IfcRelAggregates") and inverse.RelatingObject == from_element:
continue
elif inverse.is_a("IfcRelContainedInSpatialStructure") and inverse.RelatingStructure == from_element:
diff --git a/src/ifcopenshell-python/ifcopenshell/api/root/remove_product.py b/src/ifcopenshell-python/ifcopenshell/api/root/remove_product.py
index 37206881be..eb61cea031 100644
--- a/src/ifcopenshell-python/ifcopenshell/api/root/remove_product.py
+++ b/src/ifcopenshell-python/ifcopenshell/api/root/remove_product.py
@@ -125,7 +125,12 @@ class Usecase:
elif inverse.is_a("IfcRelContainedInSpatialStructure"):
if inverse.RelatingStructure == self.settings["product"] or len(inverse.RelatedElements) == 1:
self.file.remove(inverse)
- elif inverse.is_a("IfcRelConnectsPathElements"):
+ elif inverse.is_a("IfcRelConnectsElements"):
+ if inverse.is_a("IfcRelConnectsWithRealizingElements"):
+ if self.settings["product"] not in (inverse.RelatingElement, inverse.RelatedElement) and any(
+ el for el in inverse.RealizingElements if el != self.settings["product"]
+ ):
+ continue
self.file.remove(inverse)
elif inverse.is_a("IfcRelAssignsToGroup"):
if len(inverse.RelatedObjects) == 1:
diff --git a/src/ifcopenshell-python/ifcopenshell/util/placement.py b/src/ifcopenshell-python/ifcopenshell/util/placement.py
index fe6e8f78bb..7219cd6856 100644
--- a/src/ifcopenshell-python/ifcopenshell/util/placement.py
+++ b/src/ifcopenshell-python/ifcopenshell/util/placement.py
@@ -101,3 +101,30 @@ def get_storey_elevation(storey):
matrix = get_local_placement(storey.ObjectPlacement)
return matrix[2][3]
return getattr(storey, "Elevation", 0.0) or 0.0
+
+
+def rotation(angle, axis, is_degrees=True):
+ theta = np.radians(angle) if is_degrees else angle
+ cos, sin = np.cos(theta), np.sin(theta)
+
+ if axis == "X":
+ return np.array([
+ [1, 0, 0, 0],
+ [0, cos, -sin, 0],
+ [0, sin, cos, 0],
+ [0, 0, 0, 1]
+ ])
+ elif axis == "Y":
+ return np.array([
+ [cos, 0, sin, 0],
+ [0, 1, 0, 0],
+ [-sin, 0, cos, 0],
+ [0, 0, 0, 1]
+ ])
+ elif axis == "Z":
+ return np.array([
+ [cos, -sin, 0, 0],
+ [sin, cos, 0, 0],
+ [0, 0, 1, 0],
+ [0, 0, 0, 1]
+ ])
diff --git a/src/ifcopenshell-python/test/api/root/test_copy_class.py b/src/ifcopenshell-python/test/api/root/test_copy_class.py
index 9512fe1014..782ef05472 100644
--- a/src/ifcopenshell-python/test/api/root/test_copy_class.py
+++ b/src/ifcopenshell-python/test/api/root/test_copy_class.py
@@ -232,3 +232,15 @@ class TestCopyClass(test.bootstrap.IFC4):
new = ifcopenshell.api.run("root.copy_class", self.file, product=element)
assert len(self.file.by_type("IfcRelAssignsToGroup")) == 1
assert new.HasAssignments[0].RelatingGroup == group
+
+
+class TestCopyClassIFC2X3(test.bootstrap.IFC2X3):
+ def test_copying_distribution_ports(self):
+ element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcFlowTerminal")
+ port = ifcopenshell.api.run("system.add_port", self.file)
+ ifcopenshell.api.run("system.assign_port", self.file, element=element, port=port)
+ new = ifcopenshell.api.run("root.copy_class", self.file, product=element)
+ new_ports = ifcopenshell.util.system.get_ports(new)
+ assert port not in new_ports
+ assert new_ports[0].is_a("IfcDistributionPort")
+ assert ifcopenshell.util.system.get_ports(element) == [port]
diff --git a/src/ifcopenshell-python/test/api/root/test_remove_product.py b/src/ifcopenshell-python/test/api/root/test_remove_product.py
index d383859f88..7431551d77 100644
--- a/src/ifcopenshell-python/test/api/root/test_remove_product.py
+++ b/src/ifcopenshell-python/test/api/root/test_remove_product.py
@@ -196,6 +196,76 @@ class TestRemoveProduct(test.bootstrap.IFC4):
assert len(self.file.by_type("IfcColumn")) == 1
assert len(self.file.by_type("IfcBeam")) == 0
+ def test_removing_connection_relationships_of_an_element(self):
+ element1 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
+ element2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcSlab")
+ ifcopenshell.api.run(
+ "geometry.connect_element",
+ self.file,
+ related_element=element1,
+ relating_element=element2,
+ )
+ total_entities = len(list(self.file))
+ ifcopenshell.api.run("root.remove_product", self.file, product=element1)
+ assert len(list(self.file)) == total_entities - 2
+ assert len(self.file.by_type("IfcRelConnectsElements")) == 0
+ assert len(self.file.by_type("IfcSlab")) == 1
+ assert len(self.file.by_type("IfcWall")) == 0
+
+ def test_removing_connection_relationships_of_an_element_with_additional_realizing_element(self):
+ wall = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
+ slab1 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcSlab")
+ slab2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcSlab")
+ self.file.createIfcRelConnectsWithRealizingElements(
+ ifcopenshell.guid.new(),
+ OwnerHistory=ifcopenshell.api.run("owner.create_owner_history", self.file),
+ RelatingElement=wall,
+ RelatedElement=slab1,
+ RealizingElements=(wall, slab1, slab2),
+ )
+ total_entities = len(list(self.file))
+ ifcopenshell.api.run("root.remove_product", self.file, product=wall)
+ assert len(list(self.file)) == total_entities - 2
+ assert len(self.file.by_type("IfcRelConnectsElements")) == 0
+ assert len(self.file.by_type("IfcSlab")) == 2
+ assert len(self.file.by_type("IfcWall")) == 0
+
+ def test_removing_connection_relationships_of_an_element_element_is_realizing_element(self):
+ wall = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
+ slab1 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcSlab")
+ slab2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcSlab")
+ self.file.createIfcRelConnectsWithRealizingElements(
+ ifcopenshell.guid.new(),
+ OwnerHistory=ifcopenshell.api.run("owner.create_owner_history", self.file),
+ RelatingElement=wall,
+ RelatedElement=slab1,
+ RealizingElements=(wall, slab1, slab2),
+ )
+ total_entities = len(list(self.file))
+ ifcopenshell.api.run("root.remove_product", self.file, product=slab2)
+ assert len(list(self.file)) == total_entities - 1
+ assert len(self.file.by_type("IfcRelConnectsElements")) == 1
+ assert len(self.file.by_type("IfcSlab")) == 1
+ assert len(self.file.by_type("IfcWall")) == 1
+
+ def test_removing_connection_relationships_of_an_element_element_is_only_realizing_element(self):
+ wall = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
+ slab1 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcSlab")
+ slab2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcSlab")
+ self.file.createIfcRelConnectsWithRealizingElements(
+ ifcopenshell.guid.new(),
+ OwnerHistory=ifcopenshell.api.run("owner.create_owner_history", self.file),
+ RelatingElement=wall,
+ RelatedElement=slab1,
+ RealizingElements=(slab2,),
+ )
+ total_entities = len(list(self.file))
+ ifcopenshell.api.run("root.remove_product", self.file, product=slab2)
+ assert len(list(self.file)) == total_entities - 2
+ assert len(self.file.by_type("IfcRelConnectsElements")) == 0
+ assert len(self.file.by_type("IfcSlab")) == 1
+ assert len(self.file.by_type("IfcWall")) == 1
+
def test_removing_all_property_relationships_of_an_element(self):
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
pset = ifcopenshell.api.run("pset.add_pset", self.file, product=element, name="Foo_Bar")
diff --git a/src/ifcopenshell-python/test/test_wall_opening.py b/src/ifcopenshell-python/test/test_wall_opening.py
index f331297e73..6c9ed4242d 100644
--- a/src/ifcopenshell-python/test/test_wall_opening.py
+++ b/src/ifcopenshell-python/test/test_wall_opening.py
@@ -64,6 +64,7 @@ X = 1.0, 0.0, 0.0
Y = 0.0, 1.0, 0.0
Z = 0.0, 0.0, 1.0
+
# Creates an IfcAxis2Placement3D from Location, Axis and RefDirection specified as Python tuples
def create_ifcaxis2placement(f, point=O, dir1=Z, dir2=X):
point = f.createIfcCartesianPoint(point)
@@ -100,7 +101,6 @@ def create_ifcextrudedareasolid(f, point_list, ifcaxis2placement, extrude_dir, e
def create_case(fn, openings):
-
f = ifcopenshell.template.create()
owner_history = f.by_type("IfcOwnerHistory")[0]
@@ -127,7 +127,6 @@ def create_case(fn, openings):
)
for opening in openings:
-
opening_placement = create_ifclocalplacement(
f, (opening.x, 0.0, opening.z), (0.0, 1.0, 0.0), (1.0, 0.0, 0.0), wall_placement
)
@@ -147,7 +146,6 @@ def create_case(fn, openings):
class TestWallOpenings:
@pytest.mark.skipif(shutil.which("IfcConvert") is None, reason="Requires IfcConvert in path")
def test_all(self):
-
cases = [
(
"wall-openings-non-intersecting-rect-circle.ifc",
@@ -205,7 +203,6 @@ class TestWallOpenings:
result.append([fn])
for i in range(2 if PERF else 1):
-
args = [
shutil.which("IfcConvert") or "IfcConvert",
"-qyvvv",
@@ -236,12 +233,16 @@ class TestWallOpenings:
# breakpoint()
+ temp_files = [fn, f"{fn}.obj", f"{fn}.log.json", f"{fn}.mtl"]
+ for temp_fn in temp_files:
+ os.unlink(temp_fn)
+
try:
import tabulate
except:
return
- print(tabulate.tabulate(result, headers=["file", "", "--no-2d-boolean"], tablefmt="github"))
+ print("\n" + tabulate.tabulate(result, headers=["file", "", "--no-2d-boolean"], tablefmt="github"))
if __name__ == "__main__":