Various tweaks and fixes (#1683)

* Use operator's poll method to ensure correct execution

* Add utilities to get active collection item

* Minor fixes
zombie code
list comprehensions
typos
cleanup
tooltips, descriptions
ensure file extension in folder operator
use custom UI List instead of generic one
ensure active object stays the same after operator execution
use passed context instead of bpy

* Jump through hoops to not display empty dynamic enums

* Group mesh calculation methods in helper module
If not using bmesh, the polygon selection status
is updated only when getting out of Edit mode.
Using bmesh we can get the values in realtime

* Remove unused arg name in dosctring
This commit is contained in:
Gorgious56
2021-08-23 00:07:29 +02:00
committed by GitHub
parent 4d7ff493a8
commit 67c055579c
19 changed files with 315 additions and 148 deletions
@@ -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
@@ -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:
@@ -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]
@@ -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")
@@ -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"}
@@ -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")
@@ -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
@@ -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")
@@ -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")
@@ -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())
@@ -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")
@@ -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"}
@@ -17,6 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import 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")
@@ -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"}
@@ -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")
@@ -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
@@ -18,7 +18,6 @@
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
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:
+20 -8
View File
@@ -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]:
+1 -2
View File
@@ -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