diff --git a/src/blenderbim/blenderbim/bim/module/bimtester/operator.py b/src/blenderbim/blenderbim/bim/module/bimtester/operator.py
index 5e71a956cd..7bf9905313 100644
--- a/src/blenderbim/blenderbim/bim/module/bimtester/operator.py
+++ b/src/blenderbim/blenderbim/bim/module/bimtester/operator.py
@@ -39,6 +39,11 @@ class ExecuteBIMTester(bpy.types.Operator):
bl_idname = "bim.execute_bim_tester"
bl_label = "Execute BIMTester"
+ @classmethod
+ def poll(cls, context):
+ props = context.scene.BimTesterProperties
+ return props.ifc_file and props.feature
+
def execute(self, context):
props = context.scene.BimTesterProperties
diff --git a/src/blenderbim/blenderbim/bim/module/clash/operator.py b/src/blenderbim/blenderbim/bim/module/clash/operator.py
index b924905483..90223e46d4 100644
--- a/src/blenderbim/blenderbim/bim/module/clash/operator.py
+++ b/src/blenderbim/blenderbim/bim/module/clash/operator.py
@@ -25,6 +25,7 @@ import logging
import numpy as np
from mathutils import Matrix
from math import radians
+from blenderbim.bim.ifc import IfcStore
class ExportClashSets(bpy.types.Operator):
@@ -124,7 +125,7 @@ class AddClashSource(bpy.types.Operator):
group: bpy.props.StringProperty()
def execute(self, context):
- clash_set = context.scene.BIMClashProperties.clash_sets[context.scene.BIMClashProperties.active_clash_set_index]
+ clash_set = context.scene.BIMClashProperties.active_clash_set
source = getattr(clash_set, self.group).add()
return {"FINISHED"}
@@ -137,7 +138,7 @@ class RemoveClashSource(bpy.types.Operator):
group: bpy.props.StringProperty()
def execute(self, context):
- clash_set = context.scene.BIMClashProperties.clash_sets[context.scene.BIMClashProperties.active_clash_set_index]
+ clash_set = context.scene.BIMClashProperties.active_clash_set
getattr(clash_set, self.group).remove(self.index)
return {"FINISHED"}
@@ -152,7 +153,7 @@ class SelectClashSource(bpy.types.Operator):
group: bpy.props.StringProperty()
def execute(self, context):
- clash_set = context.scene.BIMClashProperties.clash_sets[context.scene.BIMClashProperties.active_clash_set_index]
+ clash_set = context.scene.BIMClashProperties.active_clash_set
getattr(clash_set, self.group)[self.index].name = self.filepath
return {"FINISHED"}
@@ -276,9 +277,7 @@ class SelectIfcClashResults(bpy.types.Operator):
self.filepath = bpy.path.ensure_ext(self.filepath, ".json")
with open(self.filepath) as f:
clash_sets = json.load(f)
- clash_set_name = context.scene.BIMClashProperties.clash_sets[
- context.scene.BIMClashProperties.active_clash_set_index
- ].name
+ clash_set_name = context.scene.BIMClashProperties.active_clash_set.name
global_ids = []
for clash_set in clash_sets:
if clash_set["name"] != clash_set_name:
@@ -303,6 +302,10 @@ class SmartClashGroup(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
+ @classmethod
+ def poll(cls, context):
+ return context.scene.BIMClashProperties.clash_results_path
+
def execute(self, context):
import ifcclash
@@ -326,9 +329,7 @@ class SmartClashGroup(bpy.types.Operator):
with open(save_path, "w") as f:
f.write(json.dumps(smart_grouped_clashes))
- clash_set_name = context.scene.BIMClashProperties.clash_sets[
- context.scene.BIMClashProperties.active_clash_set_index
- ].name
+ clash_set_name = context.scene.BIMClashProperties.active_clash_set.name
# Reset the list of smart_clash_groups for the UI
context.scene.BIMClashProperties.smart_clash_groups.clear()
@@ -355,12 +356,14 @@ class LoadSmartGroupsForActiveClashSet(bpy.types.Operator):
bl_label = "Load Smart Groups for Active Clash Set"
bl_options = {"REGISTER", "UNDO"}
+ @classmethod
+ def poll(cls, context):
+ return context.scene.BIMClashProperties.active_clash_set
+
def execute(self, context):
smart_groups_path = bpy.path.ensure_ext(context.scene.BIMClashProperties.smart_grouped_clashes_path, ".json")
- clash_set_name = context.scene.BIMClashProperties.clash_sets[
- context.scene.BIMClashProperties.active_clash_set_index
- ].name
+ clash_set_name = context.scene.BIMClashProperties.active_clash_set.name
with open(smart_groups_path) as f:
smart_grouped_clashes = json.load(f)
@@ -389,11 +392,14 @@ class SelectSmartGroup(bpy.types.Operator):
bl_label = "Select Smart Group"
bl_options = {"REGISTER", "UNDO"}
+ @classmethod
+ def poll(cls, context):
+ return IfcStore.get_file() and context.visible_objects and context.scene.BIMClashProperties.active_smart_group
+
def execute(self, context):
+ self.file = IfcStore.get_file()
# Select smart group in view
- selected_smart_group = context.scene.BIMClashProperties.smart_clash_groups[
- context.scene.BIMCLashProperties.active_smart_group_index
- ]
+ selected_smart_group = context.scene.BIMClashProperties.active_smart_group
# print(selected_smart_group.number)
for obj in context.visible_objects:
diff --git a/src/blenderbim/blenderbim/bim/module/clash/prop.py b/src/blenderbim/blenderbim/bim/module/clash/prop.py
index 5ed121757f..e721db86ce 100644
--- a/src/blenderbim/blenderbim/bim/module/clash/prop.py
+++ b/src/blenderbim/blenderbim/bim/module/clash/prop.py
@@ -69,3 +69,15 @@ class BIMClashProperties(PropertyGroup):
smart_clash_grouping_max_distance: IntProperty(
name="Smart Clash Grouping Max Distance", default=3, soft_min=1, soft_max=10
)
+
+ @property
+ def active_clash_set(self):
+ if not self.clash_sets:
+ return None
+ return self.clash_sets[self.active_clash_set_index]
+
+ @property
+ def active_smart_group(self):
+ if not self.smart_clash_groups:
+ return None
+ return self.smart_clash_groups[self.active_smart_group_index]
diff --git a/src/blenderbim/blenderbim/bim/module/clash/ui.py b/src/blenderbim/blenderbim/bim/module/clash/ui.py
index 2080a0c723..9b6d021a42 100644
--- a/src/blenderbim/blenderbim/bim/module/clash/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/clash/ui.py
@@ -57,7 +57,7 @@ class BIM_PT_ifcclash(Panel):
layout.template_list("BIM_UL_clash_sets", "", props, "clash_sets", props, "active_clash_set_index")
if props.active_clash_set_index < len(props.clash_sets):
- clash_set = props.clash_sets[props.active_clash_set_index]
+ clash_set = props.active_clash_set
row = layout.row(align=True)
row.prop(clash_set, "name")
diff --git a/src/blenderbim/blenderbim/bim/module/debug/operator.py b/src/blenderbim/blenderbim/bim/module/debug/operator.py
index a70f5e8afb..7ef5b31b99 100644
--- a/src/blenderbim/blenderbim/bim/module/debug/operator.py
+++ b/src/blenderbim/blenderbim/bim/module/debug/operator.py
@@ -165,7 +165,7 @@ class SelectHighPolygonMeshes(bpy.types.Operator):
def execute(self, context):
[o.select_set(True) for o in context.view_layer.objects
- if o.type == 'MESH'
+ if o.type == "MESH"
and len(o.data.polygons) > context.scene.BIMDebugProperties.number_of_polygons]
return {"FINISHED"}
diff --git a/src/blenderbim/blenderbim/bim/module/drawing/helper.py b/src/blenderbim/blenderbim/bim/module/drawing/helper.py
index eab5fbf9b0..e7a1c9ad19 100644
--- a/src/blenderbim/blenderbim/bim/module/drawing/helper.py
+++ b/src/blenderbim/blenderbim/bim/module/drawing/helper.py
@@ -263,7 +263,7 @@ def get_active_drawing(scene):
if props.active_drawing_index is None or len(props.drawings) == 0:
return None, None
try:
- drawing = props.drawings[props.active_drawing_index]
+ drawing = props.active_drawing
return scene.collection.children["Views"].children[f"IfcGroup/{drawing.name}"], drawing.camera
except (KeyError, IndexError):
raise RuntimeError("missing drawing collection")
diff --git a/src/blenderbim/blenderbim/bim/module/drawing/operator.py b/src/blenderbim/blenderbim/bim/module/drawing/operator.py
index eca290ca9c..d78db7b340 100644
--- a/src/blenderbim/blenderbim/bim/module/drawing/operator.py
+++ b/src/blenderbim/blenderbim/bim/module/drawing/operator.py
@@ -60,6 +60,10 @@ class AddDrawing(bpy.types.Operator):
bl_label = "Add Drawing"
bl_options = {"REGISTER", "UNDO"}
+ @classmethod
+ def poll(cls, context):
+ return IfcStore.get_file()
+
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
@@ -502,12 +506,14 @@ class AddAnnotation(bpy.types.Operator):
obj_name: bpy.props.StringProperty()
data_type: bpy.props.StringProperty()
+ @classmethod
+ def poll(cls, context):
+ return IfcStore.get_file() and context.scene.camera
+
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
- if not context.scene.camera:
- return {"FINISHED"}
subcontext = ifcopenshell.util.representation.get_context(
IfcStore.get_file(), "Plan", "Annotation", context.scene.camera.data.BIMCameraProperties.target_view
)
@@ -563,7 +569,7 @@ class OpenSheet(bpy.types.Operator):
open_with_user_command(
context.preferences.addons["blenderbim"].preferences.svg_command,
os.path.join(
- context.scene.BIMProperties.data_dir, "sheets", props.sheets[props.active_sheet_index].name + ".svg"
+ context.scene.BIMProperties.data_dir, "sheets", props.active_sheet.name + ".svg"
),
)
return {"FINISHED"}
@@ -581,9 +587,9 @@ class AddDrawingToSheet(bpy.types.Operator):
sheet_builder.data_dir = context.scene.BIMProperties.data_dir
try:
sheet_builder.add_drawing(
- props.drawings[props.active_drawing_index].name, props.sheets[props.active_sheet_index].name
+ props.drawings.active_drawing.name, props.active_sheet.name
)
- except:
+ except FileNotFoundError:
self.report({"ERROR"}, "Drawings need to be created before being added to a sheet")
return {"FINISHED"}
@@ -596,7 +602,7 @@ class CreateSheets(bpy.types.Operator):
def execute(self, context):
scene = context.scene
props = scene.DocProperties
- name = props.sheets[props.active_sheet_index].name
+ name = props.active_sheet.name
sheet_builder = sheeter.SheetBuilder()
sheet_builder.data_dir = scene.BIMProperties.data_dir
sheet_builder.build(name)
@@ -650,18 +656,23 @@ class OpenView(bpy.types.Operator):
class OpenViewCamera(bpy.types.Operator):
+ """Select this drawing's camera object and expand its drawing properties"""
bl_idname = "bim.open_view_camera"
bl_label = "Open View Camera"
bl_options = {"REGISTER", "UNDO"}
view_name: bpy.props.StringProperty()
+ @classmethod
+ def poll(cls, context):
+ return bpy.context.object.mode == "OBJECT"
+
def execute(self, context):
- new_drawing_index = context.scene.DocProperties.drawings.find(self.view_name)
- context.scene.DocProperties.active_drawing_index = new_drawing_index
- drawing = context.scene.DocProperties.drawings[new_drawing_index]
- context.view_layer.objects.active = drawing.camera
+ doc_props = context.scene.DocProperties
+ doc_props.active_drawing_index = doc_props.drawings.find(self.view_name)
+ drawing = doc_props.active_drawing
bpy.ops.object.select_all(action="DESELECT")
drawing.camera.select_set(True)
+ context.view_layer.objects.active = drawing.camera
for area in context.screen.areas:
if area.ui_type == "PROPERTIES":
for space in area.spaces:
@@ -712,6 +723,7 @@ class SelectDocIfcFile(bpy.types.Operator):
bl_idname = "bim.select_doc_ifc_file"
bl_label = "Select Documentation IFC File"
bl_options = {"REGISTER", "UNDO"}
+ filter_glob: bpy.props.StringProperty(default="*.ifc;*.ifczip;*.ifcxml", options={"HIDDEN"})
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
index: bpy.props.IntProperty()
@@ -1059,7 +1071,7 @@ class SelectScheduleFile(bpy.types.Operator):
def execute(self, context):
props = context.scene.DocProperties
- props.schedules[props.active_schedule_index].file = self.filepath
+ props.active_schedule.file = self.filepath
return {"FINISHED"}
def invoke(self, context, event):
@@ -1071,9 +1083,13 @@ class BuildSchedule(bpy.types.Operator):
bl_idname = "bim.build_schedule"
bl_label = "Build Schedule"
+ @classmethod
+ def poll(cls, context):
+ return context.scene.DocProperties.active_schedule.file
+
def execute(self, context):
props = context.scene.DocProperties
- schedule = props.schedules[props.active_schedule_index]
+ schedule = props.active_schedule
schedule_creator = scheduler.Scheduler()
outfile = os.path.join(context.scene.BIMProperties.data_dir, "schedules", schedule.name + ".svg")
schedule_creator.schedule(schedule.file, outfile)
@@ -1092,7 +1108,7 @@ class AddScheduleToSheet(bpy.types.Operator):
sheet_builder = sheeter.SheetBuilder()
sheet_builder.data_dir = context.scene.BIMProperties.data_dir
sheet_builder.add_schedule(
- props.schedules[props.active_schedule_index].name, props.sheets[props.active_sheet_index].name
+ props.active_schedule.name, props.active_sheet.name
)
return {"FINISHED"}
@@ -1143,13 +1159,12 @@ class CleanWireframes(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
- objects = context.scene.objects
if context.selected_objects:
objects = context.selected_objects
- for obj in objects:
- if not isinstance(obj.data, bpy.types.Mesh):
- continue
- if "EDGE_SPLIT" not in [m.type for m in obj.modifiers]:
+ else:
+ objects = context.scene.objects
+ for obj in (o for o in objects if o.type == "MESH"):
+ if "EDGE_SPLIT" not in (m.type for m in obj.modifiers):
obj.modifiers.new("EdgeSplit", "EDGE_SPLIT")
return {"FINISHED"}
@@ -1159,11 +1174,13 @@ class CopyGrid(bpy.types.Operator):
bl_label = "Add Grid"
bl_options = {"REGISTER", "UNDO"}
+ @classmethod
+ def poll(cls, context):
+ return helper.get_active_drawing(context.scene)[0] is not None
+
def execute(self, context):
proj_coll = helper.get_project_collection(context.scene)
view_coll, camera = helper.get_active_drawing(context.scene)
- if view_coll is None:
- return {"CANCELLED"}
is_ortho = camera.data.type == "ORTHO"
bounds = helper.ortho_view_frame(camera.data) if is_ortho else None
clipping = is_ortho and camera.data.BIMCameraProperties.target_view in ("PLAN_VIEW", "REFLECTED_PLAN_VIEW")
@@ -1240,13 +1257,15 @@ class AddSectionsAnnotations(bpy.types.Operator):
bl_label = "Add Sections"
bl_options = {"REGISTER", "UNDO"}
+ @classmethod
+ def poll(cls, context):
+ camera = helper.get_active_drawing(context.scene)[1]
+ return camera and camera.data.type == "ORTHO"
+
def execute(self, context):
scene = context.scene
view_coll, camera = helper.get_active_drawing(scene)
- is_ortho = camera.data.type == "ORTHO"
- if not is_ortho:
- return {"CANCELLED"}
- bounds = helper.ortho_view_frame(camera.data) if is_ortho else None
+ bounds = helper.ortho_view_frame(camera.data)
drawings = [
d
diff --git a/src/blenderbim/blenderbim/bim/module/drawing/prop.py b/src/blenderbim/blenderbim/bim/module/drawing/prop.py
index 210ad986e8..150d800e32 100644
--- a/src/blenderbim/blenderbim/bim/module/drawing/prop.py
+++ b/src/blenderbim/blenderbim/bim/module/drawing/prop.py
@@ -269,6 +269,17 @@ class DocProperties(PropertyGroup):
name="Decorations Colour", subtype="COLOR", default=(1, 0, 0, 1), min=0.0, max=1.0, size=4
)
+ @property
+ def active_schedule(self):
+ return self.schedules[self.active_schedule_index]
+
+ @property
+ def active_drawing(self):
+ return self.drawings[self.active_drawing_index]
+
+ @property
+ def active_sheet(self):
+ return self.sheets[self.active_sheet_index]
class BIMCameraProperties(PropertyGroup):
view_name: StringProperty(name="View Name")
diff --git a/src/blenderbim/blenderbim/bim/module/drawing/ui.py b/src/blenderbim/blenderbim/bim/module/drawing/ui.py
index 464f3713e9..2662e885ec 100644
--- a/src/blenderbim/blenderbim/bim/module/drawing/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/drawing/ui.py
@@ -170,9 +170,9 @@ class BIM_PT_drawings(Panel):
if props.drawings:
if props.active_drawing_index < len(props.drawings):
op = row.operator("bim.open_view", icon="URL", text="")
- op.view = props.drawings[props.active_drawing_index].name
+ op.view = props.active_drawing.name
row.operator("bim.remove_drawing", icon="X", text="").index = props.active_drawing_index
- layout.template_list("BIM_UL_generic", "", props, "drawings", props, "active_drawing_index")
+ layout.template_list("BIM_UL_drawinglist", "", props, "drawings", props, "active_drawing_index")
row = layout.row()
row.operator("bim.add_ifc_file")
@@ -206,7 +206,7 @@ class BIM_PT_schedules(Panel):
layout.template_list("BIM_UL_generic", "", props, "schedules", props, "active_schedule_index")
row = layout.row()
- row.prop(props.schedules[props.active_schedule_index], "file")
+ row.prop(props.active_schedule, "file")
row.operator("bim.select_schedule_file", icon="FILE_FOLDER", text="")
@@ -343,7 +343,7 @@ class BIM_PT_annotation_utilities(Panel):
if props.drawings:
if props.active_drawing_index < len(props.drawings):
op = row.operator("bim.open_view", icon="URL", text="")
- op.view = props.drawings[props.active_drawing_index].name
+ op.view = props.active_drawing.name
row.operator("bim.remove_drawing", icon="X", text="").index = props.active_drawing_index
layout.template_list("BIM_UL_drawinglist", "", props, "drawings", props, "active_drawing_index")
diff --git a/src/blenderbim/blenderbim/bim/module/georeference/operator.py b/src/blenderbim/blenderbim/bim/module/georeference/operator.py
index b9066ab2ac..2026de99e4 100644
--- a/src/blenderbim/blenderbim/bim/module/georeference/operator.py
+++ b/src/blenderbim/blenderbim/bim/module/georeference/operator.py
@@ -237,6 +237,12 @@ class ConvertLocalToGlobal(bpy.types.Operator):
bl_label = "Convert Local To Global"
bl_options = {"REGISTER", "UNDO"}
+ @classmethod
+ def poll(cls, context):
+ file = IfcStore.get_file()
+ props = context.scene.BIMGeoreferenceProperties
+ return file and props.coordinate_input.count(",") == 2
+
def execute(self, context):
if not Data.is_loaded:
Data.load(IfcStore.get_file())
@@ -284,6 +290,13 @@ class ConvertGlobalToLocal(bpy.types.Operator):
bl_label = "Convert Global To Local"
bl_options = {"REGISTER", "UNDO"}
+ @classmethod
+ def poll(cls, context):
+ file = IfcStore.get_file()
+ props = context.scene.BIMGeoreferenceProperties
+ return file and file.by_type("IfcUnitAssignment") \
+ and props.coordinate_input.count(",") == 2
+
def execute(self, context):
if not Data.is_loaded:
Data.load(IfcStore.get_file())
@@ -330,6 +343,11 @@ class GetCursorLocation(bpy.types.Operator):
bl_label = "Get Cursor Location"
bl_options = {"REGISTER", "UNDO"}
+ @classmethod
+ def poll(cls, context):
+ file = IfcStore.get_file()
+ return file and file.by_type("IfcUnitAssignment")
+
def execute(self, context):
props = context.scene.BIMGeoreferenceProperties
scale = ifcopenshell.util.unit.calculate_unit_scale(IfcStore.get_file())
@@ -343,6 +361,12 @@ class SetCursorLocation(bpy.types.Operator):
bl_label = "Set Cursor Location"
bl_options = {"REGISTER", "UNDO"}
+ @classmethod
+ def poll(cls, context):
+ file = IfcStore.get_file()
+ props = context.scene.BIMGeoreferenceProperties
+ return file and file.by_type("IfcUnitAssignment") and props.coordinate_output.count(",") == 2
+
def execute(self, context):
props = context.scene.BIMGeoreferenceProperties
scale = ifcopenshell.util.unit.calculate_unit_scale(IfcStore.get_file())
diff --git a/src/blenderbim/blenderbim/bim/module/georeference/prop.py b/src/blenderbim/blenderbim/bim/module/georeference/prop.py
index f1cda83e62..900a1bdaaa 100644
--- a/src/blenderbim/blenderbim/bim/module/georeference/prop.py
+++ b/src/blenderbim/blenderbim/bim/module/georeference/prop.py
@@ -52,8 +52,8 @@ class BIMGeoreferenceProperties(PropertyGroup):
default="foot",
)
is_map_unit_null: BoolProperty(name="Is Map Unit Null")
- coordinate_input: StringProperty(name="Coordinate Input")
- coordinate_output: StringProperty(name="Coordinate Output")
+ coordinate_input: StringProperty(name="Coordinate Input", description="Formatted \"x,y,z\" (without quotes)")
+ coordinate_output: StringProperty(name="Coordinate Output", description="Formatted \"x,y,z\" (without quotes)")
has_blender_offset: BoolProperty(name="Has Blender Offset")
blender_eastings: StringProperty(name="Blender Eastings", default="0")
blender_northings: StringProperty(name="Blender Northings", default="0")
diff --git a/src/blenderbim/blenderbim/bim/module/model/product.py b/src/blenderbim/blenderbim/bim/module/model/product.py
index bb3c92e584..c03062d007 100644
--- a/src/blenderbim/blenderbim/bim/module/model/product.py
+++ b/src/blenderbim/blenderbim/bim/module/model/product.py
@@ -129,6 +129,10 @@ class AddTypeInstance(bpy.types.Operator):
else:
if collection_obj and collection_obj.BIMObjectProperties.ifc_definition_id:
obj.location[2] = collection_obj.location[2] - min([v[2] for v in obj.bound_box])
+
+ bpy.ops.object.select_all(action='DESELECT')
+ obj.select_set(True)
+ context.view_layer.objects.active = obj
return {"FINISHED"}
def generate_layered_element(self, ifc_class, relating_type):
@@ -205,8 +209,14 @@ class DynamicallyVoidProduct(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
+ @classmethod
+ def poll(cls, context):
+ return IfcStore.get_file()
+
def execute(self, context):
obj = bpy.data.objects.get(self.obj)
+ if obj is None:
+ return {"FINISHED"}
product = IfcStore.get_file().by_id(obj.BIMObjectProperties.ifc_definition_id)
if not product.HasOpenings:
return {"FINISHED"}
diff --git a/src/blenderbim/blenderbim/bim/module/model/ui.py b/src/blenderbim/blenderbim/bim/module/model/ui.py
index dbec2fe8c7..00a34969bf 100644
--- a/src/blenderbim/blenderbim/bim/module/model/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/model/ui.py
@@ -17,6 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see .
+import blenderbim.bim.module.type.prop as type_prop
from bpy.types import Panel
from blenderbim.bim.ifc import IfcStore
@@ -35,9 +36,21 @@ class BIM_PT_authoring(Panel):
def draw(self, context):
tprops = context.scene.BIMTypeProperties
col = self.layout.column(align=True)
- col.prop(tprops, "ifc_class", text="", icon="FILE_VOLUME")
- col.prop(tprops, "relating_type", text="", icon="FILE_3D")
- col.operator("bim.add_type_instance", icon="ADD")
+ enabled = True
+
+ if type_prop.getIfcTypes(tprops, context):
+ col.prop(tprops, "ifc_class", text="", icon="FILE_VOLUME")
+ else:
+ col.label(text="No IFC Class", icon="FILE_VOLUME")
+ enabled = False
+ if type_prop.getAvailableTypes(tprops, context):
+ col.prop(tprops, "relating_type", text="", icon="FILE_3D")
+ else:
+ col.label(text="No Relating Type", icon="FILE_3D")
+ enabled = False
+ row = col.row()
+ row.operator("bim.add_type_instance", icon="ADD")
+ row.enabled = enabled
class BIM_PT_authoring_architectural(Panel):
@@ -76,9 +89,8 @@ class BIM_PT_misc_utilities(Panel):
layout = self.layout
props = context.scene.BIMProperties
- row = layout.row()
+ row = layout.split(factor=0.2, align=True)
row.prop(props, "override_colour", text="")
- row = layout.row(align=True)
row.operator("bim.set_override_colour")
row = layout.row(align=True)
row.operator("bim.set_viewport_shadow_from_sun")
diff --git a/src/blenderbim/blenderbim/bim/module/model/wall.py b/src/blenderbim/blenderbim/bim/module/model/wall.py
index c18acfba78..3678422016 100644
--- a/src/blenderbim/blenderbim/bim/module/model/wall.py
+++ b/src/blenderbim/blenderbim/bim/module/model/wall.py
@@ -74,12 +74,14 @@ class JoinWall(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
join_type: bpy.props.StringProperty()
+ @classmethod
+ def poll(cls, context):
+ return context.selected_objects
+
def execute(self, context):
- selected_objs = context.selected_objects
+ selected_objs = [o for o in context.selected_objects if o.BIMObjectProperties.ifc_definition_id]
for obj in selected_objs:
bpy.ops.bim.dynamically_void_product(obj=obj.name)
- if len(selected_objs) == 0:
- return {"FINISHED"}
if not self.join_type:
for obj in selected_objs:
DumbWallJoiner(obj, obj).unjoin()
@@ -114,11 +116,14 @@ class AlignWall(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
align_type: bpy.props.StringProperty()
+ @classmethod
+ def poll(cls, context):
+ selected_valid_objects = [o for o in context.selected_objects if o.data and hasattr(o.data, "transform")]
+ return context.active_object and len(selected_valid_objects) > 1
+
def execute(self, context):
- selected_objs = context.selected_objects
- if len(selected_objs) < 2 or not context.active_object:
- return {"FINISHED"}
- for obj in selected_objs:
+ selected_objects = [o for o in context.selected_objects if o.data and hasattr(o.data, "transform")]
+ for obj in selected_objects:
if obj == context.active_object:
continue
aligner = DumbWallAligner(obj, context.active_object)
@@ -137,10 +142,12 @@ class FlipWall(bpy.types.Operator):
bl_label = "Flip Wall"
bl_options = {"REGISTER", "UNDO"}
+ @classmethod
+ def poll(cls, context):
+ return context.selected_objects
+
def execute(self, context):
- selected_objs = context.selected_objects
- if len(selected_objs) == 0:
- return {"FINISHED"}
+ selected_objs = [o for o in context.selected_objects if o.data and hasattr(o.data, "transform")]
for obj in selected_objs:
DumbWallFlipper(obj).flip()
IfcStore.edited_objs.add(obj)
@@ -152,12 +159,14 @@ class SplitWall(bpy.types.Operator):
bl_label = "Split Wall"
bl_options = {"REGISTER", "UNDO"}
+ @classmethod
+ def poll(cls, context):
+ return context.selected_objects
+
def execute(self, context):
- selected_objs = context.selected_objects
- if len(selected_objs) == 0:
- return {"FINISHED"}
+ selected_objs = [o for o in context.selected_objects if o.data and hasattr(o.data, "transform")]
for obj in selected_objs:
- DumbWallSplitter(obj, bpy.context.scene.cursor.location).split()
+ DumbWallSplitter(obj, context.scene.cursor.location).split()
IfcStore.edited_objs.add(obj)
return {"FINISHED"}
diff --git a/src/blenderbim/blenderbim/bim/module/model/workspace.py b/src/blenderbim/blenderbim/bim/module/model/workspace.py
index 59c8fd3234..7215a1bb2d 100644
--- a/src/blenderbim/blenderbim/bim/module/model/workspace.py
+++ b/src/blenderbim/blenderbim/bim/module/model/workspace.py
@@ -19,6 +19,7 @@
import os
import bpy
+import blenderbim.bim.module.type.prop as type_prop
from bpy.types import WorkSpaceTool
from blenderbim.bim.ifc import IfcStore
@@ -52,9 +53,20 @@ class BimTool(WorkSpaceTool):
def draw_settings(context, layout, tool):
row = layout.row(align=True)
+ if not IfcStore.get_file():
+ row.label(text="No IFC Project", icon="ERROR")
+ return
props = context.scene.BIMTypeProperties
- row.prop(props, "ifc_class", text="")
- row.prop(props, "relating_type", text="")
+ ifc_classes_is_empty = not bool(type_prop.getIfcTypes(props, context))
+ if ifc_classes_is_empty:
+ row.label(text="No IFC Class")
+ else:
+ row.prop(props, "ifc_class", text="")
+ if type_prop.getAvailableTypes(props, context):
+ row.prop(props, "relating_type", text="")
+ else:
+ row.label(text="No Relating Type")
+
row.label(text="", icon="BLANK1")
@@ -62,34 +74,35 @@ class BimTool(WorkSpaceTool):
row.label(text="", icon="EVENT_SHIFT")
row.label(text="Add Type Instance", icon="EVENT_A")
- if props.ifc_class == "IfcWallType":
- row = layout.row()
- row.label(text="Join")
- row = layout.row(align=True)
- row.label(text="", icon="EVENT_SHIFT")
- row.label(text="Extend", icon="EVENT_E")
- row = layout.row(align=True)
- row.label(text="", icon="EVENT_SHIFT")
- row.label(text="Butt", icon="EVENT_T")
- row = layout.row(align=True)
- row.label(text="", icon="EVENT_SHIFT")
- row.label(text="Mitre", icon="EVENT_Y")
+ if not ifc_classes_is_empty:
+ if props.ifc_class == "IfcWallType":
+ row = layout.row()
+ row.label(text="Join")
+ row = layout.row(align=True)
+ row.label(text="", icon="EVENT_SHIFT")
+ row.label(text="Extend", icon="EVENT_E")
+ row = layout.row(align=True)
+ row.label(text="", icon="EVENT_SHIFT")
+ row.label(text="Butt", icon="EVENT_T")
+ row = layout.row(align=True)
+ row.label(text="", icon="EVENT_SHIFT")
+ row.label(text="Mitre", icon="EVENT_Y")
- row = layout.row()
- row.label(text="Wall Tools")
- row = layout.row(align=True)
- row.label(text="", icon="EVENT_SHIFT")
- row.label(text="Flip", icon="EVENT_F")
- row = layout.row(align=True)
- row.label(text="", icon="EVENT_SHIFT")
- row.label(text="Split", icon="EVENT_S")
+ row = layout.row()
+ row.label(text="Wall Tools")
+ row = layout.row(align=True)
+ row.label(text="", icon="EVENT_SHIFT")
+ row.label(text="Flip", icon="EVENT_F")
+ row = layout.row(align=True)
+ row.label(text="", icon="EVENT_SHIFT")
+ row.label(text="Split", icon="EVENT_S")
- if props.ifc_class in ["IfcColumnType", "IfcBeamType", "IfcMemberType"]:
- row = layout.row()
- row.label(text="Join")
- row = layout.row(align=True)
- row.label(text="", icon="EVENT_SHIFT")
- row.label(text="Extend", icon="EVENT_E")
+ if props.ifc_class in ("IfcColumnType", "IfcBeamType", "IfcMemberType"):
+ row = layout.row()
+ row.label(text="Join")
+ row = layout.row(align=True)
+ row.label(text="", icon="EVENT_SHIFT")
+ row.label(text="Extend", icon="EVENT_E")
row = layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
@@ -124,11 +137,16 @@ class Hotkey(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
hotkey: bpy.props.StringProperty()
+ @classmethod
+ def poll(cls, context):
+ return IfcStore.get_file()
+
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
self.props = context.scene.BIMTypeProperties
+ self.ifc_classes_is_empty = not bool(type_prop.getIfcTypes(self.props, context))
getattr(self, f"hotkey_{self.hotkey}")()
return {"FINISHED"}
@@ -136,26 +154,29 @@ class Hotkey(bpy.types.Operator):
bpy.ops.bim.add_type_instance()
def hotkey_S_C(self):
- if self.props.ifc_class == "IfcWallType":
+ if not self.ifc_classes_is_empty and self.props.ifc_class == "IfcWallType":
bpy.ops.bim.align_wall(align_type="CENTERLINE")
else:
bpy.ops.bim.align_product(align_type="CENTERLINE")
def hotkey_S_E(self):
+ if self.ifc_classes_is_empty:
+ return
if self.props.ifc_class == "IfcWallType":
bpy.ops.bim.join_wall(join_type="T")
elif self.props.ifc_class in ["IfcColumnType", "IfcBeamType", "IfcMemberType"]:
bpy.ops.bim.extend_profile()
def hotkey_S_V(self):
- if self.props.ifc_class == "IfcWallType":
+ if not self.ifc_classes_is_empty and self.props.ifc_class == "IfcWallType":
bpy.ops.bim.align_wall(align_type="INTERIOR")
else:
bpy.ops.bim.align_product(align_type="POSITIVE")
def hotkey_S_X(self):
- if self.props.ifc_class == "IfcWallType":
- bpy.ops.bim.align_wall(align_type="EXTERIOR")
+ if not self.ifc_classes_is_empty and self.props.ifc_class == "IfcWallType":
+ if bpy.ops.bim.align_wall.poll():
+ bpy.ops.bim.align_wall(align_type="EXTERIOR")
else:
bpy.ops.bim.align_product(align_type="NEGATIVE")
diff --git a/src/blenderbim/blenderbim/bim/module/qto/helper.py b/src/blenderbim/blenderbim/bim/module/qto/helper.py
index d2c53baaff..8826a8d5b7 100644
--- a/src/blenderbim/blenderbim/bim/module/qto/helper.py
+++ b/src/blenderbim/blenderbim/bim/module/qto/helper.py
@@ -25,12 +25,38 @@ def calculate_height(obj):
return obj.dimensions[2]
-def calculate_volume(obj):
- bm = bmesh.new()
- bm.from_mesh(obj.data)
- result = bm.calc_volume()
- bm.free()
- return result
+def calculate_edges_lengths(objs, context):
+ return calculate_mesh_quantity(objs, context, lambda bm: sum((e.calc_length() for e in bm.edges if e.select)))
+
+
+def calculate_faces_areas(objs, context):
+ return calculate_mesh_quantity(objs, context, lambda bm: sum((f.calc_area() for f in bm.faces if f.select)))
+
+
+def calculate_volumes(objs, context):
+ return calculate_mesh_quantity(objs, context, lambda bm: bm.calc_volume())
+
+
+def calculate_mesh_quantity(objs: bpy.types.Object, context, operation):
+ """Get the sum of the target quantity on all passed mesh objects
+
+ :param objs: iterable of mesh object
+ :param context: current execution context
+ :param operation: function which takes a single bmesh as an argument, returns a float value
+ :returns float:
+ """
+ result = 0
+ edit_mode = context.active_object.mode == "EDIT"
+ for obj in objs:
+ if edit_mode:
+ bm = bmesh.from_edit_mesh(obj.data)
+ result += operation(bm)
+ else:
+ bm = bmesh.new()
+ bm.from_mesh(obj.data)
+ result += operation(bm)
+ bm.free()
+ return result
def calculate_formwork_area(objs, context):
@@ -49,10 +75,11 @@ def calculate_formwork_area(objs, context):
context.collection.objects.link(new_obj)
copied_objs.append(new_obj)
- context_override = {}
- context_override["object"] = context_override["active_object"] = copied_objs[0]
- context_override["selected_objects"] = context_override["selected_editable_objects"] = copied_objs
- bpy.ops.object.join(context_override)
+ if len(objs) > 1:
+ context_override = {}
+ context_override["object"] = context_override["active_object"] = copied_objs[0]
+ context_override["selected_objects"] = context_override["selected_editable_objects"] = copied_objs
+ bpy.ops.object.join(context_override)
copied_objs[0].name = "Formwork"
copied_objs[0].BIMObjectProperties.ifc_definition_id = 0
diff --git a/src/blenderbim/blenderbim/bim/module/qto/operator.py b/src/blenderbim/blenderbim/bim/module/qto/operator.py
index b77d067e14..de4b968764 100644
--- a/src/blenderbim/blenderbim/bim/module/qto/operator.py
+++ b/src/blenderbim/blenderbim/bim/module/qto/operator.py
@@ -18,7 +18,6 @@
# along with BlenderBIM Add-on. If not, see .
import bpy
-import bmesh
import ifcopenshell
import ifcopenshell.api
from blenderbim.bim.ifc import IfcStore
@@ -31,14 +30,12 @@ class CalculateEdgeLengths(bpy.types.Operator):
bl_label = "Calculate Edge Lengths"
bl_options = {"REGISTER", "UNDO"}
+ @classmethod
+ def poll(cls, context):
+ return context.selected_objects and context.active_object
+
def execute(self, context):
- result = 0
- for obj in context.selected_objects:
- if not obj.data or not obj.data.edges:
- continue
- for edge in obj.data.edges:
- if edge.select:
- result += (obj.data.vertices[edge.vertices[1]].co - obj.data.vertices[edge.vertices[0]].co).length
+ result = helper.calculate_edges_lengths([o for o in context.selected_objects if o.type == "MESH"], context)
context.scene.BIMQtoProperties.qto_result = str(round(result, 3))
return {"FINISHED"}
@@ -48,14 +45,12 @@ class CalculateFaceAreas(bpy.types.Operator):
bl_label = "Calculate Face Areas"
bl_options = {"REGISTER", "UNDO"}
+ @classmethod
+ def poll(cls, context):
+ return context.selected_objects and context.active_object
+
def execute(self, context):
- result = 0
- for obj in context.selected_objects:
- if not obj.data or not obj.data.polygons:
- continue
- for polygon in obj.data.polygons:
- if polygon.select:
- result += polygon.area
+ result = helper.calculate_faces_areas([o for o in context.selected_objects if o.type == "MESH"], context)
context.scene.BIMQtoProperties.qto_result = str(round(result, 3))
return {"FINISHED"}
@@ -65,15 +60,12 @@ class CalculateObjectVolumes(bpy.types.Operator):
bl_label = "Calculate Object Volumes"
bl_options = {"REGISTER", "UNDO"}
+ @classmethod
+ def poll(cls, context):
+ return context.selected_objects and context.active_object
+
def execute(self, context):
- result = 0
- for obj in context.selected_objects:
- if not obj.data or not isinstance(obj.data, bpy.types.Mesh):
- continue
- bm = bmesh.new()
- bm.from_mesh(obj.data)
- result += bm.calc_volume()
- bm.free()
+ result = helper.calculate_volumes([o for o in context.selected_objects if o.type == "MESH"], context)
context.scene.BIMQtoProperties.qto_result = str(round(result, 3))
return {"FINISHED"}
@@ -83,17 +75,21 @@ class ExecuteQtoMethod(bpy.types.Operator):
bl_label = "Execute Qto Method"
bl_options = {"REGISTER", "UNDO"}
+ @classmethod
+ def poll(cls, context):
+ return context.selected_objects
+
def execute(self, context):
+ selected_mesh_objects = [o for o in context.selected_objects if o.type == "MESH"]
props = context.scene.BIMQtoProperties
result = 0
if props.qto_methods == "HEIGHT":
- for obj in context.selected_objects:
+ for obj in selected_mesh_objects:
result += helper.calculate_height(obj)
elif props.qto_methods == "VOLUME":
- for obj in context.selected_objects:
- result += helper.calculate_volume(obj)
+ result = helper.calculate_volumes(selected_mesh_objects, context)
elif props.qto_methods == "FORMWORK":
- result = helper.calculate_formwork_area(context.selected_objects, context)
+ result = helper.calculate_formwork_area(selected_mesh_objects, context)
props.qto_result = str(round(result, 3))
return {"FINISHED"}
@@ -103,20 +99,24 @@ class QuantifyObjects(bpy.types.Operator):
bl_label = "Quantify Objects"
bl_options = {"REGISTER", "UNDO"}
+ @classmethod
+ def poll(cls, context):
+ return IfcStore.get_file() and context.selected_objects
+
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
props = context.scene.BIMQtoProperties
self.file = IfcStore.get_file()
- for obj in context.selected_objects:
+ for obj in (o for o in context.selected_objects if o.type == "MESH"):
if not obj.BIMObjectProperties.ifc_definition_id:
continue
result = 0
if props.qto_methods == "HEIGHT":
result = helper.calculate_height(obj)
elif props.qto_methods == "VOLUME":
- result = helper.calculate_volume(obj)
+ result = helper.calculate_volumes([obj], context)
elif props.qto_methods == "FORMWORK":
result = helper.calculate_formwork_area([obj], context)
if not result:
diff --git a/src/blenderbim/blenderbim/bim/operator.py b/src/blenderbim/blenderbim/bim/operator.py
index fccaab384a..14cf470a42 100644
--- a/src/blenderbim/blenderbim/bim/operator.py
+++ b/src/blenderbim/blenderbim/bim/operator.py
@@ -411,11 +411,13 @@ class RemoveSectionPlane(bpy.types.Operator):
bl_label = "Remove Temporary Section Cutaway"
bl_options = {"REGISTER", "UNDO"}
+ @classmethod
+ def poll(cls, context):
+ return context.active_object and bpy.data.node_groups.get("Section Override")
+
def execute(self, context):
name = context.active_object.name
section_override = bpy.data.node_groups.get("Section Override")
- if not section_override:
- return {"FINISHED"}
for node in section_override.nodes:
if node.type != "TEX_COORD" or node.object.name != name:
continue
@@ -497,8 +499,11 @@ class SetOverrideColour(bpy.types.Operator):
bl_label = "Set Override Colour"
bl_options = {"REGISTER", "UNDO"}
+ @classmethod
+ def poll(cls, context):
+ return context.selected_objects
+
def execute(self, context):
- result = 0
for obj in context.selected_objects:
obj.color = context.scene.BIMProperties.override_colour
area = next(area for area in context.screen.areas if area.type == "VIEW_3D")
@@ -511,6 +516,10 @@ class SetViewportShadowFromSun(bpy.types.Operator):
bl_label = "Set Viewport Shadow from Sun"
bl_options = {"REGISTER", "UNDO"}
+ @classmethod
+ def poll(cls, context):
+ return context.active_object
+
def execute(self, context):
# The vector used for the light direction is a bit funny
mat = Matrix(((-1.0, 0.0, 0.0, 0.0), (0.0, 0, 1.0, 0.0), (-0.0, -1.0, 0, 0.0), (0.0, 0.0, 0.0, 1.0)))
@@ -553,17 +562,20 @@ class SnapSpacesTogether(bpy.types.Operator):
bl_label = "Snap Spaces Together"
bl_options = {"REGISTER", "UNDO"}
+ @classmethod
+ def poll(cls, context):
+ return context.selected_objects
+
def execute(self, context):
threshold = 0.5
processed_polygons = set()
- for obj in context.selected_objects:
- if obj.type != "MESH":
- continue
+ selected_mesh_objects = [o for o in context.selected_objects if o.type == "MESH"]
+ for obj in selected_mesh_objects:
for polygon in obj.data.polygons:
center = obj.matrix_world @ polygon.center
distance = None
- for obj2 in context.selected_objects:
- if obj2 == obj or obj.type != "MESH":
+ for obj2 in selected_mesh_objects:
+ if obj2 == obj:
continue
result = obj2.ray_cast(obj2.matrix_world.inverted() @ center, polygon.normal, distance=threshold)
if not result[0]:
diff --git a/src/ifcpatch/ifcpatch/__init__.py b/src/ifcpatch/ifcpatch/__init__.py
index b9e9166d66..74f38c36dd 100644
--- a/src/ifcpatch/ifcpatch/__init__.py
+++ b/src/ifcpatch/ifcpatch/__init__.py
@@ -63,8 +63,7 @@ def extract_docs(
method_name: str="__init__",
boilerplate_args : typing.Iterable[str]=None):
"""Extract class docstrings and method arguments
-
- :param module: Parent module from which to extract the submodule class
+
:param submodule_name: Submodule from which to extract the class
:param cls_name: Class from which to extract the docstring and method arguments
:param method_name: Class Method name from which to extract arguments